diff --git a/python/helpers/pydev/django_debug.py b/python/helpers/pydev/django_debug.py new file mode 100644 index 000000000000..754c01a9781c --- /dev/null +++ b/python/helpers/pydev/django_debug.py @@ -0,0 +1,151 @@ +import inspect +from pydev.pydevd_comm import CMD_SET_BREAK +from pydev.pydevd_constants import DJANGO_SUSPEND, GetThreadId +from pydev.pydevd_file_utils import NormFileToServer +from pydev.runfiles import DictContains +from pydevd_breakpoints import LineBreakpoint +import traceback + + +def get_source(frame): + try: + return frame.f_locals['self'].source + except: + return None + +def get_template_file_name(frame): + try: + source = get_source(frame) + return source[0].name + except: + return None + +def get_template_line(frame): + source = get_source(frame) + file_name = get_template_file_name(frame) + try: + return offset_to_line_number(read_file(file_name), source[1][0]) + except: + return None + +class DjangoTemplateFrame: + def __init__(self, frame): + file_name = get_template_file_name(frame) + context = frame.f_locals['context'] + self.f_code = FCode('Django Template', file_name) + self.f_lineno = get_template_line(frame) + self.f_back = frame + self.f_globals = {} + self.f_locals = collect_context(context) + self.f_trace = None + + +class FCode: + def __init__(self, name, filename): + self.co_name = name + self.co_filename = filename + + +def collect_context(context): + res = {} + for d in context.dicts: + for k,v in d.items(): + res[k] = v + return res + +def read_file(filename): + f = open(filename, "r") + s = f.read() + f.close() + return s + +def offset_to_line_number(text, offset): + curLine = 1 + curOffset = 0 + while curOffset < offset: + if curOffset == len(text): + return -1 + c = text[curOffset] + if c == '\n': + curLine += 1 + elif c == '\r': + curLine += 1 + if curOffset < len(text) and text[curOffset + 1] == '\n': + curOffset += 1 + + curOffset += 1 + + return curLine + +class DjangoLineBreakpoint(LineBreakpoint): + def __init__(self, type, file, line, flag, condition, func_name, expression): + self.file = file + self.line = line + LineBreakpoint.__init__(self, type, flag, condition, func_name, expression) + + def __eq__(self, other): + if not isinstance(other, DjangoLineBreakpoint): + return False + return self.file == other.file and self.line == other.line + + def is_triggered(self, frame): + file = get_template_file_name(frame) + line = get_template_line(frame) + return self.file == file and self.line == line + +def is_django_render_call(frame): + try: + name = frame.f_code.co_name + if name != 'render': + return False + + if not DictContains(frame.f_locals, 'self'): + return False + + cls = frame.f_locals['self'].__class__ + + inherits_node = False + for base in inspect.getmro(cls): + if base.__name__ == 'Node': + inherits_node = True + break + + if not inherits_node: + return False + + clsname = cls.__name__ + return clsname != 'TextNode' and clsname != 'NodeList' + except : + traceback.print_exc() + return False + +def is_django_suspended(thread): + return thread.additionalInfo.suspend_type == DJANGO_SUSPEND + +def suspend_django(py_db_frame, mainDebugger, thread, frame): + frame = DjangoTemplateFrame(frame) + + if frame.f_lineno is None: + return None + + #try: + # if thread.additionalInfo.filename == frame.f_code.co_filename and thread.additionalInfo.line == frame.f_lineno: + # return None # don't stay twice on the same line + #except AttributeError: + # pass + + mainDebugger.additional_frames.addAdditionalFrameById(GetThreadId(thread), {id(frame): frame}) + + + py_db_frame.setSuspend(thread, CMD_SET_BREAK) + thread.additionalInfo.suspend_type = DJANGO_SUSPEND + + thread.additionalInfo.filename = frame.f_code.co_filename + thread.additionalInfo.line = frame.f_lineno + + return frame + + + + + diff --git a/python/helpers/pydev/pydevd.py b/python/helpers/pydev/pydevd.py index ca21c92baeed..2255455ba938 100644 --- a/python/helpers/pydev/pydevd.py +++ b/python/helpers/pydev/pydevd.py @@ -1,4 +1,5 @@ #IMPORTANT: pydevd_constants must be the 1st thing defined because it'll keep a reference to the original sys._getframe +from pydev.django_debug import DjangoLineBreakpoint from pydevd_constants import * #@UnusedWildImport from pydevd_breakpoints import * #@UnusedWildImport @@ -48,6 +49,7 @@ from pydevd_comm import CMD_CHANGE_VARIABLE, \ from pydevd_file_utils import NormFileToServer, GetFilenameAndBase import pydevd_vars +from pydevd_vars import getAdditionalFramesContainer import traceback import pydevd_vm_type import pydevd_tracing @@ -194,6 +196,8 @@ class PyDB: self.cmdFactory = NetCommandFactory() self.cmdQueue = {} # the hash of Queues. Key is thread id, value is thread self.breakpoints = {} + self.django_breakpoints = {} + self.additional_frames = getAdditionalFramesContainer() self.readyToRun = False self.lock = threading.RLock() self.internalQueueLock = threading.Lock() @@ -529,7 +533,8 @@ class PyDB: #command to add some breakpoint. # text is file\tline. Add to breakpoints dictionary - file, line, condition, expression = text.split('\t', 3) + type, file, line, condition, expression = text.split('\t', 4) + if condition.startswith('**FUNC**'): func_name, condition = condition.split('\t', 1) @@ -552,32 +557,29 @@ class PyDB: line = int(line) - if DEBUG_TRACE_BREAKPOINTS > 0: - sys.stderr.write('Added breakpoint:%s - line:%s - func_name:%s\n' % (file, line, func_name)) - sys.stderr.flush() - - if DictContains(self.breakpoints, file): - breakDict = self.breakpoints[file] - else: - breakDict = {} - if len(condition) <= 0 or condition is None or condition == "None": condition = None if len(expression) <= 0 or expression is None or expression == "None": expression = None - breakDict[line] = (True, condition, func_name, expression) + if type == 'python-line': + breakpoint = LineBreakpoint(type, True, condition, func_name, expression) + breakpoint.add(self.breakpoints, file, line, func_name) + elif type == 'django-line': + breakpoint = DjangoLineBreakpoint(type, file, line, True, condition, func_name, expression) + breakpoint.add(self.django_breakpoints, file, line, func_name) + else: + raise NameError(type) - self.breakpoints[file] = breakDict self.enable_tracing() elif cmd_id == CMD_REMOVE_BREAK: #command to remove some breakpoint #text is file\tline. Remove from breakpoints dictionary - file, line = text.split('\t', 1) + type, file, line = text.split('\t', 2) file = NormFileToServer(file) try: line = int(line) @@ -586,7 +588,10 @@ class PyDB: else: try: - del self.breakpoints[file][line] #remove the breakpoint in that line + if type == 'django-line': + del self.django_breakpoints[file][line] + else: + del self.breakpoints[file][line] #remove the breakpoint in that line if DEBUG_TRACE_BREAKPOINTS > 0: sys.stderr.write('Removed breakpoint:%s\n' % (file,)) sys.stderr.flush() @@ -685,6 +690,7 @@ class PyDB: thread.additionalInfo.pydev_notify_kill = True def setSuspend(self, thread, stop_reason): + thread.additionalInfo.suspend_type = PYTHON_SUSPEND thread.additionalInfo.pydev_state = STATE_SUSPEND thread.stop_reason = stop_reason @@ -816,13 +822,13 @@ class PyDB: self.force_post_mortem_stop -= 1 frame, frames_byid = additionalInfo.pydev_force_stop_at_exception thread_id = GetThreadId(t) - used_id = pydevd_vars.addAdditionalFrameById(thread_id, frames_byid) + used_id = pydev_vars.additional_frames_container.addAdditionalFrameById(thread_id, frames_byid) try: self.setSuspend(t, CMD_ADD_EXCEPTION_BREAK) self.doWaitSuspend(t, frame, 'exception', None) finally: additionalInfo.pydev_force_stop_at_exception = None - pydevd_vars.removeAdditionalFrameById(thread_id) + pydev_vars.additional_frames_container.removeAdditionalFrameById(thread_id) # if thread is not alive, cancel trace_dispatch processing if not t.isAlive(): diff --git a/python/helpers/pydev/pydevd_breakpoints.py b/python/helpers/pydev/pydevd_breakpoints.py index a9317b479422..ebac70801a8d 100644 --- a/python/helpers/pydev/pydevd_breakpoints.py +++ b/python/helpers/pydev/pydevd_breakpoints.py @@ -21,6 +21,34 @@ class ExceptionBreakpoint: self.type = exctype self.notify = {NOTIFY_ALWAYS: notify_always, NOTIFY_ON_TERMINATE: notify_on_terminate} +class LineBreakpoint: + def __init__(self, type, flag, condition, func_name, expression): + self.type = type + self.condition = condition + self.func_name = func_name + self.expression = expression + + def get_break_dict(self, breakpoints, file): + if DictContains(breakpoints, file): + breakDict = breakpoints[file] + else: + breakDict = {} + breakpoints[file] = breakDict + return breakDict + + def trace(self, file, line, func_name): + if DEBUG_TRACE_BREAKPOINTS > 0: + sys.stderr.write('Added breakpoint:%s - line:%s - func_name:%s\n' % (file, line, func_name)) + sys.stderr.flush() + + def add(self, breakpoints, file, line, func_name): + self.trace(file, line, func_name) + + breakDict = self.get_break_dict(breakpoints, file) + + breakDict[line] = self + + def get_exception_breakpoint(exctype, exceptions, notify_class): exc = None if exceptions is not None: diff --git a/python/helpers/pydev/pydevd_comm.py b/python/helpers/pydev/pydevd_comm.py index e0a51a0a2f7e..788d4cf7f92b 100644 --- a/python/helpers/pydev/pydevd_comm.py +++ b/python/helpers/pydev/pydevd_comm.py @@ -481,38 +481,41 @@ class NetCommandFactory: try: cmdTextList = [""] cmdTextList.append('' % (thread_id, stop_reason, message)) - - curFrame = frame - while curFrame: - #print cmdText - myId = str(id(curFrame)) - #print "id is ", myId - - if curFrame.f_code is None: - break #Iron Python sometimes does not have it! - - myName = curFrame.f_code.co_name #method name (if in method) or ? if global - if myName is None: - break #Iron Python sometimes does not have it! - - #print "name is ", myName - - myFile = pydevd_file_utils.NormFileToClient(curFrame.f_code.co_filename) - #print "file is ", myFile - #myFile = inspect.getsourcefile(curFrame) or inspect.getfile(frame) - - myLine = str(curFrame.f_lineno) - #print "line is ", myLine - - #the variables are all gotten 'on-demand' - #variables = pydevd_vars.frameVarsToXML(curFrame) - variables = '' - cmdTextList.append('"' % (quote(myFile, '/>_= \t'), myLine)) - cmdTextList.append(variables) - cmdTextList.append("") - curFrame = curFrame.f_back + curFrame = frame + try: + while curFrame: + #print cmdText + myId = str(id(curFrame)) + #print "id is ", myId + + if curFrame.f_code is None: + break #Iron Python sometimes does not have it! + + myName = curFrame.f_code.co_name #method name (if in method) or ? if global + if myName is None: + break #Iron Python sometimes does not have it! + + #print "name is ", myName + + myFile = pydevd_file_utils.NormFileToClient(curFrame.f_code.co_filename) + #print "file is ", myFile + #myFile = inspect.getsourcefile(curFrame) or inspect.getfile(frame) + + myLine = str(curFrame.f_lineno) + #print "line is ", myLine + + #the variables are all gotten 'on-demand' + #variables = pydevd_vars.frameVarsToXML(curFrame) + + variables = '' + cmdTextList.append('"' % (quote(myFile, '/>_= \t'), myLine)) + cmdTextList.append(variables) + cmdTextList.append("") + curFrame = curFrame.f_back + except : + traceback.print_exc() cmdTextList.append("") cmdText = ''.join(cmdTextList) @@ -778,7 +781,7 @@ class InternalGetCompletions(InternalThreadCommand): try: frame = pydevd_vars.findFrame(self.thread_id, self.frame_id) - + #Not using frame.f_globals because of https://sourceforge.net/tracker2/?func=detail&aid=2541355&group_id=85796&atid=577329 #(Names not resolved in generator expression in method) #See message: http://mail.python.org/pipermail/python-list/2009-January/526522.html diff --git a/python/helpers/pydev/pydevd_constants.py b/python/helpers/pydev/pydevd_constants.py index b8b1aa4c2c6d..4f0f88a2f44b 100644 --- a/python/helpers/pydev/pydevd_constants.py +++ b/python/helpers/pydev/pydevd_constants.py @@ -5,6 +5,9 @@ This module holds the constants used for specifying the states of the debugger. STATE_RUN = 1 STATE_SUSPEND = 2 +PYTHON_SUSPEND = 1 +DJANGO_SUSPEND = 2 + try: __setFalse = False except: diff --git a/python/helpers/pydev/pydevd_file_utils.py b/python/helpers/pydev/pydevd_file_utils.py index ab5357ce38a9..f29169ea51c8 100644 --- a/python/helpers/pydev/pydevd_file_utils.py +++ b/python/helpers/pydev/pydevd_file_utils.py @@ -191,11 +191,11 @@ if PATHS_FROM_ECLIPSE_TO_PYTHON: except KeyError: #used to translate a path from the debug server to the client translated = _NormFile(filename) - for eclipse_prefix, pyhon_prefix in PATHS_FROM_ECLIPSE_TO_PYTHON: - if translated.startswith(pyhon_prefix): + for eclipse_prefix, python_prefix in PATHS_FROM_ECLIPSE_TO_PYTHON: + if translated.startswith(python_prefix): if DEBUG_CLIENT_SERVER_TRANSLATION: sys.stderr.write('pydev debugger: replacing to client: %s\n' % (translated,)) - translated = translated.replace(pyhon_prefix, eclipse_prefix) + translated = translated.replace(python_prefix, eclipse_prefix) if DEBUG_CLIENT_SERVER_TRANSLATION: sys.stderr.write('pydev debugger: sent to client: %s\n' % (translated,)) break diff --git a/python/helpers/pydev/pydevd_frame.py b/python/helpers/pydev/pydevd_frame.py index 44edd63c7a8a..14d748350c3d 100644 --- a/python/helpers/pydev/pydevd_frame.py +++ b/python/helpers/pydev/pydevd_frame.py @@ -1,3 +1,5 @@ +from pydev.django_debug import DjangoTemplateFrame, DjangoLineBreakpoint, is_django_render_call, get_template_file_name, get_template_line, is_django_suspended, suspend_django +import pydevd_vars from pydevd_comm import * #@UnusedWildImport from pydevd_constants import * #@UnusedWildImport from pydevd_breakpoints import * #@UnusedWildImport @@ -15,18 +17,18 @@ class PyDBFrame: is used initially when we enter into a new context ('call') and then is reused for the entire context. ''' - + def __init__(self, *args): #args = mainDebugger, filename, base, info, t, frame #yeap, much faster than putting in self and then getting it from self later on self._args = args[:-1] - + def setSuspend(self, *args, **kwargs): self._args[0].setSuspend(*args, **kwargs) - + def doWaitSuspend(self, *args, **kwargs): self._args[0].doWaitSuspend(*args, **kwargs) - + def trace_dispatch(self, frame, event, arg): if event not in ('line', 'call', 'return', 'exception'): return None @@ -34,10 +36,11 @@ class PyDBFrame: mainDebugger, filename, info, thread = self._args if event is not 'exception': - breakpoint = mainDebugger.breakpoints.get(filename) + breakpoints_for_file = mainDebugger.breakpoints.get(filename) can_skip = False - if len(always_exception_set) == 0: + + if len(always_exception_set): if info.pydev_state == STATE_RUN: #we can skip if: #- we have no stop marked @@ -45,14 +48,16 @@ class PyDBFrame: can_skip = (info.pydev_step_cmd is None and info.pydev_step_stop is None)\ or (info.pydev_step_cmd in (CMD_STEP_RETURN, CMD_STEP_OVER) and info.pydev_step_stop is not frame) + if mainDebugger.django_breakpoints: + can_skip = False # Let's check to see if we are in a function that has a breakpoint. If we don't have a breakpoint, # we will return nothing for the next trace #also, after we hit a breakpoint and go to some other debugging state, we have to force the set trace anyway, #so, that's why the additional checks are there. - if not breakpoint: + if not breakpoints_for_file: if can_skip: - return None + return None else: #checks the breakpoint to see if there is a context match in some function @@ -62,9 +67,9 @@ class PyDBFrame: if curr_func_name in ('?', ''): curr_func_name = '' - for _b, condition, func_name, expression in breakpoint.values(): #jython does not support itervalues() + for breakpoint in breakpoints_for_file.values(): #jython does not support itervalues() #will match either global or some function - if func_name in ('None', curr_func_name): + if breakpoint.func_name in ('None', curr_func_name): break else: # if we had some break, it won't get here (so, that's a context that we want to skip) @@ -72,7 +77,7 @@ class PyDBFrame: #print 'skipping', frame.f_lineno, info.pydev_state, info.pydev_step_stop, info.pydev_step_cmd return None else: - breakpoint = None + breakpoints_for_file = None #We may have hit a breakpoint or we are already in step mode. Either way, let's check what we should do in this frame #print 'NOT skipped', frame.f_lineno, frame.f_code.co_name, event @@ -91,31 +96,47 @@ class PyDBFrame: thread.additionalInfo.message = exception_breakpoint.qname #self.doWaitSuspend(thread, frame, event, arg) + elif event == 'call' and info.pydev_state != STATE_SUSPEND and mainDebugger.django_breakpoints \ + and is_django_render_call(frame): + flag = False + filename = get_template_file_name(frame) + django_breakpoints_for_file = mainDebugger.django_breakpoints.get(filename) + if django_breakpoints_for_file: + template_line = get_template_line(frame) + if DictContains(django_breakpoints_for_file, template_line): + django_breakpoint = django_breakpoints_for_file[template_line] + + if django_breakpoint.is_triggered(frame): + frame = suspend_django(self, mainDebugger, thread, frame) + flag = True + + #if not flag: + # return self.trace_dispatch + + #return is not taken into account for breakpoint hit because we'd have a double-hit in this case #(one for the line and the other for the return). - elif event != 'return' and info.pydev_state != STATE_SUSPEND and breakpoint is not None \ - and DictContains(breakpoint, line): - + elif event != 'return' and info.pydev_state != STATE_SUSPEND and breakpoints_for_file is not None\ + and DictContains(breakpoints_for_file, line): #ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint # lets do the conditional stuff here - condition = breakpoint[line][1] + breakpoint = breakpoints_for_file[line] - if condition is not None: + if breakpoint.condition is not None: try: - val = eval(condition, frame.f_globals, frame.f_locals) + val = eval(breakpoint.condition, frame.f_globals, frame.f_locals) if not val: return self.trace_dispatch - + except: - sys.stderr.write('Error while evaluating condition \'%s\': %s\n'%(condition, sys.exc_info()[1])) + sys.stderr.write('Error while evaluating condition \'%s\': %s\n' % (breakpoint.condition, sys.exc_info()[1])) sys.stderr.flush() return self.trace_dispatch - expression = breakpoint[line][3] - if expression is not None: + if breakpoint.expression is not None: try: try: - val = eval(expression, frame.f_globals, frame.f_locals) + val = eval(breakpoint.expression, frame.f_globals, frame.f_locals) except: val = sys.exc_info()[1] finally: @@ -123,41 +144,45 @@ class PyDBFrame: thread.log_expression = val self.setSuspend(thread, CMD_SET_BREAK) - + # if thread has a suspend flag, we suspend with a busy wait if info.pydev_state == STATE_SUSPEND: self.doWaitSuspend(thread, frame, event, arg) return self.trace_dispatch - + except: raise - + #step handling. We stop when we hit the right frame try: - + django_stop = False if info.pydev_step_cmd == CMD_STEP_INTO: - stop = event in ('line', 'return') - + elif info.pydev_step_cmd == CMD_STEP_OVER: - - stop = info.pydev_step_stop is frame and event in ('line', 'return') - + if is_django_suspended(thread): + + django_stop = event == 'call' and is_django_render_call(frame) + + stop = False + else: + stop = info.pydev_step_stop is frame and event in ('line', 'return') + elif info.pydev_step_cmd == CMD_STEP_RETURN: - stop = event == 'return' and info.pydev_step_stop is frame - + elif info.pydev_step_cmd == CMD_RUN_TO_LINE: stop = False + if event == 'line': #Yes, we can only act on line events (weird hum?) #Note: This code is duplicated at pydevd.py curr_func_name = frame.f_code.co_name - + #global context is set with an empty name if curr_func_name in ('?', ''): curr_func_name = '' - + if curr_func_name == info.pydev_func_name: line = info.pydev_next_line if frame.f_lineno == line: @@ -168,11 +193,15 @@ class PyDBFrame: frame.f_lineno = line frame.f_trace = None stop = True - + else: stop = False - - if stop: + + if django_stop: + frame = suspend_django(self, mainDebugger, thread, frame) + if frame: + self.doWaitSuspend(thread, frame, event, arg) + elif stop: #event is always == line or return at this point if event == 'line': self.setSuspend(thread, info.pydev_step_cmd) @@ -180,14 +209,12 @@ class PyDBFrame: else: #return event back = frame.f_back if back is not None: - #When we get to the pydevd run function, the debugging has actually finished for the main thread #(note that it can still go on for other threads, but for this one, we just make it finish) #So, just setting it to None should be OK if basename(back.f_code.co_filename) == 'pydevd.py' and back.f_code.co_name == 'run': back = None - - + if back is not None: #if we're in a return, we want it to appear to the user in the previous frame! self.setSuspend(thread, info.pydev_step_cmd) @@ -197,22 +224,23 @@ class PyDBFrame: info.pydev_step_stop = None info.pydev_step_cmd = None info.pydev_state = STATE_RUN - - + + except: traceback.print_exc() info.pydev_step_cmd = None - + #if we are quitting, let's stop the tracing retVal = None if not mainDebugger.quitting: retVal = self.trace_dispatch return retVal - + if USE_PSYCO_OPTIMIZATION: try: import psyco + trace_dispatch = psyco.proxy(trace_dispatch) except ImportError: if hasattr(sys, 'exc_clear'): #jython does not have it diff --git a/python/helpers/pydev/pydevd_vars.py b/python/helpers/pydev/pydevd_vars.py index b73a7154d834..c2c1fa93a4d8 100644 --- a/python/helpers/pydev/pydevd_vars.py +++ b/python/helpers/pydev/pydevd_vars.py @@ -1,6 +1,7 @@ """ pydevd_vars deals with variables: resolution/conversion to XML. """ +import pickle from pydevd_constants import * #@UnusedWildImport from types import * #@UnusedWildImport from console import pydevconsole @@ -13,6 +14,7 @@ try: except ImportError: from io import StringIO import sys #@Reimport + try: from urllib import quote except: @@ -32,30 +34,32 @@ try: __setFalse = False except: import __builtin__ + setattr(__builtin__, 'True', 1) setattr(__builtin__, 'False', 0) #------------------------------------------------------------------------------------------------------ class for errors -class VariableError(RuntimeError):pass -class FrameNotFoundError(RuntimeError):pass +class VariableError(RuntimeError): pass + +class FrameNotFoundError(RuntimeError): pass #------------------------------------------------------------------------------------------------------ resolvers in map if not sys.platform.startswith("java"): typeMap = [ - #None means that it should not be treated as a compound variable + #None means that it should not be treated as a compound variable - #isintance does not accept a tuple on some versions of python, so, we must declare it expanded - (type(None), None,), - (int, None), - (float, None), - (complex, None), - (str, None), - (tuple, pydevd_resolver.tupleResolver), - (list, pydevd_resolver.tupleResolver), - (dict, pydevd_resolver.dictResolver), + #isintance does not accept a tuple on some versions of python, so, we must declare it expanded + (type(None), None,), + (int, None), + (float, None), + (complex, None), + (str, None), + (tuple, pydevd_resolver.tupleResolver), + (list, pydevd_resolver.tupleResolver), + (dict, pydevd_resolver.dictResolver), ] try: @@ -80,17 +84,18 @@ if not sys.platform.startswith("java"): else: #platform is java from org.python import core #@UnresolvedImport + typeMap = [ - (core.PyNone, None), - (core.PyInteger, None), - (core.PyLong, None), - (core.PyFloat, None), - (core.PyComplex, None), - (core.PyString, None), - (core.PyTuple, pydevd_resolver.tupleResolver), - (core.PyList, pydevd_resolver.tupleResolver), - (core.PyDictionary, pydevd_resolver.dictResolver), - (core.PyStringMap, pydevd_resolver.dictResolver), + (core.PyNone, None), + (core.PyInteger, None), + (core.PyLong, None), + (core.PyFloat, None), + (core.PyComplex, None), + (core.PyString, None), + (core.PyTuple, pydevd_resolver.tupleResolver), + (core.PyList, pydevd_resolver.tupleResolver), + (core.PyDictionary, pydevd_resolver.dictResolver), + (core.PyStringMap, pydevd_resolver.dictResolver), ] if hasattr(core, 'PyJavaInstance'): @@ -115,7 +120,6 @@ def getType(o): return 'Unable to get Type', 'Unable to get Type', None try: - if type_name == 'org.python.core.PyJavaInstance': return (type_object, type_name, pydevd_resolver.instanceResolver) @@ -134,8 +138,9 @@ def getType(o): try: from xml.sax.saxutils import escape + def makeValidXmlValue(s): - return escape(s, {'"':'"'}) + return escape(s, {'"': '"'}) except: #Simple replacement if it's not there. def makeValidXmlValue(s): @@ -177,7 +182,7 @@ def varToXML(val, name, doTrim=True): except: value = 'Unable to get repr for %s' % v.__class__ - xml = '0: + if self.skip > 0: self.skip = self.skip - 1 else: if data == "Traceback (most recent call last):\n": @@ -478,7 +508,6 @@ def consoleExec(thread_id, frame_id, expression): return False - def changeAttrExpression(thread_id, frame_id, attr, expression): '''Changes some attribute in a given frame. @note: it will not (currently) work if we're not in the topmost frame (that's a python @@ -489,16 +518,16 @@ def changeAttrExpression(thread_id, frame_id, attr, expression): try: expression = expression.replace('@LINE@', '\n') -#tests (needs proposed patch in python accepted) -# if hasattr(frame, 'savelocals'): -# if attr in frame.f_locals: -# frame.f_locals[attr] = eval(expression, frame.f_globals, frame.f_locals) -# frame.savelocals() -# return -# -# elif attr in frame.f_globals: -# frame.f_globals[attr] = eval(expression, frame.f_globals, frame.f_locals) -# return + #tests (needs proposed patch in python accepted) + # if hasattr(frame, 'savelocals'): + # if attr in frame.f_locals: + # frame.f_locals[attr] = eval(expression, frame.f_globals, frame.f_locals) + # frame.savelocals() + # return + # + # elif attr in frame.f_globals: + # frame.f_globals[attr] = eval(expression, frame.f_globals, frame.f_locals) + # return if attr[:7] == "Globals": diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/LineBreakpointCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/LineBreakpointCommand.java index 173fe04eccdf..7e31323cf873 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/LineBreakpointCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/LineBreakpointCommand.java @@ -6,20 +6,23 @@ import org.jetbrains.annotations.NotNull; * @author traff */ public abstract class LineBreakpointCommand extends AbstractCommand { + private final String myType; @NotNull protected final String myFile; protected final int myLine; + public LineBreakpointCommand(RemoteDebugger debugger, - int commandCode, + String type, int commandCode, @NotNull final String file, final int line) { super(debugger, commandCode); + myType = type; myFile = file; myLine = line; } @Override protected void buildPayload(Payload payload) { - payload.add(myFile).add(Integer.toString(myLine)); + payload.add(myType).add(myFile).add(Integer.toString(myLine)); } } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoveBreakpointCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoveBreakpointCommand.java index 13f664f6b9da..893989de38c3 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoveBreakpointCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoveBreakpointCommand.java @@ -1,9 +1,11 @@ package com.jetbrains.python.debugger.pydev; +import org.jetbrains.annotations.NotNull; + public class RemoveBreakpointCommand extends LineBreakpointCommand { - public RemoveBreakpointCommand(final RemoteDebugger debugger, final String file, final int line) { - super(debugger, REMOVE_BREAKPOINT, file, line); + public RemoveBreakpointCommand(final RemoteDebugger debugger, @NotNull final String type, final String file, final int line) { + super(debugger, type, REMOVE_BREAKPOINT, file, line); } } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/SetBreakpointCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/SetBreakpointCommand.java index db9c538eb053..993add195579 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/SetBreakpointCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/SetBreakpointCommand.java @@ -9,17 +9,19 @@ public class SetBreakpointCommand extends LineBreakpointCommand { private @Nullable final String myLogExpression; public SetBreakpointCommand(@NotNull final RemoteDebugger debugger, + @NotNull final String type, @NotNull final String file, @NotNull final int line) { - this(debugger, file, line, null, null); + this(debugger, type, file, line, null, null); } public SetBreakpointCommand(@NotNull final RemoteDebugger debugger, + @NotNull final String type, @NotNull final String file, @NotNull final int line, @Nullable final String condition, @Nullable final String logExpression) { - super(debugger, SET_BREAKPOINT, file, line); + super(debugger, type, SET_BREAKPOINT, file, line); myCondition = condition; myLogExpression = logExpression; } diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 57abb96ad506..80632ecc2b1b 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -77,6 +77,7 @@ + diff --git a/python/src/com/jetbrains/python/debugger/AbstractLineBreakpointHandler.java b/python/src/com/jetbrains/python/debugger/AbstractLineBreakpointHandler.java new file mode 100644 index 000000000000..122aaeee461b --- /dev/null +++ b/python/src/com/jetbrains/python/debugger/AbstractLineBreakpointHandler.java @@ -0,0 +1,52 @@ +package com.jetbrains.python.debugger; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.intellij.xdebugger.XSourcePosition; +import com.intellij.xdebugger.breakpoints.XBreakpointHandler; +import com.intellij.xdebugger.breakpoints.XBreakpointProperties; +import com.intellij.xdebugger.breakpoints.XBreakpointType; +import com.intellij.xdebugger.breakpoints.XLineBreakpoint; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; + +/** + * @author traff + */ +public class AbstractLineBreakpointHandler extends XBreakpointHandler> { + protected final PyDebugProcess myDebugProcess; + private final Map, XSourcePosition> myBreakPointPositions = Maps.newHashMap(); + + public AbstractLineBreakpointHandler( + Class, ?>> breakpointTypeClass, + @NotNull final PyDebugProcess debugProcess) { + super(breakpointTypeClass); + myDebugProcess = debugProcess; + } + + public void reregisterBreakpoints() { + List> breakpoints = Lists.newArrayList(myBreakPointPositions.keySet()); + for (XLineBreakpoint breakpoint : breakpoints) { + unregisterBreakpoint(breakpoint, false); + registerBreakpoint(breakpoint); + } + } + + public void registerBreakpoint(@NotNull final XLineBreakpoint breakpoint) { + final XSourcePosition position = breakpoint.getSourcePosition(); + if (position != null) { + myDebugProcess.addBreakpoint(myDebugProcess.getPositionConverter().convert(position), breakpoint); + myBreakPointPositions.put(breakpoint, position); + } + } + + public void unregisterBreakpoint(@NotNull final XLineBreakpoint breakpoint, final boolean temporary) { + final XSourcePosition position = myBreakPointPositions.get(breakpoint); + if (position != null) { + myDebugProcess.removeBreakpoint(myDebugProcess.getPositionConverter().convert(position)); + myBreakPointPositions.remove(breakpoint); + } + } +} diff --git a/python/src/com/jetbrains/python/debugger/DjangoLineBreakpointHandler.java b/python/src/com/jetbrains/python/debugger/DjangoLineBreakpointHandler.java new file mode 100644 index 000000000000..eeb76b8ee875 --- /dev/null +++ b/python/src/com/jetbrains/python/debugger/DjangoLineBreakpointHandler.java @@ -0,0 +1,10 @@ +package com.jetbrains.python.debugger; + +import org.jetbrains.annotations.NotNull; + + +public class DjangoLineBreakpointHandler extends AbstractLineBreakpointHandler { + public DjangoLineBreakpointHandler(@NotNull final PyDebugProcess debugProcess) { + super(DjangoTemplateLineBreakpointType.class, debugProcess); + } +} diff --git a/python/src/com/jetbrains/python/debugger/DjangoTemplateLineBreakpointType.java b/python/src/com/jetbrains/python/debugger/DjangoTemplateLineBreakpointType.java new file mode 100644 index 000000000000..95242531f3ed --- /dev/null +++ b/python/src/com/jetbrains/python/debugger/DjangoTemplateLineBreakpointType.java @@ -0,0 +1,61 @@ +package com.jetbrains.python.debugger; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiComment; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.util.Processor; +import com.intellij.xdebugger.XDebuggerUtil; +import com.intellij.xdebugger.breakpoints.XBreakpointProperties; +import com.intellij.xdebugger.breakpoints.XLineBreakpointType; +import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider; +import com.jetbrains.django.util.DjangoUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + + +public class DjangoTemplateLineBreakpointType extends XLineBreakpointType { + private final PyDebuggerEditorsProvider myEditorsProvider = new PyDebuggerEditorsProvider(); + + public DjangoTemplateLineBreakpointType() { + super("django-line", "Django Line Breakpoint"); + } + + public boolean canPutAt(@NotNull final VirtualFile file, final int line, @NotNull final Project project) { + final Ref stoppable = Ref.create(false); + final Document document = FileDocumentManager.getInstance().getDocument(file); + if (document != null) { + if (DjangoUtil.isDjangoTemplateDocument(document, project)) { + XDebuggerUtil.getInstance().iterateLine(project, document, line, new Processor() { + public boolean process(PsiElement psiElement) { + if (psiElement instanceof PsiWhiteSpace || psiElement instanceof PsiComment) return true; + // Python debugger seems to be able to stop on pretty much everything + stoppable.set(true); + return false; + } + }); + } + } + + return stoppable.get(); + } + + @Nullable + public XBreakpointProperties createBreakpointProperties(@NotNull final VirtualFile file, final int line) { + return null; + } + + @Override + public String getBreakpointsDialogHelpTopic() { + return "reference.dialogs.breakpoints"; + } + + @Override + public XDebuggerEditorsProvider getEditorsProvider() { + return myEditorsProvider; + } +} diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java index 55493a3e7514..811f2ce10731 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java @@ -68,7 +68,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr super(session); session.setPauseActionSupported(true); myDebugger = new RemoteDebugger(this, serverSocket, 10); - myBreakpointHandlers = new XBreakpointHandler[]{new PyLineBreakpointHandler(this), new PyExceptionBreakpointHandler(this)}; + myBreakpointHandlers = new XBreakpointHandler[]{new PyLineBreakpointHandler(this), new PyExceptionBreakpointHandler(this), new DjangoLineBreakpointHandler(this)}; myEditorsProvider = new PyDebuggerEditorsProvider(); myProcessHandler = processHandler; myExecutionConsole = executionConsole; @@ -156,7 +156,8 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr printToConsole("Connected to pydev debugger (build " + remoteVersion + ")\n", ConsoleViewContentType.SYSTEM_OUTPUT); if (!remoteVersion.equals(currentBuild)) { - printToConsole("Warning: wrong debugger version. Use pycharm-debugger.egg from PyCharm installation folder.\n", ConsoleViewContentType.ERROR_OUTPUT); + printToConsole("Warning: wrong debugger version. Use pycharm-debugger.egg from PyCharm installation folder.\n", + ConsoleViewContentType.ERROR_OUTPUT); } } @@ -228,7 +229,8 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr dropFrameCaches(); if (myDebugger.isConnected() && !mySuspendedThreads.isEmpty()) { final PySourcePosition pyPosition = myPositionConverter.convert(position); - final SetBreakpointCommand command = new SetBreakpointCommand(myDebugger, pyPosition.getFile(), pyPosition.getLine()); + final SetBreakpointCommand command = + new SetBreakpointCommand(myDebugger, PyLineBreakpointType.ID, pyPosition.getFile(), pyPosition.getLine()); myDebugger.execute(command); // set temp. breakpoint resume(ResumeCommand.Mode.RESUME); } @@ -322,17 +324,21 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr myRegisteredBreakpoints.put(position, breakpoint); if (myDebugger.isConnected()) { final SetBreakpointCommand command = - new SetBreakpointCommand(myDebugger, position.getFile(), position.getLine(), breakpoint.getCondition(), + new SetBreakpointCommand(myDebugger, breakpoint.getType().getId(), position.getFile(), position.getLine(), + breakpoint.getCondition(), breakpoint.getLogExpression()); myDebugger.execute(command); } } public void removeBreakpoint(final PySourcePosition position) { - myRegisteredBreakpoints.remove(position); - if (myDebugger.isConnected()) { - final RemoveBreakpointCommand command = new RemoveBreakpointCommand(myDebugger, position.getFile(), position.getLine()); - myDebugger.execute(command); + XLineBreakpoint breakpoint = myRegisteredBreakpoints.get(position); + if (breakpoint != null) { + myRegisteredBreakpoints.remove(position); + if (myDebugger.isConnected()) { + final RemoveBreakpointCommand command = new RemoveBreakpointCommand(myDebugger, breakpoint.getType().getId(), position.getFile(), position.getLine()); + myDebugger.execute(command); + } } } @@ -375,7 +381,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr final PySourcePosition position = frames.get(0).getPosition(); breakpoint = myRegisteredBreakpoints.get(position); if (breakpoint == null) { - final RemoveBreakpointCommand command = new RemoveBreakpointCommand(myDebugger, position.getFile(), position.getLine()); + final RemoveBreakpointCommand command = new RemoveBreakpointCommand(myDebugger, "all", position.getFile(), position.getLine()); myDebugger.execute(command); // remove temp. breakpoint } } diff --git a/python/src/com/jetbrains/python/debugger/PyLineBreakpointHandler.java b/python/src/com/jetbrains/python/debugger/PyLineBreakpointHandler.java index d94f37677d60..b67230e3f016 100644 --- a/python/src/com/jetbrains/python/debugger/PyLineBreakpointHandler.java +++ b/python/src/com/jetbrains/python/debugger/PyLineBreakpointHandler.java @@ -1,49 +1,11 @@ package com.jetbrains.python.debugger; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.intellij.xdebugger.XSourcePosition; -import com.intellij.xdebugger.breakpoints.XBreakpointHandler; -import com.intellij.xdebugger.breakpoints.XBreakpointProperties; -import com.intellij.xdebugger.breakpoints.XLineBreakpoint; import org.jetbrains.annotations.NotNull; -import java.util.List; -import java.util.Map; - -public class PyLineBreakpointHandler extends XBreakpointHandler> { - - private final PyDebugProcess myDebugProcess; - - private final Map, XSourcePosition> myBreakPointPositions = Maps.newHashMap(); +public class PyLineBreakpointHandler extends AbstractLineBreakpointHandler { public PyLineBreakpointHandler(@NotNull final PyDebugProcess debugProcess) { - super(PyLineBreakpointType.class); - myDebugProcess = debugProcess; - } - - public void reregisterBreakpoints() { - List> breakpoints = Lists.newArrayList(myBreakPointPositions.keySet()); - for (XLineBreakpoint breakpoint : breakpoints) { - unregisterBreakpoint(breakpoint, false); - registerBreakpoint(breakpoint); - } - } - - public void registerBreakpoint(@NotNull final XLineBreakpoint breakpoint) { - final XSourcePosition position = breakpoint.getSourcePosition(); - if (position != null) { - myDebugProcess.addBreakpoint(myDebugProcess.getPositionConverter().convert(position), breakpoint); - myBreakPointPositions.put(breakpoint, position); - } - } - - public void unregisterBreakpoint(@NotNull final XLineBreakpoint breakpoint, final boolean temporary) { - final XSourcePosition position = myBreakPointPositions.get(breakpoint); - if (position != null) { - myDebugProcess.removeBreakpoint(myDebugProcess.getPositionConverter().convert(position)); - myBreakPointPositions.remove(breakpoint); - } + super(PyLineBreakpointType.class, debugProcess); } } diff --git a/python/src/com/jetbrains/python/debugger/PyLineBreakpointType.java b/python/src/com/jetbrains/python/debugger/PyLineBreakpointType.java index 825962a1c3b6..0abe87752d94 100644 --- a/python/src/com/jetbrains/python/debugger/PyLineBreakpointType.java +++ b/python/src/com/jetbrains/python/debugger/PyLineBreakpointType.java @@ -5,7 +5,9 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; +import com.intellij.psi.PsiComment; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiWhiteSpace; import com.intellij.util.Processor; import com.intellij.xdebugger.XDebuggerUtil; import com.intellij.xdebugger.breakpoints.XBreakpointProperties; @@ -17,17 +19,20 @@ import org.jetbrains.annotations.Nullable; public class PyLineBreakpointType extends XLineBreakpointType { + public static final String ID = "python-line"; + private static final String NAME = "Python Line Breakpoint"; + private final PyDebuggerEditorsProvider myEditorsProvider = new PyDebuggerEditorsProvider(); public PyLineBreakpointType() { - super("python-line", "Python Line Breakpoint"); + super(ID, NAME); } public boolean canPutAt(@NotNull final VirtualFile file, final int line, @NotNull final Project project) { final Ref stoppable = Ref.create(false); - if (file.getFileType() == PythonFileType.INSTANCE) { - final Document document = FileDocumentManager.getInstance().getDocument(file); - if (document != null) { + final Document document = FileDocumentManager.getInstance().getDocument(file); + if (document != null) { + if (file.getFileType() == PythonFileType.INSTANCE) { XDebuggerUtil.getInstance().iterateLine(project, document, line, new Processor() { public boolean process(PsiElement psiElement) { if (psiElement instanceof PsiWhiteSpace || psiElement instanceof PsiComment) return true; @@ -42,6 +47,7 @@ public class PyLineBreakpointType extends XLineBreakpointType