Improvements to the base BPyTextPlugin module:

- Added a centralized function for resolving targets (aaa.bbb.ccc)
 - Added documentation support for locally defined classes and methods
 - The time taken to parse now dictates how long to use the cache before parsing again
 - Other tweaks and comments and support for numeric var types

The text plugin scripts have been updated to make use of these features.
This commit is contained in:
2008-08-15 23:14:22 +00:00
parent bda3e4f8e2
commit d1d1d2b870
4 changed files with 257 additions and 169 deletions

View File

@@ -1,8 +1,34 @@
"""The BPyTextPlugin Module
Use get_cached_descriptor(txt) to retrieve information about the script held in
the txt Text object.
Use print_cache_for(txt) to print the information to the console.
Use line, cursor = current_line(txt) to get the logical line and cursor position
Use get_targets(line, cursor) to find out what precedes the cursor:
aaa.bbb.cc|c.ddd -> ['aaa', 'bbb', 'cc']
Use resolve_targets(txt, targets) to turn a target list into a usable object if
one is found to match.
"""
import bpy, sys, os import bpy, sys, os
import __builtin__, tokenize import __builtin__, tokenize
from Blender.sys import time from Blender.sys import time
from tokenize import generate_tokens, TokenError, \ from tokenize import generate_tokens, TokenError, \
COMMENT, DEDENT, INDENT, NAME, NEWLINE, NL, STRING COMMENT, DEDENT, INDENT, NAME, NEWLINE, NL, STRING, NUMBER
class Definition():
"""Describes a definition or defined object through its name, line number
and docstring. This is the base class for definition based descriptors.
"""
def __init__(self, name, lineno, doc=''):
self.name = name
self.lineno = lineno
self.doc = doc
class ScriptDesc(): class ScriptDesc():
"""Describes a script through lists of further descriptor objects (classes, """Describes a script through lists of further descriptor objects (classes,
@@ -19,43 +45,40 @@ class ScriptDesc():
self.defs = defs self.defs = defs
self.vars = vars self.vars = vars
self.incomplete = incomplete self.incomplete = incomplete
self.time = 0 self.parse_due = 0
def set_time(self): def set_delay(self, delay):
self.time = time() self.parse_due = time() + delay
class ClassDesc(): class ClassDesc(Definition):
"""Describes a class through lists of further descriptor objects (defs and """Describes a class through lists of further descriptor objects (defs and
vars). The name of the class is held by the name field and the line on vars). The name of the class is held by the name field and the line on
which it is defined is held in lineno. which it is defined is held in lineno.
""" """
def __init__(self, name, defs, vars, lineno): def __init__(self, name, defs, vars, lineno, doc=''):
self.name = name Definition.__init__(self, name, lineno, doc)
self.defs = defs self.defs = defs
self.vars = vars self.vars = vars
self.lineno = lineno
class FunctionDesc(): class FunctionDesc(Definition):
"""Describes a function through its name and list of parameters (name, """Describes a function through its name and list of parameters (name,
params) and the line on which it is defined (lineno). params) and the line on which it is defined (lineno).
""" """
def __init__(self, name, params, lineno): def __init__(self, name, params, lineno, doc=''):
self.name = name Definition.__init__(self, name, lineno, doc)
self.params = params self.params = params
self.lineno = lineno
class VarDesc(): class VarDesc(Definition):
"""Describes a variable through its name and type (if ascertainable) and the """Describes a variable through its name and type (if ascertainable) and the
line on which it is defined (lineno). If no type can be determined, type line on which it is defined (lineno). If no type can be determined, type
will equal None. will equal None.
""" """
def __init__(self, name, type, lineno): def __init__(self, name, type, lineno):
self.name = name Definition.__init__(self, name, lineno)
self.type = type # None for unknown (supports: dict/list/str) self.type = type # None for unknown (supports: dict/list/str)
self.lineno = lineno
# Context types # Context types
CTX_UNSET = -1 CTX_UNSET = -1
@@ -64,9 +87,6 @@ CTX_SINGLE_QUOTE = 1
CTX_DOUBLE_QUOTE = 2 CTX_DOUBLE_QUOTE = 2
CTX_COMMENT = 3 CTX_COMMENT = 3
# Special time period constants
TP_AUTO = -1
# Python keywords # Python keywords
KEYWORDS = ['and', 'del', 'from', 'not', 'while', 'as', 'elif', 'global', KEYWORDS = ['and', 'del', 'from', 'not', 'while', 'as', 'elif', 'global',
'or', 'with', 'assert', 'else', 'if', 'pass', 'yield', 'or', 'with', 'assert', 'else', 'if', 'pass', 'yield',
@@ -104,8 +124,76 @@ def _load_module_names():
_load_module_names() _load_module_names()
def _trim_doc(doc):
"""Trims the quotes from a quoted STRING token (eg. "'''text'''" -> "text")
"""
l = len(doc)
i = 0
while i < l/2 and (doc[i] == "'" or doc[i] == '"'):
i += 1
return doc[i:-i]
def get_cached_descriptor(txt, period=TP_AUTO): def resolve_targets(txt, targets):
"""Attempts to return a useful object for the locally or externally defined
entity described by targets. If the object is local (defined in txt), a
Definition instance is returned. If the object is external (imported or
built in), the object itself is returned. If no object can be found, None is
returned.
"""
count = len(targets)
if count==0: return None
obj = None
local = None
i = 1
desc = get_cached_descriptor(txt)
if desc.classes.has_key(targets[0]):
local = desc.classes[targets[0]]
elif desc.defs.has_key(targets[0]):
local = desc.defs[targets[0]]
elif desc.vars.has_key(targets[0]):
obj = desc.vars[targets[0]].type
if local:
while i < count:
if hasattr(local, 'classes') and local.classes.has_key(targets[i]):
local = local.classes[targets[i]]
elif hasattr(local, 'defs') and local.defs.has_key(targets[i]):
local = local.defs[targets[i]]
elif hasattr(local, 'vars') and local.vars.has_key(targets[i]):
obj = local.vars[targets[i]].type
local = None
i += 1
break
else:
local = None
break
i += 1
if local: return local
if not obj:
if desc.imports.has_key(targets[0]):
obj = desc.imports[targets[0]]
else:
builtins = get_builtins()
if builtins.has_key(targets[0]):
obj = builtins[targets[0]]
while obj and i < count:
if hasattr(obj, targets[i]):
obj = getattr(obj, targets[i])
else:
obj = None
break
i += 1
return obj
def get_cached_descriptor(txt, force_parse=0):
"""Returns the cached ScriptDesc for the specified Text object 'txt'. If the """Returns the cached ScriptDesc for the specified Text object 'txt'. If the
script has not been parsed in the last 'period' seconds it will be reparsed script has not been parsed in the last 'period' seconds it will be reparsed
to obtain this descriptor. to obtain this descriptor.
@@ -116,20 +204,11 @@ def get_cached_descriptor(txt, period=TP_AUTO):
global _parse_cache global _parse_cache
if period == TP_AUTO:
m = txt.nlines
r = 1
while True:
m = m >> 2
if not m: break
r = r << 1
period = r
parse = True parse = True
key = hash(txt) key = hash(txt)
if _parse_cache.has_key(key): if not force_parse and _parse_cache.has_key(key):
desc = _parse_cache[key] desc = _parse_cache[key]
if desc.time >= time() - period: if desc.parse_due > time():
parse = desc.incomplete parse = desc.incomplete
if parse: if parse:
@@ -147,6 +226,7 @@ def parse_text(txt):
flag set and information processed up to this point will still be accessible. flag set and information processed up to this point will still be accessible.
""" """
start_time = time()
txt.reset() txt.reset()
tokens = generate_tokens(txt.readline) # Throws TokenError tokens = generate_tokens(txt.readline) # Throws TokenError
@@ -171,12 +251,12 @@ def parse_text(txt):
indent = 0 indent = 0
prev_type = -1 prev_type = -1
prev_string = '' prev_text = ''
incomplete = False incomplete = False
while True: while True:
try: try:
type, string, start, end, line = tokens.next() type, text, start, end, line = tokens.next()
except StopIteration: except StopIteration:
break break
except TokenError, IndentationError: except TokenError, IndentationError:
@@ -204,33 +284,33 @@ def parse_text(txt):
# Default, look for 'from' or 'import' to start # Default, look for 'from' or 'import' to start
if imp_step == 0: if imp_step == 0:
if string == 'from': if text == 'from':
imp_tmp = [] imp_tmp = []
imp_step = 1 imp_step = 1
elif string == 'import': elif text == 'import':
imp_from = None imp_from = None
imp_tmp = [] imp_tmp = []
imp_step = 2 imp_step = 2
# Found a 'from', create imp_from in form '???.???...' # Found a 'from', create imp_from in form '???.???...'
elif imp_step == 1: elif imp_step == 1:
if string == 'import': if text == 'import':
imp_from = '.'.join(imp_tmp) imp_from = '.'.join(imp_tmp)
imp_tmp = [] imp_tmp = []
imp_step = 2 imp_step = 2
elif type == NAME: elif type == NAME:
imp_tmp.append(string) imp_tmp.append(text)
elif string != '.': elif text != '.':
imp_step = 0 # Invalid syntax imp_step = 0 # Invalid syntax
# Found 'import', imp_from is populated or None, create imp_name # Found 'import', imp_from is populated or None, create imp_name
elif imp_step == 2: elif imp_step == 2:
if string == 'as': if text == 'as':
imp_name = '.'.join(imp_tmp) imp_name = '.'.join(imp_tmp)
imp_step = 3 imp_step = 3
elif type == NAME or string == '*': elif type == NAME or text == '*':
imp_tmp.append(string) imp_tmp.append(text)
elif string != '.': elif text != '.':
imp_name = '.'.join(imp_tmp) imp_name = '.'.join(imp_tmp)
imp_symb = imp_name imp_symb = imp_name
imp_store = True imp_store = True
@@ -238,7 +318,7 @@ def parse_text(txt):
# Found 'as', change imp_symb to this value and go back to step 2 # Found 'as', change imp_symb to this value and go back to step 2
elif imp_step == 3: elif imp_step == 3:
if type == NAME: if type == NAME:
imp_symb = string imp_symb = text
else: else:
imp_store = True imp_store = True
@@ -268,7 +348,7 @@ def parse_text(txt):
imports[imp_symb] = module imports[imp_symb] = module
# More to import from the same module? # More to import from the same module?
if string == ',': if text == ',':
imp_tmp = [] imp_tmp = []
imp_step = 2 imp_step = 2
else: else:
@@ -283,7 +363,7 @@ def parse_text(txt):
# Look for 'class' # Look for 'class'
if cls_step == 0: if cls_step == 0:
if string == 'class': if text == 'class':
cls_name = None cls_name = None
cls_lineno = start[0] cls_lineno = start[0]
cls_indent = indent cls_indent = indent
@@ -293,30 +373,32 @@ def parse_text(txt):
elif cls_step == 1: elif cls_step == 1:
if not cls_name: if not cls_name:
if type == NAME: if type == NAME:
cls_name = string cls_name = text
cls_sline = False cls_sline = False
cls_defs = dict() cls_defs = dict()
cls_vars = dict() cls_vars = dict()
elif string == ':': elif text == ':':
cls_step = 2 cls_step = 2
# Found 'class' name ... ':', now check if it's a single line statement # Found 'class' name ... ':', now check if it's a single line statement
elif cls_step == 2: elif cls_step == 2:
if type == NEWLINE: if type == NEWLINE:
cls_sline = False cls_sline = False
cls_step = 3
else: else:
cls_sline = True cls_sline = True
cls_step = 3 cls_doc = ''
cls_step = 3
elif cls_step == 3: elif cls_step == 3:
if not cls_doc and type == STRING:
cls_doc = _trim_doc(text)
if cls_sline: if cls_sline:
if type == NEWLINE: if type == NEWLINE:
classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars, cls_lineno) classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars, cls_lineno, cls_doc)
cls_step = 0 cls_step = 0
else: else:
if type == DEDENT and indent <= cls_indent: if type == DEDENT and indent <= cls_indent:
classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars, cls_lineno) classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars, cls_lineno, cls_doc)
cls_step = 0 cls_step = 0
################# #################
@@ -325,7 +407,7 @@ def parse_text(txt):
# Look for 'def' # Look for 'def'
if def_step == 0: if def_step == 0:
if string == 'def': if text == 'def':
def_name = None def_name = None
def_lineno = start[0] def_lineno = start[0]
def_step = 1 def_step = 1
@@ -333,21 +415,43 @@ def parse_text(txt):
# Found 'def', look for def_name followed by '(' # Found 'def', look for def_name followed by '('
elif def_step == 1: elif def_step == 1:
if type == NAME: if type == NAME:
def_name = string def_name = text
def_params = [] def_params = []
elif def_name and string == '(': elif def_name and text == '(':
def_step = 2 def_step = 2
# Found 'def' name '(', now identify the parameters upto ')' # Found 'def' name '(', now identify the parameters upto ')'
# TODO: Handle ellipsis '...' # TODO: Handle ellipsis '...'
elif def_step == 2: elif def_step == 2:
if type == NAME: if type == NAME:
def_params.append(string) def_params.append(text)
elif string == ')': elif text == ':':
def_step = 3
# Found 'def' ... ':', now check if it's a single line statement
elif def_step == 3:
if type == NEWLINE:
def_sline = False
else:
def_sline = True
def_doc = ''
def_step = 4
elif def_step == 4:
if type == STRING:
def_doc = _trim_doc(text)
newdef = None
if def_sline:
if type == NEWLINE:
newdef = FunctionDesc(def_name, def_params, def_lineno, def_doc)
else:
if type == NAME:
newdef = FunctionDesc(def_name, def_params, def_lineno, def_doc)
if newdef:
if cls_step > 0: # Parsing a class if cls_step > 0: # Parsing a class
cls_defs[def_name] = FunctionDesc(def_name, def_params, def_lineno) cls_defs[def_name] = newdef
else: else:
defs[def_name] = FunctionDesc(def_name, def_params, def_lineno) defs[def_name] = newdef
def_step = 0 def_step = 0
########################## ##########################
@@ -357,39 +461,42 @@ def parse_text(txt):
if cls_step > 0: # Parsing a class if cls_step > 0: # Parsing a class
# Look for 'self.???' # Look for 'self.???'
if var1_step == 0: if var1_step == 0:
if string == 'self': if text == 'self':
var1_step = 1 var1_step = 1
elif var1_step == 1: elif var1_step == 1:
if string == '.': if text == '.':
var_name = None var_name = None
var1_step = 2 var1_step = 2
else: else:
var1_step = 0 var1_step = 0
elif var1_step == 2: elif var1_step == 2:
if type == NAME: if type == NAME:
var_name = string var_name = text
if cls_vars.has_key(var_name): if cls_vars.has_key(var_name):
var_step = 0 var_step = 0
else: else:
var1_step = 3 var1_step = 3
elif var1_step == 3: elif var1_step == 3:
if string == '=': if text == '=':
var1_step = 4 var1_step = 4
elif var1_step == 4: elif var1_step == 4:
var_type = None var_type = None
if string == '[': if type == NUMBER:
close = line.find(']', end[1]) if text.find('.') != -1: var_type = float
var_type = list else: var_type = int
elif type == STRING: elif type == STRING:
close = end[1] close = end[1]
var_type = str var_type = str
elif string == '(': elif text == '[':
close = line.find(']', end[1])
var_type = list
elif text == '(':
close = line.find(')', end[1]) close = line.find(')', end[1])
var_type = tuple var_type = tuple
elif string == '{': elif text == '{':
close = line.find('}', end[1]) close = line.find('}', end[1])
var_type = dict var_type = dict
elif string == 'dict': elif text == 'dict':
close = line.find(')', end[1]) close = line.find(')', end[1])
var_type = dict var_type = dict
if var_type and close+1 < len(line): if var_type and close+1 < len(line):
@@ -401,27 +508,28 @@ def parse_text(txt):
elif def_step > 0: # Parsing a def elif def_step > 0: # Parsing a def
# Look for 'global ???[,???]' # Look for 'global ???[,???]'
if var2_step == 0: if var2_step == 0:
if string == 'global': if text == 'global':
var2_step = 1 var2_step = 1
elif var2_step == 1: elif var2_step == 1:
if type == NAME: if type == NAME:
vars[string] = True if not vars.has_key(text):
elif string != ',' and type != NL: vars[text] = VarDesc(text, None, start[0])
elif text != ',' and type != NL:
var2_step == 0 var2_step == 0
else: # In global scope else: # In global scope
if var3_step == 0: if var3_step == 0:
# Look for names # Look for names
if string == 'for': if text == 'for':
var_accum = dict() var_accum = dict()
var_forflag = True var_forflag = True
elif string == '=' or (var_forflag and string == 'in'): elif text == '=' or (var_forflag and text == 'in'):
var_forflag = False var_forflag = False
var3_step = 1 var3_step = 1
elif type == NAME: elif type == NAME:
if prev_string != '.' and not vars.has_key(string): if prev_text != '.' and not vars.has_key(text):
var_accum[string] = VarDesc(string, None, start[0]) var_accum[text] = VarDesc(text, None, start[0])
elif not string in [',', '(', ')', '[', ']']: elif not text in [',', '(', ')', '[', ']']:
var_accum = dict() var_accum = dict()
var_forflag = False var_forflag = False
elif var3_step == 1: elif var3_step == 1:
@@ -431,10 +539,13 @@ def parse_text(txt):
else: else:
var_name = var_accum.keys()[0] var_name = var_accum.keys()[0]
var_type = None var_type = None
if string == '[': var_type = list if type == NUMBER:
if text.find('.') != -1: var_type = float
else: var_type = int
elif type == STRING: var_type = str elif type == STRING: var_type = str
elif string == '(': var_type = tuple elif text == '[': var_type = list
elif string == '{': var_type = dict elif text == '(': var_type = tuple
elif text == '{': var_type = dict
vars[var_name] = VarDesc(var_name, var_type, start[0]) vars[var_name] = VarDesc(var_name, var_type, start[0])
var3_step = 0 var3_step = 0
@@ -443,10 +554,10 @@ def parse_text(txt):
####################### #######################
prev_type = type prev_type = type
prev_string = string prev_text = text
desc = ScriptDesc(txt.name, imports, classes, defs, vars, incomplete) desc = ScriptDesc(txt.name, imports, classes, defs, vars, incomplete)
desc.set_time() desc.set_delay(10 * (time()-start_time) + 0.05)
global _parse_cache global _parse_cache
_parse_cache[hash(txt)] = desc _parse_cache[hash(txt)] = desc
@@ -587,13 +698,11 @@ def get_targets(line, cursor):
returns them as a list in the same order. returns them as a list in the same order.
""" """
targets = []
i = cursor - 1 i = cursor - 1
while i >= 0 and (line[i].isalnum() or line[i] == '_' or line[i] == '.'): while i >= 0 and (line[i].isalnum() or line[i] == '_' or line[i] == '.'):
i -= 1 i -= 1
pre = line[i+1:cursor] return line[i+1:cursor].split('.')
return pre.split('.')
def get_defs(txt): def get_defs(txt):
"""Returns a dictionary which maps definition names in the source code to """Returns a dictionary which maps definition names in the source code to
@@ -650,6 +759,7 @@ def print_cache_for(txt, period=sys.maxint):
print 'Defs:' print 'Defs:'
for name, ddesc in desc.defs.items(): for name, ddesc in desc.defs.items():
print ' ', name, ddesc.params, ddesc.lineno print ' ', name, ddesc.params, ddesc.lineno
print ' ', ddesc.doc
print '------------------------------------------------' print '------------------------------------------------'
print 'Vars:' print 'Vars:'
for name, vdesc in desc.vars.items(): for name, vdesc in desc.vars.items():
@@ -663,10 +773,12 @@ def print_cache_for(txt, period=sys.maxint):
for clsnme, clsdsc in desc.classes.items(): for clsnme, clsdsc in desc.classes.items():
print ' *********************************' print ' *********************************'
print ' Name:', clsnme print ' Name:', clsnme
print ' ', clsdsc.doc
print ' ---------------------------------' print ' ---------------------------------'
print ' Defs:' print ' Defs:'
for name, ddesc in clsdsc.defs.items(): for name, ddesc in clsdsc.defs.items():
print ' ', name, ddesc.params, ddesc.lineno print ' ', name, ddesc.params, ddesc.lineno
print ' ', ddesc.doc
print ' ---------------------------------' print ' ---------------------------------'
print ' Vars:' print ' Vars:'
for name, vdesc in clsdsc.vars.items(): for name, vdesc in clsdsc.vars.items():

