2008-06-24 15:25:25 +00:00
|
|
|
#!BPY
|
|
|
|
"""
|
|
|
|
Name: 'Suggest'
|
|
|
|
Blender: 243
|
|
|
|
Group: 'TextPlugin'
|
2008-07-15 07:04:31 +00:00
|
|
|
Shortcut: 'Ctrl+Space'
|
2008-06-24 15:25:25 +00:00
|
|
|
Tooltip: 'Suggests completions for the word at the cursor in a python script'
|
|
|
|
"""
|
|
|
|
|
2008-06-25 13:51:54 +00:00
|
|
|
import bpy, __builtin__, token
|
2008-06-24 15:25:25 +00:00
|
|
|
from Blender import Text
|
|
|
|
from StringIO import StringIO
|
|
|
|
from inspect import *
|
|
|
|
from tokenize import generate_tokens
|
|
|
|
|
|
|
|
TK_TYPE = 0
|
|
|
|
TK_TOKEN = 1
|
|
|
|
TK_START = 2 #(srow, scol)
|
|
|
|
TK_END = 3 #(erow, ecol)
|
|
|
|
TK_LINE = 4
|
|
|
|
TK_ROW = 0
|
|
|
|
TK_COL = 1
|
|
|
|
|
2008-06-25 13:51:54 +00:00
|
|
|
execs = [] # Used to establish the same import context across defs
|
|
|
|
|
2008-06-24 15:25:25 +00:00
|
|
|
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' ]
|
|
|
|
|
2008-06-25 13:51:54 +00:00
|
|
|
|
|
|
|
def getBuiltins():
|
|
|
|
builtins = []
|
|
|
|
bi = dir(__builtin__)
|
|
|
|
for k in bi:
|
2008-06-25 21:00:39 +00:00
|
|
|
v = getattr(__builtin__, k)
|
2008-06-25 13:51:54 +00:00
|
|
|
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]
|
|
|
|
|
2008-06-24 15:25:25 +00:00
|
|
|
|
|
|
|
def getTokens(txt):
|
2008-06-25 21:00:39 +00:00
|
|
|
txt.reset()
|
|
|
|
g = generate_tokens(txt.readline)
|
2008-06-25 13:51:54 +00:00
|
|
|
tokens = []
|
|
|
|
for t in g: tokens.append(t)
|
|
|
|
return tokens
|
|
|
|
|
2008-06-24 15:25:25 +00:00
|
|
|
|
|
|
|
def isNameChar(s):
|
2008-06-25 13:51:54 +00:00
|
|
|
return (s.isalnum() or s == '_')
|
2008-06-24 15:25:25 +00:00
|
|
|
|
2008-06-25 13:51:54 +00:00
|
|
|
|
|
|
|
# Returns words preceding the cursor that are separated by periods as a list in
|
|
|
|
# the same order
|
2008-06-24 15:25:25 +00:00
|
|
|
def getCompletionSymbols(txt):
|
|
|
|
(l, c)= txt.getCursorPos()
|
|
|
|
lines = txt.asLines()
|
|
|
|
line = lines[l]
|
|
|
|
a=0
|
|
|
|
for a in range(1, c+1):
|
|
|
|
if not isNameChar(line[c-a]) and line[c-a]!='.':
|
|
|
|
a -= 1
|
|
|
|
break
|
|
|
|
return line[c-a:c].split('.')
|
|
|
|
|
|
|
|
|
2008-07-15 07:04:31 +00:00
|
|
|
def getImports(txt):
|
|
|
|
imports = []
|
|
|
|
|
|
|
|
# Unfortunately, tokenize may fail if the script leaves brackets or strings
|
|
|
|
# open. For now we return an empty list until I have a better idea. Maybe
|
|
|
|
# parse manually.
|
|
|
|
try:
|
|
|
|
tokens = getTokens(txt)
|
|
|
|
except:
|
|
|
|
return []
|
|
|
|
|
|
|
|
for i in range(1, len(tokens)):
|
|
|
|
|
|
|
|
# Handle all import statements
|
|
|
|
if tokens[i-1][TK_TOKEN] == 'import':
|
|
|
|
|
|
|
|
# Find 'from' if it exists
|
|
|
|
fr = -1
|
|
|
|
for a in range(1, i):
|
|
|
|
if tokens[i-a][TK_TYPE] == token.NEWLINE: break
|
|
|
|
if tokens[i-a][TK_TOKEN] == 'from':
|
|
|
|
fr = i-a
|
|
|
|
break
|
|
|
|
|
|
|
|
# Handle: import ___[.___][,___[.___]]
|
|
|
|
if fr<0:
|
|
|
|
parent = ''
|
|
|
|
|
|
|
|
# Handle: from ___[.___] import ___[,___]
|
|
|
|
else: # fr>=0:
|
|
|
|
parent = ''.join([t[TK_TOKEN] for t in tokens[fr+1:i-1]])
|
|
|
|
|
|
|
|
module = ''
|
|
|
|
while i < len(tokens)-1:
|
|
|
|
if tokens[i][TK_TYPE] == token.NAME:
|
|
|
|
|
|
|
|
# Get the module name
|
|
|
|
module = module + tokens[i][TK_TOKEN]
|
|
|
|
|
|
|
|
if tokens[i+1][TK_TOKEN] == '.':
|
|
|
|
module += '.'
|
|
|
|
i += 1
|
|
|
|
else:
|
|
|
|
# Add the module name and parent to the dict
|
|
|
|
imports.append((module, parent))
|
|
|
|
module = ''
|
|
|
|
|
|
|
|
elif tokens[i][TK_TOKEN]!=',':
|
|
|
|
break
|
|
|
|
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
# Process imports for: from ___ import *
|
|
|
|
for imp,frm in imports:
|
|
|
|
print imp, frm
|
|
|
|
if frm == '':
|
|
|
|
try: __import__(imp)
|
|
|
|
except: print '^ERR^'
|
|
|
|
else:
|
|
|
|
try: __import__(frm, globals(), locals(), [imp])
|
|
|
|
except: print '^ERR^'
|
|
|
|
|
|
|
|
|
|
|
|
|
2008-06-24 15:25:25 +00:00
|
|
|
# Returns a list of tuples of symbol names and their types (name, type) where
|
|
|
|
# type is one of:
|
|
|
|
# m (module/class) Has its own members (includes classes)
|
|
|
|
# v (variable) Has a type which may have its own members
|
|
|
|
# f (function) Callable and may have a return type (with its own members)
|
|
|
|
# It also updates the global import context (via execs)
|
|
|
|
def getGlobals(txt):
|
|
|
|
global execs
|
|
|
|
|
2008-06-25 13:51:54 +00:00
|
|
|
# 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 []
|
|
|
|
|
2008-06-24 15:25:25 +00:00
|
|
|
globals = dict()
|
|
|
|
for i in range(len(tokens)):
|
|
|
|
|
|
|
|
# Handle all import statements
|
|
|
|
if i>=1 and tokens[i-1][TK_TOKEN]=='import':
|
|
|
|
|
|
|
|
# Find 'from' if it exists
|
|
|
|
fr= -1
|
|
|
|
for a in range(1, i):
|
|
|
|
if tokens[i-a][TK_TYPE]==token.NEWLINE: break
|
|
|
|
if tokens[i-a][TK_TOKEN]=='from':
|
|
|
|
fr=i-a
|
|
|
|
break
|
|
|
|
|
|
|
|
# Handle: import ___[,___]
|
|
|
|
if fr<0:
|
|
|
|
|
|
|
|
while True:
|
|
|
|
if tokens[i][TK_TYPE]==token.NAME:
|
|
|
|
# Add the import to the execs list
|
|
|
|
x = tokens[i][TK_LINE].strip()
|
|
|
|
k = tokens[i][TK_TOKEN]
|
|
|
|
execs.append(x)
|
2008-06-25 13:51:54 +00:00
|
|
|
exec 'try: '+x+'\nexcept: pass'
|
2008-06-24 15:25:25 +00:00
|
|
|
|
|
|
|
# Add the symbol name to the return list
|
|
|
|
globals[k] = 'm'
|
|
|
|
elif tokens[i][TK_TOKEN]!=',':
|
|
|
|
break
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
# Handle statement: from ___[.___] import ___[,___]
|
|
|
|
else: # fr>=0:
|
|
|
|
|
|
|
|
# Add the import to the execs list
|
|
|
|
x = tokens[i][TK_LINE].strip()
|
|
|
|
execs.append(x)
|
2008-06-25 13:51:54 +00:00
|
|
|
exec 'try: '+x+'\nexcept: pass'
|
2008-06-24 15:25:25 +00:00
|
|
|
|
|
|
|
# Import parent module so we can process it for sub modules
|
|
|
|
parent = ''.join([t[TK_TOKEN] for t in tokens[fr+1:i-1]])
|
2008-06-25 13:51:54 +00:00
|
|
|
exec 'try: import '+parent+'\nexcept: pass'
|
2008-06-24 15:25:25 +00:00
|
|
|
|
|
|
|
# All submodules, functions, etc.
|
|
|
|
if tokens[i][TK_TOKEN]=='*':
|
|
|
|
|
|
|
|
# Add each symbol name to the return list
|
2008-06-25 13:51:54 +00:00
|
|
|
d = eval(parent).__dict__.items()
|
2008-06-24 15:25:25 +00:00
|
|
|
for k,v in d:
|
|
|
|
if not globals.has_key(k) or not globals[k]:
|
|
|
|
t='v'
|
|
|
|
if ismodule(v): t='m'
|
|
|
|
elif callable(v): t='f'
|
|
|
|
globals[k] = t
|
|
|
|
|
|
|
|
# Specific function, submodule, etc.
|
|
|
|
else:
|
|
|
|
while True:
|
|
|
|
if tokens[i][TK_TYPE]==token.NAME:
|
|
|
|
k = tokens[i][TK_TOKEN]
|
|
|
|
if not globals.has_key(k) or not globals[k]:
|
|
|
|
t='v'
|
|
|
|
try:
|
2008-06-25 13:51:54 +00:00
|
|
|
v = eval(parent+'.'+k)
|
2008-06-24 15:25:25 +00:00
|
|
|
if ismodule(v): t='m'
|
|
|
|
elif callable(v): t='f'
|
|
|
|
except: pass
|
|
|
|
globals[k] = t
|
|
|
|
elif tokens[i][TK_TOKEN]!=',':
|
|
|
|
break
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
elif tokens[i][TK_TYPE]==token.NAME and tokens[i][TK_TOKEN] not in keywords and (i==0 or tokens[i-1][TK_TOKEN]!='.'):
|
|
|
|
k = tokens[i][TK_TOKEN]
|
|
|
|
if not globals.has_key(k) or not globals[k]:
|
|
|
|
t=None
|
|
|
|
if (i>0 and tokens[i-1][TK_TOKEN]=='def'):
|
|
|
|
t='f'
|
|
|
|
else:
|
|
|
|
t='v'
|
|
|
|
globals[k] = t
|
|
|
|
|
2008-06-25 13:51:54 +00:00
|
|
|
return globals.items()
|
2008-06-24 15:25:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
def globalSuggest(txt, cs):
|
|
|
|
globals = getGlobals(txt)
|
2008-06-25 13:51:54 +00:00
|
|
|
return globals
|
|
|
|
|
2008-06-24 15:25:25 +00:00
|
|
|
|
|
|
|
# Only works for 'static' members (eg. Text.Get)
|
|
|
|
def memberSuggest(txt, cs):
|
|
|
|
global execs
|
|
|
|
|
|
|
|
# Populate the execs for imports
|
|
|
|
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'
|
|
|
|
|
|
|
|
suggestions = dict()
|
|
|
|
(row, col) = txt.getCursorPos()
|
|
|
|
|
2008-06-25 13:51:54 +00:00
|
|
|
sub = cs[len(cs)-1]
|
2008-06-24 15:25:25 +00:00
|
|
|
|
2008-06-25 13:51:54 +00:00
|
|
|
m=None
|
2008-06-24 15:25:25 +00:00
|
|
|
pre='.'.join(cs[:-1])
|
|
|
|
try:
|
2008-06-25 13:51:54 +00:00
|
|
|
m = eval(pre)
|
2008-06-24 15:25:25 +00:00
|
|
|
except:
|
2008-06-25 13:51:54 +00:00
|
|
|
print pre+ ' not found or not imported.'
|
2008-06-24 15:25:25 +00:00
|
|
|
|
2008-06-25 13:51:54 +00:00
|
|
|
if m!=None:
|
|
|
|
for k,v in m.__dict__.items():
|
2008-06-24 15:25:25 +00:00
|
|
|
if ismodule(v): t='m'
|
|
|
|
elif callable(v): t='f'
|
|
|
|
else: t='v'
|
2008-06-25 13:51:54 +00:00
|
|
|
suggestions[k] = t
|
2008-06-24 15:25:25 +00:00
|
|
|
|
2008-06-25 13:51:54 +00:00
|
|
|
return suggestions.items()
|
|
|
|
|
|
|
|
|
|
|
|
def cmp0(x, y):
|
|
|
|
return cmp(x[0], y[0])
|
|
|
|
|
2008-06-24 15:25:25 +00:00
|
|
|
|
|
|
|
def main():
|
|
|
|
txt = bpy.data.texts.active
|
|
|
|
if txt==None: return
|
|
|
|
|
|
|
|
cs = getCompletionSymbols(txt)
|
|
|
|
|
|
|
|
if len(cs)<=1:
|
|
|
|
l = globalSuggest(txt, cs)
|
2008-06-25 13:51:54 +00:00
|
|
|
l.extend(getBuiltins())
|
|
|
|
l.extend(getKeywords())
|
2008-06-24 15:25:25 +00:00
|
|
|
else:
|
|
|
|
l = memberSuggest(txt, cs)
|
2008-06-25 13:51:54 +00:00
|
|
|
|
|
|
|
l.sort(cmp=cmp0)
|
|
|
|
txt.suggest(l, cs[len(cs)-1])
|
2008-06-24 15:25:25 +00:00
|
|
|
|
|
|
|
main()
|