Bug 533983 - [Python *] improve debugging support

improves py4j debugger performance by pre-filtering call dispatches in
Python itself to avoid IPC overhead.

Change-Id: Iccefedfac627522b648f93d410c722d14b4bcc70
Signed-off-by: Martin Kloesch <martin@kmh-solutions.com>
diff --git a/plugins/org.eclipse.ease.lang.javascript.rhino.debugger/src/org/eclipse/ease/lang/javascript/rhino/debugger/RhinoDebugger.java b/plugins/org.eclipse.ease.lang.javascript.rhino.debugger/src/org/eclipse/ease/lang/javascript/rhino/debugger/RhinoDebugger.java
index be9e710..e67544b 100644
--- a/plugins/org.eclipse.ease.lang.javascript.rhino.debugger/src/org/eclipse/ease/lang/javascript/rhino/debugger/RhinoDebugger.java
+++ b/plugins/org.eclipse.ease.lang.javascript.rhino.debugger/src/org/eclipse/ease/lang/javascript/rhino/debugger/RhinoDebugger.java
@@ -22,6 +22,7 @@
 import org.eclipse.ease.debugging.AbstractEaseDebugger;
 import org.eclipse.ease.debugging.EaseDebugFrame;
 import org.eclipse.ease.debugging.IScriptDebugFrame;
+import org.eclipse.ease.debugging.IScriptRegistry;
 import org.eclipse.ease.lang.javascript.rhino.RhinoScriptEngine;
 import org.mozilla.javascript.Context;
 import org.mozilla.javascript.Scriptable;
@@ -150,6 +151,7 @@
 			return "";
 		}
 
+		@Override
 		public Map<String, Object> getVariables() {
 			final Map<String, Object> result = getEngine().getVariables(fScope);
 
@@ -211,14 +213,22 @@
 
 	@Override
 	public void notify(final IScriptEngine engine, final Script script, final int status) {
+		final IScriptRegistry scriptRegistry = getDispatcher();
 		switch (status) {
 
 		case SCRIPT_START:
 			// fall through
 		case SCRIPT_INJECTION_START:
+			if (scriptRegistry != null) {
+				scriptRegistry.put(script);
+			}
 			fLastScript = script;
 			break;
 
+		case SCRIPT_END:
+			// TODO: Check if scripts should be removed to lower memory usage
+			break;
+
 		case ENGINE_END:
 			fFrameToSource.clear();
 			fLastScript = null;
@@ -228,7 +238,6 @@
 			// unknown event
 			break;
 		}
-
 		super.notify(engine, script, status);
 	}
 }
diff --git a/plugins/org.eclipse.ease.lang.python.jython.debugger/src/org/eclipse/ease/lang/python/jython/debugger/JythonDebuggerEngine.java b/plugins/org.eclipse.ease.lang.python.jython.debugger/src/org/eclipse/ease/lang/python/jython/debugger/JythonDebuggerEngine.java
index d4ea533..dbe73ba 100644
--- a/plugins/org.eclipse.ease.lang.python.jython.debugger/src/org/eclipse/ease/lang/python/jython/debugger/JythonDebuggerEngine.java
+++ b/plugins/org.eclipse.ease.lang.python.jython.debugger/src/org/eclipse/ease/lang/python/jython/debugger/JythonDebuggerEngine.java
@@ -21,11 +21,11 @@
 import org.eclipse.ease.ScriptObjectType;
 import org.eclipse.ease.debugging.EaseDebugFrame;
 import org.eclipse.ease.debugging.ScriptStackTrace;
-import org.eclipse.ease.debugging.dispatcher.EventDispatchJob;
 import org.eclipse.ease.debugging.model.EaseDebugVariable;
 import org.eclipse.ease.debugging.model.EaseDebugVariable.Type;
 import org.eclipse.ease.lang.python.debugger.IPythonDebugEngine;
 import org.eclipse.ease.lang.python.debugger.PythonDebugger;
+import org.eclipse.ease.lang.python.debugger.PythonEventDispatchJob;
 import org.eclipse.ease.lang.python.debugger.ResourceHelper;
 import org.eclipse.ease.lang.python.debugger.model.PythonDebugTarget;
 import org.eclipse.ease.lang.python.jython.JythonScriptEngine;
@@ -99,7 +99,7 @@
 
 		setDebugger(debugger);
 
-		new EventDispatchJob(target, debugger);
+		new PythonEventDispatchJob(target, debugger);
 	}
 
 	@Override
diff --git a/plugins/org.eclipse.ease.lang.python.py4j/META-INF/MANIFEST.MF b/plugins/org.eclipse.ease.lang.python.py4j/META-INF/MANIFEST.MF
index fd34845..d1ad350 100644
--- a/plugins/org.eclipse.ease.lang.python.py4j/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.ease.lang.python.py4j/META-INF/MANIFEST.MF
@@ -3,6 +3,7 @@
 Bundle-Name: Py4j
 Bundle-SymbolicName: org.eclipse.ease.lang.python.py4j;singleton:=true
 Bundle-Version: 0.7.0.qualifier
+Bundle-Vendor: Eclipse.org
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Require-Bundle: org.eclipse.ui;bundle-version="[3.7.0,4.0.0)",
  org.eclipse.core.runtime;bundle-version="[3.7.0,4.0.0)",
@@ -16,3 +17,5 @@
  org.eclipse.ease.lang.python.py4j.internal.ui;x-internal:=true
 Bundle-Activator: org.eclipse.ease.lang.python.py4j.internal.Activator
 Bundle-ActivationPolicy: lazy
+Bundle-ClassPath: .
+Eclipse-BundleShape: dir
diff --git a/plugins/org.eclipse.ease.lang.python.py4j/pysrc/ease_py4j_main.py b/plugins/org.eclipse.ease.lang.python.py4j/pysrc/ease_py4j_main.py
index d49576c..4fcfe5b 100644
--- a/plugins/org.eclipse.ease.lang.python.py4j/pysrc/ease_py4j_main.py
+++ b/plugins/org.eclipse.ease.lang.python.py4j/pysrc/ease_py4j_main.py
@@ -8,14 +8,13 @@
 # Contributors:
 #     Jonah Graham (Kichwa Coders) - initial API and implementation
 ###############################################################################
-
+import sys
 if __name__ == '__main__':
     # To be able to import all the py4j and related items, we need to add to the PYTHONPATH
     # all the correct paths. Because we may be launched with -E, the command line provides
     # all the paths to what we need.
     # sys.argv[1] - required - the port to conenct o
     # sys.argv[2:] - optional - paths to prepend on sys.path
-    import sys
     sys.path[0:0] = sys.argv[2:]
 
 import code
@@ -24,8 +23,7 @@
 from py4j.clientserver import ClientServer, JavaParameters, PythonParameters
 from py4j.java_collections import MapConverter, ListConverter, SetConverter
 from py4j.java_gateway import JavaObject, JavaClass
-from py4j.protocol import Py4JJavaError, get_command_part
-import sys
+from py4j.protocol import Py4JJavaError
 import threading
 import __main__
 import ast
@@ -33,7 +31,6 @@
     from six import integer_types
     from six import string_types
 except ImportError:
-    import sys
     if sys.version_info.major == 2:
         integer_types = (int, long)
         string_types = (basestring, )
@@ -61,6 +58,7 @@
     '''
     builtins.__dict__.update({name: value})
 
+
 # To ease some debugging of the py4j engine itself it is useful to turn logging on,
 # uncomment the following lines for one way to do that
 # import logging
@@ -69,7 +67,7 @@
 # logger.addHandler(logging.StreamHandler())
 def convert_value(
         value,
-        gw,
+        gw=None,
         integer_types=integer_types, 
         string_types=string_types,
         dict_types=dict,
@@ -84,6 +82,9 @@
     :param value:    Value to be converted.
     :param gw:       py4j gateway necessary for converters.
     '''
+    if not gw:
+        gw = gateway._gateway_client
+
     # None will be mapped to Java null
     if value is None:
         return value
@@ -271,12 +272,16 @@
 
         self.locals['net'] = gateway.jvm.net
         patch_builtins('net', gateway.jvm.net)
-        
+
         self.locals['jvm'] = gateway.jvm
         patch_builtins('jvm', gateway.jvm.jvm)
 
         self.locals['gateway'] = gateway
+        patch_builtins('gateway', gateway)
+
         self.locals['py4j'] = py4j
+        patch_builtins('py4j', py4j)
+
         sys.displayhook = self.displayhook
         self.display_data = None
         self.except_data = None
@@ -343,6 +348,9 @@
     def wait_on_shutdown(self):
         self.shutdown_event.wait()
 
+    def addSearchPath(self, path):
+        sys.path.append(path)
+
     class Java:
         implements = ['org.eclipse.ease.lang.python.py4j.internal.IPythonSideEngine']
 
@@ -364,6 +372,7 @@
     timer.setDaemon(True)
     timer.start()
 
+
 def main(argv):
     port = int(argv[1])
     engine = ScriptEngineExecute()
@@ -395,4 +404,6 @@
 
 
 if __name__ == '__main__':
+    # Will be patched via builtins
+    gateway = None
     main(sys.argv)
diff --git a/plugins/org.eclipse.ease.lang.python.py4j/pysrc/py4jdb.py b/plugins/org.eclipse.ease.lang.python.py4j/pysrc/py4jdb.py
new file mode 100644
index 0000000..c4654a9
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python.py4j/pysrc/py4jdb.py
@@ -0,0 +1,460 @@
+'''
+Copyright (c) 2018 Martin Kloesch
+All rights reserved. This program and the accompanying materials
+are made available under the terms of the Eclipse Public License v1.0
+which accompanies this distribution, and is available at
+http://www.eclipse.org/legal/epl-v10.html
+
+Contributors:
+ * Martin Kloesch - initial API and implementation
+'''
+
+# Python std library imports
+import ast
+import bdb
+import functools
+import re
+import sys
+import threading
+
+# : Regular expression if we are dealing with internal module.
+INTERNAL_CHECKER = re.compile(r'^<.+>$')
+
+
+class PyFrame:
+    '''
+    Python implementation of IPyFrame used for exchanging frame data
+    with eclipse.
+
+    Simply wraps standard python frame to more easily usable format.
+
+    Python frame documentation is available here:
+    https://docs.python.org/3/reference/datamodel.html#types
+    '''
+
+    def __init__(self, frame):
+        '''
+        Constructor only stores frame to member.
+
+        :param frame:    Python frame to be converted.
+        '''
+        self._frame = frame
+
+    def _valid_frame(self):
+        '''
+        Utility method checking if the current frame is valid
+
+        A frame is valid if all information is present and
+        it's file is not ignored.
+
+        :returns:    ``True`` if frame is valid.
+        '''
+        return self._frame and not ignore_frame(self._frame)
+
+    def getFilename(self):
+        '''
+        Returns the filename of the frame.
+
+        If no frame was set, a dummy value will be returned.
+
+        :returns:    Filename of frame or dummy value.
+        '''
+        if self._valid_frame():
+            return self._frame.f_code.co_filename
+        else:
+            return '__no_frame__'
+
+    def getLineNumber(self):
+        '''
+        Returns the linenumber of the frame.
+
+        If no frame was set, -1 is returned.
+
+        :returns:    Line number of frame or -1.
+        '''
+        if self._valid_frame():
+            return self._frame.f_lineno
+        else:
+            return -1
+
+    def getParent(self):
+        '''
+        Returns the parent of the frame.
+
+        If no frame was set or the frame does not have a parent
+        `None` is returned.
+
+        :returns:    Parent frame in stack or `None`
+        '''
+        if self._valid_frame():
+            return PyFrame(self._frame.f_back)
+        else:
+            return None
+
+    def getVariable(self, name):
+        '''
+        Get a variable from the current frame.
+
+        :param: name:   variable name to look up
+        :return:        variable or <code>null</code>
+        '''
+        if self._valid_frame():
+            if name in self._frame.f_locals:
+                return convert_value(self._frame.f_locals[name])
+
+            if name in self._frame.f_globals:
+                return convert_value(self._frame.f_globals[name])
+
+        return None
+
+    def getVariables(self):
+        '''
+        Get variables visible from current frame.
+
+        :return:        variableName -> variableContent
+        '''
+        variables = {}
+        if self._valid_frame():
+            # TODO: Differentiate between global and local variables
+            variables.update(self._frame.f_globals)
+            variables.update(self._frame.f_locals)
+
+        return convert_value(variables)
+
+    def setVariable(self, name, value):
+        '''
+        Sets new value for a variable only if it exists.
+
+        :param name:    Name of variable to be updated.
+        :param value:   New value for variable.
+        '''
+        if self._valid_frame():
+            if name in self._frame.f_locals:
+                self._frame.f_locals[name] = value
+
+            elif name in self._frame.f_globals:
+                self._frame.f_globals[name] = value
+
+    def toString(self):
+        '''
+        Required override to avoid problems in py4j bridge.
+        '''
+        return '{}: #{:d}'.format(self.getFilename(), self.getLineNumber())
+
+    class Java:
+        implements = ['org.eclipse.ease.lang.python.debugger.IPyFrame']
+
+
+def ignore_frame(frame, first=True):
+    '''
+    Utility to check if a frame should be ignored.
+
+    Current reasons to ignore a frame:
+        * It is the top entry of the stack and it is a standard module.
+          e.g. <string>
+        * It is part of the py4j library or has py4j in its call chain.
+
+    Recursively checks trace until at bottom of stack.
+
+    :param frame:    Frame to check if it should be ignored.
+    :param first:    Flag to signalize if it is the first call.
+    :returns:        `True` if the frame should be ignored.
+    '''
+    # End of frame
+    if not frame:
+        return False
+
+    # ignore frames that originate from the self.run() method
+    if first:
+        if frame.f_code.co_filename == '(none)':
+            return True
+
+    # Check if we are in the standard modules
+    if INTERNAL_CHECKER.match(frame.f_code.co_filename):
+        # Only ignore standard module if its the top of the stack
+        return first
+
+    # TODO: Think of better way to identify py4j library
+    if 'py4j' in frame.f_code.co_filename.lower():
+        return True
+
+    return ignore_frame(frame.f_back, False)
+
+
+class CodeTracer(bdb.Bdb):
+    '''
+    Eclipse Debugger class.
+    '''
+
+    def __init__(self, *args, **kwargs):
+        '''
+        Constructor initializes instance variables.
+        '''
+        bdb.Bdb.__init__(self, *args, **kwargs)
+
+        # Debugger for communication with Java world
+        self._debugger = None
+
+        # Caches for currently executed code parts
+        self._current_frame = None
+        self._current_file = None
+
+        # Async continuation handling
+        self._continue_event = threading.Event()
+        self._continue_func = lambda: None
+
+        # TODO: Think about a better way to handle step return
+        self._return_hack = False
+
+    def setDebugger(self, debugger):
+        '''
+        Setter method for self._debugger.
+
+        :param org.eclipse.ease.lang.python.debugger.PythonDebugger debugger:
+            PythonDebugger object to handling communication with Eclipse.
+        '''
+        self._debugger = debugger
+
+    def trace_dispatch(self, frame, event, arg):
+        '''
+        Called for every executed line of source code.
+
+        Checks if line is of interest and dispatches the call for
+        further investigation.
+
+        :param frame:    Current stack frame.
+        :param event:    Type of dispatch event that occurred.
+        :param arg:      Optional argument for dispatching.
+        '''
+        # Pre-filter to avoid issues with library internals
+        if not ignore_frame(frame):
+            bdb.Bdb.trace_dispatch(self, frame, event, arg)
+        return self.trace_dispatch
+
+    def user_line(self, frame):
+        '''
+        Called when debugger thinks line is of interest.
+
+        :param frame:    Current stack frame with line.
+        '''
+        self._continue_func = self.set_continue
+        self.dispatch(frame, 'line')
+
+    def user_call(self, frame, argument_list):
+        '''
+        Called when debugger thinks call is of interest.
+
+        :param frame:    Current stack frame with call information.
+        :param argument_list:    ignored
+        '''
+        self.dispatch(frame, 'call')
+
+    def user_return(self, frame, return_value):
+        '''
+        Called when debugger thinks returning from function is of interest.
+
+        :param frame:    Current stack frame with return information.
+        :param return_value:    ignored
+        '''
+        self._continue_func = self.set_step
+        self.dispatch(frame, 'return')
+
+    def user_exception(self, frame, exc_info):
+        '''
+        Called when debugger thinks thrown exception is of interest.
+
+        :param frame:    Current stack frame with exception information.
+        :param exc_info:    ignored
+        '''
+        # Cache exception
+        if self._debugger:
+            self._debugger.setExceptionStackTrace(PyFrame(frame))
+
+        self._continue_func = self.set_continue
+        self.dispatch(frame, 'exception')
+
+    def dispatch(self, frame, dispatch_type):
+        '''
+        Dispatches the given frame to the interested debugger.
+
+        :param frame:    Current stack frame to be dispatched.
+        :param dispatch_type:    Type of dispatch event (call, line, ...)
+        '''
+        if not self._debugger:
+            return
+
+        # Set up caches for asynchronous continuation
+        self._current_frame = frame
+        self._continue_event.set()
+
+        # Check if we need to fake the current frame for Eclipse
+        if self._return_hack:
+            frame = frame.f_back
+            self._return_hack = False
+
+        # Dispatch call to Java
+        self._debugger.traceDispatch(PyFrame(frame), dispatch_type)
+
+        # Make asynchronous call synchronous again
+        self._continue_event.wait()
+        self._continue_func()
+
+    def suspend(self):
+        '''
+        Suspend the execution to wait for asynchronous callbacks
+        from Java.
+        '''
+        self._continue_event.clear()
+
+    def resume(self, resume_type):
+        '''
+        Resume execution by calling the given resume function.
+
+        :param resume_type:    Desired resume function identifier.
+        '''
+        frame = self._current_frame
+
+        # Step into
+        if resume_type == 1:
+            self._continue_func = self.set_step
+
+        # Step over
+        elif resume_type == 2:
+            self._continue_func = functools.partial(self.set_next, frame)
+
+        # Step return
+        elif resume_type == 4:
+            self._return_hack = True
+            self._continue_func = functools.partial(self.set_return, frame)
+
+        # Continue
+        else:
+            self._continue_func = self.set_continue
+
+        self._continue_event.set()
+
+    def set_continue(self):
+        '''
+        FIXME:  Override of bdb.Bdb.set_continue
+                Original stops debugging when no more breakpoints in file.
+                This means we cannot add breakpoints later on.
+        '''
+        self._set_stopinfo(self.botframe, None, -1)
+
+    def setBreakpoint(self, breakpoint):
+        '''
+        Interface method for Java to have simpler method of setting
+        a breakpoint.
+
+        FIXME:  This is a copy of bdb.Bdb.set_break
+                set_break performs OS level checks to see if file exists.
+
+        :param breakpoint:    Breakpoint to be set.
+        '''
+        filename = self.canonic(breakpoint.getFilename())
+        lineno = breakpoint.getLineno()
+
+        if filename not in self.breaks:
+            self.breaks[filename] = []
+        linenos = self.breaks[filename]
+        if lineno not in linenos:
+            linenos .append(lineno)
+        bdb.Breakpoint(filename, lineno, 0, None, None)
+
+    def removeBreakpoint(self, breakpoint):
+        '''
+        Interface method for Java to have simpler method of removing
+        a breakpoint.
+
+        :param breakpoint:    Breakpoint to be removed.
+        '''
+        filename = self.canonic(breakpoint.getFilename())
+        lineno = breakpoint.getLineno()
+        self.clear_break(filename, lineno)
+
+    def stop_here(self, frame):
+        '''
+        Hijacks frame check to see if currently executed file has changed.
+
+        If file changed new breakpoints will be loaded.
+
+        :param frame:    Current stack frame to check for file change.
+        '''
+        if self._debugger:
+            file = frame.f_code.co_filename
+            if file != self._current_file:
+                self._current_file = file
+                self.clear_all_file_breaks(file)
+
+                breakpoints = self._debugger.getBreakpoints(file)
+                for breakpoint in breakpoints:
+                    self.setBreakpoint(breakpoint)
+
+                # If we have breakpoints we want to let the debugger know
+                if breakpoints:
+                    return True
+
+        return bdb.Bdb.stop_here(self, frame)
+
+    def run(self, script, filename):
+        '''
+        Executes the script given using the bdb.Bdb.run method.
+
+        :param script:    Script to be executed.
+        :param filename:  Filename for easier identification of dynamic code.
+        '''
+        # Compile code for better inspection
+        code = '{}\n'.format(script.getCode())
+        ast_ = ast.parse(code, filename, 'exec')
+        final_expr = None
+        for field_ in ast.iter_fields(ast_):
+            if 'body' != field_[0]:
+                continue
+
+            # Check if last item is an expression
+            if len(field_[1]) > 0:
+                if isinstance(field_[1][-1], ast.Expr):
+                    final_expr = ast.Expression()
+                    popped = field_[1].pop(-1)
+                    final_expr.body = popped.value
+
+        # Run code up to last expression
+        return_value = None
+        compiled = compile(ast_, filename, 'exec')
+        bdb.Bdb.run(self, compiled)
+        self._keep_debugging()
+
+        # Check if last expression still needs to be run
+        if final_expr:
+            compiled = compile(final_expr, filename, 'eval')
+            return_value = self.runeval(compiled)
+            self._keep_debugging()
+
+        return return_value
+
+    def _keep_debugging(self):
+        '''
+        Resets debugger state to be able to continue debugging after
+        script injection.
+        '''
+        sys.settrace(self.trace_dispatch)
+        self.quitting = False
+
+    def toString(self):
+        '''
+        Required override to avoid problems in py4j bridge.
+        '''
+        return str(self)
+
+    def equals(self, other):
+        '''
+        Required override to avoid problems in py4j bridge.
+        '''
+        return self is other
+
+    class Java:
+        implements = ['org.eclipse.ease.lang.python.py4j.internal.ICodeTraceFilter']
+
+
+# Set up connection between eclipse and python
+CodeTracer()
\ No newline at end of file
diff --git a/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/ICodeTraceFilter.java b/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/ICodeTraceFilter.java
new file mode 100644
index 0000000..b00eabe
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/ICodeTraceFilter.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.py4j.internal;
+
+import org.eclipse.ease.IExecutionListener;
+import org.eclipse.ease.debugging.dispatcher.IEventProcessor;
+import org.eclipse.ease.lang.python.debugger.ICodeTracer;
+import org.eclipse.ease.lang.python.debugger.PythonBreakpoint;
+import org.eclipse.ease.lang.python.debugger.PythonDebugger;
+
+/**
+ * Extension of {@link ICodeTracer} performing pre-filtering before code gets to actual tracer.
+ */
+public interface ICodeTraceFilter extends ICodeTracer, IEventProcessor, IExecutionListener {
+	/**
+	 * Sets the {@link PythonDebugger} to be used by the code trace filter to perform callbacks.
+	 *
+	 * @param debugger
+	 *            {@link PythonDebugger} for callbacks.
+	 */
+	void setDebugger(PythonDebugger debugger);
+
+	/**
+	 * Sets a breakpoint in the trace filter.
+	 *
+	 * @param breakpoint
+	 *            Breakpoint to be set.
+	 */
+	void setBreakpoint(PythonBreakpoint breakpoint);
+
+	/**
+	 * Removes a breakpoint from the trace filter.
+	 *
+	 * @param breakpoint
+	 *            Breakpoint to be removed.
+	 */
+	void removeBreakpoint(PythonBreakpoint breakpoint);
+
+	/**
+	 * Resume execution after filter has notified us that execution might need to be stopped.
+	 * 
+	 * @param resumeType
+	 *            Resume type for execution continuation.
+	 */
+	void resume(int resumeType);
+
+	/**
+	 * Suspend execution after filter has notified us that execution might need to be stopped.
+	 */
+	void suspend();
+}
diff --git a/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebugger.java b/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebugger.java
new file mode 100644
index 0000000..8b2050d
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebugger.java
@@ -0,0 +1,156 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.py4j.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.debug.core.DebugEvent;
+import org.eclipse.debug.core.model.IBreakpoint;
+import org.eclipse.ease.ExitException;
+import org.eclipse.ease.IDebugEngine;
+import org.eclipse.ease.Script;
+import org.eclipse.ease.debugging.events.debugger.IDebuggerEvent;
+import org.eclipse.ease.lang.python.debugger.IPythonScriptRegistry;
+import org.eclipse.ease.lang.python.debugger.PythonBreakpoint;
+import org.eclipse.ease.lang.python.debugger.PythonDebugger;
+
+/**
+ * Extension of {@link PythonDebugger} with additional {@link ICodeTraceFilter} to lower amount of trace dispatches.
+ */
+public class Py4jDebugger extends PythonDebugger {
+	/**
+	 * Extended code tracer doing pre-filtering.
+	 */
+	private ICodeTraceFilter fTraceFilter;
+
+	/**
+	 * @see PythonDebugger#PythonDebugger(IDebugEngine, boolean)
+	 */
+	public Py4jDebugger(IDebugEngine engine, boolean showDynamicCode) {
+		super(engine, showDynamicCode);
+	}
+
+	/**
+	 * Sets extended code tracer doing pre-filtering of dispatch calls..
+	 *
+	 * @param traceFilter
+	 *            Extended code tracer.
+	 */
+	public void setTraceFilter(ICodeTraceFilter traceFilter) {
+		fTraceFilter = traceFilter;
+	}
+
+	/**
+	 * Returns list of all breakpoints in given file.
+	 *
+	 * @param filename
+	 *            Filename to get all breakpoints for.
+	 * @return List of breakpoints in given file.
+	 */
+	public List<PythonBreakpoint> getBreakpoints(String filename) {
+		final IPythonScriptRegistry registry = getScriptRegistry();
+		if (registry != null) {
+
+			final Script script = registry.getScript(filename);
+			if (script != null) {
+				final List<IBreakpoint> breakpoints = fBreakpoints.get(script);
+				if (breakpoints != null) {
+					final List<PythonBreakpoint> pythonBreakpoints = new ArrayList<>();
+					for (final IBreakpoint breakpoint : breakpoints) {
+						final int lineno = breakpoint.getMarker().getAttribute(IMarker.LINE_NUMBER, -1);
+						pythonBreakpoints.add(new PythonBreakpoint(filename, lineno));
+					}
+					return pythonBreakpoints;
+				}
+			}
+		}
+
+		return Collections.<PythonBreakpoint> emptyList();
+
+	}
+
+	@Override
+	protected void suspend(IDebuggerEvent event) {
+		fTraceFilter.suspend();
+		super.suspend(event);
+
+	}
+
+	@Override
+	protected void resume(int resumeType, Object thread) {
+		super.resume(resumeType, thread);
+		fTraceFilter.resume(resumeType);
+	}
+
+	@Override
+	public Object execute(Script script) {
+		try {
+			String reference = script.getTitle();
+			final IPythonScriptRegistry registry = getScriptRegistry();
+			if (registry != null) {
+				registry.put(script);
+				reference = registry.getReference(script);
+			}
+			return fTraceFilter.run(script, reference);
+
+		} catch (final Exception e) {
+			/*
+			 * When terminating, #handleEvent sets resume type to STEP_END, which causes #traceDispatch to raise an ExitException. The ExitException is
+			 * propagated back to python, and eventually ends up back here at Exit method. However Py4J does not support propogating the same type of exception
+			 * across the Python/Java barrier, so what ends up being thrown is a Py4JException instead.
+			 *
+			 * Therefore we catch that case and re-raise the expected ExitException()
+			 */
+			if (getThreadState(getThread()).fResumeType == DebugEvent.STEP_END) {
+				throw new ExitException("Debug aborted by user");
+			}
+			throw e;
+		}
+	}
+
+	@Override
+	protected void breakpointAdded(final Script script, final IBreakpoint breakpoint) {
+		final IPythonScriptRegistry registry = getScriptRegistry();
+		if (registry == null) {
+			return;
+		}
+
+		final String reference = registry.getReference(script);
+		if (reference != null) {
+			final int linenumber = breakpoint.getMarker().getAttribute(IMarker.LINE_NUMBER, -1);
+
+			if (linenumber != -1) {
+				fTraceFilter.setBreakpoint(new PythonBreakpoint(reference, linenumber));
+			}
+		}
+	}
+
+	@Override
+	protected void breakpointRemoved(final Script script, final IBreakpoint breakpoint) {
+		final IPythonScriptRegistry registry = getScriptRegistry();
+		if (registry == null) {
+			return;
+		}
+
+		final String reference = registry.getReference(script);
+		if (reference != null) {
+			final int linenumber = breakpoint.getMarker().getAttribute(IMarker.LINE_NUMBER, -1);
+
+			if (linenumber != -1) {
+				fTraceFilter.removeBreakpoint(new PythonBreakpoint(reference, linenumber));
+			}
+		}
+	}
+}
diff --git a/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebuggerEngine.java b/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebuggerEngine.java
index 07b6391..ed18c3d 100644
--- a/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebuggerEngine.java
+++ b/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebuggerEngine.java
@@ -13,16 +13,18 @@
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Map;
 import java.util.Map.Entry;
 
 import org.eclipse.debug.core.ILaunch;
 import org.eclipse.ease.Script;
 import org.eclipse.ease.ScriptEngineException;