View File

@@ -27,49 +27,36 @@ def main():
if get_context(txt) != CTX_NORMAL: if get_context(txt) != CTX_NORMAL:
return return
# Look backwards for first '(' without ')' # Identify the name under the cursor
b = 0 llen = len(line)
found = False while c<llen and (line[c].isalnum() or line[c]=='_'):
for i in range(c-1, -1, -1): c += 1
if line[i] == ')': b += 1
elif line[i] == '(':
b -= 1
if b < 0:
found = True
c = i
break
# Otherwise identify the name under the cursor targets = get_targets(line, c)
if not found:
llen = len(line)
while c<llen and (line[c].isalnum() or line[c]=='_'):
c += 1
pre = get_targets(line, c) # If no name under cursor, look backward to see if we're in function parens
if len(targets) == 0 or targets[0] == '':
# Look backwards for first '(' without ')'
b = 0
found = False
for i in range(c-1, -1, -1):
if line[i] == ')': b += 1
elif line[i] == '(':
b -= 1
if b < 0:
found = True
c = i
break
if found: targets = get_targets(line, c)
if len(targets) == 0 or targets[0] == '':
return
if len(pre) == 0: obj = resolve_targets(txt, targets)
return if not obj: return
imports = get_imports(txt) if isinstance(obj, Definition): # Local definition
builtins = get_builtins() txt.showDocs(obj.doc)
elif hasattr(obj, '__doc__') and obj.__doc__:
# 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__) txt.showDocs(obj.__doc__)
# Check we are running as a script and not imported as a module # Check we are running as a script and not imported as a module

