Added UI for suggestions list. Works with arrow-keys and mouse wheel, accept with Enter, reject with Esc or click elsewhere. Mouse selection not yet supported. The script is called from the File->Text Plugins menu.

Tidied python script, the C suggestions functions and fixed some bugs including suggestions not being freed properly.
This commit is contained in:
2008-06-25 13:51:54 +00:00
parent bdc030c664
commit e68834c75b
5 changed files with 295 additions and 97 deletions

View File

@@ -6,12 +6,11 @@ Group: 'TextPlugin'
Tooltip: 'Suggests completions for the word at the cursor in a python script'
"""
import bpy
import bpy, __builtin__, token
from Blender import Text
from StringIO import StringIO
from inspect import *
from tokenize import generate_tokens
import token
TK_TYPE = 0
TK_TOKEN = 1
@@ -21,32 +20,48 @@ TK_LINE = 4
TK_ROW = 0
TK_COL = 1
execs = [] # Used to establish the same import context across defs
keywords = ['and', 'del', 'from', 'not', 'while', 'as', 'elif', 'global',
'or', 'with', 'assert', 'else', 'if', 'pass', 'yield',
'break', 'except', 'import', 'print', 'class', 'exec', 'in',
'raise', 'continue', 'finally', 'is', 'return', 'def', 'for',
'lambda', 'try' ]
execs = [] # Used to establish the same import context across defs (import is scope sensitive)
def getBuiltins():
builtins = []
bi = dir(__builtin__)
for k in bi:
v = eval(k)
if ismodule(v): t='m'
elif callable(v): t='f'
else: t='v'
builtins.append((k, t))
return builtins
def getKeywords():
global keywords
return [(k, 'k') for k in keywords]
def getTokens(txt):
global tokens_cached
if tokens_cached==None:
lines = txt.asLines()
str = '\n'.join(lines)
readline = StringIO(str).readline
g = generate_tokens(readline)
tokens = []
for t in g: tokens.append(t)
tokens_cached = tokens
return tokens_cached
tokens_cached = None
lines = txt.asLines()
str = '\n'.join(lines)
readline = StringIO(str).readline
g = generate_tokens(readline)
tokens = []
for t in g: tokens.append(t)
return tokens
def isNameChar(s):
return s.isalnum() or s in ['_']
return (s.isalnum() or s == '_')
# Returns words preceding the cursor that are separated by periods as a list in the
# same order
# Returns words preceding the cursor that are separated by periods as a list in
# the same order
def getCompletionSymbols(txt):
(l, c)= txt.getCursorPos()
lines = txt.asLines()
@@ -68,7 +83,14 @@ def getCompletionSymbols(txt):
def getGlobals(txt):
global execs
tokens = getTokens(txt)
# Unfortunately, tokenize may fail if the script leaves brackets or strings
# open. For now we return an empty list, leaving builtins and keywords as
# the only globals. (on the TODO list)
try:
tokens = getTokens(txt)
except:
return []
globals = dict()
for i in range(len(tokens)):
@@ -92,6 +114,7 @@ def getGlobals(txt):
x = tokens[i][TK_LINE].strip()
k = tokens[i][TK_TOKEN]
execs.append(x)
exec 'try: '+x+'\nexcept: pass'
# Add the symbol name to the return list
globals[k] = 'm'
@@ -105,16 +128,17 @@ def getGlobals(txt):
# Add the import to the execs list
x = tokens[i][TK_LINE].strip()
execs.append(x)
exec 'try: '+x+'\nexcept: pass'
# Import parent module so we can process it for sub modules
parent = ''.join([t[TK_TOKEN] for t in tokens[fr+1:i-1]])
exec "import "+parent
exec 'try: import '+parent+'\nexcept: pass'
# All submodules, functions, etc.
if tokens[i][TK_TOKEN]=='*':
# Add each symbol name to the return list
exec "d="+parent+".__dict__.items()"
d = eval(parent).__dict__.items()
for k,v in d:
if not globals.has_key(k) or not globals[k]:
t='v'
@@ -130,7 +154,7 @@ def getGlobals(txt):
if not globals.has_key(k) or not globals[k]:
t='v'
try:
exec 'v='+parent+'.'+k
v = eval(parent+'.'+k)
if ismodule(v): t='m'
elif callable(v): t='f'
except: pass
@@ -149,35 +173,13 @@ def getGlobals(txt):
t='v'
globals[k] = t
return globals
return globals.items()
def cmpi0(x, y):
return cmp(x[0].lower(), y[0].lower())
def globalSuggest(txt, cs):
global execs
suggestions = dict()
(row, col) = txt.getCursorPos()
globals = getGlobals(txt)
# Sometimes we have conditional includes which will fail if the module
# cannot be found. So we protect outselves in a try block
for x in execs:
exec 'try: '+x+'\nexcept: pass'
if len(cs)==0:
sub = ''
else:
sub = cs[0].lower()
print 'Search:', sub
for k,t in globals.items():
if k.lower().startswith(sub):
suggestions[k] = t
l = list(suggestions.items())
return sorted (l, cmp=cmpi0)
return globals
# Only works for 'static' members (eg. Text.Get)
def memberSuggest(txt, cs):
@@ -194,28 +196,28 @@ def memberSuggest(txt, cs):
suggestions = dict()
(row, col) = txt.getCursorPos()
sub = cs[len(cs)-1].lower()
print 'Search:', sub
sub = cs[len(cs)-1]
t=None
m=None
pre='.'.join(cs[:-1])
try:
exec "t="+pre
m = eval(pre)
except:
print 'Failed to assign '+pre
print execs
print cs
print pre+ ' not found or not imported.'
if t!=None:
for k,v in t.__dict__.items():
if m!=None:
for k,v in m.__dict__.items():
if ismodule(v): t='m'
elif callable(v): t='f'
else: t='v'
if k.lower().startswith(sub):
suggestions[k] = t
suggestions[k] = t
l = list(suggestions.items())
return sorted (l, cmp=cmpi0)
return suggestions.items()
def cmp0(x, y):
return cmp(x[0], y[0])
def main():
txt = bpy.data.texts.active
@@ -225,10 +227,12 @@ def main():
if len(cs)<=1:
l = globalSuggest(txt, cs)
txt.suggest(l, cs[len(cs)-1])
l.extend(getBuiltins())
l.extend(getKeywords())
else:
l = memberSuggest(txt, cs)
txt.suggest(l, cs[len(cs)-1])
l.sort(cmp=cmp0)
txt.suggest(l, cs[len(cs)-1])
main()