From 4484907fe92f3372a6c9bfa28307a9b50c8e021f Mon Sep 17 00:00:00 2001 From: fitermay Date: Sun, 28 Aug 2016 20:55:10 -0400 Subject: [PATCH 1/4] add support for parameter info in console --- .../_pydev_bundle/_pydev_calltip_util.py | 160 ++++++++++++++++ .../_pydev_bundle/_pydev_imports_tipper.py | 178 +++++++++--------- .../_pydev_bundle/pydev_console_utils.py | 168 ++++++++--------- .../python/psi/resolve/PyResolveContext.java | 19 +- .../python/PyParameterInfoHandler.java | 4 +- .../completion/PydevConsoleReference.java | 76 ++++++-- .../psi/impl/PyReferenceExpressionImpl.java | 4 +- 7 files changed, 406 insertions(+), 203 deletions(-) create mode 100644 python/helpers/pydev/_pydev_bundle/_pydev_calltip_util.py 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/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/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 Date: Sun, 28 Aug 2016 20:55:47 -0400 Subject: [PATCH 2/4] consider language level in console parser to currectly provide parameters for print(...) --- .../com/jetbrains/python/console/parsing/PyConsoleParser.java | 4 ++-- python/src/com/jetbrains/python/psi/PyFileElementType.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/src/com/jetbrains/python/console/parsing/PyConsoleParser.java b/python/src/com/jetbrains/python/console/parsing/PyConsoleParser.java index d0a6854bf634..b09bb3300c11 100644 --- a/python/src/com/jetbrains/python/console/parsing/PyConsoleParser.java +++ b/python/src/com/jetbrains/python/console/parsing/PyConsoleParser.java @@ -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 diff --git a/python/src/com/jetbrains/python/psi/PyFileElementType.java b/python/src/com/jetbrains/python/psi/PyFileElementType.java index 6358424b5d55..11cd013273ee 100644 --- a/python/src/com/jetbrains/python/psi/PyFileElementType.java +++ b/python/src/com/jetbrains/python/psi/PyFileElementType.java @@ -106,7 +106,7 @@ public class PyFileElementType extends IStubFileElementType { 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(); } From e1aa4eac1fa3d36c796567e0f4e40c4e42c66171 Mon Sep 17 00:00:00 2001 From: fitermay Date: Tue, 23 Aug 2016 19:39:55 -0400 Subject: [PATCH 3/4] bug fix: PyDevDocumentation provider should use best available parent reference expression to lookup documentation. --- .../console/PydevDocumentationProvider.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/python/src/com/jetbrains/python/console/PydevDocumentationProvider.java b/python/src/com/jetbrains/python/console/PydevDocumentationProvider.java index fc131a44771d..65905172a12a 100644 --- a/python/src/com/jetbrains/python/console/PydevDocumentationProvider.java +++ b/python/src/com/jetbrains/python/console/PydevDocumentationProvider.java @@ -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(); + } } From 70db0e9bbff7bc6c1837626151f148c509e21c4a Mon Sep 17 00:00:00 2001 From: fitermay Date: Wed, 24 Aug 2016 21:45:52 -0400 Subject: [PATCH 4/4] --handle getDescription in debugger --- .../pydev/_pydevd_bundle/pydevd_comm.py | 38 +++++++++++++++ .../pydev/_pydevd_bundle/pydevd_console.py | 27 +++++++++-- .../pydevd_process_net_command.py | 13 +++-- .../debugger/pydev/AbstractCommand.java | 5 +- .../debugger/pydev/GetDescriptionCommand.java | 47 +++++++++++++++++++ .../debugger/pydev/MultiProcessDebugger.java | 5 ++ .../debugger/pydev/ProcessDebugger.java | 3 ++ .../python/debugger/pydev/RemoteDebugger.java | 7 +++ .../PythonDebugConsoleCommunication.java | 4 +- .../python/debugger/PyDebugProcess.java | 11 +++++ 10 files changed, 149 insertions(+), 11 deletions(-) create mode 100644 python/pydevSrc/com/jetbrains/python/debugger/pydev/GetDescriptionCommand.java 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/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/console/PythonDebugConsoleCommunication.java b/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java index 78ecbf99578b..f0a16fcb3b69 100644 --- a/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java @@ -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 diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java index b252994c0e15..75023a3f24b6 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java @@ -902,6 +902,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) { }