View File

@@ -27,23 +27,15 @@ def main():
if get_context(txt) != CTX_NORMAL: if get_context(txt) != CTX_NORMAL:
return return
pre = get_targets(line, c) targets = get_targets(line, c)
if len(pre) <= 1: if targets[0] == '': # Check if we are looking at a constant [] {} '' etc.
return i = c - len('.'.join(targets)) - 1
imports = get_imports(txt)
builtins = get_builtins()
# Identify the root (root.sub.sub.)
obj = None
if pre[0] == '':
i = c - len('.'.join(pre)) - 1
if i >= 0: if i >= 0:
if line[i] == '"' or line[i] == "'": if line[i] == '"' or line[i] == "'":
obj = str targets[0] = 'str'
elif line[i] == '}': elif line[i] == '}':
obj = dict targets[0] = 'dict'
elif line[i] == ']': # Could be array elem x[y] or list [y] elif line[i] == ']': # Could be array elem x[y] or list [y]
i = line.rfind('[', 0, i) - 1 i = line.rfind('[', 0, i) - 1
while i >= 0: while i >= 0:
@@ -54,47 +46,44 @@ def main():
break break
i -= 1 i -= 1
if i < 0: if i < 0:
obj = list targets[0] = 'list'
elif imports.has_key(pre[0]):
obj = imports[pre[0]]
elif builtins.has_key(pre[0]):
obj = builtins[pre[0]]
else:
desc = get_cached_descriptor(txt)
if desc.vars.has_key(pre[0]):
obj = desc.vars[pre[0]].type
obj = resolve_targets(txt, targets[:-1])
if not obj: if not obj:
return return
# Step through sub-attributes
try:
for name in pre[1:-1]:
obj = getattr(obj, name)
except AttributeError:
print "Attribute not found '%s' in '%s'" % (name, '.'.join(pre))
return
try:
attr = obj.__dict__.keys()
except AttributeError:
attr = dir(obj)
else:
if not attr:
attr = dir(obj)
items = [] items = []
for k in attr:
if isinstance(obj, VarDesc):
obj = obj.type
if isinstance(obj, Definition): # Locally defined
if hasattr(obj, 'classes'):
items.extend([(s, 'f') for s in obj.classes.keys()])
if hasattr(obj, 'defs'):
items.extend([(s, 'f') for s in obj.defs.keys()])
if hasattr(obj, 'vars'):
items.extend([(s, 'v') for s in obj.vars.keys()])
else: # Otherwise we have an imported or builtin object
try: try:
v = getattr(obj, k) attr = obj.__dict__.keys()
except (AttributeError, TypeError): # Some attributes are not readable except AttributeError:
pass attr = dir(obj)
else: else:
items.append((k, type_char(v))) if not attr: attr = dir(obj)
for k in attr:
try:
v = getattr(obj, k)
except (AttributeError, TypeError): # Some attributes are not readable
pass
else:
items.append((k, type_char(v)))
if items != []: if items != []:
items.sort(cmp = suggest_cmp) items.sort(cmp = suggest_cmp)
txt.suggest(items, pre[-1]) txt.suggest(items, targets[-1])
# Check we are running as a script and not imported as a module # Check we are running as a script and not imported as a module
if __name__ == "__main__" and OK: if __name__ == "__main__" and OK:

View File

@@ -45,7 +45,7 @@ def main():
if not txt: if not txt:
return return
(line, c) = current_line(txt) line, c = current_line(txt)
# Check we are in a normal context # Check we are in a normal context
if get_context(txt) != CTX_NORMAL: if get_context(txt) != CTX_NORMAL:
@@ -65,7 +65,7 @@ def main():
# Otherwise we suggest globals, keywords, etc. # Otherwise we suggest globals, keywords, etc.
list = [] list = []
pre = get_targets(line, c) targets = get_targets(line, c)
desc = get_cached_descriptor(txt) desc = get_cached_descriptor(txt)
for k in KEYWORDS: for k in KEYWORDS:
@@ -87,7 +87,7 @@ def main():
list.append((k, 'v')) list.append((k, 'v'))
list.sort(cmp = suggest_cmp) list.sort(cmp = suggest_cmp)
txt.suggest(list, pre[-1]) txt.suggest(list, targets[-1])
# Check we are running as a script and not imported as a module # Check we are running as a script and not imported as a module
if __name__ == "__main__" and OK: if __name__ == "__main__" and OK: