diff --git a/python/helpers/pydev/_pydev_bundle/_pydev_calltip_util.py b/python/helpers/pydev/_pydev_bundle/_pydev_calltip_util.py
new file mode 100644
index 000000000000..8095e12f8fae
--- /dev/null
+++ b/python/helpers/pydev/_pydev_bundle/_pydev_calltip_util.py
@@ -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
diff --git a/python/helpers/pydev/_pydev_bundle/_pydev_imports_tipper.py b/python/helpers/pydev/_pydev_bundle/_pydev_imports_tipper.py
index c05c04e521b0..7ee92090f0e8 100644
--- a/python/helpers/pydev/_pydev_bundle/_pydev_imports_tipper.py
+++ b/python/helpers/pydev/_pydev_bundle/_pydev_imports_tipper.py
@@ -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
diff --git a/python/helpers/pydev/_pydev_bundle/pydev_console_utils.py b/python/helpers/pydev/_pydev_bundle/pydev_console_utils.py
index e9d0a6998a87..9d612005e9f3 100644
--- a/python/helpers/pydev/_pydev_bundle/pydev_console_utils.py
+++ b/python/helpers/pydev/_pydev_bundle/pydev_console_utils.py
@@ -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.
diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py
index 70566d248c16..1c3f2a3ee69a 100644
--- a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py
+++ b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py
@@ -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 = '' % 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
#=======================================================================================================================
diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_console.py b/python/helpers/pydev/_pydevd_bundle/pydevd_console.py
index d35cdc9df39d..b48daae2703a 100644
--- a/python/helpers/pydev/_pydevd_bundle/pydevd_console.py
+++ b/python/helpers/pydev/_pydevd_bundle/pydevd_console.py
@@ -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
diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_process_net_command.py b/python/helpers/pydev/_pydevd_bundle/pydevd_process_net_command.py
index 2b7783cbe8ca..dec84ed73369 100644
--- a/python/helpers/pydev/_pydevd_bundle/pydevd_process_net_command.py
+++ b/python/helpers/pydev/_pydevd_bundle/pydevd_process_net_command.py
@@ -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)
diff --git a/python/psi-api/src/com/jetbrains/python/psi/resolve/PyResolveContext.java b/python/psi-api/src/com/jetbrains/python/psi/resolve/PyResolveContext.java
index 860cef734a1d..3cd3c0809bdd 100644
--- a/python/psi-api/src/com/jetbrains/python/psi/resolve/PyResolveContext.java
+++ b/python/psi-api/src/com/jetbrains/python/psi/resolve/PyResolveContext.java
@@ -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() {
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
index 941e39343ba4..9090238a6a45 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
@@ -30,7 +30,9 @@ public abstract class AbstractCommand {
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 {
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;
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetDescriptionCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetDescriptionCommand.java
new file mode 100644
index 000000000000..ca216c7eb02e
--- /dev/null
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetDescriptionCommand.java
@@ -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;
+ }
+}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java
index 042b455f3f1b..9b3231974b93 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java
@@ -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()) {
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java
index 99c8908ab5c9..186dd53a569d 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java
@@ -87,6 +87,9 @@ public interface ProcessDebugger {
List getCompletions(String threadId, String frameId, String prefix);
+ String getDescription(String threadId, String frameId, String cmd);
+
+
void addExceptionBreakpoint(ExceptionBreakpointCommandFactory factory);
void removeExceptionBreakpoint(ExceptionBreakpointCommandFactory factory);
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java
index 457947dc9431..3149e42efa5e 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java
@@ -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));
diff --git a/python/src/com/jetbrains/python/PyParameterInfoHandler.java b/python/src/com/jetbrains/python/PyParameterInfoHandler.java
index d704f26ae5ca..4307c6d6d0fa 100644
--- a/python/src/com/jetbrains/python/PyParameterInfoHandler.java
+++ b/python/src/com/jetbrains/python/PyParameterInfoHandler.java
@@ -65,7 +65,7 @@ public class PyParameterInfoHandler implements ParameterInfoHandler {
+
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 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 {
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();
}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
index 417b7f495632..5915ff454435 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
@@ -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) {