Snapshot idea/138.1696 from git://git.jetbrains.org/idea/community.git

Change-Id: I50c97b83a815ce635e49a38380ba5b8765e4b16a
diff --git a/python/helpers/pydev/pydevd.py b/python/helpers/pydev/pydevd.py
index 2077903..1733c26 100644
--- a/python/helpers/pydev/pydevd.py
+++ b/python/helpers/pydev/pydevd.py
@@ -1,4 +1,6 @@
 #IMPORTANT: pydevd_constants must be the 1st thing defined because it'll keep a reference to the original sys._getframe
+from __future__ import nested_scopes # Jython 2.1 support
+
 import traceback
 
 from django_debug import DjangoLineBreakpoint
@@ -35,7 +37,7 @@
                          CMD_ADD_DJANGO_EXCEPTION_BREAK, \
                          CMD_REMOVE_DJANGO_EXCEPTION_BREAK, \
                          CMD_SMART_STEP_INTO,\
-    InternalChangeVariable, \
+                         InternalChangeVariable, \
                          InternalGetCompletions, \
                          InternalEvaluateExpression, \
                          InternalConsoleExec, \
@@ -55,23 +57,35 @@
                          PydevdLog, \
                          StartClient, \
                          StartServer, \
-                         InternalSetNextStatementThread, ReloadCodeCommand
+                         InternalSetNextStatementThread, \
+                         ReloadCodeCommand, \
+    CMD_SET_PY_EXCEPTION, \
+                         CMD_IGNORE_THROWN_EXCEPTION_AT,\
+                         InternalGetBreakpointException, \
+                         InternalSendCurrExceptionTrace,\
+                         InternalSendCurrExceptionTraceProceeded,\
+                         CMD_ENABLE_DONT_TRACE, \
+                         CMD_GET_FILE_CONTENTS,\
+                         CMD_SET_PROPERTY_TRACE, CMD_RUN_CUSTOM_OPERATION,\
+                         InternalRunCustomOperation, CMD_EVALUATE_CONSOLE_EXPRESSION, InternalEvaluateConsoleExpression,\
+                         InternalConsoleGetCompletions
+
 from pydevd_file_utils import NormFileToServer, GetFilenameAndBase
 import pydevd_file_utils
 import pydevd_vars
 import pydevd_vm_type
 import pydevd_tracing
 import pydevd_io
-import pydev_monkey
 from pydevd_additional_thread_info import PyDBAdditionalThreadInfo
 from pydevd_custom_frames import CustomFramesContainer, CustomFramesContainerInit
+import pydevd_dont_trace
+import pydevd_traceproperty
 
+from _pydev_imps import _pydev_time as time
 
 if USE_LIB_COPY:
-    import _pydev_time as time
     import _pydev_threading as threading
 else:
-    import time
     import threading
 
 import os
@@ -80,10 +94,13 @@
 threadingEnumerate = threading.enumerate
 threadingCurrentThread = threading.currentThread
 
+try:
+    'dummy'.encode('utf-8') # Added because otherwise Jython 2.2.1 wasn't finding the encoding (if it wasn't loaded in the main thread).
+except:
+    pass
 
 DONT_TRACE = {
               # commonly used things from the stdlib that we don't want to trace
-              'threading.py':1,
               'Queue.py':1,
               'queue.py':1,
               'socket.py':1,
@@ -92,12 +109,19 @@
               'threading.py':1,
 
               #things from pydev that we don't want to trace
+              '_pydev_execfile.py':1,
+              '_pydev_jython_execfile.py':1,
+              '_pydev_threading':1,
+              'django_debug.py':1,
+              'django_frame.py':1,
+              'pydev_log.py':1,
               'pydevd.py':1 ,
               'pydevd_additional_thread_info.py':1,
-              'pydevd_custom_frames.py':1,
               'pydevd_comm.py':1,
               'pydevd_console.py':1 ,
               'pydevd_constants.py':1,
+              'pydevd_custom_frames.py':1,
+              'pydevd_dont_trace.py':1,
               'pydevd_exec.py':1,
               'pydevd_exec2.py':1,
               'pydevd_file_utils.py':1,
@@ -105,17 +129,18 @@
               'pydevd_import_class.py':1 ,
               'pydevd_io.py':1 ,
               'pydevd_psyco_stub.py':1,
+              'pydevd_referrers.py':1 ,
               'pydevd_reload.py':1 ,
               'pydevd_resolver.py':1 ,
+              'pydevd_save_locals.py':1 ,
+              'pydevd_signature.py':1,
               'pydevd_stackless.py':1 ,
               'pydevd_traceproperty.py':1,
               'pydevd_tracing.py':1 ,
-              'pydevd_signature.py':1,
               'pydevd_utils.py':1,
               'pydevd_vars.py':1,
               'pydevd_vm_type.py':1,
-              '_pydev_execfile.py':1,
-              '_pydev_jython_execfile.py':1
+              'pydevd_xml.py':1,
             }
 
 if IS_PY3K:
@@ -135,17 +160,28 @@
 from _pydev_filesystem_encoding import getfilesystemencoding
 file_system_encoding = getfilesystemencoding()
 
-def isThreadAlive(t):
-    try:
-        # If thread is not started yet we treat it as alive.
-        # It is required to debug threads started by start_new_thread in Python 3.4
-        if hasattr(t, '_is_stopped'):
-            alive = not t._is_stopped
-        else:
-            alive = not t.__stopped
-    except:
-        alive = t.isAlive()
-    return alive
+
+# Hack for https://sw-brainwy.rhcloud.com/tracker/PyDev/363 (i.e.: calling isAlive() can throw AssertionError under some circumstances)
+# It is required to debug threads started by start_new_thread in Python 3.4
+_temp = threading.Thread()
+if hasattr(_temp, '_is_stopped'): # Python 3.4 has this
+    def isThreadAlive(t):
+        try:
+            return not t._is_stopped
+        except:
+            return t.isAlive()
+    
+elif hasattr(_temp, '_Thread__stopped'): # Python 2.7 has this
+    def isThreadAlive(t):
+        try:
+            return not t._Thread__stopped
+        except:
+            return t.isAlive()
+    
+else: # Haven't checked all other versions, so, let's use the regular isAlive call in this case.
+    def isThreadAlive(t):
+        return t.isAlive()
+del _temp
 
 #=======================================================================================================================
 # PyDBCommandThread
@@ -159,7 +195,7 @@
         self.setName('pydevd.CommandThread')
 
     def OnRun(self):
-        for i in range(1, 10):
+        for i in xrange(1, 10):
             time.sleep(0.5) #this one will only start later on (because otherwise we may not have any non-daemon threads
             if self.killReceived:
                 return
@@ -187,7 +223,7 @@
     for t in threads:
         if hasattr(t, 'doKillPydevThread'):
             t.doKillPydevThread()
-    
+
 
 #=======================================================================================================================
 # PyDBCheckAliveThread
@@ -220,62 +256,6 @@
     def doKillPydevThread(self):
         pass
 
-if USE_LIB_COPY:
-    import _pydev_thread as thread
-else:
-    try:
-        import thread
-    except ImportError:
-        import _thread as thread #Py3K changed it.
-
-_original_start_new_thread = thread.start_new_thread
-
-if getattr(thread, '_original_start_new_thread', None) is None:
-    thread._original_start_new_thread = thread.start_new_thread
-
-#=======================================================================================================================
-# NewThreadStartup
-#=======================================================================================================================
-class NewThreadStartup:
-
-    def __init__(self, original_func, args, kwargs):
-        self.original_func = original_func
-        self.args = args
-        self.kwargs = kwargs
-
-    def __call__(self):
-        global_debugger = GetGlobalDebugger()
-        global_debugger.SetTrace(global_debugger.trace_dispatch)
-        self.original_func(*self.args, **self.kwargs)
-
-thread.NewThreadStartup = NewThreadStartup
-
-#=======================================================================================================================
-# pydev_start_new_thread
-#=======================================================================================================================
-def _pydev_start_new_thread(function, args, kwargs={}):
-    '''
-    We need to replace the original thread.start_new_thread with this function so that threads started through
-    it and not through the threading module are properly traced.
-    '''
-    if USE_LIB_COPY:
-        import _pydev_thread as thread
-    else:
-        try:
-            import thread
-        except ImportError:
-            import _thread as thread #Py3K changed it.
-
-    return thread._original_start_new_thread(thread.NewThreadStartup(function, args, kwargs), ())
-
-class PydevStartNewThread(object):
-    def __get__(self, obj, type=None):
-        return self
-
-    def __call__(self, function, args, kwargs={}):
-        return _pydev_start_new_thread(function, args, kwargs)
-
-pydev_start_new_thread = PydevStartNewThread()
 
 
 #=======================================================================================================================
@@ -304,10 +284,18 @@
         self.quitting = None
         self.cmdFactory = NetCommandFactory()
         self._cmd_queue = {}  # the hash of Queues. Key is thread id, value is thread
+
         self.breakpoints = {}
         self.django_breakpoints = {}
-        self.exception_set = {}
-        self.always_exception_set = set()
+
+        self.file_to_id_to_line_breakpoint = {}
+        self.file_to_id_to_django_breakpoint = {}
+
+        # Note: breakpoints dict should not be mutated: a copy should be created
+        # and later it should be assigned back (to prevent concurrency issues).
+        self.break_on_uncaught_exceptions = {}
+        self.break_on_caught_exceptions = {}
+
         self.django_exception_break = {}
         self.readyToRun = False
         self._main_lock = threading.Lock()
@@ -316,15 +304,31 @@
         CustomFramesContainer._py_db_command_thread_event = self._py_db_command_thread_event
         self._finishDebuggingSession = False
         self._terminationEventSent = False
-        self.force_post_mortem_stop = 0
         self.signature_factory = None
         self.SetTrace = pydevd_tracing.SetTrace
+        self.break_on_exceptions_thrown_in_same_context = False
+        self.ignore_exceptions_thrown_in_lines_with_ignore_exception = True
+
+        # Suspend debugger even if breakpoint condition raises an exception
+        SUSPEND_ON_BREAKPOINT_EXCEPTION = True
+        self.suspend_on_breakpoint_exception = SUSPEND_ON_BREAKPOINT_EXCEPTION
+
+        # By default user can step into properties getter/setter/deleter methods
+        self.disable_property_trace = False
+        self.disable_property_getter_trace = False
+        self.disable_property_setter_trace = False
+        self.disable_property_deleter_trace = False
 
         #this is a dict of thread ids pointing to thread ids. Whenever a command is passed to the java end that
         #acknowledges that a thread was created, the thread id should be passed here -- and if at some time we do not
         #find that thread alive anymore, we must remove it from this list and make the java side know that the thread
         #was killed.
         self._running_thread_ids = {}
+        self._set_breakpoints_with_id = False
+
+        # This attribute holds the file-> lines which have an @IgnoreException.
+        self.filename_to_lines_where_exceptions_are_ignored = {}
+
 
     def haveAliveThreads(self):
         for t in threadingEnumerate():
@@ -398,12 +402,12 @@
         global bufferStdErrToServer
 
         if bufferStdOutToServer:
-                initStdoutRedirect()
-                self.checkOutput(sys.stdoutBuf, 1) #@UndefinedVariable
+            initStdoutRedirect()
+            self.checkOutput(sys.stdoutBuf, 1) #@UndefinedVariable
 
         if bufferStdErrToServer:
