From 123407e0b4ec616aef40216ab76e559e0dee5711 Mon Sep 17 00:00:00 2001 From: Ian Thompson Date: Fri, 18 Jul 2008 23:12:19 +0000 Subject: [PATCH] Added a documentation panel with primitive word-wrap functionality. It can be displayed by Text.showDoc(string) in python and has a text-plugin script for function docs which may be invoked with Ctrl+I inside its params list. Eg. type "dir(" --- release/scripts/textplugin_functiondocs.py | 68 ++++++ source/blender/blenkernel/BKE_suggestions.h | 14 +- .../blender/blenkernel/intern/suggestions.c | 74 ++++++- source/blender/python/api2_2x/Text.c | 43 +++- source/blender/src/drawtext.c | 197 +++++++++++++----- 5 files changed, 325 insertions(+), 71 deletions(-) create mode 100644 release/scripts/textplugin_functiondocs.py diff --git a/release/scripts/textplugin_functiondocs.py b/release/scripts/textplugin_functiondocs.py new file mode 100644 index 00000000000..2277b7e68b5 --- /dev/null +++ b/release/scripts/textplugin_functiondocs.py @@ -0,0 +1,68 @@ +#!BPY +""" +Name: 'Function Documentation' +Blender: 246 +Group: 'TextPlugin' +Shortcut: 'Ctrl+I' +Tooltip: 'Attempts to display documentation about the function preceding the cursor.' +""" + +# Only run if we have the required modules +try: + import bpy + from BPyTextPlugin import * + OK = True +except ImportError: + OK = False + +def main(): + txt = bpy.data.texts.active + if not txt: + return + + (line, c) = current_line(txt) + + # Check we are in a normal context + if get_context(txt) != NORMAL: + return + + # Look backwards for first '(' without ')' + b = 0 + for i in range(c-1, -1, -1): + if line[i] == ')': b += 1 + elif line[i] == '(': + b -= 1 + if b < 0: + c = i + break + + pre = get_targets(line, c) + + if len(pre) == 0: + return + + imports = get_imports(txt) + builtins = get_builtins() + + # Identify the root (root.sub.sub.) + if imports.has_key(pre[0]): + obj = imports[pre[0]] + elif builtins.has_key(pre[0]): + obj = builtins[pre[0]] + else: + return + + # Step through sub-attributes + try: + for name in pre[1:]: + obj = getattr(obj, name) + except AttributeError: + print "Attribute not found '%s' in '%s'" % (name, '.'.join(pre)) + return + + if hasattr(obj, '__doc__') and obj.__doc__: + txt.showDocs(obj.__doc__) + +# Check we are running as a script and not imported as a module +if __name__ == "__main__" and OK: + main() diff --git a/source/blender/blenkernel/BKE_suggestions.h b/source/blender/blenkernel/BKE_suggestions.h index d0f982263c0..cbddbcfc8ad 100644 --- a/source/blender/blenkernel/BKE_suggestions.h +++ b/source/blender/blenkernel/BKE_suggestions.h @@ -60,20 +60,26 @@ typedef struct SuggList { SuggItem *selected; } SuggList; +/* Free all suggestion related memory */ void free_suggestions(); +/* Used to identify which Text object the current tools should appear against */ +void suggest_set_active(Text *text); +void suggest_clear_active(); +short suggest_is_active(Text *text); + void suggest_add(const char *name, char type); void suggest_prefix(const char *prefix); SuggItem *suggest_first(); SuggItem *suggest_last(); -void suggest_set_text(Text *text); -void suggest_clear_text(); -short suggest_is_active(Text *text); - void suggest_set_selected(SuggItem *sel); SuggItem *suggest_get_selected(); +void suggest_documentation(const char *docs); +char *suggest_get_docs(); +void suggest_clear_docs(); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/intern/suggestions.c b/source/blender/blenkernel/intern/suggestions.c index dd5b770db93..e2c951f2284 100644 --- a/source/blender/blenkernel/intern/suggestions.c +++ b/source/blender/blenkernel/intern/suggestions.c @@ -37,20 +37,23 @@ #include "BKE_text.h" #include "BKE_suggestions.h" -static SuggList suggestions= {NULL, NULL, NULL, NULL, NULL}; +static SuggList suggestions = {NULL, NULL, NULL, NULL, NULL}; static Text *suggText = NULL; -static SuggItem *lastInsert= NULL; +static SuggItem *lastInsert = NULL; +static char *documentation = NULL; +static int doc_lines = 0; -static suggest_cmp(const char *first, const char *second, int len) { +static int suggest_cmp(const char *first, const char *second, int len) { int cmp, i; for (cmp=0, i=0; iprev; @@ -61,6 +64,18 @@ void free_suggestions() { suggestions.selected = NULL; } +static void docs_free() { + if (documentation) { + MEM_freeN(documentation); + documentation = NULL; + } +} + +void free_suggestions() { + sugg_free(); + docs_free(); +} + void suggest_add(const char *name, char type) { SuggItem *newitem; @@ -87,7 +102,7 @@ void suggest_add(const char *name, char type) { void suggest_prefix(const char *prefix) { SuggItem *match, *first, *last; - int cmp, len = strlen(prefix), i; + int cmp, len = strlen(prefix); if (!suggestions.first) return; if (len==0) { @@ -111,10 +126,13 @@ void suggest_prefix(const char *prefix) { } if (first) { if (!last) last = suggestions.last; - suggestions.selected = suggestions.firstmatch = first; + suggestions.firstmatch = first; suggestions.lastmatch = last; + suggestions.selected = first; } else { - suggestions.firstmatch = suggestions.lastmatch = NULL; + suggestions.firstmatch = NULL; + suggestions.lastmatch = NULL; + suggestions.selected = NULL; } } @@ -126,11 +144,13 @@ SuggItem *suggest_last() { return suggestions.lastmatch; } -void suggest_set_text(Text *text) { +void suggest_set_active(Text *text) { + if (suggText == text) return; + suggest_clear_active(); suggText = text; } -void suggest_clear_text() { +void suggest_clear_active() { free_suggestions(); suggText = NULL; } @@ -146,3 +166,37 @@ void suggest_set_selected(SuggItem *sel) { SuggItem *suggest_get_selected() { return suggestions.selected; } + +/* Documentation methods */ + +void suggest_documentation(const char *docs) { + int len; + + if (!docs) return; + + len = strlen(docs); + + if (documentation) { + MEM_freeN(documentation); + documentation = NULL; + } + + /* Ensure documentation ends with a '\n' */ + if (docs[len-1] != '\n') { + documentation = MEM_mallocN(len+2, "Documentation"); + strncpy(documentation, docs, len); + documentation[len++] = '\n'; + } else { + documentation = MEM_mallocN(len+1, "Documentation"); + strncpy(documentation, docs, len); + } + documentation[len] = '\0'; +} + +char *suggest_get_docs() { + return documentation; +} + +void suggest_clear_docs() { + docs_free(); +} diff --git a/source/blender/python/api2_2x/Text.c b/source/blender/python/api2_2x/Text.c index 836d824e3b2..ecfb6ba1018 100644 --- a/source/blender/python/api2_2x/Text.c +++ b/source/blender/python/api2_2x/Text.c @@ -103,6 +103,7 @@ static PyObject *Text_asLines( BPy_Text * self ); static PyObject *Text_getCursorPos( BPy_Text * self ); static PyObject *Text_setCursorPos( BPy_Text * self, PyObject * args ); static PyObject *Text_suggest( BPy_Text * self, PyObject * args ); +static PyObject *Text_showDocs( BPy_Text * self, PyObject * args ); /*****************************************************************************/ /* Python BPy_Text methods table: */ @@ -136,7 +137,9 @@ static PyMethodDef BPy_Text_methods[] = { {"setCursorPos", ( PyCFunction ) Text_setCursorPos, METH_VARARGS, "(row, col) - Set the cursor position to (row, col)"}, {"suggest", ( PyCFunction ) Text_suggest, METH_VARARGS, - "(list) - List of tuples of the form (name, type) where type is one of 'm', 'v', 'f', 'k' for module, variable, function and keyword respectively"}, + "(list, prefix) - List of tuples of the form (name, type) where type is one of 'm', 'v', 'f', 'k' for module, variable, function and keyword respectively"}, + {"showDocs", ( PyCFunction ) Text_showDocs, METH_VARARGS, + "(docs) - Documentation string"}, {NULL, NULL, 0, NULL} }; @@ -548,7 +551,7 @@ static PyObject *Text_setCursorPos( BPy_Text * self, PyObject * args ) int row, col; int oldstate; - if(!self->text) + if (!self->text) return EXPP_ReturnPyObjError(PyExc_RuntimeError, "This object isn't linked to a Blender Text Object"); @@ -573,12 +576,12 @@ static PyObject *Text_suggest( BPy_Text * self, PyObject * args ) char *prefix, *name, type; SpaceText *st; - if(!self->text) + if (!self->text) return EXPP_ReturnPyObjError(PyExc_RuntimeError, "This object isn't linked to a Blender Text Object"); /* Parse args for a list of tuples */ - if(!PyArg_ParseTuple(args, "O!s", &PyList_Type, &list, &prefix)) + if (!PyArg_ParseTuple(args, "O!s", &PyList_Type, &list, &prefix)) return EXPP_ReturnPyObjError(PyExc_TypeError, "expected list of tuples followed by a string"); @@ -591,8 +594,8 @@ static PyObject *Text_suggest( BPy_Text * self, PyObject * args ) return EXPP_ReturnPyObjError(PyExc_RuntimeError, "Active text area has no Text object"); + suggest_set_active(st->text); list_len = PyList_Size(list); - suggest_clear_text(); for (i = 0; i < list_len; i++) { item = PyList_GetItem(list, i); @@ -610,7 +613,35 @@ static PyObject *Text_suggest( BPy_Text * self, PyObject * args ) suggest_add(name, type); } suggest_prefix(prefix); - suggest_set_text(st->text); + scrarea_queue_redraw(curarea); + + Py_RETURN_NONE; +} + +static PyObject *Text_showDocs( BPy_Text * self, PyObject * args ) +{ + char *docs; + SpaceText *st; + + if (!self->text) + return EXPP_ReturnPyObjError(PyExc_RuntimeError, + "This object isn't linked to a Blender Text Object"); + + if (!PyArg_ParseTuple(args, "s", &docs)) + return EXPP_ReturnPyObjError( PyExc_TypeError, + "expected a string as argument" ); + + if (curarea->spacetype != SPACE_TEXT) + return EXPP_ReturnPyObjError(PyExc_RuntimeError, + "Active space type is not text"); + + st = curarea->spacedata.first; + if (!st || !st->text) + return EXPP_ReturnPyObjError(PyExc_RuntimeError, + "Active text area has no Text object"); + + suggest_set_active(st->text); + suggest_documentation(docs); scrarea_queue_redraw(curarea); Py_RETURN_NONE; diff --git a/source/blender/src/drawtext.c b/source/blender/src/drawtext.c index ae994b3e61d..4ae87f64c47 100644 --- a/source/blender/src/drawtext.c +++ b/source/blender/src/drawtext.c @@ -87,10 +87,14 @@ #include "blendef.h" #include "winlay.h" -#define TEXTXLOC 38 +#define TEXTXLOC 38 -#define SUGG_LIST_SIZE 7 -#define SUGG_LIST_WIDTH 20 +#define SUGG_LIST_SIZE 7 +#define SUGG_LIST_WIDTH 20 +#define DOC_WIDTH 40 + +#define TOOL_SUGG_LIST 0x01 +#define TOOL_DOCUMENT 0x02 /* forward declarations */ @@ -106,6 +110,7 @@ static int check_numbers(char *string); static int check_builtinfuncs(char *string); static int check_specialvars(char *string); static int check_identifier(char ch); +static int check_whitespace(char ch); static void get_suggest_prefix(Text *text); static void confirm_suggestion(Text *text, int skipleft); @@ -1068,7 +1073,78 @@ static int do_suggest_select(SpaceText *st) return 1; } -void draw_suggestion_list(SpaceText *st) { +void draw_documentation(SpaceText *st) +{ + TextLine *tmp; + char *docs, buf[DOC_WIDTH+1]; + int len, prevsp, i, a; + int boxw=0, boxh, l, x, y; + + if (!st || !st->text) return; + if (!suggest_is_active(st->text)) return; + + docs = suggest_get_docs(); + + if (!docs) return; + + /* Count the visible lines to the cursor */ + for (tmp=st->text->curl, l=-st->top; tmp; tmp=tmp->prev, l++); + if (l<0) return; + + if(st->showlinenrs) { + x = spacetext_get_fontwidth(st)*(st->text->curc-st->left) + TXT_OFFSET + TEXTXLOC - 4; + } else { + x = spacetext_get_fontwidth(st)*(st->text->curc-st->left) + TXT_OFFSET - 4; + } + if (suggest_first()) { + x += SUGG_LIST_WIDTH*spacetext_get_fontwidth(st) + 50; + } + y = curarea->winy - st->lheight*l - 2; + + len = strlen(docs); + + boxw = DOC_WIDTH*spacetext_get_fontwidth(st) + 20; + boxh = (2*len/DOC_WIDTH+1)*st->lheight + 8; /* Rough guess at box height */ + + BIF_ThemeColor(TH_SHADE1); + glRecti(x-1, y+1, x+boxw+1, y-boxh-1); + BIF_ThemeColor(TH_BACK); + glRecti(x, y, x+boxw, y-boxh); + BIF_ThemeColor(TH_TEXT); + + len = strlen(docs); + prevsp = a = 0; + + for (i=0; i DOC_WIDTH) { + y -= st->lheight; + buf[a] = '\0'; + text_draw(st, buf, 0, 0, 1, x+4, y-1, NULL); + a = 0; + } + + /* Buffer up the next bit ready to draw */ + if (i-prevsp > DOC_WIDTH) break; /* TODO: Deal with long, unbroken strings */ + strncpy(buf+a, docs+prevsp, i-prevsp); + a += i-prevsp; + prevsp = i; + + /* Hit a new line, print what we have */ + if (docs[i] == '\n') { + y -= st->lheight; + buf[a] = '\0'; + text_draw(st, buf, 0, 0, 1, x+4, y-1, NULL); + a = 0; + } + } + } +} + +void draw_suggestion_list(SpaceText *st) +{ SuggItem *item, *first, *last, *sel; TextLine *tmp; char str[SUGG_LIST_WIDTH+1]; @@ -1079,6 +1155,9 @@ void draw_suggestion_list(SpaceText *st) { first = suggest_first(); last = suggest_last(); + + if (!first || !last) return; + sel = suggest_get_selected(); /* Count the visible lines to the cursor */ @@ -1208,6 +1287,7 @@ void drawtextspace(ScrArea *sa, void *spacedata) } draw_textscroll(st); + draw_documentation(st); draw_suggestion_list(st); curarea->win_swap= WIN_BACK_OK; @@ -1693,7 +1773,7 @@ static void confirm_suggestion(Text *text, int skipleft) { for (i=0; ispacedata.first; Text *text; int do_draw=0, p; - int suggesting=0, do_suggest=0; /* 0:just redraw, -1:clear, 1:update prefix */ + int tools=0, tools_cancel=0, tools_update=0; /* Bitmasks for operations */ if (st==NULL || st->spacetype != SPACE_TEXT) return; @@ -1768,7 +1848,10 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) return; } - suggesting = st->showsyntax && suggest_is_active(text); + if (st->showsyntax && suggest_is_active(text)) { + if (suggest_first()) tools |= TOOL_SUGG_LIST; + if (suggest_get_docs()) tools |= TOOL_DOCUMENT; + } if (event==LEFTMOUSE) { if (val) { @@ -1779,10 +1862,9 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) if (mval[0]>2 && mval[0]<20 && mval[1]>2 && mval[1]winy-2) { do_textscroll(st, 2); - do_suggest= -1; + tools_cancel |= TOOL_SUGG_LIST | TOOL_DOCUMENT; } else if (do_suggest_select(st)) { do_draw= 1; - do_suggest= 0; } else { do_selection(st, G.qual&LR_SHIFTKEY); if (txt_has_sel(text)) { @@ -1791,7 +1873,7 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) MEM_freeN(buffer); } do_draw= 1; - do_suggest= -1; + tools_cancel |= TOOL_SUGG_LIST | TOOL_DOCUMENT; } } } else if (event==MIDDLEMOUSE) { @@ -1799,15 +1881,16 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) if (do_suggest_select(st)) { confirm_suggestion(text, 0); do_draw= 1; - do_suggest= 0; - } else if (U.uiflag & USER_MMB_PASTE) { - do_selection(st, G.qual&LR_SHIFTKEY); - get_selection_buffer(text); - do_draw= 1; } else { - do_textscroll(st, 1); + if (U.uiflag & USER_MMB_PASTE) { + do_selection(st, G.qual&LR_SHIFTKEY); + get_selection_buffer(text); + do_draw= 1; + } else { + do_textscroll(st, 1); + } + tools_cancel |= TOOL_SUGG_LIST | TOOL_DOCUMENT; } - do_suggest= -1; } } else if (event==RIGHTMOUSE) { if (val) { @@ -1840,7 +1923,7 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) default: break; } - do_suggest= -1; + tools_cancel |= TOOL_SUGG_LIST | TOOL_DOCUMENT; } } else if (ascii) { if (text && text->id.lib) { @@ -1850,19 +1933,23 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) if (st->showsyntax) get_format_string(st); pop_space_text(st); do_draw= 1; - if (suggesting) { - if (ispunct(ascii) || check_whitespace(ascii)) { + if (tools & TOOL_SUGG_LIST) { + if ((ascii != '_' && ascii != '*' && ispunct(ascii)) || check_whitespace(ascii)) { confirm_suggestion(text, 1); if (st->showsyntax) get_format_string(st); - do_suggest= 0; } else { - do_suggest= 1; + tools_update |= TOOL_SUGG_LIST; } } + tools_cancel |= TOOL_DOCUMENT; } } else if (val) { - do_suggest= -1; /* Note that the default label sets this to 0, - so -1 only applies to the explicit cases below */ + + /* Cases that require tools not to be cancelled must explicitly say so. + * The default case does this to prevent window/mousemove events + * from cancelling. */ + tools_cancel |= TOOL_SUGG_LIST | TOOL_DOCUMENT; + switch (event) { case AKEY: if (G.qual & LR_ALTKEY) { @@ -2126,7 +2213,10 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) } break; case ESCKEY: - do_suggest= -1; + /* To allow ESC to close one tool at a time we remove all others from the cancel list */ + if (tools & TOOL_DOCUMENT) { + tools_cancel &= ~TOOL_SUGG_LIST; + } break; case TABKEY: if (text && text->id.lib) { @@ -2157,7 +2247,7 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) error_libdata(); break; } - if (suggesting) { + if (tools & TOOL_SUGG_LIST) { confirm_suggestion(text, 0); if (st->showsyntax) get_format_string(st); break; @@ -2186,17 +2276,14 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) } if (G.qual & (LR_ALTKEY | LR_CTRLKEY)) { txt_backspace_word(text); - do_suggest= -1; + tools_cancel |= TOOL_SUGG_LIST; } else { /* Work out which char we are about to delete */ if (text && text->curl && text->curc > 0) { char ch= text->curl->line[text->curc-1]; - if (ispunct(ch) || check_whitespace(ch)) - do_suggest= -1; - else - do_suggest= 1; - } else { - do_suggest= -1; + if (!ispunct(ch) && !check_whitespace(ch)) { + tools_update |= TOOL_SUGG_LIST; + } } txt_backspace_char(text); } @@ -2223,17 +2310,17 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) case INSERTKEY: st->overwrite= !st->overwrite; do_draw= 1; - do_suggest= 0; + tools_cancel = 0; break; case DOWNARROWKEY: - if (suggesting) { + if (tools & TOOL_SUGG_LIST) { SuggItem *sel = suggest_get_selected(); if (!sel) { suggest_set_selected(suggest_first()); } else if (sel!=suggest_last() && sel->next) { suggest_set_selected(sel->next); } - do_suggest= 0; + tools_cancel &= ~TOOL_SUGG_LIST; break; } txt_move_down(text, G.qual & LR_SHIFTKEY); @@ -2264,11 +2351,11 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) pop_space_text(st); break; case UPARROWKEY: - if (suggesting) { + if (tools & TOOL_SUGG_LIST) { SuggItem *sel = suggest_get_selected(); if (sel && sel!=suggest_first() && sel->prev) suggest_set_selected(sel->prev); - do_suggest= 0; + tools_cancel &= ~TOOL_SUGG_LIST; break; } txt_move_up(text, G.qual & LR_SHIFTKEY); @@ -2277,14 +2364,14 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) pop_space_text(st); break; case PAGEDOWNKEY: - if (suggesting) { + if (tools & TOOL_SUGG_LIST) { int i; SuggItem *sel = suggest_get_selected(); if (!sel) sel = suggest_first(); for (i=0; inext; i++, sel=sel->next) suggest_set_selected(sel->next); - do_suggest= 0; + tools_cancel &= ~TOOL_SUGG_LIST; break; } else { screen_skip(st, st->viewlines); @@ -2292,12 +2379,12 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) do_draw= 1; break; case PAGEUPKEY: - if (suggesting) { + if (tools & TOOL_SUGG_LIST) { int i; SuggItem *sel = suggest_get_selected(); for (i=0; iprev; i++, sel=sel->prev) suggest_set_selected(sel->prev); - do_suggest= 0; + tools_cancel &= ~TOOL_SUGG_LIST; break; } else { screen_skip(st, -st->viewlines); @@ -2315,33 +2402,35 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) pop_space_text(st); break; case WHEELUPMOUSE: - if (suggesting) { + if (tools & TOOL_SUGG_LIST) { SuggItem *sel = suggest_get_selected(); if (sel && sel!=suggest_first() && sel->prev) suggest_set_selected(sel->prev); - do_suggest= 0; + tools_cancel &= ~TOOL_SUGG_LIST; } else { screen_skip(st, -U.wheellinescroll); + tools_cancel &= ~TOOL_DOCUMENT; } do_draw= 1; break; case WHEELDOWNMOUSE: - if (suggesting) { + if (tools & TOOL_SUGG_LIST) { SuggItem *sel = suggest_get_selected(); if (!sel) { suggest_set_selected(suggest_first()); } else if (sel && sel!=suggest_last() && sel->next) { suggest_set_selected(sel->next); } - do_suggest= 0; + tools_cancel &= ~TOOL_SUGG_LIST; } else { screen_skip(st, U.wheellinescroll); + tools_cancel &= ~TOOL_DOCUMENT; } do_draw= 1; break; default: /* We don't want all sorts of events closing the suggestions box */ - do_suggest= 0; + tools_cancel &= ~TOOL_SUGG_LIST & ~TOOL_DOCUMENT; } } @@ -2404,11 +2493,17 @@ void winqreadtextspace(ScrArea *sa, void *spacedata, BWinEvent *evt) } } - if (suggesting) { - if (do_suggest == -1) { - suggest_clear_text(); - } else if (do_suggest == 1) { + if (tools & TOOL_SUGG_LIST) { + if (tools_update & TOOL_SUGG_LIST) { get_suggest_prefix(text); + } else if (tools_cancel & TOOL_SUGG_LIST) { + suggest_clear_active(); + } + do_draw= 1; + } + if (tools & TOOL_DOCUMENT) { + if (tools_cancel & TOOL_DOCUMENT) { + suggest_clear_docs(); } do_draw= 1; }