PythonCompleter.py
161 lines
| 4.8 KiB
| text/x-python
|
PythonLexer
Jeandet Alexis
|
r0 | # -*- coding: utf-8 -*- | ||
""" python-part of PythonCompleter | ||||
code/ideas taken from IPython, but IPython doesn't work with PythonQt... | ||||
Authors: | ||||
"Melven Zoellner" <melven@topen.org> | ||||
""" | ||||
# system imports | ||||
import sys | ||||
import rlcompleter | ||||
import imp | ||||
from __main__ import PYMODULES | ||||
sys.path.append(PYMODULES+'/pygments/') | ||||
sys.path.insert(0,PYMODULES) | ||||
# Third-party imports | ||||
from pygments.lexers import PythonLexer | ||||
from pygments.token import Token, is_token_subtype | ||||
from module_completion import module_completion | ||||
# undo sys.path modifications | ||||
#if not dot_in_path: | ||||
# sys.path.remove('/opt/SocExplorer/python') | ||||
# ugly fix to make rlcompleter work with PythonQt: | ||||
# problem: | ||||
# hasattr raises SystemError for | ||||
# <type 'PythonQt.PythonQtInstanceWrapper'> | ||||
# this is probably a PythonQt-problem and should be fixed there? | ||||
def _get_class_members(klass): | ||||
ret = dir(klass) | ||||
try: | ||||
for base in klass.__bases__: | ||||
ret = ret + _get_class_members(base) | ||||
except SystemError: | ||||
pass | ||||
return ret | ||||
rlcompleter.get_class_members = _get_class_members | ||||
def pythonqt_specific_completions(context, parent): | ||||
""" determines pythonqt-specific completions, | ||||
currently it adds only named children of QObjects """ | ||||
try: | ||||
completions = list() | ||||
# for completions for instances of PythonQt-Wrapper classes | ||||
# we need to do some ugly stuff here | ||||
# 1. get the base name | ||||
base_name = '.'.join(context[0:-1]) | ||||
# 2. eval the name in the parent context | ||||
base_object = eval(base_name, parent.__dict__) | ||||
# 2. check if it is really an instance of a PythonQt-Wrapper class | ||||
if repr(type(type(base_object))) == "<type 'PythonQt.PythonQtClassWrapper'>": | ||||
# 3. look for the names of it's children | ||||
if( hasattr(base_object, 'children') and callable(base_object.children) ): | ||||
for child in base_object.children(): | ||||
if( hasattr(child, 'objectName') and child.objectName != '' ): | ||||
completions.append(child.objectName) | ||||
return completions | ||||
except: | ||||
return list() | ||||
def autocompleteCode(code, parent = sys.modules['__main__']): | ||||
""" try to find useful completions for code """ | ||||
# get last line and context | ||||
all_lines = code.split('\n') | ||||
line = all_lines[-1] | ||||
context = get_context(line) | ||||
word = '.'.join(context) | ||||
completions = None | ||||
# module completion | ||||
if line.startswith('from ') or line.startswith('import '): | ||||
completions = module_completion(line) | ||||
# rlcompleter | ||||
if completions is None: | ||||
completer = rlcompleter.Completer(parent.__dict__) | ||||
if '.' in word: | ||||
completions = completer.attr_matches(word) | ||||
# also append PythonQt-specific completions | ||||
completions += pythonqt_specific_completions(context, parent) | ||||
else: | ||||
completions = completer.global_matches(word) | ||||
# we only need the last part of the completion | ||||
# (e.g. my_function from my_class.my_function) | ||||
completions = [ c.split('.')[-1] for c in completions ] | ||||
# sort completions and remove duplicates | ||||
completions = sorted(set(completions), key=(lambda s: s.lower())) | ||||
# hide all members that begin with '_', | ||||
# except the user starts a name with '_' | ||||
if not (context and context[-1].startswith('_')): | ||||
completions = [c for c in completions if not c.startswith('_')] | ||||
# return results | ||||
return completions | ||||
def get_context(string): | ||||
""" Assuming the cursor is at the end of the specified string, get the | ||||
context (a list of names) for the symbol at cursor position. | ||||
""" | ||||
lexer = PythonLexer() | ||||
context = [] | ||||
reversed_tokens = list(lexer.get_tokens(string)) | ||||
reversed_tokens.reverse() | ||||
# Pygments often tacks on a newline when none is specified in the input. | ||||
# Remove this newline. | ||||
if reversed_tokens and reversed_tokens[0][1].endswith('\n') and \ | ||||
not string.endswith('\n'): | ||||
reversed_tokens.pop(0) | ||||
current_op = '' | ||||
for token, text in reversed_tokens: | ||||
if is_token_subtype(token, Token.Name): | ||||
# Handle a trailing separator, e.g 'foo.bar.' | ||||
if current_op == '.': | ||||
if not context: | ||||
context.insert(0, '') | ||||
# Handle non-separator operators and punction. | ||||
elif current_op: | ||||
break | ||||
context.insert(0, text) | ||||
current_op = '' | ||||
# Pygments doesn't understand that, e.g., '->' is a single operator | ||||
# in C++. This is why we have to build up an operator from | ||||
# potentially several tokens. | ||||
elif token is Token.Operator or token is Token.Punctuation: | ||||
current_op = text + current_op | ||||
# Break on anything that is not a Operator, Punctuation, or Name. | ||||
else: | ||||
break | ||||
return context | ||||