-                initStderrRedirect()
-                self.checkOutput(sys.stderrBuf, 2) #@UndefinedVariable
+            initStderrRedirect()
+            self.checkOutput(sys.stderrBuf, 2) #@UndefinedVariable
 
     def checkOutput(self, out, outCtx):
         '''Checks the output to see if we have to send some buffered output to the debug server
@@ -521,6 +525,58 @@
             additionalInfo = None
 
 
+    def consolidate_breakpoints(self, file, id_to_breakpoint, breakpoints):
+        break_dict = {}
+        for breakpoint_id, pybreakpoint in DictIterItems(id_to_breakpoint):
+            break_dict[pybreakpoint.line] = pybreakpoint
+
+        breakpoints[file] = break_dict
+
+
+    def add_break_on_exception(
+        self,
+        exception,
+        notify_always,
+        notify_on_terminate,
+        notify_on_first_raise_only,
+        ):
+        eb = ExceptionBreakpoint(
+            exception,
+            notify_always,
+            notify_on_terminate,
+            notify_on_first_raise_only,
+        )
+
+        if eb.notify_on_terminate:
+            cp = self.break_on_uncaught_exceptions.copy()
+            cp[exception] = eb
+            if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
+                pydev_log.error("Exceptions to hook on terminate: %s\n" % (cp,))
+            self.break_on_uncaught_exceptions = cp
+
+        if eb.notify_always:
+            cp = self.break_on_caught_exceptions.copy()
+            cp[exception] = eb
+            if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
+                pydev_log.error("Exceptions to hook always: %s\n" % (cp,))
+            self.break_on_caught_exceptions = cp
+
+        return eb
+
+    def update_after_exceptions_added(self, added):
+        updated_on_caught = False
+        updated_on_uncaught = False
+
+        for eb in added:
+            if not updated_on_uncaught and eb.notify_on_terminate:
+                updated_on_uncaught = True
+                update_exception_hook(self)
+
+            if not updated_on_caught and eb.notify_always:
+                updated_on_caught = True
+                self.setTracingForUntracedContexts()
+
+
     def processNetCommand(self, cmd_id, seq, text):
         '''Processes a command received from the Java side
 