+import org.eclipse.ease.debugging.EaseDebugFrame;
 import org.eclipse.ease.debugging.ScriptStackTrace;
-import org.eclipse.ease.debugging.dispatcher.EventDispatchJob;
 import org.eclipse.ease.debugging.model.EaseDebugVariable;
 import org.eclipse.ease.lang.python.debugger.IPythonDebugEngine;
 import org.eclipse.ease.lang.python.debugger.PythonDebugger;
+import org.eclipse.ease.lang.python.debugger.PythonEventDispatchJob;
 import org.eclipse.ease.lang.python.debugger.ResourceHelper;
 import org.eclipse.ease.lang.python.debugger.model.PythonDebugTarget;
 
@@ -36,10 +38,15 @@
 
 	public static final String ENGINE_ID = "org.eclipse.ease.lang.python.py4j.debugger.engine";
 
-	private PythonDebugger fDebugger = null;
+	private Py4jDebugger fDebugger = null;
+	private ICodeTraceFilter fPyDebugger = null;
 
 	@Override
 	public void setDebugger(final PythonDebugger debugger) {
+		setDebugger((Py4jDebugger) debugger);
+	}
+
+	private void setDebugger(final Py4jDebugger debugger) {
 		fDebugger = debugger;
 	}
 
@@ -51,10 +58,12 @@
 		if (fDebugger != null) {
 			try {
 				// load python part of debugger
-				final InputStream stream = ResourceHelper.getResourceStream("org.eclipse.ease.lang.python", "pysrc/edb.py");
+				final InputStream stream = ResourceHelper.getResourceStream("org.eclipse.ease.lang.python.py4j", "pysrc/py4jdb.py");
+				fPyDebugger = (ICodeTraceFilter) super.internalExecute(new Script("Load Python debugger", stream), null);
 
-				internalSetVariable(PythonDebugger.PYTHON_DEBUGGER_VARIABLE, fDebugger);
-				super.internalExecute(new Script("Load Python debugger", stream), null);
+				// Connect both sides
+				fPyDebugger.setDebugger(fDebugger);
+				fDebugger.setTraceFilter(fPyDebugger);
 
 			} catch (final Throwable e) {
 				throw new ScriptEngineException("Failed to load Python Debugger", e);
@@ -75,17 +84,16 @@
 		final PythonDebugTarget target = new PythonDebugTarget(launch, suspendOnStartup, suspendOnScriptLoad, showDynamicCode);
 		launch.addDebugTarget(target);
 
-		final PythonDebugger debugger = new PythonDebugger(this, showDynamicCode);
+		final Py4jDebugger debugger = new Py4jDebugger(this, showDynamicCode);
 
 		setDebugger(debugger);
 
-		new EventDispatchJob(target, debugger);
+		new PythonEventDispatchJob(target, debugger);
 	}
 
 	@Override
 	public ScriptStackTrace getExceptionStackTrace() {
-		// FIXME to be implemented
-		return null;
+		return getExceptionStackTrace(getThread());
 	}
 
 	@Override
@@ -101,11 +109,17 @@
 
 	@Override
 	public Collection<EaseDebugVariable> getVariables(Object scope) {
-		// FIXME to be implemented correctly on the scope
+		final Map<String, Object> variablesMap;
+		if (scope instanceof EaseDebugFrame) {
+			final EaseDebugFrame frame = (EaseDebugFrame) scope;
+			variablesMap = frame.getVariables();
+		} else {
+			variablesMap = getVariables();
+		}
 
 		final Collection<EaseDebugVariable> variables = new ArrayList<>();
 
-		for (final Entry<String, Object> entry : getVariables().entrySet())
+		for (final Entry<String, Object> entry : variablesMap.entrySet())
 			variables.add(createVariable(entry.getKey(), entry.getValue()));
 
 		return variables;
diff --git a/plugins/org.eclipse.ease.lang.python/pysrc/edb.py b/plugins/org.eclipse.ease.lang.python/pysrc/edb.py
index fe9a706..5ac2d02 100644
--- a/plugins/org.eclipse.ease.lang.python/pysrc/edb.py
+++ b/plugins/org.eclipse.ease.lang.python/pysrc/edb.py
@@ -12,14 +12,13 @@
 
 # Python std library imports
 import sys
-import __main__
 import re
 import ast
-from pygments.lexers._scilab_builtins import variables_kw
 
 # : Regular expression if we are dealing with internal module.
 _pyease_INTERNAL_CHECKER = re.compile(r"^<.+>$")
 
+
 class _pyease_PyFrame:
     '''
     Python implementation of IPyFrame used for exchanging frame data
@@ -30,6 +29,7 @@
     Python frame documentation is available here:
     https://docs.python.org/3/reference/datamodel.html#types
     '''
+
     def __init__(self, frame):
         '''
         Constructor only stores frame to member.
@@ -177,7 +177,7 @@
             if len(field_[1]) > 0:
                 if isinstance(field_[1][-1], ast.Expr):
                     final_expr = ast.Expression()
-                    popped = field_[1].pop(len(field_[1]) - 1); # Jython pop() will not accept -1 as parameter
+                    popped = field_[1].pop(len(field_[1]) - 1);  # Jython pop() will not accept -1 as parameter
                     final_expr.body = popped.value
         
         return_value = None
@@ -224,7 +224,6 @@
     
         # Check parent in stack
         return self.ignore_frame(frame.f_back, False)
-
         
     class Java:
         implements = ['org.eclipse.ease.lang.python.debugger.ICodeTracer']
@@ -233,4 +232,4 @@
 # Set up connection between eclipse and python
 _pyease_eclipse_python_debugger = _pyease_CodeTracer()
 _pyease_eclipse_python_debugger.set_debugger(_pyease_debugger)
-_pyease_debugger.setCodeTracer(_pyease_eclipse_python_debugger)
\ No newline at end of file
+_pyease_debugger.setCodeTracer(_pyease_eclipse_python_debugger)
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/IPythonScriptRegistry.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/IPythonScriptRegistry.java
new file mode 100644
index 0000000..e7221a2
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/IPythonScriptRegistry.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.debugger;
+
+import org.eclipse.ease.Script;
+import org.eclipse.ease.debugging.IScriptRegistry;
+
+/**
+ * Extension of {@link IScriptRegistry} to also add mapping from {@link Script} used in EASE world to {@link String} used in Python world.
+ */
+public interface IPythonScriptRegistry extends IScriptRegistry {
+
+	/**
+	 * Return the {@link Script} identified by this reference {@link String}.
+	 *
+	 * @param reference
+	 *            Reference {@link String} to get {@link Script} for.
+	 * @return {@link Script} identified by reference {@link String} or {@code null} if no mapping found.
+	 */
+	Script getScript(String reference);
+
+	/**
+	 * Return the reference {@link String} for this {@link Script}.
+	 *
+	 * @param script
+	 *            {@link Script} to get reference {@link String} for.
+	 * @return Reference {@link String} for {@link Script} or {@code null} if no mapping found.
+	 */
+	String getReference(Script script);
+}
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonBreakpoint.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonBreakpoint.java
new file mode 100644
index 0000000..325a94a
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonBreakpoint.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.debugger;
+
+/**
+ * Helper class to have simpler data exchange format between EASE breakpoint information and Python counterpart.
+ *
+ * FIXME: Could reuse functionality from org.python.pydev.debug.model.PyBreakpoint.
+ */
+public class PythonBreakpoint {
+	private final String fFilename;
+	private final int fLineno;
+
+	/**
+	 * Constructor only stores parameters to members.
+	 *
+	 * @param filename
+	 *            Filename for the breakpoint.
+	 * @param lineno
+	 *            Linenumber for the breakpoint.
+	 */
+	public PythonBreakpoint(final String filename, final int lineno) {
+		fFilename = filename;
+		fLineno = lineno;
+	}
+
+	/**
+	 * Returns the filename for the breakpoint.
+	 *
+	 * @return breakpoint's filename.
+	 */
+	public String getFilename() {
+		return fFilename;
+	}
+
+	/**
+	 * Returns the line number for the breakpoint.
+	 *
+	 * @return breakpoint's line number.
+	 */
+	public int getLineno() {
+		return fLineno;
+	}
+
+}
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonDebugger.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonDebugger.java
index cd2cf7e..cd97951 100644
--- a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonDebugger.java
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonDebugger.java
@@ -12,10 +12,7 @@
 package org.eclipse.ease.lang.python.debugger;
 
 import java.io.File;
-import java.util.HashMap;
 import java.util.Map;
-import java.util.Random;
-import java.util.Set;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.debug.core.DebugEvent;
@@ -27,6 +24,7 @@
 import org.eclipse.ease.debugging.AbstractEaseDebugger;
 import org.eclipse.ease.debugging.EaseDebugFrame;
 import org.eclipse.ease.debugging.IScriptDebugFrame;
+import org.eclipse.ease.debugging.IScriptRegistry;
 import org.eclipse.ease.debugging.ScriptStackTrace;
 import org.eclipse.ease.debugging.dispatcher.IEventProcessor;
 
@@ -52,7 +50,7 @@
 		 *            {@link IPyFrame} with information about the current execution frame.
 		 */
 		public PythonDebugFrame(final IPyFrame frame) {
-			super(fScriptRegistry.get(frame.getFilename()), frame.getLineNumber(), TYPE_FILE);
+			super(getScriptRegistry() != null ? getScriptRegistry().getScript(frame.getFilename()) : null, frame.getLineNumber(), TYPE_FILE);
 			fFrame = frame;
 
 		}
@@ -129,14 +127,19 @@
 	 *            Top frame of stack.
 	 * @return Stack based on given {@link IPyFrame}
 	 */
-	private ScriptStackTrace getStacktrace(final IPyFrame origin) {
+	protected ScriptStackTrace getStacktrace(final IPyFrame origin) {
 		final ScriptStackTrace trace = new ScriptStackTrace();
 
+		final IPythonScriptRegistry registry = getScriptRegistry();
+		if (registry == null) {
+			return trace;
+		}
+
 		IPyFrame frame = origin;
 		while (frame != null) {
-			final Script script = fScriptRegistry.get(frame.getFilename());
+			final Script script = registry.getScript(frame.getFilename());
 			if (script != null) {
-				if (isTrackedScript(fScriptRegistry.get(frame.getFilename())))
+				if (isTrackedScript(registry.getScript(frame.getFilename())))
 					trace.add(new PythonDebugFrame(frame));
 			}
 
@@ -155,7 +158,12 @@
 	 *            Type of trace step that occurred (ignored).
 	 */
 	public void traceDispatch(final IPyFrame frame, final String type) {
-		final Script script = fScriptRegistry.get(frame.getFilename());
+		Script script = null;
+		final IPythonScriptRegistry registry = getScriptRegistry();
+		if (registry != null) {
+			script = registry.getScript(frame.getFilename());
+		}
+
 		if (script != null) {
 			if (isTrackedScript(script)) {
 
@@ -192,7 +200,13 @@
 	 */
 	public Object execute(final Script script) {
 		try {
-			return fCodeTracer.run(script, registerScript(script));
+			String reference = script.getTitle();
+			final IPythonScriptRegistry registry = getScriptRegistry();
+			if (registry != null) {
+				registry.put(script);
+				reference = registry.getReference(script);
+			}
+			return fCodeTracer.run(script, reference);
 
 		} catch (final Exception e) {
 			/*
@@ -210,39 +224,17 @@
 	}
 
 	/**
-	 * Map from custom filename to actual {@link Script} for easily identifying different scripts.
-	 * <p>
-	 * For each new file a unique filename is created using {@link #getHash(Script, Set)}.
-	 */
-	private final Map<String, Script> fScriptRegistry = new HashMap<>();
-
-	private String registerScript(final Script script) {
-		final String reference = getHash(script, fScriptRegistry.keySet());
-		fScriptRegistry.put(reference, script);
-
-		return reference;
-	}
-
-	/**
-	 * Creates a unique filename for the given {@link Script}.
+	 * Returns the {@link IPythonScriptRegistry} used by the debugger.
 	 *
-	 * @param script
-	 *            {@link Script} to get unique filename for.
-	 * @param existingKeys
-	 *            Existing keys to avoid duplicates.
-	 * @return Unique filename for given {@link Script}.
+	 * If debugger has been set up incorrectly (e.g. script registry not tailored for python) this will return {@code null}.
+	 *
+	 * @return {@link IPythonScriptRegistry} or {@code null}.
 	 */
-	private static String getHash(final Script script, final Set<String> existingKeys) {
-		final StringBuilder buffer = new StringBuilder("__ref_");
-		buffer.append(script.isDynamic() ? "dyn" : script.getCommand().toString());
-		buffer.append("_");
-
-		for (int index = 0; index < 10; index++)
-			buffer.append((char) ('a' + new Random().nextInt(26)));
-
-		if (existingKeys.contains(buffer.toString()))
-			return getHash(script, existingKeys);
-
-		return buffer.toString();
+	public IPythonScriptRegistry getScriptRegistry() {
+		final IScriptRegistry scriptRegistry = super.getScriptRegistry();
+		if (scriptRegistry instanceof IPythonScriptRegistry) {
+			return (IPythonScriptRegistry) scriptRegistry;
+		}
+		return null;
 	}
 }
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonEventDispatchJob.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonEventDispatchJob.java
new file mode 100644
index 0000000..aa4d111
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonEventDispatchJob.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.debugger;
+
+import org.eclipse.ease.Script;
+import org.eclipse.ease.debugging.IScriptRegistry;
+import org.eclipse.ease.debugging.dispatcher.EventDispatchJob;
+import org.eclipse.ease.debugging.dispatcher.IEventProcessor;
+
+/**
+ * Extension of {@link EventDispatchJob} using {@link IPythonScriptRegistry} rather than {@link IScriptRegistry}.
+ */
+public class PythonEventDispatchJob extends EventDispatchJob implements IPythonScriptRegistry {
+	/**
+	 * {@link IPythonScriptRegistry} to be used instead of parent's {@link IScriptRegistry}.
+	 */
+	private final IPythonScriptRegistry fScriptRegistry;
+
+	/**
+	 * Constructor creating new {@link PythonScriptRegistry} for overload.
+	 *
+	 * @see EventDispatchJob#EventDispatchJob(IEventProcessor, IEventProcessor, IScriptRegistry)
+	 */
+	public PythonEventDispatchJob(IEventProcessor host, IEventProcessor debugger) {
+		super(host, debugger, new PythonScriptRegistry());
+		fScriptRegistry = (IPythonScriptRegistry) getScriptRegistry();
+	}
+
+	@Override
+	public Script getScript(String reference) {
+		return fScriptRegistry.getScript(reference);
+	}
+
+	@Override
+	public String getReference(Script script) {
+		return fScriptRegistry.getReference(script);
+	}
+
+}
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonScriptRegistry.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonScriptRegistry.java
new file mode 100644
index 0000000..abc8dfb
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonScriptRegistry.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.debugger;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import org.eclipse.ease.Script;
+import org.eclipse.ease.debugging.ScriptRegistry;
+
+/**
+ * Default implementation of {@link IPythonScriptRegistry} simply using maps to perform 1:1 mapping.
+ */
+public class PythonScriptRegistry extends ScriptRegistry implements IPythonScriptRegistry {
+
+	/** List of framework files that should be ignored by script registry. */
+	private static final List<String> IGNORED_FILES = Arrays.asList(new String[] { "cp1252.py", "bdb.py", "ast.py", "threading.py" });
+
+	/** Lookup from {@link Script} in EASE world to {@link String} in Python world */
+	private final Map<Script, String> fScriptMap = new HashMap<>();
+
+	/** Reverse lookup from {@link String} in Python world to {@link Script} in EASE world */
+	private final Map<String, Script> fStringMap = new HashMap<>();
+
+	@Override
+	public void put(Script script) {
+		// IResource <-> Script mapping
+		super.put(script);
+
+		// Script <-> String mapping
+		final String reference = uid(script);
+		fStringMap.put(reference, script);
+		fScriptMap.put(script, reference);
+
+	}
+
+	@Override
+	public Script getScript(String reference) {
+		Script script = fStringMap.get(reference);
+		if (script == null) {
+			final File file = new File(reference);
+			if (file.exists()) {
+				if (!IGNORED_FILES.contains(file.getName())) {
+					script = new Script(file);
+					put(script);
+				}
+			}
+		}
+
+		return script;
+	}
+
+	@Override
+	public String getReference(Script script) {
+		return fScriptMap.get(script);
+	}
+
+	/**
+	 * Creates a unique reference identifier for the given script.
+	 *
+	 * @param script
+	 *            Script to get unique identifier for.
+	 * @return Unique identifer for script.
+	 */
+	private String uid(final Script script) {
+		final Set<String> existingKeys = fStringMap.keySet();
+		final StringBuilder buffer = new StringBuilder("__ref_");
+		buffer.append(script.isDynamic() ? "dyn" : script.getCommand().toString());
+		buffer.append("_");
+
+		for (int index = 0; index < 10; index++)
+			buffer.append((char) ('a' + new Random().nextInt(26)));
+
+		if (existingKeys.contains(buffer.toString()))
+			return uid(script);
+
+		return buffer.toString();
+	}
+
+}
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/AbstractEaseDebugger.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/AbstractEaseDebugger.java
index 905e15f..bdd24bf 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/AbstractEaseDebugger.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/AbstractEaseDebugger.java
@@ -71,9 +71,11 @@
 
 	private EventDispatchJob fDispatcher;
 
+	private IScriptRegistry fScriptRegistry;
+
 	private IDebugEngine fEngine;
 
-	private final Map<Script, List<IBreakpoint>> fBreakpoints = new HashMap<>();
+	protected final Map<Script, List<IBreakpoint>> fBreakpoints = new HashMap<>();
 
 	private final boolean fShowDynamicCode;
 
@@ -92,6 +94,15 @@
 	}
 
 	/**
+	 * Protected getter for event dispatch job.
+	 *
+	 * @return {@link #fDispatcher}.
+	 */
+	protected EventDispatchJob getDispatcher() {
+		return fDispatcher;
+	}
+
+	/**
 	 * Setter method for dispatcher.
 	 *
 	 * @param dispatcher
@@ -100,6 +111,28 @@
 	@Override
 	public void setDispatcher(final EventDispatchJob dispatcher) {
 		fDispatcher = dispatcher;
+
+		// TODO: Use setScriptRegistry explicitly
+		setScriptRegistry(dispatcher);
+	}
+
+	/**
+	 * Setter method for script registry for lookups between different types of file identifications.
+	 *
+	 * @param registry
+	 *            Script registry to be used.
+	 */
+	public void setScriptRegistry(final IScriptRegistry registry) {
+		fScriptRegistry = registry;
+	}
+
+	/**
+	 * Protected getter for subclasses to have access to {@link #fScriptRegistry}.
+	 *
+	 * @return {@link #fScriptRegistry}
+	 */
+	protected IScriptRegistry getScriptRegistry() {
+		return fScriptRegistry;
 	}
 
 	/**
@@ -127,10 +160,15 @@
 					if (!fBreakpoints.containsKey(script))
 						fBreakpoints.put(script, new ArrayList<IBreakpoint>());
 
-					if (((BreakpointRequest) event).getMode() == BreakpointRequest.Mode.ADD)
-						fBreakpoints.get(script).add(((BreakpointRequest) event).getBreakpoint());
-					else
-						fBreakpoints.get(script).remove(((BreakpointRequest) event).getBreakpoint());
+					if (((BreakpointRequest) event).getMode() == BreakpointRequest.Mode.ADD) {
+						final IBreakpoint breakpoint = ((BreakpointRequest) event).getBreakpoint();
+						fBreakpoints.get(script).add(breakpoint);
+						breakpointAdded(script, breakpoint);
+					} else {
+						final IBreakpoint breakpoint = ((BreakpointRequest) event).getBreakpoint();
+						fBreakpoints.get(script).remove(breakpoint);
+						breakpointRemoved(script, breakpoint);
+					}
 				}
 
 			} else if (event instanceof TerminateRequest) {
@@ -149,6 +187,34 @@
 		}
 	}
 
+	/**
+	 * Callback triggered when breakpoint has been added.
+	 *
+	 * No-op implementations can be overridden by subclasses.
+	 *
+	 * @param script
+	 *            Script this breakpoint belongs to.
+	 * @param breakpoint
+	 *            Breakpoint information.
+	 */
+	protected void breakpointAdded(final Script script, final IBreakpoint breakpoint) {
+
+	}
+
+	/**
+	 * Callback triggered when breakpoint has been removed.
+	 *
+	 * No-op implementations can be overridden by subclasses.
+	 *
+	 * @param script
+	 *            Script this breakpoint belonged to.
+	 * @param breakpoint
+	 *            Breakpoint information.
+	 */
+	protected void breakpointRemoved(final Script script, final IBreakpoint breakpoint) {
+
+	}
+
 	protected void suspend(final IDebuggerEvent event) {
 
 		// only suspend if there possibly exists someone to wake us up again
@@ -239,7 +305,7 @@
 		}
 	}
 
-	private void resume(final int resumeType, Object thread) {
+	protected void resume(final int resumeType, Object thread) {
 		// called by engine thread
 
 		final ThreadState threadState = fThreadStates.get(thread);
@@ -331,7 +397,14 @@
 		return null;
 	}
 
-	protected boolean isTrackedScript(final Script script) {
+	/**
+	 * Checks if the given script is tracked by debugger.
+	 *
+	 * @param script
+	 *            Script to be checked.
+	 * @return {@code true} if script is being tracked by debugger.
+	 */
+	public boolean isTrackedScript(final Script script) {
 		return (!script.isDynamic()) || fShowDynamicCode;
 	}
 
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/IScriptRegistry.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/IScriptRegistry.java
new file mode 100644
index 0000000..0132f36
--- /dev/null
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/IScriptRegistry.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.debugging;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.ease.Script;
+
+/**
+ * Simple interface for mapping between data types in different debugger realms.
+ *
+ * The eclipse framework uses {@link IResource} objects to identify files while EASE relies on {@link Script} objects.
+ */
+public interface IScriptRegistry {
+	/**
+	 * Add a new {@link Script} to the registry and store its mapping.
+	 *
+	 * @param script
+	 *            Script to be stored in registry.
+	 */
+	public void put(Script script);
+
+	/**
+	 * Return the {@link Script} identified by this {@link IResource}.
+	 *
+	 * @param resource
+	 *            {@link IResource} to get {@link Script} for.
+	 * @return {@link Script} identified by {@link IResource} or {@code null} if no mapping found.
+	 */
+	public Script getScript(IResource resource);
+
+	/**
+	 * Get the {@link IResource} identified by this {@link Script}.
+	 *
+	 * @param resource
+	 *            {@link Script} to get {@link IResource} for.
+	 * @return {@link IResource} identified by {@link Script} or {@code null} if no mapping found.
+	 */
+	public IResource getResource(Script script);
+}
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/ScriptRegistry.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/ScriptRegistry.java
new file mode 100644
index 0000000..5a78e88
--- /dev/null
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/ScriptRegistry.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.debugging;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.ease.Script;
+
+/**
+ * Default implementation of {@link IScriptRegistry} simply using maps to perform 1:1 mapping.
+ */
+public class ScriptRegistry implements IScriptRegistry {
+	/** Lookup from {@link IResource} in eclipse world to {@link Script} in EASE world */
+	private final Map<IResource, Script> fResourceMap = new HashMap<>();
+
+	/** Reverse lookup from {@link Script} in EASE world to {@link IResource} in eclipse world */
+	private final Map<Script, IResource> fScriptMap = new HashMap<>();
+
+	@Override
+	public Script getScript(IResource resource) {
+		return fResourceMap.get(resource);
+	}
+
+	@Override
+	public IResource getResource(Script script) {
+		return fScriptMap.get(script);
+	}
+
+	@Override
+	public void put(Script script) {
+		if (script.getCommand() instanceof IResource) {
+			fResourceMap.put((IResource) script.getCommand(), script);
+			fScriptMap.put(script, (IResource) script.getCommand());
+		}
+	}
+}
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/dispatcher/EventDispatchJob.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/dispatcher/EventDispatchJob.java
index 8bd730b..b488c34 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/dispatcher/EventDispatchJob.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/dispatcher/EventDispatchJob.java
@@ -13,6 +13,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
@@ -20,14 +21,17 @@
 import org.eclipse.debug.core.DebugPlugin;
 import org.eclipse.ease.Activator;
 import org.eclipse.ease.Logger;
+import org.eclipse.ease.Script;
 import org.eclipse.ease.debugging.DebugTracer;
+import org.eclipse.ease.debugging.IScriptRegistry;
+import org.eclipse.ease.debugging.ScriptRegistry;
 import org.eclipse.ease.debugging.events.IDebugEvent;
 import org.eclipse.ease.debugging.events.debugger.IDebuggerEvent;
 import org.eclipse.ease.debugging.events.model.IModelRequest;
 import org.eclipse.jface.dialogs.ErrorDialog;
 import org.eclipse.swt.widgets.Display;
 
-public class EventDispatchJob extends Job {
+public class EventDispatchJob extends Job implements IScriptRegistry {
 
 	/** Cached events. */
 	private final List<IDebugEvent> fEvents = new ArrayList<>();
@@ -41,6 +45,9 @@
 	/** Debug engine. */
 	private final IEventProcessor fDebugger;
 
+	/** Script registry for performing script lookups */
+	private final IScriptRegistry fScriptRegistry;
+
 	/**
 	 * Creates a new dispatcher. The dispatcher automatically attaches to the host and the debugger. Further the job get automatically scheduled.
 	 *
@@ -50,6 +57,20 @@
 	 *            debugger implementation
 	 */
 	public EventDispatchJob(final IEventProcessor host, final IEventProcessor debugger) {
+		this(host, debugger, new ScriptRegistry());
+	}
+
+	/**
+	 * Protected constructor for externally setting {@link IScriptRegistry}.
+	 *
+	 * @param host
+	 *            debug model
+	 * @param debugger
+	 *            debugger implementation
+	 * @param scriptRegistry
+	 *            IScriptRegistry to be used (must not be {@code null})
+	 */
+	protected EventDispatchJob(final IEventProcessor host, final IEventProcessor debugger, final IScriptRegistry scriptRegistry) {
 		super(debugger + " event dispatcher");
 
 		fModel = host;
@@ -58,10 +79,19 @@
 		fModel.setDispatcher(this);
 		fDebugger.setDispatcher(this);
 
+		fScriptRegistry = scriptRegistry;
+
 		setSystem(true);
 		schedule();
 	}
 
+	/**
+	 * Protected getter for the {@link #fScriptRegistry}.
+	 */
+	protected IScriptRegistry getScriptRegistry() {
+		return fScriptRegistry;
+	}
+
 	public void addEvent(final IDebugEvent event) {
 		synchronized (fEvents) {
 			DebugTracer.debug("Dispatcher", "[+] " + event);
@@ -135,4 +165,19 @@
 			fEvents.notifyAll();
 		}
 	}
+
+	@Override
+	public Script getScript(IResource resource) {
+		return fScriptRegistry.getScript(resource);
+	}
+
+	@Override
+	public IResource getResource(Script script) {
+		return fScriptRegistry.getResource(script);
+	}
+
+	@Override
+	public void put(Script script) {
+		fScriptRegistry.put(script);
+	}
 }
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugElement.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugElement.java
index cd63d0e..ae80c63 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugElement.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugElement.java
@@ -61,7 +61,8 @@
 
 	@Override
 	public synchronized void terminate() {
-		getDebugTarget().getProcess().terminate();
+		if (getDebugTarget().getProcess() != null)
+			getDebugTarget().getProcess().terminate();
 	}
 
 	@Override
@@ -78,17 +79,22 @@
 
 	@Override
 	public boolean canDisconnect() {
-		return getDebugTarget().getProcess().canDisconnect();
+		if (getDebugTarget().getProcess() != null)
+			return getDebugTarget().getProcess().canDisconnect();
+		return false;
 	}
 
 	@Override
 	public void disconnect() {
-		getDebugTarget().getProcess().disconnect();
+		if (getDebugTarget().getProcess() != null)
+			getDebugTarget().getProcess().disconnect();
 	}
 
 	@Override
 	public boolean isDisconnected() {
-		return getDebugTarget().getProcess().isDisconnected();
+		if (getDebugTarget().getProcess() != null)
+			return getDebugTarget().getProcess().isDisconnected();
+		return false;
 	}
 
 	// ************************************************************
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugProcess.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugProcess.java
index 85c942b..9afc1d3 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugProcess.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugProcess.java
@@ -101,8 +101,11 @@
 			else if ((((ScriptReadyEvent) event).isRoot()) && (getDebugTarget().isSuspendOnStartup()))
 				debugThread.stepInto();
 
-			// default resume request
-			debugThread.resume();
+			else {
+				if (!debugThread.getState().equals(State.STEPPING)) {
+					debugThread.resume();
+				}
+			}
 
 		} else if (event instanceof ThreadCreatedEvent) {
 			EaseDebugThread debugThread = findDebugThread(((ThreadCreatedEvent) event).getThread());
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugTarget.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugTarget.java
index a9bbe66..589c1b4 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugTarget.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugTarget.java
@@ -28,6 +28,7 @@
 import org.eclipse.ease.Activator;
 import org.eclipse.ease.Script;
 import org.eclipse.ease.debugging.DebugTracer;
+import org.eclipse.ease.debugging.IScriptRegistry;
 import org.eclipse.ease.debugging.dispatcher.EventDispatchJob;
 import org.eclipse.ease.debugging.dispatcher.IEventProcessor;
 import org.eclipse.ease.debugging.events.IDebugEvent;
@@ -49,6 +50,8 @@
 
 	private EventDispatchJob fDispatcher;
 
+	private IScriptRegistry fScriptRegistry;
+
 	private EaseDebugProcess fProcess = null;
 
 	private final ILaunch fLaunch;
@@ -137,6 +140,19 @@
 	@Override
 	public void setDispatcher(final EventDispatchJob dispatcher) {
 		fDispatcher = dispatcher;
+
+		// TODO: Use setScriptRegistry explicitly
+		setScriptRegistry(dispatcher);
+	}
+
+	/**
+	 * Setter method for script registry for lookups between different types of file identifications.
+	 *
+	 * @param registry
+	 *            Script registry to be used.
+	 */
+	public void setScriptRegistry(final IScriptRegistry registry) {
+		fScriptRegistry = registry;
 	}
 
 	@Override
@@ -250,6 +266,14 @@
 	private synchronized void handleBreakpointChange(final IBreakpoint breakpoint, final Mode mode) {
 		final IResource affectedResource = breakpoint.getMarker().getResource();
 
+		// Try to perform lookup via script registry (Thread independent)
+		if (fScriptRegistry != null) {
+			final Script script = fScriptRegistry.getScript(affectedResource);
+			if (script != null) {
+				fireDispatchEvent(new BreakpointRequest(script, breakpoint, mode));
+				return;
+			}
+		}
 		// see if we are affected by this breakpoint
 		for (final EaseDebugThread thread : getThreads()) {
 			for (final IStackFrame frame : thread.getStackFrames()) {
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugValue.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugValue.java
index 4bbdf6e..47fdf90 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugValue.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugValue.java
@@ -169,6 +169,13 @@
 					fVariables.add(variable);
 				}
 
+			} else if (getValue() instanceof Collection<?>) {
+				final Collection<?> asCollection = (Collection<?>) getValue();
+				int index = 0;
+				for (final Object value : asCollection) {
+					final EaseDebugVariable variable = new EaseDebugVariable(String.format("[%d]", index++), value, fParent, null);
+					fVariables.add(variable);
+				}
 			} else {
 
 				// populate generic Java fields
diff --git a/tests/org.eclipse.ease.lang.python.py4j.test/build.properties b/tests/org.eclipse.ease.lang.python.py4j.test/build.properties
index b107977..ab59e59 100644
--- a/tests/org.eclipse.ease.lang.python.py4j.test/build.properties
+++ b/tests/org.eclipse.ease.lang.python.py4j.test/build.properties
@@ -1,3 +1,4 @@
 source.. = src/
 bin.includes = META-INF/,\
-               .
+               .,\
+               fragment.xml
diff --git a/tests/org.eclipse.ease.lang.python.py4j.test/src/org/eclipse/ease/lang/python/py4j/Py4jDebugTest.java b/tests/org.eclipse.ease.lang.python.py4j.test/src/org/eclipse/ease/lang/python/py4j/Py4jDebugTest.java
new file mode 100644
index 0000000..7b5e43a
--- /dev/null
+++ b/tests/org.eclipse.ease.lang.python.py4j.test/src/org/eclipse/ease/lang/python/py4j/Py4jDebugTest.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.py4j;
+
+import static org.junit.Assume.assumeTrue;
+
+import java.io.IOException;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.model.IBreakpoint;
+import org.eclipse.debug.core.model.LineBreakpoint;
+import org.eclipse.ease.lang.python.py4j.internal.Py4jDebuggerEngine;
+import org.eclipse.ease.testhelper.AbstractDebugTest;
+
+public class Py4jDebugTest extends AbstractDebugTest {
+
+	@Override
+	protected String getEngineId() {
+		return Py4jDebuggerEngine.ENGINE_ID;
+	}
+
+	@Override
+	protected String getScriptSource() throws IOException {
+		return readResource("org.eclipse.ease.testhelper", "/resources/DebugTest/main.py");
+	}
+
+	@Override
+	protected LineBreakpoint setBreakpoint(IFile file, int lineNumber) throws CoreException {
+		final IMarker marker = file.createMarker("org.python.pydev.debug.pyStopBreakpointMarker");
+		marker.setAttribute("org.eclipse.debug.core.enabled", true);
+		marker.setAttribute("org.eclipse.debug.core.id", "org.python.pydev.debug");
+		marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
+
+		final LineBreakpoint breakpoint = new LineBreakpoint() {
+
+			@Override
+			public String getModelIdentifier() {
+				return "org.python.pydev.debug";
+			}
+		};
+
+		breakpoint.setMarker(marker);
+
+		DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(breakpoint);
+
+		return breakpoint;
+	}
+
+	@Override
+	protected IBreakpoint[] getBreakpoints() throws CoreException {
+		return DebugPlugin.getDefault().getBreakpointManager().getBreakpoints("org.python.pydev.debug");
+	}
+
+	@Override
+	public void nativeObjectVariable() throws CoreException, IOException {
+		// TODO: Currently not possible to access native Python variables
+		assumeTrue(false);
+	}
+
+	@Override
+	public void nativeArrayVariable() throws CoreException, IOException {
+		// TODO: Currently not possible to access native Python variables
+		assumeTrue(false);
+	}
+}
diff --git a/tests/org.eclipse.ease.testhelper/src/org/eclipse/ease/testhelper/AbstractDebugTest.java b/tests/org.eclipse.ease.testhelper/src/org/eclipse/ease/testhelper/AbstractDebugTest.java
index 2eccab5..b5cd86b 100644
--- a/tests/org.eclipse.ease.testhelper/src/org/eclipse/ease/testhelper/AbstractDebugTest.java
+++ b/tests/org.eclipse.ease.testhelper/src/org/eclipse/ease/testhelper/AbstractDebugTest.java
@@ -56,7 +56,7 @@
 
 public abstract class AbstractDebugTest extends WorkspaceTestHelper {
 
-	private static final int TEST_TIMEOUT = 5000;
+	private static final int TEST_TIMEOUT = 7000;
 
 	private interface IDebugElementProvider {
 		EaseDebugElement getDebugElement();