This commit is contained in:
Dmitry Trofimov
2016-08-30 18:48:23 +02:00
20 changed files with 571 additions and 228 deletions
@@ -0,0 +1,160 @@
'''
Id: "$Id$"
Copyright: Copyright (c) 2015 Bank of America Merrill Lynch, All Rights Reserved
Description:
Test:
'''
# noinspection PyBroadException
import types
from _pydevd_bundle.pydevd_constants import IS_JYTHON, IS_PY3K
try:
import inspect
except:
try:
from _pydev_imps import _pydev_inspect as inspect
except:
import traceback;
traceback.print_exc() # Ok, no inspect available (search will not work)from _pydevd_bundle.pydevd_constants import IS_JYTHON, IS_PY3K
from _pydev_bundle._pydev_imports_tipper import signature_from_docstring
def is_bound_method(obj):
if isinstance(obj, types.MethodType):
return getattr(obj, '__self__', getattr(obj, 'im_self', None)) is not None
else:
return False
def get_class_name(instance):
return getattr(getattr(instance, "__class__", None), "__name__", None)
def get_bound_class_name(obj):
my_self = getattr(obj, '__self__', getattr(obj, 'im_self', None))
if my_self is None:
return None
return get_class_name(my_self)
def get_description(obj):
try:
ob_call = obj.__call__
except:
ob_call = None
if isinstance(obj, type) or type(obj).__name__ == 'classobj':
fob = getattr(obj, '__init__', lambda: None)
if not isinstance(fob, (types.FunctionType, types.MethodType)):
fob = obj
elif is_bound_method(ob_call):
fob = ob_call
else:
fob = obj
argspec = ""
fn_name = None
fn_class = None
if isinstance(fob, (types.FunctionType, types.MethodType)):
spec_info = inspect.getfullargspec(fob) if IS_PY3K else inspect.getargspec(fob)
argspec = inspect.formatargspec(*spec_info)
fn_name = getattr(fob, '__name__', None)
if isinstance(obj, type) or type(obj).__name__ == 'classobj':
fn_name = "__init__"
fn_class = getattr(obj, "__name__", "UnknownClass")
elif is_bound_method(obj) or is_bound_method(ob_call):
fn_class = get_bound_class_name(obj) or "UnknownClass"
else:
fn_name = getattr(fob, '__name__', None)
fn_self = getattr(fob, '__self__', None)
if fn_self is not None and not isinstance(fn_self, types.ModuleType):
fn_class = get_class_name(fn_self)
doc_string = get_docstring(ob_call) if is_bound_method(ob_call) else get_docstring(obj)
return create_method_stub(fn_name, fn_class, argspec, doc_string)
def create_method_stub(fn_name, fn_class, argspec, doc_string):
if fn_name and argspec:
doc_string = "" if doc_string is None else doc_string
fn_stub = create_function_stub(fn_name, argspec, doc_string, indent=1 if fn_class else 0)
if fn_class:
expr = fn_class if fn_name == '__init__' else fn_class + '().' + fn_name
return create_class_stub(fn_class, fn_stub) + "\n" + expr
else:
expr = fn_name
return fn_stub + "\n" + expr
elif doc_string:
if fn_name:
restored_signature, _ = signature_from_docstring(doc_string, fn_name)
if restored_signature:
return create_method_stub(fn_name, fn_class, restored_signature, doc_string)
return create_function_stub('unknown', '(*args, **kwargs)', doc_string) + '\nunknown'
else:
return ''
def get_docstring(obj):
if obj is not None:
try:
if IS_JYTHON:
# Jython
doc = obj.__doc__
if doc is not None:
return doc
from _pydev_bundle import _pydev_jy_imports_tipper
is_method, infos = _pydev_jy_imports_tipper.ismethod(obj)
ret = ''
if is_method:
for info in infos:
ret += info.get_as_doc()
return ret
else:
doc = inspect.getdoc(obj)
if doc is not None:
return doc
except:
pass
else:
return ''
try:
# if no attempt succeeded, try to return repr()...
return repr(obj)
except:
try:
# otherwise the class
return str(obj.__class__)
except:
# if all fails, go to an empty string
return ''
def create_class_stub(class_name, contents):
return "class %s(object):\n%s" % (class_name, contents)
def create_function_stub(fn_name, fn_argspec, fn_docstring, indent=0):
def shift_right(string, prefix):
return ''.join(prefix + line for line in string.splitlines(True))
fn_docstring = shift_right(inspect.cleandoc(fn_docstring), " " * (indent + 1))
ret = '''
def %s%s:
"""%s"""
pass
''' % (fn_name, fn_argspec, fn_docstring)
ret = ret[1:] # remove first /n
ret = ret.replace('\t', " ")
if indent:
prefix = " " * indent
ret = shift_right(ret, prefix)
return ret
@@ -1,5 +1,5 @@
import os.path
import inspect
import os.path
import sys
from _pydev_bundle._pydev_tipper_common import do_find
@@ -222,96 +222,7 @@ def generate_imports_tip_for_module(obj_to_complete, dirComps=None, getattr=geta
args = '(%s)' % (r)
except TypeError:
#ok, let's see if we can get the arguments from the doc
args = '()'
try:
found = False
if len(doc) > 0:
if IS_IPY:
#Handle case where we have the situation below
#sort(self, object cmp, object key)
#sort(self, object cmp, object key, bool reverse)
#sort(self)
#sort(self, object cmp)
#Or: sort(self: list, cmp: object, key: object)
#sort(self: list, cmp: object, key: object, reverse: bool)
#sort(self: list)
#sort(self: list, cmp: object)
if hasattr(obj, '__name__'):
name = obj.__name__+'('
#Fix issue where it was appearing sort(aa)sort(bb)sort(cc) in the same line.
lines = doc.splitlines()
if len(lines) == 1:
c = doc.count(name)
if c > 1:
doc = ('\n'+name).join(doc.split(name))
major = ''
for line in doc.splitlines():
if line.startswith(name) and line.endswith(')'):
if len(line) > len(major):
major = line
if major:
args = major[major.index('('):]
found = True
if not found:
i = doc.find('->')
if i < 0:
i = doc.find('--')
if i < 0:
i = doc.find('\n')
if i < 0:
i = doc.find('\r')
if i > 0:
s = doc[0:i]
s = s.strip()
#let's see if we have a docstring in the first line
if s[-1] == ')':
start = s.find('(')
if start >= 0:
end = s.find('[')
if end <= 0:
end = s.find(')')
if end <= 0:
end = len(s)
args = s[start:end]
if not args[-1] == ')':
args = args + ')'
#now, get rid of unwanted chars
l = len(args) - 1
r = []
for i in xrange(len(args)):
if i == 0 or i == l:
r.append(args[i])
else:
r.append(check_char(args[i]))
args = ''.join(r)
if IS_IPY:
if args.startswith('(self:'):
i = args.find(',')
if i >= 0:
args = '(self'+args[i:]
else:
args = '(self)'
i = args.find(')')
if i > 0:
args = args[:i+1]
except:
pass
args, doc = signature_from_docstring(doc, getattr(obj, '__name__', None))
retType = TYPE_FUNCTION
@@ -350,5 +261,90 @@ def generate_imports_tip_for_module(obj_to_complete, dirComps=None, getattr=geta
return ret
def signature_from_docstring(doc, obj_name):
args = '()'
try:
found = False
if len(doc) > 0:
if IS_IPY:
# Handle case where we have the situation below
# sort(self, object cmp, object key)
# sort(self, object cmp, object key, bool reverse)
# sort(self)
# sort(self, object cmp)
# Or: sort(self: list, cmp: object, key: object)
# sort(self: list, cmp: object, key: object, reverse: bool)
# sort(self: list)
# sort(self: list, cmp: object)
if obj_name:
name = obj_name + '('
# Fix issue where it was appearing sort(aa)sort(bb)sort(cc) in the same line.
lines = doc.splitlines()
if len(lines) == 1:
c = doc.count(name)
if c > 1:
doc = ('\n' + name).join(doc.split(name))
major = ''
for line in doc.splitlines():
if line.startswith(name) and line.endswith(')'):
if len(line) > len(major):
major = line
if major:
args = major[major.index('('):]
found = True
if not found:
i = doc.find('->')
if i < 0:
i = doc.find('--')
if i < 0:
i = doc.find('\n')
if i < 0:
i = doc.find('\r')
if i > 0:
s = doc[0:i]
s = s.strip()
# let's see if we have a docstring in the first line
if s[-1] == ')':
start = s.find('(')
if start >= 0:
end = s.find('[')
if end <= 0:
end = s.find(')')
if end <= 0:
end = len(s)
args = s[start:end]
if not args[-1] == ')':
args = args + ')'
# now, get rid of unwanted chars
l = len(args) - 1
r = []
for i in xrange(len(args)):
if i == 0 or i == l:
r.append(args[i])
else:
r.append(check_char(args[i]))
args = ''.join(r)
if IS_IPY:
if args.startswith('(self:'):
i = args.find(',')
if i >= 0:
args = '(self' + args[i:]
else:
args = '(self)'
i = args.find(')')
if i > 0:
args = args[:i + 1]
except:
pass
return args, doc
@@ -1,17 +1,15 @@
import os
from _pydev_bundle.pydev_imports import xmlrpclib, _queue, Exec
import sys
from _pydevd_bundle.pydevd_constants import IS_JYTHON
from _pydev_bundle._pydev_calltip_util import get_description
from _pydev_imps._pydev_saved_modules import thread
from _pydevd_bundle import pydevd_xml
from _pydevd_bundle import pydevd_vars
from _pydevd_bundle import pydevd_xml
from _pydevd_bundle.pydevd_constants import IS_JYTHON
from _pydevd_bundle.pydevd_utils import * # @UnusedWildImport
import traceback
#=======================================================================================================================
# =======================================================================================================================
# Null
#=======================================================================================================================
# =======================================================================================================================
class Null:
"""
Gotten from: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68205
@@ -54,39 +52,39 @@ class Null:
return 0
#=======================================================================================================================
# =======================================================================================================================
# BaseStdIn
#=======================================================================================================================
# =======================================================================================================================
class BaseStdIn:
def __init__(self, original_stdin=sys.stdin, *args, **kwargs):
try:
self.encoding = sys.stdin.encoding
except:
#Not sure if it's available in all Python versions...
# Not sure if it's available in all Python versions...
pass
self.original_stdin = original_stdin
def readline(self, *args, **kwargs):
#sys.stderr.write('Cannot readline out of the console evaluation\n') -- don't show anything
#This could happen if the user had done input('enter number).<-- upon entering this, that message would appear,
#which is not something we want.
# sys.stderr.write('Cannot readline out of the console evaluation\n') -- don't show anything
# This could happen if the user had done input('enter number).<-- upon entering this, that message would appear,
# which is not something we want.
return '\n'
def isatty(self):
return False #not really a file
return False # not really a file
def write(self, *args, **kwargs):
pass #not available StdIn (but it can be expected to be in the stream interface)
pass # not available StdIn (but it can be expected to be in the stream interface)
def flush(self, *args, **kwargs):
pass #not available StdIn (but it can be expected to be in the stream interface)
pass # not available StdIn (but it can be expected to be in the stream interface)
def read(self, *args, **kwargs):
#in the interactive interpreter, a read and a readline are the same.
# in the interactive interpreter, a read and a readline are the same.
return self.readline()
def close(self, *args, **kwargs):
pass #expected in StdIn
pass # expected in StdIn
def __getattr__(self, item):
# it's called if the attribute wasn't found
@@ -95,9 +93,9 @@ class BaseStdIn:
raise AttributeError("%s has no attribute %s" % (self.original_stdin, item))
#=======================================================================================================================
# =======================================================================================================================
# StdIn
#=======================================================================================================================
# =======================================================================================================================
class StdIn(BaseStdIn):
'''
Object to be added to stdin (to emulate it as non-blocking while the next line arrives)
@@ -110,16 +108,18 @@ class StdIn(BaseStdIn):
self.host = host
def readline(self, *args, **kwargs):
#Ok, callback into the client to get the new input
# Ok, callback into the client to get the new input
try:
server = xmlrpclib.Server('http://%s:%s' % (self.host, self.client_port))
requested_input = server.RequestInput()
if not requested_input:
return '\n' #Yes, a readline must return something (otherwise we can get an EOFError on the input() call).
return '\n' # Yes, a readline must return something (otherwise we can get an EOFError on the input() call).
return requested_input
except:
return '\n'
def close(self, *args, **kwargs):
pass # expected in StdIn
#=======================================================================================================================
# DebugConsoleStdIn
@@ -155,9 +155,10 @@ class CodeFragment:
if not code_fragment.is_single_line:
self.is_single_line = False
#=======================================================================================================================
# =======================================================================================================================
# BaseInterpreterInterface
#=======================================================================================================================
# =======================================================================================================================
class BaseInterpreterInterface:
def __init__(self, mainThread):
self.mainThread = mainThread
@@ -206,15 +207,15 @@ class BaseInterpreterInterface:
try:
help = None
if 'pydoc' in sys.modules:
pydoc = sys.modules['pydoc'] #Don't import it if it still is not there.
pydoc = sys.modules['pydoc'] # Don't import it if it still is not there.
if hasattr(pydoc, 'help'):
#You never know how will the API be changed, so, let's code defensively here
# You never know how will the API be changed, so, let's code defensively here
help = pydoc.help
if not hasattr(help, 'input'):
help = None
except:
#Just ignore any error here
# Just ignore any error here
pass
more = False
@@ -222,7 +223,7 @@ class BaseInterpreterInterface:
sys.stdin = self.create_std_in(debugger, original_in)
try:
if help is not None:
#This will enable the help() function to work.
# This will enable the help() function to work.
try:
try:
help.input = sys.stdin
@@ -268,7 +269,6 @@ class BaseInterpreterInterface:
return more
def do_add_exec(self, codeFragment):
'''
Subclasses should override.
@@ -277,7 +277,6 @@ class BaseInterpreterInterface:
'''
raise NotImplementedError()
def get_namespace(self):
'''
Subclasses should override.
@@ -286,66 +285,48 @@ class BaseInterpreterInterface:
'''
raise NotImplementedError()
def __resolve_reference__(self, text):
"""
def getDescription(self, text):
try:
obj = None
if '.' not in text:
:type text: str
"""
obj = None
if '.' not in text:
try:
obj = self.get_namespace()[text]
except KeyError:
pass
if obj is None:
try:
obj = self.get_namespace()[text]
except KeyError:
return ''
else:
try:
splitted = text.split('.')
obj = self.get_namespace()[splitted[0]]
for t in splitted[1:]:
obj = getattr(obj, t)
except:
return ''
if obj is not None:
try:
if sys.platform.startswith("java"):
#Jython
doc = obj.__doc__
if doc is not None:
return doc
from _pydev_bundle import _pydev_jy_imports_tipper
is_method, infos = _pydev_jy_imports_tipper.ismethod(obj)
ret = ''
if is_method:
for info in infos:
ret += info.get_as_doc()
return ret
else:
#Python and Iron Python
import inspect #@UnresolvedImport
doc = inspect.getdoc(obj)
if doc is not None:
return doc
obj = self.get_namespace()['__builtins__'][text]
except:
pass
try:
#if no attempt succeeded, try to return repr()...
return repr(obj)
except:
if obj is None:
try:
#otherwise the class
return str(obj.__class__)
obj = getattr(self.get_namespace()['__builtins__'], text, None)
except:
#if all fails, go to an empty string
return ''
except:
traceback.print_exc()
return ''
pass
else:
try:
last_dot = text.rindex('.')
parent_context = text[0:last_dot]
res = pydevd_vars.eval_in_context(parent_context, self.get_namespace(), self.get_namespace())
obj = getattr(res, text[last_dot + 1:])
except:
pass
return obj
def getDescription(self, text):
try:
obj = self.__resolve_reference__(text)
if obj is None:
return ''
return get_description(obj)
except:
return ''
def do_exec_code(self, code, is_single_line):
try:
@@ -364,7 +345,6 @@ class BaseInterpreterInterface:
def execLine(self, line):
return self.do_exec_code(line, True)
def execMultipleLines(self, lines):
if IS_JYTHON:
for line in lines.split('\n'):
@@ -372,9 +352,8 @@ class BaseInterpreterInterface:
else:
return self.do_exec_code(lines, False)
def interrupt(self):
self.buffer = None # Also clear the buffer when it's interrupted.
self.buffer = None # Also clear the buffer when it's interrupted.
try:
if self.interruptable:
called = False
@@ -412,10 +391,10 @@ class BaseInterpreterInterface:
pass
if not called:
if hasattr(thread, 'interrupt_main'): #Jython doesn't have it
if hasattr(thread, 'interrupt_main'): # Jython doesn't have it
thread.interrupt_main()
else:
self.mainThread._thread.interrupt() #Jython
self.mainThread._thread.interrupt() # Jython
return True
except:
traceback.print_exc()
@@ -495,11 +474,11 @@ class BaseInterpreterInterface:
Used to show console with variables connection.
Always return a frame where the locals map to our internal namespace.
'''
VIRTUAL_FRAME_ID = "1" # matches PyStackFrameConsole.java
VIRTUAL_CONSOLE_ID = "console_main" # matches PyThreadConsole.java
VIRTUAL_FRAME_ID = "1" # matches PyStackFrameConsole.java
VIRTUAL_CONSOLE_ID = "console_main" # matches PyThreadConsole.java
if thread_id == VIRTUAL_CONSOLE_ID and frame_id == VIRTUAL_FRAME_ID:
f = FakeFrame()
f.f_globals = {} #As globals=locals here, let's simply let it empty (and save a bit of network traffic).
f.f_globals = {} # As globals=locals here, let's simply let it empty (and save a bit of network traffic).
f.f_locals = self.get_namespace()
return f
else:
@@ -527,7 +506,7 @@ class BaseInterpreterInterface:
except:
# This happens on Jython embedded in host eclipse
traceback.print_exc()
sys.stderr.write('pydevd is not available, cannot connect\n',)
sys.stderr.write('pydevd is not available, cannot connect\n', )
from _pydev_bundle import pydev_localhost
threading.currentThread().__pydevd_id__ = "console_main"
@@ -589,9 +568,10 @@ class BaseInterpreterInterface:
# it to run in the main thread.
self.exec_queue.put(do_enable_gui)
#=======================================================================================================================
# =======================================================================================================================
# FakeFrame
#=======================================================================================================================
# =======================================================================================================================
class FakeFrame:
'''
Used to show console with variables connection.
@@ -139,6 +139,7 @@ CMD_STEP_INTO_MY_CODE = 144
CMD_GET_CONCURRENCY_EVENT = 145
CMD_SHOW_RETURN_VALUES = 146
CMD_INPUT_REQUESTED = 147
CMD_GET_DESCRIPTION = 148
CMD_VERSION = 501
CMD_RETURN = 502
@@ -193,6 +194,7 @@ ID_TO_MEANING = {
'145': 'CMD_GET_CONCURRENCY_EVENT',
'146': 'CMD_SHOW_RETURN_VALUES',
'147': 'CMD_INPUT_REQUESTED',
'148': 'CMD_GET_DESCRIPTION',
'501': 'CMD_VERSION',
'502': 'CMD_RETURN',
@@ -700,6 +702,12 @@ class NetCommandFactory:
except Exception:
return self.make_error_message(seq, get_exception_traceback_str())
def make_get_description_message(self, seq, payload):
try:
return NetCommand(CMD_GET_DESCRIPTION, seq, payload)
except Exception:
return self.make_error_message(seq, get_exception_traceback_str())
def make_get_frame_message(self, seq, payload):
try:
return NetCommand(CMD_GET_FRAME, seq, payload)
@@ -1135,6 +1143,36 @@ class InternalGetCompletions(InternalThreadCommand):
cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error evaluating expression " + exc)
dbg.writer.add_command(cmd)
# =======================================================================================================================
# InternalGetDescription
# =======================================================================================================================
class InternalGetDescription(InternalThreadCommand):
""" Fetch the variable description stub from the debug console
"""
def __init__(self, seq, thread_id, frame_id, expression):
self.sequence = seq
self.thread_id = thread_id
self.frame_id = frame_id
self.expression = expression
def do_it(self, dbg):
""" Get completions and write back to the client
"""
try:
frame = pydevd_vars.find_frame(self.thread_id, self.frame_id)
description = pydevd_console.get_description(frame, self.thread_id, self.frame_id, self.expression)
description = pydevd_vars.make_valid_xml_value(quote(description, '/>_= \t'))
description_xml = '<xml><var name="" type="" value="%s"/></xml>' % description
cmd = dbg.cmd_factory.make_get_description_message(self.sequence, description_xml)
dbg.writer.add_command(cmd)
except:
exc = get_exception_traceback_str()
cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error in fetching description" + exc)
dbg.writer.add_command(cmd)
#=======================================================================================================================
# InternalGetBreakpointException
#=======================================================================================================================
@@ -1,17 +1,17 @@
'''An helper file for the pydev debugger (REPL) console
'''
from code import InteractiveConsole
import sys
import traceback
from code import InteractiveConsole
from _pydev_bundle import _pydev_completer
from _pydevd_bundle.pydevd_tracing import get_exception_traceback_str
from _pydevd_bundle.pydevd_vars import make_valid_xml_value
from _pydev_bundle.pydev_imports import Exec
from _pydevd_bundle.pydevd_io import IOBuf
from _pydev_bundle.pydev_console_utils import BaseInterpreterInterface, BaseStdIn
from _pydev_bundle.pydev_imports import Exec
from _pydev_bundle.pydev_override import overrides
from _pydevd_bundle import pydevd_save_locals
from _pydevd_bundle.pydevd_io import IOBuf
from _pydevd_bundle.pydevd_tracing import get_exception_traceback_str
from _pydevd_bundle.pydevd_vars import make_valid_xml_value
CONSOLE_OUTPUT = "output"
CONSOLE_ERROR = "error"
@@ -160,6 +160,12 @@ class DebugConsole(InteractiveConsole, BaseInterpreterInterface):
except:
self.showtraceback()
def get_namespace(self):
dbg_namespace = {}
dbg_namespace.update(self.frame.f_globals)
dbg_namespace.update(self.frame.f_locals) # locals later because it has precedence over the actual globals
return dbg_namespace
#=======================================================================================================================
# InteractiveConsoleCache
@@ -175,6 +181,7 @@ class InteractiveConsoleCache:
def get_interactive_console(thread_id, frame_id, frame, console_message):
"""returns the global interactive console.
interactive console should have been initialized by this time
:rtype: DebugConsole
"""
if InteractiveConsoleCache.thread_id == thread_id and InteractiveConsoleCache.frame_id == frame_id:
return InteractiveConsoleCache.interactive_console_instance
@@ -218,6 +225,16 @@ def execute_console_command(frame, thread_id, frame_id, line, buffer_output=True
return console_message
def get_description(frame, thread_id, frame_id, expression):
console_message = ConsoleMessage()
interpreter = get_interactive_console(thread_id, frame_id, frame, console_message)
try:
interpreter.frame = frame
return interpreter.getDescription(expression)
finally:
interpreter.frame = None
def get_completions(frame, act_tok):
""" fetch all completions, create xml for the same
return the completions xml
@@ -16,11 +16,10 @@ from _pydevd_bundle.pydevd_comm import CMD_RUN, CMD_VERSION, CMD_LIST_THREADS, C
CMD_SET_PY_EXCEPTION, CMD_GET_FILE_CONTENTS, CMD_SET_PROPERTY_TRACE, CMD_ADD_EXCEPTION_BREAK, \
CMD_REMOVE_EXCEPTION_BREAK, CMD_LOAD_SOURCE, CMD_ADD_DJANGO_EXCEPTION_BREAK, CMD_REMOVE_DJANGO_EXCEPTION_BREAK, \
CMD_EVALUATE_CONSOLE_EXPRESSION, InternalEvaluateConsoleExpression, InternalConsoleGetCompletions, \
CMD_RUN_CUSTOM_OPERATION, InternalRunCustomOperation, CMD_IGNORE_THROWN_EXCEPTION_AT, CMD_ENABLE_DONT_TRACE,\
CMD_SHOW_RETURN_VALUES, ID_TO_MEANING
CMD_RUN_CUSTOM_OPERATION, InternalRunCustomOperation, CMD_IGNORE_THROWN_EXCEPTION_AT, CMD_ENABLE_DONT_TRACE, \
CMD_SHOW_RETURN_VALUES, ID_TO_MEANING, CMD_GET_DESCRIPTION, InternalGetDescription
from _pydevd_bundle.pydevd_constants import get_thread_id, IS_PY3K, DebugInfoHolder, dict_contains, dict_keys, dict_pop, \
STATE_RUN
import pydevd_file_utils
def process_net_command(py_db, cmd_id, seq, text):
@@ -229,6 +228,14 @@ def process_net_command(py_db, cmd_id, seq, text):
except:
traceback.print_exc()
elif cmd_id == CMD_GET_DESCRIPTION:
try:
thread_id, frame_id, expression = text.split('\t', 2)
int_cmd = InternalGetDescription(seq, thread_id, frame_id, expression)
py_db.post_internal_command(int_cmd, thread_id)
except:
traceback.print_exc()
elif cmd_id == CMD_GET_FRAME:
thread_id, frame_id, scope = text.split('\t', 2)
@@ -24,17 +24,22 @@ import org.jetbrains.annotations.NotNull;
public class PyResolveContext {
private final boolean myAllowImplicits;
private final boolean myAllowProperties;
private final boolean myAllowRemote;
private final TypeEvalContext myTypeEvalContext;
private PyResolveContext(boolean allowImplicits, boolean allowProperties) {
myAllowImplicits = allowImplicits;
myAllowProperties = allowProperties;
myTypeEvalContext = null;
myAllowRemote = false;
}
private PyResolveContext(boolean allowImplicits, boolean allowProperties, TypeEvalContext typeEvalContext) {
private PyResolveContext(boolean allowImplicits, boolean allowProperties, boolean allowRemote, TypeEvalContext typeEvalContext) {
myAllowImplicits = allowImplicits;
myAllowProperties = allowProperties;
myAllowRemote = allowRemote;
myTypeEvalContext = typeEvalContext;
}
@@ -46,6 +51,10 @@ public class PyResolveContext {
return myAllowProperties;
}
public boolean allowRemote() {
return myAllowRemote;
}
private static final PyResolveContext ourDefaultContext = new PyResolveContext(true, true);
private static final PyResolveContext ourNoImplicitsContext = new PyResolveContext(false, true);
private static final PyResolveContext ourNoPropertiesContext = new PyResolveContext(false, false);
@@ -63,11 +72,15 @@ public class PyResolveContext {
}
public PyResolveContext withTypeEvalContext(@NotNull TypeEvalContext context) {
return new PyResolveContext(myAllowImplicits, myAllowProperties, context);
return new PyResolveContext(myAllowImplicits, myAllowProperties, myAllowRemote, context);
}
public PyResolveContext withoutImplicits() {
return new PyResolveContext(false, myAllowProperties, myTypeEvalContext);
return new PyResolveContext(false, myAllowProperties, myAllowRemote, myTypeEvalContext);
}
public PyResolveContext withRemote() {
return new PyResolveContext(myAllowImplicits, myAllowProperties, true, myTypeEvalContext);
}
public TypeEvalContext getTypeEvalContext() {
@@ -30,7 +30,9 @@ public abstract class AbstractCommand<T> {
public static final int LOAD_SOURCE = 124;
public static final int SMART_STEP_INTO = 128;
public static final int EXIT = 129;
public static final int GET_DESCRIPTION = 148;
public static final int CALL_SIGNATURE_TRACE = 130;
public static final int CMD_SET_PY_EXCEPTION = 131;
@@ -52,6 +54,7 @@ public abstract class AbstractCommand<T> {
public static final int SHOW_RETURN_VALUES = 146;
public static final int INPUT_REQUESTED = 147;
public static final int ERROR = 901;
public static final int VERSION = 501;
@@ -0,0 +1,47 @@
package com.jetbrains.python.debugger.pydev;
import com.jetbrains.python.debugger.PyDebugValue;
import com.jetbrains.python.debugger.PyDebuggerException;
/**
* @author traff
*/
public class GetDescriptionCommand extends AbstractFrameCommand {
private String myActionToken;
private String result = null;
public GetDescriptionCommand(final RemoteDebugger debugger, String threadId, String frameId, final String myActionToken) {
super(debugger, GET_DESCRIPTION, threadId, frameId);
this.myActionToken = myActionToken;
}
@Override
public boolean isResponseExpected() {
return true;
}
@Override
protected void processResponse(ProtocolFrame response) throws PyDebuggerException {
super.processResponse(response);
try {
PyDebugValue pyDebugValue = ProtocolParser.parseValue(response.getPayload(), getDebugger().getDebugProcess());
result = pyDebugValue.getValue();
}
catch (Exception e) {
throw new PyDebuggerException("cant obtain completions", e);
}
}
@Override
protected void buildPayload(Payload payload) {
super.buildPayload(payload);
payload.add(myActionToken);
}
public String getResult() {
return result;
}
}
@@ -518,6 +518,11 @@ public class MultiProcessDebugger implements ProcessDebugger {
return debugger(threadId).getCompletions(threadId, frameId, prefix);
}
@Override
public String getDescription(String threadId, String frameId, String cmd) {
return debugger(threadId).getDescription(threadId, frameId, cmd);
}
@Override
public void addExceptionBreakpoint(ExceptionBreakpointCommandFactory factory) {
for (RemoteDebugger d : allDebuggers()) {
@@ -87,6 +87,9 @@ public interface ProcessDebugger {
List<PydevCompletionVariant> getCompletions(String threadId, String frameId, String prefix);
String getDescription(String threadId, String frameId, String cmd);
void addExceptionBreakpoint(ExceptionBreakpointCommandFactory factory);
void removeExceptionBreakpoint(ExceptionBreakpointCommandFactory factory);
@@ -761,6 +761,13 @@ public class RemoteDebugger implements ProcessDebugger {
return command.getCompletions();
}
@Override
public String getDescription(String threadId, String frameId, String cmd) {
final GetDescriptionCommand command = new GetDescriptionCommand(this, threadId, frameId, cmd);
execute(command);
return command.getResult();
}
@Override
public void addExceptionBreakpoint(ExceptionBreakpointCommandFactory factory) {
execute(factory.createAddCommand(this));
@@ -65,7 +65,7 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
final PyCallExpression callExpr = argumentList.getCallExpression();
if (callExpr != null) {
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(argumentList.getProject(), argumentList.getContainingFile());
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(typeEvalContext);
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withRemote().withTypeEvalContext(typeEvalContext);
final PyCallExpression.PyArgumentsMapping mapping = callExpr.mapArguments(resolveContext);
if (mapping.getMarkedCallee() != null) {
context.setItemsToShow(new Object[] { mapping });
@@ -152,7 +152,7 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
PyPsiUtils.assertValid(callExpression);
// really we need to redo analysis every UI update; findElementForParameterInfo isn't called while typing
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(callExpression.getProject(), callExpression.getContainingFile());
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(typeEvalContext);
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withRemote().withTypeEvalContext(typeEvalContext);
final PyCallExpression.PyArgumentsMapping mapping = callExpression.mapArguments(resolveContext);
final PyMarkedCallee marked = mapping.getMarkedCallee();
if (marked == null) return; // resolution failed
@@ -16,13 +16,15 @@
package com.jetbrains.python.console;
import com.intellij.lang.documentation.AbstractDocumentationProvider;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.console.completion.PydevConsoleElement;
import com.jetbrains.python.console.pydev.ConsoleCommunication;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.console.completion.PydevConsoleReference;
import com.jetbrains.python.documentation.PyDocumentationBuilder;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.psi.PyUtil;
import org.jetbrains.annotations.Nullable;
/**
@@ -49,21 +51,21 @@ public class PydevDocumentationProvider extends AbstractDocumentationProvider {
@Nullable
public static String createDoc(final PsiElement element, final PsiElement originalElement) {
final PyExpression expression = PsiTreeUtil.getParentOfType(originalElement, PyExpression.class);
final PyReferenceExpression expression = PsiTreeUtil.getNonStrictParentOfType(originalElement, PyReferenceExpression.class);
// Indicates that we are inside console, not a lookup element!
if (expression == null){
return null;
}
final ConsoleCommunication communication = PydevConsoleRunner.getConsoleCommunication(originalElement);
if (communication == null){
PydevConsoleReference consoleRef = PyUtil.as(expression.getReference(), PydevConsoleReference.class);
if (consoleRef == null) { //shouldn't really happen!
return null;
}
try {
final String description = communication.getDescription(expression.getText());
return StringUtil.isEmptyOrSpaces(description) ? null : description;
}
catch (Exception e) {
PyElement documentationElement = consoleRef.getDocumentationElement();
if (documentationElement == null) {
return null;
}
return new PyDocumentationBuilder(documentationElement, null).build();
}
}
@@ -55,8 +55,8 @@ public class PythonDebugConsoleCommunication extends AbstractConsoleCommunicatio
}
@Override
public String getDescription(String text) {
return null;
public String getDescription(String refExpression) throws Exception {
return myDebugProcess.getDescription(refExpression);
}
@Override
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.console.completion;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.intellij.codeInsight.completion.CompletionInitializationContext;
import com.intellij.codeInsight.completion.InsertHandler;
@@ -24,19 +25,18 @@ import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiPolyVariantReferenceBase;
import com.intellij.psi.ResolveResult;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.console.pydev.ConsoleCommunication;
import com.jetbrains.python.console.pydev.IToken;
import com.jetbrains.python.console.pydev.PyCodeCompletionImages;
import com.jetbrains.python.console.pydev.PydevCompletionVariant;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -46,20 +46,75 @@ import java.util.Map;
* @author oleg
*/
public class PydevConsoleReference extends PsiPolyVariantReferenceBase<PyReferenceExpression> {
private final ConsoleCommunication myCommunication;
private final String myPrefix;
private final boolean myAllowRemoteResolve;
public PydevConsoleReference(final PyReferenceExpression expression,
final ConsoleCommunication communication,
final String prefix) {
final ConsoleCommunication communication, final String prefix, boolean allowRemoteResolve) {
super(expression, true);
myCommunication = communication;
myPrefix = prefix;
myAllowRemoteResolve = allowRemoteResolve;
}
@NotNull
public ResolveResult[] multiResolve(boolean incompleteCode) {
return ResolveResult.EMPTY_ARRAY;
if (!myAllowRemoteResolve) {
return RatedResolveResult.EMPTY_ARRAY;
}
PyExpression pyExpression = resolveFromStub();
if (pyExpression == null) {
return RatedResolveResult.EMPTY_ARRAY;
}
if (pyExpression instanceof PyReferenceExpression) {
final PsiReference redirectedRef = pyExpression.getReference();
if (redirectedRef != null) {
PsiElement resolved = redirectedRef.resolve();
if (resolved != null) {
return new ResolveResult[]{new RatedResolveResult(RatedResolveResult.RATE_HIGH, resolved)};
}
}
}
return RatedResolveResult.EMPTY_ARRAY;
}
public PyElement getDocumentationElement() {
return resolveFromStub();
}
private PyExpression resolveFromStub() {
String qualifiedName = myElement.getText();
if (qualifiedName == null) {
return null;
}
String description;
try {
description = myCommunication.getDescription(qualifiedName);
if (Strings.isNullOrEmpty(description)) {
return null;
}
}
catch (Exception e) {
return null;
}
PyElementGenerator generator = PyElementGenerator.getInstance(myElement.getProject());
PyFile dummyFile = (PyFile)generator.createDummyFile(LanguageLevel.forElement(myElement), description);
Module module = ModuleUtilCore.findModuleForPsiElement(myElement);
if (module != null) {
dummyFile.putUserData(ModuleUtilCore.KEY_MODULE, module);
}
List<PyStatement> statements = dummyFile.getStatements();
PyStatement pyStatement = statements.get(statements.size() - 1);
return pyStatement instanceof PyExpressionStatement ? ((PyExpressionStatement)pyStatement).getExpression() : null;
}
@NotNull
@@ -71,8 +126,7 @@ public class PydevConsoleReference extends PsiPolyVariantReferenceBase<PyReferen
final PsiManager manager = myElement.getManager();
final String name = completion.getName();
final int type = completion.getType();
LookupElementBuilder builder = LookupElementBuilder
.create(new PydevConsoleElement(manager, name, completion.getDescription()))
LookupElementBuilder builder = LookupElementBuilder.create(new PydevConsoleElement(manager, name, completion.getDescription()))
.withIcon(PyCodeCompletionImages.getImageForType(type));
@@ -33,9 +33,9 @@ public class PyConsoleParser extends PyParser{
private PythonConsoleData myPythonConsoleData;
private boolean myIPythonStartSymbol;
public PyConsoleParser(PythonConsoleData pythonConsoleData) {
public PyConsoleParser(PythonConsoleData pythonConsoleData, LanguageLevel languageLevel) {
myPythonConsoleData = pythonConsoleData;
myLanguageLevel = LanguageLevel.getDefault();
myLanguageLevel = languageLevel;
}
@NotNull
@@ -901,6 +901,17 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
return Lists.newArrayList();
}
@NotNull
public String getDescription(String prefix) throws Exception {
if (isConnected()) {
dropFrameCaches();
final PyStackFrame frame = currentFrame();
return myDebugger.getDescription(frame.getThreadId(), frame.getFrameId(), prefix);
}
return "";
}
@Override
public void startNotified(ProcessEvent event) {
}
@@ -106,7 +106,7 @@ public class PyFileElementType extends IStubFileElementType<PyFileStub> {
final Project project = psi.getProject();
final PsiBuilderFactory factory = PsiBuilderFactory.getInstance();
final PsiBuilder builder = factory.createBuilder(project, node, lexer, getLanguage(), node.getChars());
final PyParser parser = new PyConsoleParser(consoleData);
final PyParser parser = new PyConsoleParser(consoleData, getLanguageLevel(psi));
return parser.parse(this, builder).getFirstChildNode();
}
@@ -83,9 +83,9 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
final ConsoleCommunication communication = file.getCopyableUserData(PydevConsoleRunner.CONSOLE_KEY);
if (communication != null) {
if (qualifier != null) {
return new PydevConsoleReference(this, communication, qualifier.getText() + ".");
return new PydevConsoleReference(this, communication, qualifier.getText() + ".", context.allowRemote());
}
return new PydevConsoleReference(this, communication, "");
return new PydevConsoleReference(this, communication, "", context.allowRemote());
}
if (qualifier != null) {