@@ -536,6 +592,7 @@
         it may be worth refactoring it (actually, reordering the ifs so that the ones used mostly come before
         probably will give better performance).
         '''
+        #print ID_TO_MEANING[str(cmd_id)], repr(text)
 
         self._main_lock.acquire()
         try:
@@ -546,9 +603,28 @@
 
                 elif cmd_id == CMD_VERSION:
                     # response is version number
-                    local_version, pycharm_os = text.split('\t', 1)
+                    # ide_os should be 'WINDOWS' or 'UNIX'.
+                    ide_os = 'WINDOWS'
 
-                    pydevd_file_utils.set_pycharm_os(pycharm_os)
+                    # Breakpoints can be grouped by 'LINE' or by 'ID'.
+                    breakpoints_by = 'LINE'
+
+                    splitted = text.split('\t')
+                    if len(splitted) == 1:
+                        _local_version = splitted
+
+                    elif len(splitted) == 2:
+                        _local_version, ide_os = splitted
+
+                    elif len(splitted) == 3:
+                        _local_version, ide_os, breakpoints_by = splitted
+
+                    if breakpoints_by == 'ID':
+                        self._set_breakpoints_with_id = True
+                    else:
+                        self._set_breakpoints_with_id = False
+
+                    pydevd_file_utils.set_ide_os(ide_os)
 
                     cmd = self.cmdFactory.makeVersionMessage(seq)
 
@@ -684,26 +760,42 @@
 
                 elif cmd_id == CMD_SET_BREAK:
                     # func name: 'None': match anything. Empty: match global, specified: only method context.
-
                     # command to add some breakpoint.
                     # text is file\tline. Add to breakpoints dictionary
-                    type, file, line, condition, expression = text.split('\t', 4)
+                    if self._set_breakpoints_with_id:
+                        breakpoint_id, type, file, line, func_name, condition, expression = text.split('\t', 6)
 
-                    if not IS_PY3K:  # In Python 3, the frame object will have unicode for the file, whereas on python 2 it has a byte-array encoded with the filesystem encoding.
-                        file = file.encode(file_system_encoding)
-
-                    if condition.startswith('**FUNC**'):
-                        func_name, condition = condition.split('\t', 1)
+                        breakpoint_id = int(breakpoint_id)
+                        line = int(line)
 
                         # We must restore new lines and tabs as done in
                         # AbstractDebugTarget.breakpointAdded
                         condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n').\
                             replace("@_@TAB_CHAR@_@", '\t').strip()
 
-                        func_name = func_name[8:]
+                        expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n').\
+                            replace("@_@TAB_CHAR@_@", '\t').strip()
                     else:
-                        func_name = 'None'  # Match anything if not specified.
+                        #Note: this else should be removed after PyCharm migrates to setting
+                        #breakpoints by id (and ideally also provides func_name).
+                        type, file, line, condition, expression = text.split('\t', 4)
+                        # If we don't have an id given for each breakpoint, consider
+                        # the id to be the line.
+                        breakpoint_id = line = int(line)
+                        if condition.startswith('**FUNC**'):
+                            func_name, condition = condition.split('\t', 1)
 
+                            # We must restore new lines and tabs as done in
+                            # AbstractDebugTarget.breakpointAdded
+                            condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n').\
+                                replace("@_@TAB_CHAR@_@", '\t').strip()
+
+                            func_name = func_name[8:]
+                        else:
+                            func_name = 'None'  # Match anything if not specified.
+
+                    if not IS_PY3K:  # In Python 3, the frame object will have unicode for the file, whereas on python 2 it has a byte-array encoded with the filesystem encoding.
+                        file = file.encode(file_system_encoding)
 
                     file = NormFileToServer(file)
 
@@ -712,7 +804,6 @@
                             ' to file that does not exist: %s (will have no effect)\n' % (file,))
                         sys.stderr.flush()
 
-                    line = int(line)
 
                     if len(condition) <= 0 or condition is None or condition == "None":
                         condition = None
@@ -721,57 +812,68 @@
                         expression = None
 
                     if type == 'python-line':
-                        breakpoint = LineBreakpoint(type, True, condition, func_name, expression)
-                        breakpoint.add(self.breakpoints, file, line, func_name)
+                        breakpoint = LineBreakpoint(line, condition, func_name, expression)
+                        breakpoints = self.breakpoints
+                        file_to_id_to_breakpoint = self.file_to_id_to_line_breakpoint
                     elif type == 'django-line':
-                        breakpoint = DjangoLineBreakpoint(type, file, line, True, condition, func_name, expression)
-                        breakpoint.add(self.django_breakpoints, file, line, func_name)
+                        breakpoint = DjangoLineBreakpoint(file, line, condition, func_name, expression)
+                        breakpoints = self.django_breakpoints
+                        file_to_id_to_breakpoint = self.file_to_id_to_django_breakpoint
                     else:
                         raise NameError(type)
 
+                    if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
+                        pydev_log.debug('Added breakpoint:%s - line:%s - func_name:%s\n' % (file, line, func_name.encode('utf-8')))
+                        sys.stderr.flush()
+
+                    if DictContains(file_to_id_to_breakpoint, file):
+                        id_to_pybreakpoint = file_to_id_to_breakpoint[file]
+                    else:
+                        id_to_pybreakpoint = file_to_id_to_breakpoint[file] = {}
+
+                    id_to_pybreakpoint[breakpoint_id] = breakpoint
+                    self.consolidate_breakpoints(file, id_to_pybreakpoint, breakpoints)
+
                     self.setTracingForUntracedContexts()
 
                 elif cmd_id == CMD_REMOVE_BREAK:
                     #command to remove some breakpoint
-                    #text is file\tline. Remove from breakpoints dictionary
-                    type, file, line = text.split('\t', 2)
+                    #text is type\file\tid. Remove from breakpoints dictionary
+                    breakpoint_type, file, breakpoint_id = text.split('\t', 2)
+
+                    if not IS_PY3K:  # In Python 3, the frame object will have unicode for the file, whereas on python 2 it has a byte-array encoded with the filesystem encoding.
+                        file = file.encode(file_system_encoding)
+
                     file = NormFileToServer(file)
+
                     try:
-                        line = int(line)
+                        breakpoint_id = int(breakpoint_id)
                     except ValueError:
-                        pass
+                        pydev_log.error('Error removing breakpoint. Expected breakpoint_id to be an int. Found: %s' % (breakpoint_id,))
 
                     else:
-                        found = False
+                        if breakpoint_type == 'python-line':
+                            breakpoints = self.breakpoints
+                            file_to_id_to_breakpoint = self.file_to_id_to_line_breakpoint
+                        elif breakpoint_type == 'django-line':
+                            breakpoints = self.django_breakpoints
+                            file_to_id_to_breakpoint = self.file_to_id_to_django_breakpoint
+                        else:
+                            raise NameError(breakpoint_type)
+
                         try:
-                            if type == 'django-line':
-                                del self.django_breakpoints[file][line]
-                            elif type == 'python-line':
-                                del self.breakpoints[file][line] #remove the breakpoint in that line
-                            else:
-                                try:
-                                    del self.django_breakpoints[file][line]
-                                    found = True
-                                except:
-                                    pass
-                                try:
-                                    del self.breakpoints[file][line] #remove the breakpoint in that line
-                                    found = True
-                                except:
-                                    pass
-
+                            id_to_pybreakpoint = file_to_id_to_breakpoint[file]
                             if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
-                                sys.stderr.write('Removed breakpoint:%s - %s\n' % (file, line))
-                                sys.stderr.flush()
+                                existing = id_to_pybreakpoint[breakpoint_id]
+                                sys.stderr.write('Removed breakpoint:%s - line:%s - func_name:%s (id: %s)\n' % (
+                                    file, existing.line, existing.func_name.encode('utf-8'), breakpoint_id))
+
+                            del id_to_pybreakpoint[breakpoint_id]
+                            self.consolidate_breakpoints(file, id_to_pybreakpoint, breakpoints)
                         except KeyError:
-                            found = False
+                            pydev_log.error("Error removing breakpoint: Breakpoint id not found: %s id: %s. Available ids: %s\n" % (
+                                file, breakpoint_id, DictKeys(id_to_pybreakpoint)))
 
-                        if not found:
-                            #ok, it's not there...
-                            if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
-                                #Sometimes, when adding a breakpoint, it adds a remove command before (don't really know why)
-                                sys.stderr.write("breakpoint not found: %s - %s\n" % (file, line))
-                                sys.stderr.flush()
 
                 elif cmd_id == CMD_EVALUATE_EXPRESSION or cmd_id == CMD_EXEC_EXPRESSION:
                     #command to evaluate the given expression
@@ -790,29 +892,118 @@
                     int_cmd = InternalConsoleExec(seq, thread_id, frame_id, expression)
                     self.postInternalCommand(int_cmd, thread_id)
 
+                elif cmd_id == CMD_SET_PY_EXCEPTION:
+                    # Command which receives set of exceptions on which user wants to break the debugger
+                    # text is: break_on_uncaught;break_on_caught;TypeError;ImportError;zipimport.ZipImportError;
+                    # This API is optional and works 'in bulk' -- it's possible
+                    # to get finer-grained control with CMD_ADD_EXCEPTION_BREAK/CMD_REMOVE_EXCEPTION_BREAK
+                    # which allows setting caught/uncaught per exception.
+                    #
+                    splitted = text.split(';')
+                    self.break_on_uncaught_exceptions = {}
+                    self.break_on_caught_exceptions = {}
+                    added = []
+                    if len(splitted) >= 4:
+                        if splitted[0] == 'true':
+                            break_on_uncaught = True
+                        else:
+                            break_on_uncaught = False
+
+                        if splitted[1] == 'true':
+                            break_on_caught = True
+                        else:
+                            break_on_caught = False
+
+                        if splitted[2] == 'true':
+                            self.break_on_exceptions_thrown_in_same_context = True
+                        else:
+                            self.break_on_exceptions_thrown_in_same_context = False
+
+                        if splitted[3] == 'true':
+                            self.ignore_exceptions_thrown_in_lines_with_ignore_exception = True
+                        else:
+                            self.ignore_exceptions_thrown_in_lines_with_ignore_exception = False
+
+                        for exception_type in splitted[4:]:
+                            exception_type = exception_type.strip()
+                            if not exception_type:
+                                continue
+
+                            exception_breakpoint = self.add_break_on_exception(
+                                exception_type,
+                                notify_always=break_on_caught,
+                                notify_on_terminate=break_on_uncaught,
+                                notify_on_first_raise_only=False,
+                            )
+                            added.append(exception_breakpoint)
+
+                        self.update_after_exceptions_added(added)
+
+                    else:
+                        sys.stderr.write("Error when setting exception list. Received: %s\n" % (text,))
+
+                elif cmd_id == CMD_GET_FILE_CONTENTS:
+
+                    if not IS_PY3K:  # In Python 3, the frame object will have unicode for the file, whereas on python 2 it has a byte-array encoded with the filesystem encoding.
+                        text = text.encode(file_system_encoding)
+
+                    if os.path.exists(text):
+                        f = open(text, 'r')
+                        try:
+                            source = f.read()
+                        finally:
+                            f.close()
+                        cmd = self.cmdFactory.makeGetFileContents(seq, source)
+
+                elif cmd_id == CMD_SET_PROPERTY_TRACE:
+                    # Command which receives whether to trace property getter/setter/deleter
+                    # text is feature_state(true/false);disable_getter/disable_setter/disable_deleter
+                    if text != "":
+                        splitted = text.split(';')
+                        if len(splitted) >= 3:
+                            if self.disable_property_trace is False and splitted[0] == 'true':
+                                # Replacing property by custom property only when the debugger starts
+                                pydevd_traceproperty.replace_builtin_property()
+                                self.disable_property_trace = True
+                            # Enable/Disable tracing of the property getter
+                            if splitted[1] == 'true':
+                                self.disable_property_getter_trace = True
+                            else:
+                                self.disable_property_getter_trace = False
+                            # Enable/Disable tracing of the property setter
+                            if splitted[2] == 'true':
+                                self.disable_property_setter_trace = True
+                            else:
+                                self.disable_property_setter_trace = False
+                            # Enable/Disable tracing of the property deleter
+                            if splitted[3] == 'true':
+                                self.disable_property_deleter_trace = True
+                            else:
+                                self.disable_property_deleter_trace = False
+                    else:
+                        # User hasn't configured any settings for property tracing
+                        pass
+
                 elif cmd_id == CMD_ADD_EXCEPTION_BREAK:
                     exception, notify_always, notify_on_terminate = text.split('\t', 2)
-
-                    eb = ExceptionBreakpoint(exception, notify_always, notify_on_terminate)
-
-                    self.exception_set[exception] = eb
-
-                    if eb.notify_on_terminate:
-                        update_exception_hook(self)
-                    if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
-                        pydev_log.error("Exceptions to hook on terminate: %s\n" % (self.exception_set,))
-
-                    if eb.notify_always:
-                        self.always_exception_set.add(exception)
-                        if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
-                            pydev_log.error("Exceptions to hook always: %s\n" % (self.always_exception_set,))
-                        self.setTracingForUntracedContexts()
+                    exception_breakpoint = self.add_break_on_exception(
+                        exception,
+                        notify_always=int(notify_always) > 0,
+                        notify_on_terminate = int(notify_on_terminate) == 1,
+                        notify_on_first_raise_only=int(notify_always) == 2
+                    )
+                    self.update_after_exceptions_added([exception_breakpoint])
 
                 elif cmd_id == CMD_REMOVE_EXCEPTION_BREAK:
                     exception = text
                     try:
-                        del self.exception_set[exception]
-                        self.always_exception_set.remove(exception)
+                        cp = self.break_on_uncaught_exceptions.copy()
+                        DictPop(cp, exception, None)
+                        self.break_on_uncaught_exceptions = cp
+
+                        cp = self.break_on_caught_exceptions.copy()
+                        DictPop(cp, exception, None)
+                        self.break_on_caught_exceptions = cp
                     except:
                         pydev_log.debug("Error while removing exception %s"%sys.exc_info()[0]);
                     update_exception_hook(self)
@@ -840,6 +1031,77 @@
                     except :
                         pass
 
+                elif cmd_id == CMD_EVALUATE_CONSOLE_EXPRESSION:
+                    # Command which takes care for the debug console communication
+                    if text != "":
+                        thread_id, frame_id, console_command = text.split('\t', 2)
+                        console_command, line = console_command.split('\t')
+                        if console_command == 'EVALUATE':
+                            int_cmd = InternalEvaluateConsoleExpression(seq, thread_id, frame_id, line)
+                        elif console_command == 'GET_COMPLETIONS':
+                            int_cmd = InternalConsoleGetCompletions(seq, thread_id, frame_id, line)
+                        self.postInternalCommand(int_cmd, thread_id)
+
+                elif cmd_id == CMD_RUN_CUSTOM_OPERATION:
+                    # Command which runs a custom operation
+                    if text != "":
+                        try:
+                            location, custom = text.split('||', 1)
+                        except:
+                            sys.stderr.write('Custom operation now needs a || separator. Found: %s\n' % (text,))
+                            raise
+
+                        thread_id, frame_id, scopeattrs = location.split('\t', 2)
+
+                        if scopeattrs.find('\t') != -1:  # there are attributes beyond scope
+                            scope, attrs = scopeattrs.split('\t', 1)
+                        else:
+                            scope, attrs = (scopeattrs, None)
+
+                        # : style: EXECFILE or EXEC
+                        # : encoded_code_or_file: file to execute or code
+                        # : fname: name of function to be executed in the resulting namespace
+                        style, encoded_code_or_file, fnname = custom.split('\t', 3)
+                        int_cmd = InternalRunCustomOperation(seq, thread_id, frame_id, scope, attrs,
+                                                             style, encoded_code_or_file, fnname)
+                        self.postInternalCommand(int_cmd, thread_id)
+
+                elif cmd_id == CMD_IGNORE_THROWN_EXCEPTION_AT:
+                    if text:
+                        replace = 'REPLACE:'  # Not all 3.x versions support u'REPLACE:', so, doing workaround.
+                        if not IS_PY3K:
+                            replace = unicode(replace)
+
+                        if text.startswith(replace):
+                            text = text[8:]
+                            self.filename_to_lines_where_exceptions_are_ignored.clear()
+
+                        if text:
+                            for line in text.split('||'):  # Can be bulk-created (one in each line)
+                                filename, line_number = line.split('|')
+                                if not IS_PY3K:
+                                    filename = filename.encode(file_system_encoding)
+
+                                filename = NormFileToServer(filename)
+
+                                if os.path.exists(filename):
+                                    lines_ignored = self.filename_to_lines_where_exceptions_are_ignored.get(filename)
+                                    if lines_ignored is None:
+                                        lines_ignored = self.filename_to_lines_where_exceptions_are_ignored[filename] = {}
+                                    lines_ignored[int(line_number)] = 1
+                                else:
+                                    sys.stderr.write('pydev debugger: warning: trying to ignore exception thrown'\
+                                        ' on file that does not exist: %s (will have no effect)\n' % (filename,))
+
+                elif cmd_id == CMD_ENABLE_DONT_TRACE:
+                    if text:
+                        true_str = 'true'  # Not all 3.x versions support u'str', so, doing workaround.
+                        if not IS_PY3K:
+                            true_str = unicode(true_str)
+
+                        mode = text.strip() == true_str
+                        pydevd_dont_trace.trace_filter(mode)
+
                 else:
                     #I have no idea what this is all about
                     cmd = self.cmdFactory.makeErrorMessage(seq, "unexpected command " + str(cmd_id))
@@ -881,6 +1143,44 @@
         thread.additionalInfo.pydev_state = STATE_SUSPEND
         thread.stop_reason = stop_reason
 
+        # If conditional breakpoint raises any exception during evaluation send details to Java
+        if stop_reason == CMD_SET_BREAK and self.suspend_on_breakpoint_exception:
+            self.sendBreakpointConditionException(thread)
+
+
+    def sendBreakpointConditionException(self, thread):
+        """If conditional breakpoint raises an exception during evaluation
+        send exception details to java
+        """
+        thread_id = GetThreadId(thread)
+        conditional_breakpoint_exception_tuple = thread.additionalInfo.conditional_breakpoint_exception
+        # conditional_breakpoint_exception_tuple - should contain 2 values (exception_type, stacktrace)
+        if conditional_breakpoint_exception_tuple and len(conditional_breakpoint_exception_tuple) == 2:
+            exc_type, stacktrace = conditional_breakpoint_exception_tuple
+            int_cmd = InternalGetBreakpointException(thread_id, exc_type, stacktrace)
+            # Reset the conditional_breakpoint_exception details to None
+            thread.additionalInfo.conditional_breakpoint_exception = None
+            self.postInternalCommand(int_cmd, thread_id)
+
+
+    def sendCaughtExceptionStack(self, thread, arg, curr_frame_id):
+        """Sends details on the exception which was caught (and where we stopped) to the java side.
+
+        arg is: exception type, description, traceback object
+        """
+        thread_id = GetThreadId(thread)
+        int_cmd = InternalSendCurrExceptionTrace(thread_id, arg, curr_frame_id)
+        self.postInternalCommand(int_cmd, thread_id)
+
+
+    def sendCaughtExceptionStackProceeded(self, thread):
+        """Sends that some thread was resumed and is no longer showing an exception trace.
+        """
+        thread_id = GetThreadId(thread)
+        int_cmd = InternalSendCurrExceptionTraceProceeded(thread_id)
+        self.postInternalCommand(int_cmd, thread_id)
+        self.processInternalCommands()
+
 
     def doWaitSuspend(self, thread, frame, event, arg): #@UnusedVariable
         """ busy waits until the thread state changes to RUN
@@ -898,7 +1198,7 @@
         try:
             from_this_thread = []
 
-            for frame_id, custom_frame in CustomFramesContainer.custom_frames.items():
+            for frame_id, custom_frame in DictIterItems(CustomFramesContainer.custom_frames):
                 if custom_frame.thread_id == thread.ident:
                     # print >> sys.stderr, 'Frame created: ', frame_id
                     self.writer.addCommand(self.cmdFactory.makeCustomFrameCreatedMessage(frame_id, custom_frame.name))
@@ -991,7 +1291,6 @@
 
     def handle_post_mortem_stop(self, additionalInfo, t):
         pydev_log.debug("We are stopping in post-mortem\n")
-        self.force_post_mortem_stop -= 1
         frame, frames_byid = additionalInfo.pydev_force_stop_at_exception
         thread_id = GetThreadId(t)
         pydevd_vars.addAdditionalFrameById(thread_id, frames_byid)
@@ -1060,9 +1359,9 @@
             if additionalInfo.is_tracing:
                 f = frame
                 while f is not None:
-                    fname, bs = GetFilenameAndBase(f)
-                    if bs == 'pydevd_frame.py':
-                        if 'trace_dispatch' == f.f_code.co_name:
+                    if 'trace_dispatch' == f.f_code.co_name:
+                        _fname, bs = GetFilenameAndBase(f)
+                        if bs == 'pydevd_frame.py':
                             return None  #we don't wan't to trace code invoked from pydevd_frame.trace_dispatch
                     f = f.f_back
 
@@ -1071,9 +1370,6 @@
                 self.processThreadNotAlive(GetThreadId(t))
                 return None  # suspend tracing
 
-            if is_file_to_ignore:
-                return None
-
             # each new frame...
             return additionalInfo.CreateDbFrame((self, filename, additionalInfo, t, frame)).trace_dispatch(frame, event, arg)
 
@@ -1120,18 +1416,18 @@
 
     def update_trace(self, frame, dispatch_func, overwrite_prev):
         if frame.f_trace is None:
-          frame.f_trace = dispatch_func
+            frame.f_trace = dispatch_func
         else:
-          if overwrite_prev:
-              frame.f_trace = dispatch_func
-          else:
-              try:
-                  #If it's the trace_exception, go back to the frame trace dispatch!
-                  if frame.f_trace.im_func.__name__ == 'trace_exception':
-                      frame.f_trace = frame.f_trace.im_self.trace_dispatch
-              except AttributeError:
-                  pass
-              frame = frame.f_back
+            if overwrite_prev:
+                frame.f_trace = dispatch_func
+            else:
+                try:
+                    #If it's the trace_exception, go back to the frame trace dispatch!
+                    if frame.f_trace.im_func.__name__ == 'trace_exception':
+                        frame.f_trace = frame.f_trace.im_self.trace_dispatch
+                except AttributeError:
+                    pass
+                frame = frame.f_back
         del frame
 
     def prepareToRun(self):
@@ -1150,6 +1446,7 @@
         PyDBCommandThread(self).start()
         PyDBCheckAliveThread(self).start()
 
+
     def patch_threads(self):
         try:
             # not available in jython!
@@ -1157,11 +1454,8 @@
         except:
             pass
 
-        try:
-            thread.start_new_thread = pydev_start_new_thread
-            thread.start_new = pydev_start_new_thread
-        except:
-            pass
+        from pydev_monkey import patch_thread_modules
+        patch_thread_modules()
 
 
     def run(self, file, globals=None, locals=None, set_trace=True):
@@ -1185,7 +1479,7 @@
             sys.modules['__main__'] = m
             if hasattr(sys.modules['pydevd'], '__loader__'):
                 setattr(m, '__loader__', getattr(sys.modules['pydevd'], '__loader__'))
-                
+
             m.__file__ = file
             globals = m.__dict__
             try:
@@ -1246,7 +1540,8 @@
     setup['server'] = False
     setup['port'] = 0
     setup['file'] = ''
-    setup['multiproc'] = False
+    setup['multiproc'] = False #Used by PyCharm (reuses connection: ssh tunneling)
+    setup['multiprocess'] = False # Used by PyDev (creates new connection to ide)
     setup['save-signatures'] = False
     i = 0
     del argv[0]
@@ -1279,6 +1574,9 @@
         elif (argv[i] == '--multiproc'):
             del argv[i]
             setup['multiproc'] = True
+        elif (argv[i] == '--multiprocess'):
+            del argv[i]
+            setup['multiprocess'] = True
         elif (argv[i] == '--save-signatures'):
             del argv[i]
             setup['save-signatures'] = True
@@ -1423,7 +1721,7 @@
 
         CustomFramesContainer.custom_frames_lock.acquire()
         try:
-            for _frameId, custom_frame in CustomFramesContainer.custom_frames.items():
+            for _frameId, custom_frame in DictIterItems(CustomFramesContainer.custom_frames):
                 debugger.SetTraceForFrameAndParents(custom_frame.frame, False)
         finally:
             CustomFramesContainer.custom_frames_lock.release()
@@ -1492,24 +1790,21 @@
             threading.settrace(None) # for all future threads
         except:
             pass
-        
-        try:
-            thread.start_new_thread = _original_start_new_thread
-            thread.start_new = _original_start_new_thread
-        except:
-            pass
-    
+
+        from pydev_monkey import undo_patch_thread_modules
+        undo_patch_thread_modules()
+
         debugger = GetGlobalDebugger()
-        
+
         if debugger:
             debugger.trace_dispatch = None
-    
+
             debugger.SetTraceForFrameAndParents(GetFrame(), False)
-        
+
             debugger.exiting()
-        
-            killAllPydevThreads()  
-        
+
+            killAllPydevThreads()
+
         connected = False
 
 class Dispatcher(object):
@@ -1544,21 +1839,28 @@
             self.killReceived = True
 
 
+DISPATCH_APPROACH_NEW_CONNECTION = 1 # Used by PyDev
+DISPATCH_APPROACH_EXISTING_CONNECTION = 2 # Used by PyCharm
+DISPATCH_APPROACH = DISPATCH_APPROACH_NEW_CONNECTION
+
 def dispatch():
-    argv = sys.original_argv[:]
-    setup = processCommandLine(argv)
+    setup = SetupHolder.setup
     host = setup['client']
     port = setup['port']
-    dispatcher = Dispatcher()
-    try:
-        dispatcher.connect(host, port)
-        port = dispatcher.port
-    finally:
-        dispatcher.close()
+    if DISPATCH_APPROACH == DISPATCH_APPROACH_EXISTING_CONNECTION:
+        dispatcher = Dispatcher()
+        try:
+            dispatcher.connect(host, port)
+            port = dispatcher.port
+        finally:
+            dispatcher.close()
     return host, port
 
 
 def settrace_forked():
+    '''
+    When creating a fork from a process in the debugger, we need to reset the whole debugger environment!
+    '''
     host, port = dispatch()
 
     import pydevd_tracing
@@ -1578,6 +1880,15 @@
             overwrite_prev_trace=True,
             patch_multiprocessing=True,
             )
+
+#=======================================================================================================================
+# SetupHolder
+#=======================================================================================================================
+class SetupHolder:
+
+    setup = None
+
+
 #=======================================================================================================================
 # main
 #=======================================================================================================================
@@ -1586,6 +1897,7 @@
     try:
         sys.original_argv = sys.argv[:]
         setup = processCommandLine(sys.argv)
+        SetupHolder.setup = setup
     except ValueError:
         traceback.print_exc()
         usage(1)
@@ -1611,62 +1923,73 @@
     f = setup['file']
     fix_app_engine_debug = False
 
-    if setup['multiproc']:
-        pydev_log.debug("Started in multiproc mode\n")
 
-        dispatcher = Dispatcher()
-        try:
-            dispatcher.connect(host, port)
-            if dispatcher.port is not None:
-                port = dispatcher.port
-                pydev_log.debug("Received port %d\n" %port)
-                pydev_log.info("pydev debugger: process %d is connecting\n"% os.getpid())
-
-                try:
-                    pydev_monkey.patch_new_process_functions()
-                except:
-                    pydev_log.error("Error patching process functions\n")
-                    traceback.print_exc()
-            else:
-                pydev_log.error("pydev debugger: couldn't get port for new debug process\n")
-        finally:
-            dispatcher.close()
+    try:
+        import pydev_monkey
+    except:
+        pass #Not usable on jython 2.1
     else:
-        pydev_log.info("pydev debugger: starting\n")
+        if setup['multiprocess']: # PyDev
+            pydev_monkey.patch_new_process_functions()
 
-        try:
-            pydev_monkey.patch_new_process_functions_with_warning()
-        except:
-            pydev_log.error("Error patching process functions\n")
-            traceback.print_exc()
+        elif setup['multiproc']: # PyCharm
+            pydev_log.debug("Started in multiproc mode\n")
+            # Note: we're not inside method, so, no need for 'global'
+            DISPATCH_APPROACH = DISPATCH_APPROACH_EXISTING_CONNECTION
 
-        # Only do this patching if we're not running with multiprocess turned on.
-        if f.find('dev_appserver.py') != -1:
-            if os.path.basename(f).startswith('dev_appserver.py'):
-                appserver_dir = os.path.dirname(f)
-                version_file = os.path.join(appserver_dir, 'VERSION')
-                if os.path.exists(version_file):
+            dispatcher = Dispatcher()
+            try:
+                dispatcher.connect(host, port)
+                if dispatcher.port is not None:
+                    port = dispatcher.port
+                    pydev_log.debug("Received port %d\n" %port)
+                    pydev_log.info("pydev debugger: process %d is connecting\n"% os.getpid())
+
                     try:
-                        stream = open(version_file, 'r')
-                        try:
-                            for line in stream.read().splitlines():
-                                line = line.strip()
-                                if line.startswith('release:'):
-                                    line = line[8:].strip()
-                                    version = line.replace('"', '')
-                                    version = version.split('.')
-                                    if int(version[0]) > 1:
-                                        fix_app_engine_debug = True
-
-                                    elif int(version[0]) == 1:
-                                        if int(version[1]) >= 7:
-                                            # Only fix from 1.7 onwards
-                                            fix_app_engine_debug = True
-                                    break
-                        finally:
-                            stream.close()
+                        pydev_monkey.patch_new_process_functions()
                     except:
+                        pydev_log.error("Error patching process functions\n")
                         traceback.print_exc()
+                else:
+                    pydev_log.error("pydev debugger: couldn't get port for new debug process\n")
+            finally:
+                dispatcher.close()
+        else:
+            pydev_log.info("pydev debugger: starting\n")
+
+            try:
+                pydev_monkey.patch_new_process_functions_with_warning()
+            except:
+                pydev_log.error("Error patching process functions\n")
+                traceback.print_exc()
+
+            # Only do this patching if we're not running with multiprocess turned on.
+            if f.find('dev_appserver.py') != -1:
+                if os.path.basename(f).startswith('dev_appserver.py'):
+                    appserver_dir = os.path.dirname(f)
+                    version_file = os.path.join(appserver_dir, 'VERSION')
+                    if os.path.exists(version_file):
+                        try:
+                            stream = open(version_file, 'r')
+                            try:
+                                for line in stream.read().splitlines():
+                                    line = line.strip()
+                                    if line.startswith('release:'):
+                                        line = line[8:].strip()
+                                        version = line.replace('"', '')
+                                        version = version.split('.')
+                                        if int(version[0]) > 1:
+                                            fix_app_engine_debug = True
+
+                                        elif int(version[0]) == 1:
+                                            if int(version[1]) >= 7:
+                                                # Only fix from 1.7 onwards
+                                                fix_app_engine_debug = True
+                                        break
+                            finally:
+                                stream.close()
+                        except:
+                            traceback.print_exc()
 
     try:
         # In the default run (i.e.: run directly on debug mode), we try to patch stackless as soon as possible
@@ -1718,16 +2041,21 @@
             import pydevd_psyco_stub
             sys.modules['psyco'] = pydevd_psyco_stub
 
-    debugger = PyDB()
+        debugger = PyDB()
 
-    if setup['save-signatures']:
-        if pydevd_vm_type.GetVmType() == pydevd_vm_type.PydevdVmType.JYTHON:
-            sys.stderr.write("Collecting run-time type information is not supported for Jython\n")
-        else:
-            debugger.signature_factory = SignatureFactory()
+        if setup['save-signatures']:
+            if pydevd_vm_type.GetVmType() == pydevd_vm_type.PydevdVmType.JYTHON:
+                sys.stderr.write("Collecting run-time type information is not supported for Jython\n")
+            else:
+                debugger.signature_factory = SignatureFactory()
 
-    debugger.connect(host, port)
+        try:
+            debugger.connect(host, port)
+        except:
+            sys.stderr.write("Could not connect to %s: %s\n" % (host, port))
+            traceback.print_exc()
+            sys.exit(1)
 
-    connected = True #Mark that we're connected when started from inside ide.
+        connected = True  # Mark that we're connected when started from inside ide.
 
-    debugger.run(setup['file'], None, None)
+        debugger.run(setup['file'], None, None)