示例#1
0
class _PyDevCommandProcessor(object):
    def __init__(self):
        self.api = PyDevdAPI()

    def process_net_command(self, py_db, cmd_id, seq, text):
        '''Processes a command received from the Java side

        @param cmd_id: the id of the command
        @param seq: the sequence of the command
        @param text: the text received in the command
        '''

        # We can only proceed if the client is already authenticated or if it's the
        # command to authenticate.
        if cmd_id != CMD_AUTHENTICATE and not py_db.authentication.is_authenticated(
        ):
            cmd = py_db.cmd_factory.make_error_message(
                seq, 'Client not authenticated.')
            py_db.writer.add_command(cmd)
            return

        meaning = ID_TO_MEANING[str(cmd_id)]

        # print('Handling %s (%s)' % (meaning, text))

        method_name = meaning.lower()

        on_command = getattr(self, method_name.lower(), None)
        if on_command is None:
            # I have no idea what this is all about
            cmd = py_db.cmd_factory.make_error_message(
                seq, "unexpected command " + str(cmd_id))
            py_db.writer.add_command(cmd)
            return

        lock = py_db._main_lock
        if method_name == 'cmd_thread_dump_to_stderr':
            # We can skip the main debugger locks for cases where we know it's not needed.
            lock = NULL

        with lock:
            try:
                cmd = on_command(py_db, cmd_id, seq, text)
                if cmd is not None:
                    py_db.writer.add_command(cmd)
            except:
                if traceback is not None and sys is not None and pydev_log_exception is not None:
                    pydev_log_exception()

                    stream = StringIO()
                    traceback.print_exc(file=stream)
                    cmd = py_db.cmd_factory.make_error_message(
                        seq,
                        "Unexpected exception in process_net_command.\nInitial params: %s. Exception: %s"
                        % (((cmd_id, seq, text), stream.getvalue())))
                    if cmd is not None:
                        py_db.writer.add_command(cmd)

    def cmd_authenticate(self, py_db, cmd_id, seq, text):
        access_token = text
        py_db.authentication.login(access_token)
        if py_db.authentication.is_authenticated():
            return NetCommand(cmd_id, seq,
                              py_db.authentication.client_access_token)

        return py_db.cmd_factory.make_error_message(
            seq, 'Client not authenticated.')

    def cmd_run(self, py_db, cmd_id, seq, text):
        return self.api.run(py_db)

    def cmd_list_threads(self, py_db, cmd_id, seq, text):
        return self.api.list_threads(py_db, seq)

    def cmd_get_completions(self, py_db, cmd_id, seq, text):
        # we received some command to get a variable
        # the text is: thread_id\tframe_id\tactivation token
        thread_id, frame_id, _scope, act_tok = text.split('\t', 3)

        return self.api.request_completions(py_db, seq, thread_id, frame_id,
                                            act_tok)

    def cmd_get_thread_stack(self, py_db, cmd_id, seq, text):
        # Receives a thread_id and a given timeout, which is the time we should
        # wait to the provide the stack if a given thread is still not suspended.
        if '\t' in text:
            thread_id, timeout = text.split('\t')
            timeout = float(timeout)
        else:
            thread_id = text
            timeout = .5  # Default timeout is .5 seconds

        return self.api.request_stack(py_db,
                                      seq,
                                      thread_id,
                                      fmt={},
                                      timeout=timeout)

    def cmd_set_protocol(self, py_db, cmd_id, seq, text):
        return self.api.set_protocol(py_db, seq, text.strip())

    def cmd_thread_suspend(self, py_db, cmd_id, seq, text):
        return self.api.request_suspend_thread(py_db, text.strip())

    def cmd_version(self, py_db, cmd_id, seq, text):
        if IS_PY2 and isinstance(text, unicode):
            text = text.encode('utf-8')

        # Default based on server process (although ideally the IDE should
        # provide it).
        if IS_WINDOWS:
            ide_os = 'WINDOWS'
        else:
            ide_os = 'UNIX'

        # 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

        version_msg = self.api.set_ide_os_and_breakpoints_by(
            py_db, seq, ide_os, breakpoints_by)

        # Enable thread notifications after the version command is completed.
        self.api.set_enable_thread_notifications(py_db, True)

        return version_msg

    def cmd_thread_run(self, py_db, cmd_id, seq, text):
        return self.api.request_resume_thread(text.strip())

    def _cmd_step(self, py_db, cmd_id, seq, text):
        return self.api.request_step(py_db, text.strip(), cmd_id)

    cmd_step_into = _cmd_step
    cmd_step_into_my_code = _cmd_step
    cmd_step_over = _cmd_step
    cmd_step_over_my_code = _cmd_step
    cmd_step_return = _cmd_step
    cmd_step_return_my_code = _cmd_step

    def _cmd_set_next(self, py_db, cmd_id, seq, text):
        thread_id, line, func_name = text.split('\t', 2)
        return self.api.request_set_next(py_db, seq, thread_id, cmd_id, line,
                                         func_name)

    cmd_run_to_line = _cmd_set_next
    cmd_set_next_statement = _cmd_set_next
    cmd_smart_step_into = _cmd_set_next

    def cmd_reload_code(self, py_db, cmd_id, seq, text):
        module_name = text.strip()
        self.api.request_reload_code(py_db, seq, module_name)

    def cmd_change_variable(self, py_db, cmd_id, seq, text):
        # the text is: thread\tstackframe\tFRAME|GLOBAL\tattribute_to_change\tvalue_to_change
        thread_id, frame_id, scope, attr_and_value = text.split('\t', 3)

        tab_index = attr_and_value.rindex('\t')
        attr = attr_and_value[0:tab_index].replace('\t', '.')
        value = attr_and_value[tab_index + 1:]
        self.api.request_change_variable(py_db, seq, thread_id, frame_id,
                                         scope, attr, value)

    def cmd_get_variable(self, py_db, cmd_id, seq, text):
        # we received some command to get a variable
        # the text is: thread_id\tframe_id\tFRAME|GLOBAL\tattributes*
        thread_id, frame_id, scopeattrs = text.split('\t', 2)

        if scopeattrs.find('\t') != -1:  # there are attributes beyond scope
            scope, attrs = scopeattrs.split('\t', 1)
        else:
            scope, attrs = (scopeattrs, None)

        self.api.request_get_variable(py_db, seq, thread_id, frame_id, scope,
                                      attrs)

    def cmd_get_array(self, py_db, cmd_id, seq, text):
        # Note: untested and unused in pydev
        # we received some command to get an array variable
        # the text is: thread_id\tframe_id\tFRAME|GLOBAL\tname\ttemp\troffs\tcoffs\trows\tcols\tformat
        roffset, coffset, rows, cols, format, thread_id, frame_id, scopeattrs = text.split(
            '\t', 7)

        if scopeattrs.find('\t') != -1:  # there are attributes beyond scope
            scope, attrs = scopeattrs.split('\t', 1)
        else:
            scope, attrs = (scopeattrs, None)

        self.api.request_get_array(py_db, seq, roffset, coffset, rows, cols,
                                   format, thread_id, frame_id, scope, attrs)

    def cmd_show_return_values(self, py_db, cmd_id, seq, text):
        show_return_values = text.split('\t')[1]
        self.api.set_show_return_values(py_db, int(show_return_values) == 1)

    def cmd_load_full_value(self, py_db, cmd_id, seq, text):
        # Note: untested and unused in pydev
        thread_id, frame_id, scopeattrs = text.split('\t', 2)
        vars = scopeattrs.split(NEXT_VALUE_SEPARATOR)

        self.api.request_load_full_value(py_db, seq, thread_id, frame_id, vars)

    def cmd_get_description(self, py_db, cmd_id, seq, text):
        # Note: untested and unused in pydev
        thread_id, frame_id, expression = text.split('\t', 2)
        self.api.request_get_description(py_db, seq, thread_id, frame_id,
                                         expression)

    def cmd_get_frame(self, py_db, cmd_id, seq, text):
        thread_id, frame_id, scope = text.split('\t', 2)
        self.api.request_get_frame(py_db, seq, thread_id, frame_id)

    def cmd_set_break(self, py_db, cmd_id, seq, text):
        # func name: 'None': match anything. Empty: match global, specified: only method context.
        # command to add some breakpoint.
        # text is filename\tline. Add to breakpoints dictionary
        suspend_policy = u"NONE"  # Can be 'NONE' or 'ALL'
        is_logpoint = False
        hit_condition = None
        if py_db._set_breakpoints_with_id:
            try:
                try:
                    breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint, suspend_policy = text.split(
                        u'\t', 9)
                except ValueError:  # not enough values to unpack
                    # No suspend_policy passed (use default).
                    breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint = text.split(
                        u'\t', 8)
                is_logpoint = is_logpoint == u'True'
            except ValueError:  # not enough values to unpack
                breakpoint_id, btype, filename, line, func_name, condition, expression = text.split(
                    u'\t', 6)

            breakpoint_id = int(breakpoint_id)
            line = int(line)

            # We must restore new lines and tabs as done in
            # AbstractDebugTarget.breakpointAdded
            condition = condition.replace(u"@_@NEW_LINE_CHAR@_@", u'\n').\
                replace(u"@_@TAB_CHAR@_@", u'\t').strip()

            expression = expression.replace(u"@_@NEW_LINE_CHAR@_@", u'\n').\
                replace(u"@_@TAB_CHAR@_@", u'\t').strip()
        else:
            # Note: this else should be removed after PyCharm migrates to setting
            # breakpoints by id (and ideally also provides func_name).
            btype, filename, line, func_name, suspend_policy, condition, expression = text.split(
                u'\t', 6)
            # If we don't have an id given for each breakpoint, consider
            # the id to be the line.
            breakpoint_id = line = int(line)

            condition = condition.replace(u"@_@NEW_LINE_CHAR@_@", u'\n'). \
                replace(u"@_@TAB_CHAR@_@", u'\t').strip()

            expression = expression.replace(u"@_@NEW_LINE_CHAR@_@", u'\n'). \
                replace(u"@_@TAB_CHAR@_@", u'\t').strip()

        if condition is not None and (len(condition) <= 0
                                      or condition == u"None"):
            condition = None

        if expression is not None and (len(expression) <= 0
                                       or expression == u"None"):
            expression = None

        if hit_condition is not None and (len(hit_condition) <= 0
                                          or hit_condition == u"None"):
            hit_condition = None

        result = self.api.add_breakpoint(py_db,
                                         self.api.filename_to_str(filename),
                                         btype, breakpoint_id, line, condition,
                                         func_name, expression, suspend_policy,
                                         hit_condition, is_logpoint)
        error_code = result.error_code

        if error_code:
            translated_filename = result.translated_filename
            if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND:
                pydev_log.critical(
                    'pydev debugger: warning: Trying to add breakpoint to file that does not exist: %s (will have no effect).'
                    % (translated_filename, ))

            elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS:
                pydev_log.critical(
                    'pydev debugger: warning: Trying to add breakpoint to file that is excluded by filters: %s (will have no effect).'
                    % (translated_filename, ))

            else:
                # Shouldn't get here.
                pydev_log.critical(
                    'pydev debugger: warning: Breakpoint not validated (reason unknown -- please report as error): %s.'
                    % (translated_filename, ))

    def cmd_remove_break(self, py_db, cmd_id, seq, text):
        # command to remove some breakpoint
        # text is type\file\tid. Remove from breakpoints dictionary
        breakpoint_type, filename, breakpoint_id = text.split('\t', 2)

        filename = self.api.filename_to_str(filename)

        try:
            breakpoint_id = int(breakpoint_id)
        except ValueError:
            pydev_log.critical(
                'Error removing breakpoint. Expected breakpoint_id to be an int. Found: %s',
                breakpoint_id)

        else:
            self.api.remove_breakpoint(py_db, filename, breakpoint_type,
                                       breakpoint_id)

    def _cmd_exec_or_evaluate_expression(self, py_db, cmd_id, seq, text):
        # command to evaluate the given expression
        # text is: thread\tstackframe\tLOCAL\texpression
        attr_to_set_result = ""
        try:
            thread_id, frame_id, scope, expression, trim, attr_to_set_result = text.split(
                '\t', 5)
        except ValueError:
            thread_id, frame_id, scope, expression, trim = text.split('\t', 4)
        is_exec = cmd_id == CMD_EXEC_EXPRESSION
        trim_if_too_big = int(trim) == 1

        self.api.request_exec_or_evaluate(py_db, seq, thread_id, frame_id,
                                          expression, is_exec, trim_if_too_big,
                                          attr_to_set_result)

    cmd_evaluate_expression = _cmd_exec_or_evaluate_expression
    cmd_exec_expression = _cmd_exec_or_evaluate_expression

    def cmd_console_exec(self, py_db, cmd_id, seq, text):
        # command to exec expression in console, in case expression is only partially valid 'False' is returned
        # text is: thread\tstackframe\tLOCAL\texpression

        thread_id, frame_id, scope, expression = text.split('\t', 3)
        self.api.request_console_exec(py_db, seq, thread_id, frame_id,
                                      expression)

    def cmd_set_py_exception(self, py_db, cmd_id, seq, text):
        # Command which receives set of exceptions on which user wants to break the debugger
        # text is:
        #
        # break_on_uncaught;
        # break_on_caught;
        # skip_on_exceptions_thrown_in_same_context;
        # ignore_exceptions_thrown_in_lines_with_ignore_exception;
        # ignore_libraries;
        # TypeError;ImportError;zipimport.ZipImportError;
        #
        # i.e.: true;true;true;true;true;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(';')
        py_db.break_on_uncaught_exceptions = {}
        py_db.break_on_caught_exceptions = {}
        py_db.break_on_user_uncaught_exceptions = {}
        if len(splitted) >= 5:
            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':
                py_db.skip_on_exceptions_thrown_in_same_context = True
            else:
                py_db.skip_on_exceptions_thrown_in_same_context = False

            if splitted[3] == 'true':
                py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = True
            else:
                py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = False

            if splitted[4] == 'true':
                ignore_libraries = True
            else:
                ignore_libraries = False

            for exception_type in splitted[5:]:
                exception_type = exception_type.strip()
                if not exception_type:
                    continue

                exception_breakpoint = py_db.add_break_on_exception(
                    exception_type,
                    condition=None,
                    expression=None,
                    notify_on_handled_exceptions=break_on_caught,
                    notify_on_unhandled_exceptions=break_on_uncaught,
                    notify_on_user_unhandled_exceptions=
                    False,  # TODO (not currently supported in this API).
                    notify_on_first_raise_only=True,
                    ignore_libraries=ignore_libraries,
                )

            py_db.on_breakpoints_changed()

        else:
            sys.stderr.write(
                "Error when setting exception list. Received: %s\n" % (text, ))

    def _load_source(self, py_db, cmd_id, seq, text):
        filename = text
        filename = self.api.filename_to_str(filename)
        self.api.request_load_source(py_db, seq, filename)

    cmd_load_source = _load_source
    cmd_get_file_contents = _load_source

    def cmd_load_source_from_frame_id(self, py_db, cmd_id, seq, text):
        frame_id = text
        self.api.request_load_source_from_frame_id(py_db, seq, frame_id)

    def cmd_set_property_trace(self, py_db, cmd_id, seq, text):
        # 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 py_db.disable_property_trace is False and splitted[
                        0] == 'true':
                    # Replacing property by custom property only when the debugger starts
                    pydevd_traceproperty.replace_builtin_property()
                    py_db.disable_property_trace = True
                # Enable/Disable tracing of the property getter
                if splitted[1] == 'true':
                    py_db.disable_property_getter_trace = True
                else:
                    py_db.disable_property_getter_trace = False
                # Enable/Disable tracing of the property setter
                if splitted[2] == 'true':
                    py_db.disable_property_setter_trace = True
                else:
                    py_db.disable_property_setter_trace = False
                # Enable/Disable tracing of the property deleter
                if splitted[3] == 'true':
                    py_db.disable_property_deleter_trace = True
                else:
                    py_db.disable_property_deleter_trace = False

    def cmd_add_exception_break(self, py_db, cmd_id, seq, text):
        # Note that this message has some idiosyncrasies...
        #
        # notify_on_handled_exceptions can be 0, 1 or 2
        # 0 means we should not stop on handled exceptions.
        # 1 means we should stop on handled exceptions showing it on all frames where the exception passes.
        # 2 means we should stop on handled exceptions but we should only notify about it once.
        #
        # To ignore_libraries properly, besides setting ignore_libraries to 1, the IDE_PROJECT_ROOTS environment
        # variable must be set (so, we'll ignore anything not below IDE_PROJECT_ROOTS) -- this is not ideal as
        # the environment variable may not be properly set if it didn't start from the debugger (we should
        # create a custom message for that).
        #
        # There are 2 global settings which can only be set in CMD_SET_PY_EXCEPTION. Namely:
        #
        # py_db.skip_on_exceptions_thrown_in_same_context
        # - If True, we should only show the exception in a caller, not where it was first raised.
        #
        # py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception
        # - If True exceptions thrown in lines with '@IgnoreException' will not be shown.

        condition = ""
        expression = ""
        if text.find('\t') != -1:
            try:
                exception, condition, expression, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text.split(
                    '\t', 5)
            except:
                exception, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text.split(
                    '\t', 3)
        else:
            exception, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text, 0, 0, 0

        condition = condition.replace("@_@NEW_LINE_CHAR@_@",
                                      '\n').replace("@_@TAB_CHAR@_@",
                                                    '\t').strip()

        if condition is not None and (len(condition) == 0
                                      or condition == "None"):
            condition = None

        expression = expression.replace("@_@NEW_LINE_CHAR@_@",
                                        '\n').replace("@_@TAB_CHAR@_@",
                                                      '\t').strip()

        if expression is not None and (len(expression) == 0
                                       or expression == "None"):
            expression = None

        if exception.find('-') != -1:
            breakpoint_type, exception = exception.split('-')
        else:
            breakpoint_type = 'python'

        if breakpoint_type == 'python':
            self.api.add_python_exception_breakpoint(
                py_db,
                exception,
                condition,
                expression,
                notify_on_handled_exceptions=int(notify_on_handled_exceptions)
                > 0,
                notify_on_unhandled_exceptions=int(
                    notify_on_unhandled_exceptions) == 1,
                notify_on_user_unhandled_exceptions=
                0,  # TODO (not currently supported in this API).
                notify_on_first_raise_only=int(
                    notify_on_handled_exceptions) == 2,
                ignore_libraries=int(ignore_libraries) > 0,
            )
        else:
            self.api.add_plugins_exception_breakpoint(py_db, breakpoint_type,
                                                      exception)

    def cmd_remove_exception_break(self, py_db, cmd_id, seq, text):
        exception = text
        if exception.find('-') != -1:
            exception_type, exception = exception.split('-')
        else:
            exception_type = 'python'

        if exception_type == 'python':
            self.api.remove_python_exception_breakpoint(py_db, exception)
        else:
            self.api.remove_plugins_exception_breakpoint(
                py_db, exception_type, exception)

    def cmd_add_django_exception_break(self, py_db, cmd_id, seq, text):
        self.api.add_plugins_exception_breakpoint(py_db,
                                                  breakpoint_type='django',
                                                  exception=text)

    def cmd_remove_django_exception_break(self, py_db, cmd_id, seq, text):
        self.api.remove_plugins_exception_breakpoint(py_db,
                                                     exception_type='django',
                                                     exception=text)

    def cmd_evaluate_console_expression(self, py_db, cmd_id, seq, text):
        # 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,
                                                            buffer_output=True)

            elif console_command == 'EVALUATE_UNBUFFERED':
                int_cmd = InternalEvaluateConsoleExpression(
                    seq, thread_id, frame_id, line, buffer_output=False)

            elif console_command == 'GET_COMPLETIONS':
                int_cmd = InternalConsoleGetCompletions(
                    seq, thread_id, frame_id, line)

            else:
                raise ValueError('Unrecognized command: %s' %
                                 (console_command, ))

            py_db.post_internal_command(int_cmd, thread_id)

    def cmd_run_custom_operation(self, py_db, cmd_id, seq, text):
        # 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)
            py_db.post_internal_command(int_cmd, thread_id)

    def cmd_ignore_thrown_exception_at(self, py_db, cmd_id, seq, text):
        if text:
            replace = 'REPLACE:'  # Not all 3.x versions support u'REPLACE:', so, doing workaround.
            if not IS_PY3K:
                replace = unicode(replace)  # noqa

            if text.startswith(replace):
                text = text[8:]
                py_db.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('|')
                    filename = self.api.filename_to_server(filename)

                    if os.path.exists(filename):
                        lines_ignored = py_db.filename_to_lines_where_exceptions_are_ignored.get(
                            filename)
                        if lines_ignored is None:
                            lines_ignored = py_db.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,))

    def cmd_enable_dont_trace(self, py_db, cmd_id, seq, text):
        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)  # noqa

            mode = text.strip() == true_str
            pydevd_dont_trace.trace_filter(mode)

    def cmd_redirect_output(self, py_db, cmd_id, seq, text):
        if text:
            py_db.enable_output_redirection('STDOUT' in text, 'STDERR' in text)

    def cmd_get_next_statement_targets(self, py_db, cmd_id, seq, text):
        thread_id, frame_id = text.split('\t', 1)

        py_db.post_method_as_internal_command(
            thread_id, internal_get_next_statement_targets, seq, thread_id,
            frame_id)

    def cmd_set_project_roots(self, py_db, cmd_id, seq, text):
        self.api.set_project_roots(py_db, text.split(u'\t'))

    def cmd_thread_dump_to_stderr(self, py_db, cmd_id, seq, text):
        pydevd_utils.dump_threads()

    def cmd_stop_on_start(self, py_db, cmd_id, seq, text):
        if text.strip() in ('True', 'true', '1'):
            self.api.stop_on_entry()

    def cmd_pydevd_json_config(self, py_db, cmd_id, seq, text):
        # Expected to receive a json string as:
        # {
        #     'skip_suspend_on_breakpoint_exception': [<exception names where we should suspend>],
        #     'skip_print_breakpoint_exception': [<exception names where we should print>],
        #     'multi_threads_single_notification': bool,
        # }
        msg = json.loads(text.strip())
        if 'skip_suspend_on_breakpoint_exception' in msg:
            py_db.skip_suspend_on_breakpoint_exception = tuple(
                get_exception_class(x)
                for x in msg['skip_suspend_on_breakpoint_exception'])

        if 'skip_print_breakpoint_exception' in msg:
            py_db.skip_print_breakpoint_exception = tuple(
                get_exception_class(x)
                for x in msg['skip_print_breakpoint_exception'])

        if 'multi_threads_single_notification' in msg:
            py_db.multi_threads_single_notification = msg[
                'multi_threads_single_notification']

    def cmd_get_exception_details(self, py_db, cmd_id, seq, text):
        thread_id = text
        t = pydevd_find_thread_by_id(thread_id)
        frame = None
        if t and not getattr(t, 'pydev_do_not_trace', None):
            additional_info = set_additional_thread_info(t)
            frame = additional_info.get_topmost_frame(t)
        try:
            return py_db.cmd_factory.make_get_exception_details_message(
                py_db, seq, thread_id, frame)
        finally:
            frame = None
            t = None
示例#2
0
class PyDevJsonCommandProcessor(object):
    def __init__(self, from_json):
        self.from_json = from_json
        self.api = PyDevdAPI()
        self._debug_options = {}
        self._next_breakpoint_id = partial(next, itertools.count(0))
        self._goto_targets_map = IDMap()
        self._launch_or_attach_request_done = False

    def process_net_command_json(self,
                                 py_db,
                                 json_contents,
                                 send_response=True):
        '''
        Processes a debug adapter protocol json command.
        '''

        DEBUG = False

        try:
            request = self.from_json(json_contents, update_ids_from_dap=True)
        except KeyError as e:
            request = self.from_json(json_contents, update_ids_from_dap=False)
            error_msg = str(e)
            if error_msg.startswith("'") and error_msg.endswith("'"):
                error_msg = error_msg[1:-1]

            # This means a failure updating ids from the DAP (the client sent a key we didn't send).
            def on_request(py_db, request):
                error_response = {
                    'type': 'response',
                    'request_seq': request.seq,
                    'success': False,
                    'command': request.command,
                    'message': error_msg,
                }
                return NetCommand(CMD_RETURN, 0, error_response, is_json=True)

        else:
            if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS and DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1:
                pydev_log.info('Process %s: %s\n' % (
                    request.__class__.__name__,
                    json.dumps(request.to_dict(), indent=4, sort_keys=True),
                ))

            assert request.type == 'request'
            method_name = 'on_%s_request' % (request.command.lower(), )
            on_request = getattr(self, method_name, None)
            if on_request is None:
                print(
                    'Unhandled: %s not available in PyDevJsonCommandProcessor.\n'
                    % (method_name, ))
                return

            if DEBUG:
                print(
                    'Handled in pydevd: %s (in PyDevJsonCommandProcessor).\n' %
                    (method_name, ))

        with py_db._main_lock:
            if request.__class__ == PydevdAuthorizeRequest:
                authorize_request = request  # : :type authorize_request: PydevdAuthorizeRequest
                access_token = authorize_request.arguments.debugServerAccessToken
                py_db.authentication.login(access_token)

            if not py_db.authentication.is_authenticated():
                response = Response(request.seq,
                                    success=False,
                                    command=request.command,
                                    message='Client not authenticated.',
                                    body={})
                cmd = NetCommand(CMD_RETURN, 0, response, is_json=True)
                py_db.writer.add_command(cmd)
                return

            cmd = on_request(py_db, request)
            if cmd is not None and send_response:
                py_db.writer.add_command(cmd)

    def on_pydevdauthorize_request(self, py_db, request):
        ide_access_token = py_db.authentication.ide_access_token
        body = {'clientAccessToken': None}
        if ide_access_token:
            body['clientAccessToken'] = ide_access_token

        response = pydevd_base_schema.build_response(request,
                                                     kwargs={'body': body})
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_initialize_request(self, py_db, request):
        body = Capabilities(
            # Supported.
            supportsConfigurationDoneRequest=True,
            supportsConditionalBreakpoints=True,
            supportsHitConditionalBreakpoints=True,
            supportsEvaluateForHovers=True,
            supportsSetVariable=True,
            supportsGotoTargetsRequest=True,
            supportsCompletionsRequest=True,
            supportsModulesRequest=True,
            supportsExceptionOptions=True,
            supportsValueFormattingOptions=True,
            supportsExceptionInfoRequest=True,
            supportTerminateDebuggee=True,
            supportsDelayedStackTraceLoading=True,
            supportsLogPoints=True,
            supportsSetExpression=True,
            supportsTerminateRequest=True,
            exceptionBreakpointFilters=[
                {
                    'filter': 'raised',
                    'label': 'Raised Exceptions',
                    'default': False
                },
                {
                    'filter': 'uncaught',
                    'label': 'Uncaught Exceptions',
                    'default': True
                },
            ],

            # Not supported.
            supportsFunctionBreakpoints=False,
            supportsStepBack=False,
            supportsRestartFrame=False,
            supportsStepInTargetsRequest=False,
            supportsRestartRequest=False,
            supportsLoadedSourcesRequest=False,
            supportsTerminateThreadsRequest=False,
            supportsDataBreakpoints=False,
            supportsReadMemoryRequest=False,
            supportsDisassembleRequest=False,
            additionalModuleColumns=[],
            completionTriggerCharacters=[],
            supportedChecksumAlgorithms=[],
        ).to_dict()

        # Non-standard capabilities/info below.
        body['supportsDebuggerProperties'] = True

        body['pydevd'] = pydevd_info = {}
        pydevd_info['processId'] = os.getpid()
        self.api.notify_initialize(py_db)
        response = pydevd_base_schema.build_response(request,
                                                     kwargs={'body': body})
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_configurationdone_request(self, py_db, request):
        '''
        :param ConfigurationDoneRequest request:
        '''
        if not self._launch_or_attach_request_done:
            pydev_log.critical(
                'Missing launch request or attach request before configuration done request.'
            )

        self.api.run(py_db)
        self.api.notify_configuration_done(py_db)

        configuration_done_response = pydevd_base_schema.build_response(
            request)
        return NetCommand(CMD_RETURN,
                          0,
                          configuration_done_response,
                          is_json=True)

    def on_threads_request(self, py_db, request):
        '''
        :param ThreadsRequest request:
        '''
        return self.api.list_threads(py_db, request.seq)

    def on_terminate_request(self, py_db, request):
        '''
        :param TerminateRequest request:
        '''
        self._request_terminate_process(py_db)
        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def _request_terminate_process(self, py_db):
        self.api.request_terminate_process(py_db)

    def on_completions_request(self, py_db, request):
        '''
        :param CompletionsRequest request:
        '''
        arguments = request.arguments  # : :type arguments: CompletionsArguments
        seq = request.seq
        text = arguments.text
        frame_id = arguments.frameId
        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            frame_id)

        if thread_id is None:
            body = CompletionsResponseBody([])
            variables_response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body':
                    body,
                    'success':
                    False,
                    'message':
                    'Thread to get completions seems to have resumed already.'
                })
            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

        # Note: line and column are 1-based (convert to 0-based for pydevd).
        column = arguments.column - 1

        if arguments.line is None:
            # line is optional
            line = -1
        else:
            line = arguments.line - 1

        self.api.request_completions(py_db,
                                     seq,
                                     thread_id,
                                     frame_id,
                                     text,
                                     line=line,
                                     column=column)

    def _resolve_remote_root(self, local_root, remote_root):
        if remote_root == '.':
            cwd = os.getcwd()
            append_pathsep = local_root.endswith('\\') or local_root.endswith(
                '/')
            return cwd + (os.path.sep if append_pathsep else '')
        return remote_root

    def _set_debug_options(self, py_db, args, start_reason):
        rules = args.get('rules')
        stepping_resumes_all_threads = args.get('steppingResumesAllThreads',
                                                True)
        self.api.set_stepping_resumes_all_threads(
            py_db, stepping_resumes_all_threads)

        terminate_child_processes = args.get('terminateChildProcesses', True)
        self.api.set_terminate_child_processes(py_db,
                                               terminate_child_processes)

        exclude_filters = []

        if rules is not None:
            exclude_filters = _convert_rules_to_exclude_filters(
                rules, self.api.filename_to_server,
                lambda msg: self.api.send_error_message(py_db, msg))

        self.api.set_exclude_filters(py_db, exclude_filters)

        self._debug_options = _extract_debug_options(
            args.get('options'),
            args.get('debugOptions'),
        )
        self._debug_options['args'] = args

        debug_stdlib = self._debug_options.get('DEBUG_STDLIB', False)
        self.api.set_use_libraries_filter(py_db, not debug_stdlib)

        path_mappings = []
        for pathMapping in args.get('pathMappings', []):
            localRoot = pathMapping.get('localRoot', '')
            remoteRoot = pathMapping.get('remoteRoot', '')
            remoteRoot = self._resolve_remote_root(localRoot, remoteRoot)
            if (localRoot != '') and (remoteRoot != ''):
                path_mappings.append((localRoot, remoteRoot))

        if bool(path_mappings):
            pydevd_file_utils.setup_client_server_paths(path_mappings)

        if self._debug_options.get('REDIRECT_OUTPUT', False):
            py_db.enable_output_redirection(True, True)
        else:
            py_db.enable_output_redirection(False, False)

        self.api.set_show_return_values(
            py_db, self._debug_options.get('SHOW_RETURN_VALUE', False))

        if not self._debug_options.get('BREAK_SYSTEMEXIT_ZERO', False):
            ignore_system_exit_codes = [0]
            if self._debug_options.get('DJANGO_DEBUG', False):
                ignore_system_exit_codes += [3]

            self.api.set_ignore_system_exit_codes(py_db,
                                                  ignore_system_exit_codes)

        if self._debug_options.get('STOP_ON_ENTRY',
                                   False) and start_reason == 'launch':
            self.api.stop_on_entry()

    def _send_process_event(self, py_db, start_method):
        if len(sys.argv) > 0:
            name = sys.argv[0]
        else:
            name = ''

        if isinstance(name, bytes):
            name = name.decode(file_system_encoding, 'replace')
            name = name.encode('utf-8')

        body = ProcessEventBody(
            name=name,
            systemProcessId=os.getpid(),
            isLocalProcess=True,
            startMethod=start_method,
        )
        event = ProcessEvent(body)
        py_db.writer.add_command(
            NetCommand(CMD_PROCESS_EVENT, 0, event, is_json=True))

    def _handle_launch_or_attach_request(self, py_db, request, start_reason):
        self._send_process_event(py_db, start_reason)
        self._launch_or_attach_request_done = True
        self.api.set_enable_thread_notifications(py_db, True)
        self._set_debug_options(py_db,
                                request.arguments.kwargs,
                                start_reason=start_reason)
        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_launch_request(self, py_db, request):
        '''
        :param LaunchRequest request:
        '''
        return self._handle_launch_or_attach_request(py_db,
                                                     request,
                                                     start_reason='launch')

    def on_attach_request(self, py_db, request):
        '''
        :param AttachRequest request:
        '''
        return self._handle_launch_or_attach_request(py_db,
                                                     request,
                                                     start_reason='attach')

    def on_pause_request(self, py_db, request):
        '''
        :param PauseRequest request:
        '''
        arguments = request.arguments  # : :type arguments: PauseArguments
        thread_id = arguments.threadId

        self.api.request_suspend_thread(py_db, thread_id=thread_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_continue_request(self, py_db, request):
        '''
        :param ContinueRequest request:
        '''
        arguments = request.arguments  # : :type arguments: ContinueArguments
        thread_id = arguments.threadId

        def on_resumed():
            body = {'allThreadsContinued': thread_id == '*'}
            response = pydevd_base_schema.build_response(request,
                                                         kwargs={'body': body})
            cmd = NetCommand(CMD_RETURN, 0, response, is_json=True)
            py_db.writer.add_command(cmd)

        # Only send resumed notification when it has actually resumed!
        # (otherwise the user could send a continue, receive the notification and then
        # request a new pause which would be paused without sending any notification as
        # it didn't really run in the first place).
        py_db.threads_suspended_single_notification.add_on_resumed_callback(
            on_resumed)
        self.api.request_resume_thread(thread_id)

    def on_next_request(self, py_db, request):
        '''
        :param NextRequest request:
        '''
        arguments = request.arguments  # : :type arguments: NextArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_OVER_MY_CODE
        else:
            step_cmd_id = CMD_STEP_OVER

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_stepin_request(self, py_db, request):
        '''
        :param StepInRequest request:
        '''
        arguments = request.arguments  # : :type arguments: StepInArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_INTO_MY_CODE
        else:
            step_cmd_id = CMD_STEP_INTO

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_stepout_request(self, py_db, request):
        '''
        :param StepOutRequest request:
        '''
        arguments = request.arguments  # : :type arguments: StepOutArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_RETURN_MY_CODE
        else:
            step_cmd_id = CMD_STEP_RETURN

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def _get_hit_condition_expression(self, hit_condition):
        '''Following hit condition values are supported

        * x or == x when breakpoint is hit x times
        * >= x when breakpoint is hit more than or equal to x times
        * % x when breakpoint is hit multiple of x times

        Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits
        '''
        if not hit_condition:
            return None

        expr = hit_condition.strip()
        try:
            int(expr)
            return '@HIT@ == {}'.format(expr)
        except ValueError:
            pass

        if expr.startswith('%'):
            return '@HIT@ {} == 0'.format(expr)

        if expr.startswith('==') or \
            expr.startswith('>') or \
            expr.startswith('<'):
            return '@HIT@ {}'.format(expr)

        return hit_condition

    def on_disconnect_request(self, py_db, request):
        '''
        :param DisconnectRequest request:
        '''
        if request.arguments.terminateDebuggee:
            self._request_terminate_process(py_db)
            response = pydevd_base_schema.build_response(request)
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

        self._launch_or_attach_request_done = False
        py_db.enable_output_redirection(False, False)
        self.api.request_disconnect(py_db, resume_threads=True)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_setbreakpoints_request(self, py_db, request):
        '''
        :param SetBreakpointsRequest request:
        '''
        if not self._launch_or_attach_request_done:
            # Note that to validate the breakpoints we need the launch request to be done already
            # (otherwise the filters wouldn't be set for the breakpoint validation).
            body = SetBreakpointsResponseBody([])
            response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body':
                    body,
                    'success':
                    False,
                    'message':
                    'Breakpoints may only be set after the launch request is received.'
                })
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

        arguments = request.arguments  # : :type arguments: SetBreakpointsArguments
        # TODO: Path is optional here it could be source reference.
        filename = self.api.filename_to_str(arguments.source.path)
        func_name = 'None'

        self.api.remove_all_breakpoints(py_db, filename)

        btype = 'python-line'
        suspend_policy = 'ALL'

        if not filename.lower().endswith(
                '.py'):  # Note: check based on original file, not mapping.
            if self._debug_options.get('DJANGO_DEBUG', False):
                btype = 'django-line'
            elif self._debug_options.get('FLASK_DEBUG', False):
                btype = 'jinja2-line'

        breakpoints_set = []

        for source_breakpoint in arguments.breakpoints:
            source_breakpoint = SourceBreakpoint(**source_breakpoint)
            line = source_breakpoint.line
            condition = source_breakpoint.condition
            breakpoint_id = line

            hit_condition = self._get_hit_condition_expression(
                source_breakpoint.hitCondition)
            log_message = source_breakpoint.logMessage
            if not log_message:
                is_logpoint = None
                expression = None
            else:
                is_logpoint = True
                expression = convert_dap_log_message_to_expression(log_message)

            result = self.api.add_breakpoint(py_db,
                                             filename,
                                             btype,
                                             breakpoint_id,
                                             line,
                                             condition,
                                             func_name,
                                             expression,
                                             suspend_policy,
                                             hit_condition,
                                             is_logpoint,
                                             adjust_line=True)
            error_code = result.error_code

            if error_code:
                if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND:
                    error_msg = 'Breakpoint in file that does not exist.'

                elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS:
                    error_msg = 'Breakpoint in file excluded by filters.'
                    if py_db.get_use_libraries_filter():
                        error_msg += (
                            '\nNote: may be excluded because of "justMyCode" option (default == true).'
                            'Try setting \"justMyCode\": false in the debug configuration (e.g., launch.json).'
                        )

                else:
                    # Shouldn't get here.
                    error_msg = 'Breakpoint not validated (reason unknown -- please report as bug).'

                breakpoints_set.append(
                    pydevd_schema.Breakpoint(
                        verified=False,
                        line=result.translated_line,
                        message=error_msg,
                        source=arguments.source).to_dict())
            else:
                # Note that the id is made up (the id for pydevd is unique only within a file, so, the
                # line is used for it).
                # Also, the id is currently not used afterwards, so, we don't even keep a mapping.
                breakpoints_set.append(
                    pydevd_schema.Breakpoint(
                        verified=True,
                        id=self._next_breakpoint_id(),
                        line=result.translated_line,
                        source=arguments.source).to_dict())

        body = {'breakpoints': breakpoints_set}
        set_breakpoints_response = pydevd_base_schema.build_response(
            request, kwargs={'body': body})
        return NetCommand(CMD_RETURN,
                          0,
                          set_breakpoints_response,
                          is_json=True)

    def on_setexceptionbreakpoints_request(self, py_db, request):
        '''
        :param SetExceptionBreakpointsRequest request:
        '''
        # : :type arguments: SetExceptionBreakpointsArguments
        arguments = request.arguments
        filters = arguments.filters
        exception_options = arguments.exceptionOptions
        self.api.remove_all_exception_breakpoints(py_db)

        # Can't set these in the DAP.
        condition = None
        expression = None
        notify_on_first_raise_only = False

        ignore_libraries = 1 if py_db.get_use_libraries_filter() else 0

        if exception_options:
            break_raised = True
            break_uncaught = True

            for option in exception_options:
                option = ExceptionOptions(**option)
                if not option.path:
                    continue

                notify_on_handled_exceptions = 1 if option.breakMode == 'always' else 0
                notify_on_unhandled_exceptions = 1 if option.breakMode in (
                    'unhandled', 'userUnhandled') else 0
                exception_paths = option.path

                exception_names = []
                if len(exception_paths) == 0:
                    continue

                elif len(exception_paths) == 1:
                    if 'Python Exceptions' in exception_paths[0]['names']:
                        exception_names = ['BaseException']

                else:
                    path_iterator = iter(exception_paths)
                    if 'Python Exceptions' in next(path_iterator)['names']:
                        for path in path_iterator:
                            for ex_name in path['names']:
                                exception_names.append(ex_name)

                for exception_name in exception_names:
                    self.api.add_python_exception_breakpoint(
                        py_db, exception_name, condition, expression,
                        notify_on_handled_exceptions,
                        notify_on_unhandled_exceptions,
                        notify_on_first_raise_only, ignore_libraries)

        else:
            break_raised = 'raised' in filters
            break_uncaught = 'uncaught' in filters
            if break_raised or break_uncaught:
                notify_on_handled_exceptions = 1 if break_raised else 0
                notify_on_unhandled_exceptions = 1 if break_uncaught else 0
                exception = 'BaseException'

                self.api.add_python_exception_breakpoint(
                    py_db, exception, condition, expression,
                    notify_on_handled_exceptions,
                    notify_on_unhandled_exceptions, notify_on_first_raise_only,
                    ignore_libraries)

        if break_raised or break_uncaught:
            btype = None
            if self._debug_options.get('DJANGO_DEBUG', False):
                btype = 'django'
            elif self._debug_options.get('FLASK_DEBUG', False):
                btype = 'jinja2'

            if btype:
                self.api.add_plugins_exception_breakpoint(
                    py_db, btype, 'BaseException'
                )  # Note: Exception name could be anything here.

        # Note: no body required on success.
        set_breakpoints_response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN,
                          0,
                          set_breakpoints_response,
                          is_json=True)

    def on_stacktrace_request(self, py_db, request):
        '''
        :param StackTraceRequest request:
        '''
        # : :type stack_trace_arguments: StackTraceArguments
        stack_trace_arguments = request.arguments
        thread_id = stack_trace_arguments.threadId
        start_frame = stack_trace_arguments.startFrame
        levels = stack_trace_arguments.levels

        fmt = stack_trace_arguments.format
        if hasattr(fmt, 'to_dict'):
            fmt = fmt.to_dict()
        self.api.request_stack(py_db,
                               request.seq,
                               thread_id,
                               fmt=fmt,
                               start_frame=start_frame,
                               levels=levels)

    def on_exceptioninfo_request(self, py_db, request):
        '''
        :param ExceptionInfoRequest request:
        '''
        # : :type exception_into_arguments: ExceptionInfoArguments
        exception_into_arguments = request.arguments
        thread_id = exception_into_arguments.threadId
        max_frames = int(self._debug_options['args'].get(
            'maxExceptionStackFrames', 0))
        self.api.request_exception_info_json(py_db, request, thread_id,
                                             max_frames)

    def on_scopes_request(self, py_db, request):
        '''
        Scopes are the top-level items which appear for a frame (so, we receive the frame id
        and provide the scopes it has).

        :param ScopesRequest request:
        '''
        frame_id = request.arguments.frameId

        variables_reference = frame_id
        scopes = [Scope('Locals', int(variables_reference), False).to_dict()]
        body = ScopesResponseBody(scopes)
        scopes_response = pydevd_base_schema.build_response(
            request, kwargs={'body': body})
        return NetCommand(CMD_RETURN, 0, scopes_response, is_json=True)

    def on_evaluate_request(self, py_db, request):
        '''
        :param EvaluateRequest request:
        '''
        # : :type arguments: EvaluateArguments
        arguments = request.arguments

        if arguments.frameId is None:
            self.api.request_exec_or_evaluate_json(py_db,
                                                   request,
                                                   thread_id='*')
        else:
            thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
                arguments.frameId)

            if thread_id is not None:
                self.api.request_exec_or_evaluate_json(py_db, request,
                                                       thread_id)
            else:
                body = EvaluateResponseBody('', 0)
                response = pydevd_base_schema.build_response(
                    request,
                    kwargs={
                        'body': body,
                        'success': False,
                        'message': 'Unable to find thread for evaluation.'
                    })
                return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_setexpression_request(self, py_db, request):
        # : :type arguments: SetExpressionArguments
        arguments = request.arguments

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            arguments.frameId)

        if thread_id is not None:
            self.api.request_set_expression_json(py_db, request, thread_id)
        else:
            body = SetExpressionResponseBody('')
            response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body': body,
                    'success': False,
                    'message': 'Unable to find thread to set expression.'
                })
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_variables_request(self, py_db, request):
        '''
        Variables can be asked whenever some place returned a variables reference (so, it
        can be a scope gotten from on_scopes_request, the result of some evaluation, etc.).

        Note that in the DAP the variables reference requires a unique int... the way this works for
        pydevd is that an instance is generated for that specific variable reference and we use its
        id(instance) to identify it to make sure all items are unique (and the actual {id->instance}
        is added to a dict which is only valid while the thread is suspended and later cleared when
        the related thread resumes execution).

        see: SuspendedFramesManager

        :param VariablesRequest request:
        '''
        arguments = request.arguments  # : :type arguments: VariablesArguments
        variables_reference = arguments.variablesReference

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            variables_reference)
        if thread_id is not None:
            self.api.request_get_variable_json(py_db, request, thread_id)
        else:
            variables = []
            body = VariablesResponseBody(variables)
            variables_response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body':
                    body,
                    'success':
                    False,
                    'message':
                    'Unable to find thread to evaluate variable reference.'
                })
            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

    def on_setvariable_request(self, py_db, request):
        arguments = request.arguments  # : :type arguments: SetVariableArguments
        variables_reference = arguments.variablesReference

        if arguments.name.startswith('(return) '):
            response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body': SetVariableResponseBody(''),
                    'success': False,
                    'message': 'Cannot change return value'
                })
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            variables_reference)
        if thread_id is not None:
            self.api.request_change_variable_json(py_db, request, thread_id)
        else:
            response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body':
                    SetVariableResponseBody(''),
                    'success':
                    False,
                    'message':
                    'Unable to find thread to evaluate variable reference.'
                })
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_modules_request(self, py_db, request):
        modules_manager = py_db.cmd_factory.modules_manager  # : :type modules_manager: ModulesManager
        modules_info = modules_manager.get_modules_info()
        body = ModulesResponseBody(modules_info)
        variables_response = pydevd_base_schema.build_response(
            request, kwargs={'body': body})
        return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

    def on_source_request(self, py_db, request):
        '''
        :param SourceRequest request:
        '''
        source_reference = request.arguments.sourceReference
        server_filename = None
        content = None

        if source_reference != 0:
            server_filename = pydevd_file_utils.get_server_filename_from_source_reference(
                source_reference)
            if server_filename:
                # Try direct file access first - it's much faster when available.
                try:
                    with open(server_filename, 'r') as stream:
                        content = stream.read()
                except:
                    pass

                if content is None:
                    # File might not exist at all, or we might not have a permission to read it,
                    # but it might also be inside a zipfile, or an IPython cell. In this case,
                    # linecache might still be able to retrieve the source.
                    lines = (linecache.getline(server_filename, i)
                             for i in itertools.count(1))
                    lines = itertools.takewhile(
                        bool, lines)  # empty lines are '\n', EOF is ''

                    # If we didn't get at least one line back, reset it to None so that it's
                    # reported as error below, and not as an empty file.
                    content = ''.join(lines) or None

        body = SourceResponseBody(content or '')
        response_args = {'body': body}

        if content is None:
            if source_reference == 0:
                message = 'Source unavailable'
            elif server_filename:
                message = 'Unable to retrieve source for %s' % (
                    server_filename, )
            else:
                message = 'Invalid sourceReference %d' % (source_reference, )
            response_args.update({'success': False, 'message': message})

        response = pydevd_base_schema.build_response(request,
                                                     kwargs=response_args)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_gototargets_request(self, py_db, request):
        path = request.arguments.source.path
        line = request.arguments.line
        target_id = self._goto_targets_map.obtain_key((path, line))
        target = {
            'id': target_id,
            'label': '%s:%s' % (path, line),
            'line': line
        }
        body = GotoTargetsResponseBody(targets=[target])
        response_args = {'body': body}
        response = pydevd_base_schema.build_response(request,
                                                     kwargs=response_args)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_goto_request(self, py_db, request):
        target_id = int(request.arguments.targetId)
        thread_id = request.arguments.threadId
        try:
            _, line = self._goto_targets_map.obtain_value(target_id)
        except KeyError:
            response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body': {},
                    'success': False,
                    'message': 'Unknown goto target id: %d' % (target_id, ),
                })
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

        self.api.request_set_next(py_db, request.seq, thread_id,
                                  CMD_SET_NEXT_STATEMENT, line, '*')
        # See 'NetCommandFactoryJson.make_set_next_stmnt_status_message' for response
        return None

    def on_setdebuggerproperty_request(self, py_db, request):
        args = request.arguments  # : :type args: SetDebuggerPropertyArguments
        if args.ideOS is not None:
            self.api.set_ide_os(args.ideOS)

        if args.dontTraceStartPatterns is not None and args.dontTraceEndPatterns is not None:
            start_patterns = tuple(args.dontTraceStartPatterns)
            end_patterns = tuple(args.dontTraceEndPatterns)
            self.api.set_dont_trace_start_end_patterns(py_db, start_patterns,
                                                       end_patterns)

        if args.skipSuspendOnBreakpointException is not None:
            py_db.skip_suspend_on_breakpoint_exception = tuple(
                get_exception_class(x)
                for x in args.skipSuspendOnBreakpointException)

        if args.skipPrintBreakpointException is not None:
            py_db.skip_print_breakpoint_exception = tuple(
                get_exception_class(x)
                for x in args.skipPrintBreakpointException)

        if args.multiThreadsSingleNotification is not None:
            py_db.multi_threads_single_notification = args.multiThreadsSingleNotification

        # TODO: Support other common settings. Note that not all of these might be relevant to python.
        # JustMyCodeStepping: 0 or 1
        # AllowOutOfProcessSymbols: 0 or 1
        # DisableJITOptimization: 0 or 1
        # InterpreterOptions: 0 or 1
        # StopOnExceptionCrossingManagedBoundary: 0 or 1
        # WarnIfNoUserCodeOnLaunch: 0 or 1
        # EnableStepFiltering: true of false

        response = pydevd_base_schema.build_response(request,
                                                     kwargs={'body': {}})
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_pydevdsysteminfo_request(self, py_db, request):
        try:
            pid = os.getpid()
        except AttributeError:
            pid = None

        try:
            ppid = os.getppid()
        except AttributeError:
            ppid = None

        try:
            impl_desc = platform.python_implementation()
        except AttributeError:
            impl_desc = PY_IMPL_NAME

        py_info = pydevd_schema.PydevdPythonInfo(
            version=PY_VERSION_STR,
            implementation=pydevd_schema.PydevdPythonImplementationInfo(
                name=PY_IMPL_NAME,
                version=PY_IMPL_VERSION_STR,
                description=impl_desc,
            ))
        platform_info = pydevd_schema.PydevdPlatformInfo(name=sys.platform)
        process_info = pydevd_schema.PydevdProcessInfo(
            pid=pid,
            ppid=ppid,
            executable=sys.executable,
            bitness=64 if IS_64BIT_PROCESS else 32,
        )
        body = {
            'python': py_info,
            'platform': platform_info,
            'process': process_info,
        }
        response = pydevd_base_schema.build_response(request,
                                                     kwargs={'body': body})
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_setpydevdsourcemap_request(self, py_db, request):
        args = request.arguments  # : :type args: SetPydevdSourceMapArguments
        SourceMappingEntry = self.api.SourceMappingEntry

        path = args.source.path
        source_maps = args.pydevdSourceMaps
        # : :type source_map: PydevdSourceMap
        new_mappings = [
            SourceMappingEntry(
                source_map['line'], source_map['endLine'],
                source_map['runtimeLine'],
                self.api.filename_to_str(source_map['runtimeSource']['path']))
            for source_map in source_maps
        ]

        error_msg = self.api.set_source_mapping(py_db, path, new_mappings)
        if error_msg:
            response = pydevd_base_schema.build_response(request,
                                                         kwargs={
                                                             'body': {},
                                                             'success': False,
                                                             'message':
                                                             error_msg,
                                                         })
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)
示例#3
0
class _PyDevJsonCommandProcessor(object):
    def __init__(self, from_json):
        self.from_json = from_json
        self.api = PyDevdAPI()
        self._debug_options = {}
        self._next_breakpoint_id = partial(next, itertools.count(0))
        self._goto_targets_map = IDMap()

    def process_net_command_json(self, py_db, json_contents):
        '''
        Processes a debug adapter protocol json command.
        '''

        DEBUG = False

        request = self.from_json(json_contents, update_ids_from_dap=True)

        if DEBUG:
            print('Process %s: %s\n' % (
                request.__class__.__name__,
                json.dumps(request.to_dict(), indent=4, sort_keys=True),
            ))

        assert request.type == 'request'
        method_name = 'on_%s_request' % (request.command.lower(), )
        on_request = getattr(self, method_name, None)
        if on_request is None:
            print(
                'Unhandled: %s not available in _PyDevJsonCommandProcessor.\n'
                % (method_name, ))
            return

        if DEBUG:
            print('Handled in pydevd: %s (in _PyDevJsonCommandProcessor).\n' %
                  (method_name, ))

        py_db._main_lock.acquire()
        try:

            cmd = on_request(py_db, request)
            if cmd is not None:
                py_db.writer.add_command(cmd)
        finally:
            py_db._main_lock.release()

    def on_configurationdone_request(self, py_db, request):
        '''
        :param ConfigurationDoneRequest request:
        '''
        self.api.run(py_db)
        configuration_done_response = pydevd_base_schema.build_response(
            request)
        return NetCommand(CMD_RETURN,
                          0,
                          configuration_done_response,
                          is_json=True)

    def on_threads_request(self, py_db, request):
        '''
        :param ThreadsRequest request:
        '''
        return self.api.list_threads(py_db, request.seq)

    def on_completions_request(self, py_db, request):
        '''
        :param CompletionsRequest request:
        '''
        arguments = request.arguments  # : :type arguments: CompletionsArguments
        seq = request.seq
        text = arguments.text
        frame_id = arguments.frameId
        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            frame_id)

        # Note: line and column are 1-based (convert to 0-based for pydevd).
        column = arguments.column - 1

        if arguments.line is None:
            # line is optional
            line = -1
        else:
            line = arguments.line - 1

        self.api.request_completions(py_db,
                                     seq,
                                     thread_id,
                                     frame_id,
                                     text,
                                     line=line,
                                     column=column)

    def _set_debug_options(self, py_db, args):
        rules = args.get('rules')
        exclude_filters = []

        if rules is not None:
            exclude_filters = _convert_rules_to_exclude_filters(
                rules, self.api.filename_to_server,
                lambda msg: self.api.send_error_message(py_db, msg))

        self.api.set_exclude_filters(py_db, exclude_filters)

        self._debug_options = _extract_debug_options(
            args.get('options'),
            args.get('debugOptions'),
        )
        debug_stdlib = self._debug_options.get('DEBUG_STDLIB', False)
        self.api.set_use_libraries_filter(py_db, not debug_stdlib)

    def on_launch_request(self, py_db, request):
        '''
        :param LaunchRequest request:
        '''
        self._set_debug_options(py_db, request.arguments.kwargs)
        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_attach_request(self, py_db, request):
        '''
        :param AttachRequest request:
        '''
        self._set_debug_options(py_db, request.arguments.kwargs)
        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_pause_request(self, py_db, request):
        '''
        :param PauseRequest request:
        '''
        arguments = request.arguments  # : :type arguments: PauseArguments
        thread_id = arguments.threadId

        self.api.request_suspend_thread(py_db, thread_id=thread_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_continue_request(self, py_db, request):
        '''
        :param ContinueRequest request:
        '''
        arguments = request.arguments  # : :type arguments: ContinueArguments
        thread_id = arguments.threadId

        def on_resumed():
            body = {'allThreadsContinued': thread_id == '*'}
            response = pydevd_base_schema.build_response(request,
                                                         kwargs={'body': body})
            cmd = NetCommand(CMD_RETURN, 0, response, is_json=True)
            py_db.writer.add_command(cmd)

        # Only send resumed notification when it has actually resumed!
        # (otherwise the user could send a continue, receive the notification and then
        # request a new pause which would be paused without sending any notification as
        # it didn't really run in the first place).
        py_db.threads_suspended_single_notification.add_on_resumed_callback(
            on_resumed)
        self.api.request_resume_thread(thread_id)

    def on_next_request(self, py_db, request):
        '''
        :param NextRequest request:
        '''
        arguments = request.arguments  # : :type arguments: NextArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_OVER_MY_CODE
        else:
            step_cmd_id = CMD_STEP_OVER

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_stepin_request(self, py_db, request):
        '''
        :param StepInRequest request:
        '''
        arguments = request.arguments  # : :type arguments: StepInArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_INTO_MY_CODE
        else:
            step_cmd_id = CMD_STEP_INTO

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_stepout_request(self, py_db, request):
        '''
        :param StepOutRequest request:
        '''
        arguments = request.arguments  # : :type arguments: StepOutArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_RETURN_MY_CODE
        else:
            step_cmd_id = CMD_STEP_RETURN

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def _get_hit_condition_expression(self, hit_condition):
        '''Following hit condition values are supported

        * x or == x when breakpoint is hit x times
        * >= x when breakpoint is hit more than or equal to x times
        * % x when breakpoint is hit multiple of x times

        Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits
        '''
        if not hit_condition:
            return None

        expr = hit_condition.strip()
        try:
            int(expr)
            return '@HIT@ == {}'.format(expr)
        except ValueError:
            pass

        if expr.startswith('%'):
            return '@HIT@ {} == 0'.format(expr)

        if expr.startswith('==') or \
            expr.startswith('>') or \
            expr.startswith('<'):
            return '@HIT@ {}'.format(expr)

        return hit_condition

    def on_disconnect_request(self, py_db, request):
        '''
        :param DisconnectRequest request:
        '''
        self.api.remove_all_breakpoints(py_db, filename='*')
        self.api.remove_all_exception_breakpoints(py_db)
        self.api.request_resume_thread(thread_id='*')

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_setbreakpoints_request(self, py_db, request):
        '''
        :param SetBreakpointsRequest request:
        '''
        arguments = request.arguments  # : :type arguments: SetBreakpointsArguments
        filename = arguments.source.path
        filename = self.api.filename_to_server(filename)
        func_name = 'None'

        self.api.remove_all_breakpoints(py_db, filename)

        btype = 'python-line'
        suspend_policy = 'ALL'

        if not filename.lower().endswith('.py'):
            if self._debug_options.get('DJANGO_DEBUG', False):
                btype = 'django-line'
            elif self._debug_options.get('FLASK_DEBUG', False):
                btype = 'jinja2-line'

        breakpoints_set = []

        for source_breakpoint in arguments.breakpoints:
            source_breakpoint = SourceBreakpoint(**source_breakpoint)
            line = source_breakpoint.line
            condition = source_breakpoint.condition
            breakpoint_id = line

            hit_condition = self._get_hit_condition_expression(
                source_breakpoint.hitCondition)
            log_message = source_breakpoint.logMessage
            if not log_message:
                is_logpoint = None
                expression = None
            else:
                is_logpoint = True
                expression = convert_dap_log_message_to_expression(log_message)

            self.api.add_breakpoint(py_db, filename, btype, breakpoint_id,
                                    line, condition, func_name, expression,
                                    suspend_policy, hit_condition, is_logpoint)

            # Note that the id is made up (the id for pydevd is unique only within a file, so, the
            # line is used for it).
            # Also, the id is currently not used afterwards, so, we don't even keep a mapping.
            breakpoints_set.append({
                'id': self._next_breakpoint_id(),
                'verified': True,
                'line': line
            })

        body = {'breakpoints': breakpoints_set}
        set_breakpoints_response = pydevd_base_schema.build_response(
            request, kwargs={'body': body})
        return NetCommand(CMD_RETURN,
                          0,
                          set_breakpoints_response,
                          is_json=True)

    def on_setexceptionbreakpoints_request(self, py_db, request):
        '''
        :param SetExceptionBreakpointsRequest request:
        '''
        # : :type arguments: SetExceptionBreakpointsArguments
        arguments = request.arguments
        filters = arguments.filters
        exception_options = arguments.exceptionOptions
        self.api.remove_all_exception_breakpoints(py_db)

        # Can't set these in the DAP.
        condition = None
        expression = None
        notify_on_first_raise_only = False

        ignore_libraries = 1 if py_db.get_use_libraries_filter() else 0

        if exception_options:
            break_raised = True
            break_uncaught = True

            for option in exception_options:
                option = ExceptionOptions(**option)
                if not option.path:
                    continue

                notify_on_handled_exceptions = 1 if option.breakMode == 'always' else 0
                notify_on_unhandled_exceptions = 1 if option.breakMode in (
                    'unhandled', 'userUnhandled') else 0
                exception_paths = option.path

                exception_names = []
                if len(exception_paths) == 0:
                    continue

                elif len(exception_paths) == 1:
                    if 'Python Exceptions' in exception_paths[0]['names']:
                        exception_names = ['BaseException']

                else:
                    path_iterator = iter(exception_paths)
                    if 'Python Exceptions' in next(path_iterator)['names']:
                        for path in path_iterator:
                            for ex_name in path['names']:
                                exception_names.append(ex_name)

                for exception_name in exception_names:
                    self.api.add_python_exception_breakpoint(
                        py_db, exception_name, condition, expression,
                        notify_on_handled_exceptions,
                        notify_on_unhandled_exceptions,
                        notify_on_first_raise_only, ignore_libraries)

        else:
            break_raised = 'raised' in filters
            break_uncaught = 'uncaught' in filters
            if break_raised or break_uncaught:
                notify_on_handled_exceptions = 1 if break_raised else 0
                notify_on_unhandled_exceptions = 1 if break_uncaught else 0
                exception = 'BaseException'

                self.api.add_python_exception_breakpoint(
                    py_db, exception, condition, expression,
                    notify_on_handled_exceptions,
                    notify_on_unhandled_exceptions, notify_on_first_raise_only,
                    ignore_libraries)

        if break_raised or break_uncaught:
            btype = None
            if self._debug_options.get('DJANGO_DEBUG', False):
                btype = 'django'
            elif self._debug_options.get('FLASK_DEBUG', False):
                btype = 'jinja2'

            if btype:
                self.api.add_plugins_exception_breakpoint(
                    py_db, btype, 'BaseException'
                )  # Note: Exception name could be anything here.

        # Note: no body required on success.
        set_breakpoints_response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN,
                          0,
                          set_breakpoints_response,
                          is_json=True)

    def on_stacktrace_request(self, py_db, request):
        '''
        :param StackTraceRequest request:
        '''
        # : :type stack_trace_arguments: StackTraceArguments
        stack_trace_arguments = request.arguments
        thread_id = stack_trace_arguments.threadId

        fmt = stack_trace_arguments.format
        if hasattr(fmt, 'to_dict'):
            fmt = fmt.to_dict()
        self.api.request_stack(py_db, request.seq, thread_id, fmt)

    def on_exceptioninfo_request(self, py_db, request):
        '''
        :param ExceptionInfoRequest request:
        '''
        # : :type exception_into_arguments: ExceptionInfoArguments
        exception_into_arguments = request.arguments
        thread_id = exception_into_arguments.threadId
        self.api.request_exception_info_json(py_db, request, thread_id)

    def on_scopes_request(self, py_db, request):
        '''
        Scopes are the top-level items which appear for a frame (so, we receive the frame id
        and provide the scopes it has).

        :param ScopesRequest request:
        '''
        frame_id = request.arguments.frameId

        variables_reference = frame_id
        scopes = [Scope('Locals', int(variables_reference), False).to_dict()]
        body = ScopesResponseBody(scopes)
        scopes_response = pydevd_base_schema.build_response(
            request, kwargs={'body': body})
        return NetCommand(CMD_RETURN, 0, scopes_response, is_json=True)

    def on_evaluate_request(self, py_db, request):
        '''
        :param EvaluateRequest request:
        '''
        # : :type arguments: EvaluateArguments
        arguments = request.arguments

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            arguments.frameId)

        self.api.request_exec_or_evaluate_json(py_db, request, thread_id)

    def on_setexpression_request(self, py_db, request):
        # : :type arguments: SetExpressionArguments
        arguments = request.arguments

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            arguments.frameId)

        self.api.request_set_expression_json(py_db, request, thread_id)

    def on_variables_request(self, py_db, request):
        '''
        Variables can be asked whenever some place returned a variables reference (so, it
        can be a scope gotten from on_scopes_request, the result of some evaluation, etc.).

        Note that in the DAP the variables reference requires a unique int... the way this works for
        pydevd is that an instance is generated for that specific variable reference and we use its
        id(instance) to identify it to make sure all items are unique (and the actual {id->instance}
        is added to a dict which is only valid while the thread is suspended and later cleared when
        the related thread resumes execution).

        see: SuspendedFramesManager

        :param VariablesRequest request:
        '''
        arguments = request.arguments  # : :type arguments: VariablesArguments
        variables_reference = arguments.variablesReference

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            variables_reference)
        if thread_id is not None:
            self.api.request_get_variable_json(py_db, request, thread_id)
        else:
            variables = []
            body = VariablesResponseBody(variables)
            variables_response = pydevd_base_schema.build_response(
                request, kwargs={'body': body})
            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

    def on_setvariable_request(self, py_db, request):
        arguments = request.arguments  # : :type arguments: SetVariableArguments
        variables_reference = arguments.variablesReference

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            variables_reference)
        if thread_id is not None:
            self.api.request_change_variable_json(py_db, request, thread_id)
        else:
            body = SetVariableResponseBody('')
            variables_response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body':
                    body,
                    'success':
                    False,
                    'message':
                    'Unable to find thread to evaluate variable reference.'
                })
            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

    def on_modules_request(self, py_db, request):
        modules_manager = py_db.cmd_factory.modules_manager  # : :type modules_manager: ModulesManager
        modules_info = modules_manager.get_modules_info()
        body = ModulesResponseBody(modules_info)
        variables_response = pydevd_base_schema.build_response(
            request, kwargs={'body': body})
        return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

    def on_source_request(self, py_db, request):
        '''
        :param SourceRequest request:
        '''
        source_reference = request.arguments.sourceReference
        server_filename = None
        content = None

        if source_reference != 0:
            server_filename = pydevd_file_utils.get_server_filename_from_source_reference(
                source_reference)
            if server_filename:
                # Try direct file access first - it's much faster when available.
                try:
                    with open(server_filename, 'r') as stream:
                        content = stream.read()
                except:
                    pass

                if content is None:
                    # File might not exist at all, or we might not have a permission to read it,
                    # but it might also be inside a zipfile, or an IPython cell. In this case,
                    # linecache might still be able to retrieve the source.
                    lines = (linecache.getline(server_filename, i)
                             for i in itertools.count(1))
                    lines = itertools.takewhile(
                        bool, lines)  # empty lines are '\n', EOF is ''

                    # If we didn't get at least one line back, reset it to None so that it's
                    # reported as error below, and not as an empty file.
                    content = ''.join(lines) or None

        body = SourceResponseBody(content or '')
        response_args = {'body': body}

        if content is None:
            if server_filename:
                message = 'Unable to retrieve source for %s' % (
                    server_filename, )
            else:
                message = 'Invalid sourceReference %d' % (source_reference, )
            response_args.update({'success': False, 'message': message})

        response = pydevd_base_schema.build_response(request,
                                                     kwargs=response_args)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_gototargets_request(self, py_db, request):
        path = request.arguments.source.path
        line = request.arguments.line
        target_id = self._goto_targets_map.obtain_key((path, line))
        target = {
            'id': target_id,
            'label': '%s:%s' % (path, line),
            'line': line
        }
        body = GotoTargetsResponseBody(targets=[target])
        response_args = {'body': body}
        response = pydevd_base_schema.build_response(request,
                                                     kwargs=response_args)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_goto_request(self, py_db, request):
        target_id = int(request.arguments.targetId)
        thread_id = request.arguments.threadId
        try:
            _, line = self._goto_targets_map.obtain_value(target_id)
        except KeyError:
            response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body': {},
                    'success': False,
                    'message': 'Unknown goto target id: %d' % (target_id, ),
                })
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

        self.api.request_set_next(py_db, thread_id, CMD_SET_NEXT_STATEMENT,
                                  line, '*')
        response = pydevd_base_schema.build_response(request,
                                                     kwargs={'body': {}})
        return NetCommand(CMD_RETURN, 0, response, is_json=True)
class _PyDevCommandProcessor(object):

    def __init__(self):
        self.api = PyDevdAPI()

    def process_net_command(self, py_db, cmd_id, seq, text):
        '''Processes a command received from the Java side

        @param cmd_id: the id of the command
        @param seq: the sequence of the command
        @param text: the text received in the command
        '''
        meaning = ID_TO_MEANING[str(cmd_id)]

        # print('Handling %s (%s)' % (meaning, text))

        method_name = meaning.lower()

        on_command = getattr(self, method_name.lower(), None)
        if on_command is None:
            # I have no idea what this is all about
            cmd = py_db.cmd_factory.make_error_message(seq, "unexpected command " + str(cmd_id))
            py_db.writer.add_command(cmd)
            return

        py_db._main_lock.acquire()
        try:
            cmd = on_command(py_db, cmd_id, seq, text)
            if cmd is not None:
                py_db.writer.add_command(cmd)
        except:
            if traceback is not None and sys is not None and pydev_log_exception is not None:
                pydev_log_exception()

                stream = StringIO()
                traceback.print_exc(file=stream)
                cmd = py_db.cmd_factory.make_error_message(
                    seq,
                    "Unexpected exception in process_net_command.\nInitial params: %s. Exception: %s" % (
                        ((cmd_id, seq, text), stream.getvalue())
                    )
                )
                if cmd is not None:
                    py_db.writer.add_command(cmd)

        finally:
            py_db._main_lock.release()

    def cmd_run(self, py_db, cmd_id, seq, text):
        return self.api.run(py_db)

    def cmd_list_threads(self, py_db, cmd_id, seq, text):
        return self.api.list_threads(py_db, seq)

    def cmd_get_completions(self, py_db, cmd_id, seq, text):
        # we received some command to get a variable
        # the text is: thread_id\tframe_id\tactivation token
        thread_id, frame_id, _scope, act_tok = text.split('\t', 3)

        return self.api.request_completions(py_db, seq, thread_id, frame_id, act_tok)

    def cmd_get_thread_stack(self, py_db, cmd_id, seq, text):
        # Receives a thread_id and a given timeout, which is the time we should
        # wait to the provide the stack if a given thread is still not suspended.
        if '\t' in text:
            thread_id, timeout = text.split('\t')
            timeout = float(timeout)
        else:
            thread_id = text
            timeout = .5  # Default timeout is .5 seconds

        return self.api.request_stack(py_db, seq, thread_id, fmt={}, timeout=timeout)

    def cmd_set_protocol(self, py_db, cmd_id, seq, text):
        return self.api.set_protocol(py_db, seq, text.strip())

    def cmd_thread_suspend(self, py_db, cmd_id, seq, text):
        return self.api.request_suspend_thread(py_db, text.strip())

    def cmd_version(self, py_db, cmd_id, seq, text):
        # Default based on server process (although ideally the IDE should
        # provide it).
        if IS_WINDOWS:
            ide_os = 'WINDOWS'
        else:
            ide_os = 'UNIX'

        # 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

        version_msg = self.api.set_ide_os_and_breakpoints_by(py_db, seq, ide_os, breakpoints_by)

        # Enable thread notifications after the version command is completed.
        self.api.set_enable_thread_notifications(py_db, True)

        return version_msg

    def cmd_thread_run(self, py_db, cmd_id, seq, text):
        return self.api.request_resume_thread(text.strip())

    def _cmd_step(self, py_db, cmd_id, seq, text):
        return self.api.request_step(py_db, text.strip(), cmd_id)

    cmd_step_into = _cmd_step
    cmd_step_into_my_code = _cmd_step
    cmd_step_over = _cmd_step
    cmd_step_over_my_code = _cmd_step
    cmd_step_return = _cmd_step
    cmd_step_return_my_code = _cmd_step

    def _cmd_set_next(self, py_db, cmd_id, seq, text):
        thread_id, line, func_name = text.split('\t', 2)
        return self.api.request_set_next(py_db, seq, thread_id, cmd_id, line, func_name)

    cmd_run_to_line = _cmd_set_next
    cmd_set_next_statement = _cmd_set_next
    cmd_smart_step_into = _cmd_set_next

    def cmd_reload_code(self, py_db, cmd_id, seq, text):
        module_name = text.strip()
        self.api.request_reload_code(py_db, seq, module_name)

    def cmd_change_variable(self, py_db, cmd_id, seq, text):
        # the text is: thread\tstackframe\tFRAME|GLOBAL\tattribute_to_change\tvalue_to_change
        thread_id, frame_id, scope, attr_and_value = text.split('\t', 3)

        tab_index = attr_and_value.rindex('\t')
        attr = attr_and_value[0:tab_index].replace('\t', '.')
        value = attr_and_value[tab_index + 1:]
        self.api.request_change_variable(py_db, seq, thread_id, frame_id, scope, attr, value)

    def cmd_get_variable(self, py_db, cmd_id, seq, text):
        # we received some command to get a variable
        # the text is: thread_id\tframe_id\tFRAME|GLOBAL\tattributes*
        thread_id, frame_id, scopeattrs = text.split('\t', 2)

        if scopeattrs.find('\t') != -1:  # there are attributes beyond scope
            scope, attrs = scopeattrs.split('\t', 1)
        else:
            scope, attrs = (scopeattrs, None)

        self.api.request_get_variable(py_db, seq, thread_id, frame_id, scope, attrs)

    def cmd_get_array(self, py_db, cmd_id, seq, text):
        # Note: untested and unused in pydev
        # we received some command to get an array variable
        # the text is: thread_id\tframe_id\tFRAME|GLOBAL\tname\ttemp\troffs\tcoffs\trows\tcols\tformat
        roffset, coffset, rows, cols, format, thread_id, frame_id, scopeattrs = text.split('\t', 7)

        if scopeattrs.find('\t') != -1:  # there are attributes beyond scope
            scope, attrs = scopeattrs.split('\t', 1)
        else:
            scope, attrs = (scopeattrs, None)

        self.api.request_get_array(py_db, seq, roffset, coffset, rows, cols, format, thread_id, frame_id, scope, attrs)

    def cmd_show_return_values(self, py_db, cmd_id, seq, text):
        show_return_values = text.split('\t')[1]
        self.api.set_show_return_values(py_db, int(show_return_values) == 1)

    def cmd_load_full_value(self, py_db, cmd_id, seq, text):
        # Note: untested and unused in pydev
        thread_id, frame_id, scopeattrs = text.split('\t', 2)
        vars = scopeattrs.split(NEXT_VALUE_SEPARATOR)

        self.api.request_load_full_value(py_db, seq, thread_id, frame_id, vars)

    def cmd_get_description(self, py_db, cmd_id, seq, text):
        # Note: untested and unused in pydev
        thread_id, frame_id, expression = text.split('\t', 2)
        self.api.request_get_description(py_db, seq, thread_id, frame_id, expression)

    def cmd_get_frame(self, py_db, cmd_id, seq, text):
        thread_id, frame_id, scope = text.split('\t', 2)
        self.api.request_get_frame(py_db, seq, thread_id, frame_id)

    def cmd_set_break(self, py_db, cmd_id, seq, text):
        # func name: 'None': match anything. Empty: match global, specified: only method context.
        # command to add some breakpoint.
        # text is filename\tline. Add to breakpoints dictionary
        suspend_policy = "NONE"  # Can be 'NONE' or 'ALL'
        is_logpoint = False
        hit_condition = None
        if py_db._set_breakpoints_with_id:
            try:
                try:
                    breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint, suspend_policy = text.split('\t', 9)
                except ValueError:  # not enough values to unpack
                    # No suspend_policy passed (use default).
                    breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint = text.split('\t', 8)
                is_logpoint = is_logpoint == 'True'
            except ValueError:  # not enough values to unpack
                breakpoint_id, btype, filename, line, func_name, condition, expression = text.split('\t', 6)

            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()

            expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n').\
                replace("@_@TAB_CHAR@_@", '\t').strip()
        else:
            # Note: this else should be removed after PyCharm migrates to setting
            # breakpoints by id (and ideally also provides func_name).
            btype, filename, line, func_name, suspend_policy, condition, expression = text.split('\t', 6)
            # If we don't have an id given for each breakpoint, consider
            # the id to be the line.
            breakpoint_id = line = int(line)

            condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n'). \
                replace("@_@TAB_CHAR@_@", '\t').strip()

            expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n'). \
                replace("@_@TAB_CHAR@_@", '\t').strip()

        if condition is not None and (len(condition) <= 0 or condition == "None"):
            condition = None

        if expression is not None and (len(expression) <= 0 or expression == "None"):
            expression = None

        if hit_condition is not None and (len(hit_condition) <= 0 or hit_condition == "None"):
            hit_condition = None

        filename = self.api.filename_to_server(filename)
        func_name = self.api.to_str(func_name)

        error_code = self.api.add_breakpoint(
            py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint)

        if error_code:
            if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND:
                pydev_log.critical('pydev debugger: warning: Trying to add breakpoint to file that does not exist: %s (will have no effect).' % (filename,))

            elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS:
                pydev_log.critical('pydev debugger: warning: Trying to add breakpoint to file that is excluded by filters: %s (will have no effect).' % (filename,))

            else:
                # Shouldn't get here.
                pydev_log.critical('pydev debugger: warning: Breakpoint not validated (reason unknown -- please report as error): %s.' % (filename,))

    def cmd_remove_break(self, py_db, cmd_id, seq, text):
        # command to remove some breakpoint
        # text is type\file\tid. Remove from breakpoints dictionary
        breakpoint_type, filename, breakpoint_id = text.split('\t', 2)

        filename = self.api.filename_to_server(filename)

        try:
            breakpoint_id = int(breakpoint_id)
        except ValueError:
            pydev_log.critical('Error removing breakpoint. Expected breakpoint_id to be an int. Found: %s', breakpoint_id)

        else:
            self.api.remove_breakpoint(py_db, filename, breakpoint_type, breakpoint_id)

    def _cmd_exec_or_evaluate_expression(self, py_db, cmd_id, seq, text):
        # command to evaluate the given expression
        # text is: thread\tstackframe\tLOCAL\texpression
        attr_to_set_result = ""
        try:
            thread_id, frame_id, scope, expression, trim, attr_to_set_result = text.split('\t', 5)
        except ValueError:
            thread_id, frame_id, scope, expression, trim = text.split('\t', 4)
        is_exec = cmd_id == CMD_EXEC_EXPRESSION
        trim_if_too_big = int(trim) == 1

        self.api.request_exec_or_evaluate(
            py_db, seq, thread_id, frame_id, expression, is_exec, trim_if_too_big, attr_to_set_result)

    cmd_evaluate_expression = _cmd_exec_or_evaluate_expression
    cmd_exec_expression = _cmd_exec_or_evaluate_expression

    def cmd_console_exec(self, py_db, cmd_id, seq, text):
        # command to exec expression in console, in case expression is only partially valid 'False' is returned
        # text is: thread\tstackframe\tLOCAL\texpression

        thread_id, frame_id, scope, expression = text.split('\t', 3)
        self.api.request_console_exec(py_db, seq, thread_id, frame_id, expression)

    def cmd_set_py_exception(self, py_db, cmd_id, seq, text):
        # Command which receives set of exceptions on which user wants to break the debugger
        # text is:
        #
        # break_on_uncaught;
        # break_on_caught;
        # skip_on_exceptions_thrown_in_same_context;
        # ignore_exceptions_thrown_in_lines_with_ignore_exception;
        # ignore_libraries;
        # TypeError;ImportError;zipimport.ZipImportError;
        #
        # i.e.: true;true;true;true;true;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(';')
        py_db.break_on_uncaught_exceptions = {}
        py_db.break_on_caught_exceptions = {}
        if len(splitted) >= 5:
            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':
                py_db.skip_on_exceptions_thrown_in_same_context = True
            else:
                py_db.skip_on_exceptions_thrown_in_same_context = False

            if splitted[3] == 'true':
                py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = True
            else:
                py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = False

            if splitted[4] == 'true':
                ignore_libraries = True
            else:
                ignore_libraries = False

            for exception_type in splitted[5:]:
                exception_type = exception_type.strip()
                if not exception_type:
                    continue

                exception_breakpoint = py_db.add_break_on_exception(
                    exception_type,
                    condition=None,
                    expression=None,
                    notify_on_handled_exceptions=break_on_caught,
                    notify_on_unhandled_exceptions=break_on_uncaught,
                    notify_on_first_raise_only=True,
                    ignore_libraries=ignore_libraries,
                )

            py_db.on_breakpoints_changed()

        else:
            sys.stderr.write("Error when setting exception list. Received: %s\n" % (text,))

    def _load_source(self, py_db, cmd_id, seq, text):
        filename = text
        filename = self.api.filename_to_server(filename)
        self.api.request_load_source(py_db, seq, filename)

    cmd_load_source = _load_source
    cmd_get_file_contents = _load_source

    def cmd_set_property_trace(self, py_db, cmd_id, seq, text):
        # 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 py_db.disable_property_trace is False and splitted[0] == 'true':
                    # Replacing property by custom property only when the debugger starts
                    pydevd_traceproperty.replace_builtin_property()
                    py_db.disable_property_trace = True
                # Enable/Disable tracing of the property getter
                if splitted[1] == 'true':
                    py_db.disable_property_getter_trace = True
                else:
                    py_db.disable_property_getter_trace = False
                # Enable/Disable tracing of the property setter
                if splitted[2] == 'true':
                    py_db.disable_property_setter_trace = True
                else:
                    py_db.disable_property_setter_trace = False
                # Enable/Disable tracing of the property deleter
                if splitted[3] == 'true':
                    py_db.disable_property_deleter_trace = True
                else:
                    py_db.disable_property_deleter_trace = False

    def cmd_add_exception_break(self, py_db, cmd_id, seq, text):
        # Note that this message has some idiosyncrasies...
        #
        # notify_on_handled_exceptions can be 0, 1 or 2
        # 0 means we should not stop on handled exceptions.
        # 1 means we should stop on handled exceptions showing it on all frames where the exception passes.
        # 2 means we should stop on handled exceptions but we should only notify about it once.
        #
        # To ignore_libraries properly, besides setting ignore_libraries to 1, the IDE_PROJECT_ROOTS environment
        # variable must be set (so, we'll ignore anything not below IDE_PROJECT_ROOTS) -- this is not ideal as
        # the environment variable may not be properly set if it didn't start from the debugger (we should
        # create a custom message for that).
        #
        # There are 2 global settings which can only be set in CMD_SET_PY_EXCEPTION. Namely:
        #
        # py_db.skip_on_exceptions_thrown_in_same_context
        # - If True, we should only show the exception in a caller, not where it was first raised.
        #
        # py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception
        # - If True exceptions thrown in lines with '@IgnoreException' will not be shown.

        condition = ""
        expression = ""
        if text.find('\t') != -1:
            try:
                exception, condition, expression, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text.split('\t', 5)
            except:
                exception, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text.split('\t', 3)
        else:
            exception, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text, 0, 0, 0

        condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n').replace("@_@TAB_CHAR@_@", '\t').strip()

        if condition is not None and (len(condition) == 0 or condition == "None"):
            condition = None

        expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n').replace("@_@TAB_CHAR@_@", '\t').strip()

        if expression is not None and (len(expression) == 0 or expression == "None"):
            expression = None

        if exception.find('-') != -1:
            breakpoint_type, exception = exception.split('-')
        else:
            breakpoint_type = 'python'

        if breakpoint_type == 'python':
            self.api.add_python_exception_breakpoint(
                py_db, exception, condition, expression,
                notify_on_handled_exceptions=int(notify_on_handled_exceptions) > 0,
                notify_on_unhandled_exceptions=int(notify_on_unhandled_exceptions) == 1,
                notify_on_first_raise_only=int(notify_on_handled_exceptions) == 2,
                ignore_libraries=int(ignore_libraries) > 0,
            )
        else:
            self.api.add_plugins_exception_breakpoint(py_db, breakpoint_type, exception)

    def cmd_remove_exception_break(self, py_db, cmd_id, seq, text):
        exception = text
        if exception.find('-') != -1:
            exception_type, exception = exception.split('-')
        else:
            exception_type = 'python'

        if exception_type == 'python':
            self.api.remove_python_exception_breakpoint(py_db, exception)
        else:
            self.api.remove_plugins_exception_breakpoint(py_db, exception_type, exception)

    def cmd_add_django_exception_break(self, py_db, cmd_id, seq, text):
        self.api.add_plugins_exception_breakpoint(py_db, breakpoint_type='django', exception=text)

    def cmd_remove_django_exception_break(self, py_db, cmd_id, seq, text):
        self.api.remove_plugins_exception_breakpoint(py_db, exception_type='django', exception=text)

    def cmd_evaluate_console_expression(self, py_db, cmd_id, seq, text):
        # 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, buffer_output=True)

            elif console_command == 'EVALUATE_UNBUFFERED':
                int_cmd = InternalEvaluateConsoleExpression(
                    seq, thread_id, frame_id, line, buffer_output=False)

            elif console_command == 'GET_COMPLETIONS':
                int_cmd = InternalConsoleGetCompletions(seq, thread_id, frame_id, line)

            else:
                raise ValueError('Unrecognized command: %s' % (console_command,))

            py_db.post_internal_command(int_cmd, thread_id)

    def cmd_run_custom_operation(self, py_db, cmd_id, seq, text):
        # 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)
            py_db.post_internal_command(int_cmd, thread_id)

    def cmd_ignore_thrown_exception_at(self, py_db, cmd_id, seq, text):
        if text:
            replace = 'REPLACE:'  # Not all 3.x versions support u'REPLACE:', so, doing workaround.
            if not IS_PY3K:
                replace = unicode(replace)  # noqa

            if text.startswith(replace):
                text = text[8:]
                py_db.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('|')
                    filename = self.api.filename_to_server(filename)

                    if os.path.exists(filename):
                        lines_ignored = py_db.filename_to_lines_where_exceptions_are_ignored.get(filename)
                        if lines_ignored is None:
                            lines_ignored = py_db.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,))

    def cmd_enable_dont_trace(self, py_db, cmd_id, seq, text):
        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)  # noqa

            mode = text.strip() == true_str
            pydevd_dont_trace.trace_filter(mode)

    def cmd_redirect_output(self, py_db, cmd_id, seq, text):
        if text:
            py_db.enable_output_redirection('STDOUT' in text, 'STDERR' in text)

    def cmd_get_next_statement_targets(self, py_db, cmd_id, seq, text):
        thread_id, frame_id = text.split('\t', 1)

        py_db.post_method_as_internal_command(
            thread_id, internal_get_next_statement_targets, seq, thread_id, frame_id)

    def cmd_set_project_roots(self, py_db, cmd_id, seq, text):
        self.api.set_project_roots(py_db, text.split(u'\t'))

    def cmd_thread_dump_to_stderr(self, py_db, cmd_id, seq, text):
        pydevd_utils.dump_threads()

    def cmd_stop_on_start(self, py_db, cmd_id, seq, text):
        if text.strip() in ('True', 'true', '1'):
            self.api.stop_on_entry()

    def cmd_pydevd_json_config(self, py_db, cmd_id, seq, text):
        # Expected to receive a json string as:
        # {
        #     'skip_suspend_on_breakpoint_exception': [<exception names where we should suspend>],
        #     'skip_print_breakpoint_exception': [<exception names where we should print>],
        #     'multi_threads_single_notification': bool,
        # }
        msg = json.loads(text.strip())
        if 'skip_suspend_on_breakpoint_exception' in msg:
            py_db.skip_suspend_on_breakpoint_exception = tuple(
                get_exception_class(x) for x in msg['skip_suspend_on_breakpoint_exception'])

        if 'skip_print_breakpoint_exception' in msg:
            py_db.skip_print_breakpoint_exception = tuple(
                get_exception_class(x) for x in msg['skip_print_breakpoint_exception'])

        if 'multi_threads_single_notification' in msg:
            py_db.multi_threads_single_notification = msg['multi_threads_single_notification']

    def cmd_get_exception_details(self, py_db, cmd_id, seq, text):
        thread_id = text
        t = pydevd_find_thread_by_id(thread_id)
        frame = None
        if t and not getattr(t, 'pydev_do_not_trace', None):
            additional_info = set_additional_thread_info(t)
            frame = additional_info.get_topmost_frame(t)
        try:
            return py_db.cmd_factory.make_get_exception_details_message(seq, thread_id, frame)
        finally:
            frame = None
            t = None
class _PyDevJsonCommandProcessor(object):

    def __init__(self, from_json):
        self.from_json = from_json
        self.api = PyDevdAPI()
        self._debug_options = {}
        self._next_breakpoint_id = partial(next, itertools.count(0))
        self._goto_targets_map = IDMap()
        self._launch_or_attach_request_done = False

    def process_net_command_json(self, py_db, json_contents):
        '''
        Processes a debug adapter protocol json command.
        '''

        DEBUG = False

        try:
            request = self.from_json(json_contents, update_ids_from_dap=True)
        except KeyError as e:
            request = self.from_json(json_contents, update_ids_from_dap=False)
            error_msg = str(e)
            if error_msg.startswith("'") and error_msg.endswith("'"):
                error_msg = error_msg[1:-1]

            # This means a failure updating ids from the DAP (the client sent a key we didn't send).
            def on_request(py_db, request):
                error_response = {
                    'type': 'response',
                    'request_seq': request.seq,
                    'success': False,
                    'command': request.command,
                    'message': error_msg,
                }
                return NetCommand(CMD_RETURN, 0, error_response, is_json=True)

        else:
            if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS and DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1:
                pydev_log.info('Process %s: %s\n' % (
                    request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True),))

            assert request.type == 'request'
            method_name = 'on_%s_request' % (request.command.lower(),)
            on_request = getattr(self, method_name, None)
            if on_request is None:
                print('Unhandled: %s not available in _PyDevJsonCommandProcessor.\n' % (method_name,))
                return

            if DEBUG:
                print('Handled in pydevd: %s (in _PyDevJsonCommandProcessor).\n' % (method_name,))

        with py_db._main_lock:
            cmd = on_request(py_db, request)
            if cmd is not None:
                py_db.writer.add_command(cmd)

    def on_configurationdone_request(self, py_db, request):
        '''
        :param ConfigurationDoneRequest request:
        '''
        if not self._launch_or_attach_request_done:
            pydev_log.critical('Missing launch request or attach request before configuration done request.')

        self.api.run(py_db)
        self.api.notify_configuration_done(py_db)

        configuration_done_response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, configuration_done_response, is_json=True)

    def on_threads_request(self, py_db, request):
        '''
        :param ThreadsRequest request:
        '''
        return self.api.list_threads(py_db, request.seq)

    def on_completions_request(self, py_db, request):
        '''
        :param CompletionsRequest request:
        '''
        arguments = request.arguments  # : :type arguments: CompletionsArguments
        seq = request.seq
        text = arguments.text
        frame_id = arguments.frameId
        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            frame_id)

        if thread_id is None:
            body = CompletionsResponseBody([])
            variables_response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body': body,
                    'success': False,
                    'message': 'Thread to get completions seems to have resumed already.'
                })
            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

        # Note: line and column are 1-based (convert to 0-based for pydevd).
        column = arguments.column - 1

        if arguments.line is None:
            # line is optional
            line = -1
        else:
            line = arguments.line - 1

        self.api.request_completions(py_db, seq, thread_id, frame_id, text, line=line, column=column)

    def _resolve_remote_root(self, local_root, remote_root):
        if remote_root == '.':
            cwd = os.getcwd()
            append_pathsep = local_root.endswith('\\') or local_root.endswith('/')
            return cwd + (os.path.sep if append_pathsep else '')
        return remote_root

    def _set_debug_options(self, py_db, args, start_reason):
        rules = args.get('rules')
        exclude_filters = []

        if rules is not None:
            exclude_filters = _convert_rules_to_exclude_filters(
                rules, self.api.filename_to_server, lambda msg: self.api.send_error_message(py_db, msg))

        self.api.set_exclude_filters(py_db, exclude_filters)

        self._debug_options = _extract_debug_options(
            args.get('options'),
            args.get('debugOptions'),
        )
        self._debug_options['args'] = args

        debug_stdlib = self._debug_options.get('DEBUG_STDLIB', False)
        self.api.set_use_libraries_filter(py_db, not debug_stdlib)

        path_mappings = []
        for pathMapping in args.get('pathMappings', []):
            localRoot = pathMapping.get('localRoot', '')
            remoteRoot = pathMapping.get('remoteRoot', '')
            remoteRoot = self._resolve_remote_root(localRoot, remoteRoot)
            if (localRoot != '') and (remoteRoot != ''):
                path_mappings.append((localRoot, remoteRoot))

        if bool(path_mappings):
            pydevd_file_utils.setup_client_server_paths(path_mappings)

        if self._debug_options.get('REDIRECT_OUTPUT', False):
            py_db.enable_output_redirection(True, True)
        else:
            py_db.enable_output_redirection(False, False)

        self.api.set_show_return_values(py_db, self._debug_options.get('SHOW_RETURN_VALUE', False))

        if self._debug_options.get('STOP_ON_ENTRY', False) and start_reason == 'launch':
            self.api.stop_on_entry()

    def _send_process_event(self, py_db, start_method):
        if len(sys.argv) > 0:
            name = sys.argv[0]
        else:
            name = ''
        body = ProcessEventBody(
            name=name,
            systemProcessId=os.getpid(),
            isLocalProcess=True,
            startMethod=start_method,
        )
        event = ProcessEvent(body)
        py_db.writer.add_command(NetCommand(CMD_PROCESS_EVENT, 0, event, is_json=True))

    def _handle_launch_or_attach_request(self, py_db, request, start_reason):
        self._send_process_event(py_db, start_reason)
        self._launch_or_attach_request_done = True
        self.api.set_enable_thread_notifications(py_db, True)
        self._set_debug_options(py_db, request.arguments.kwargs, start_reason=start_reason)
        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_launch_request(self, py_db, request):
        '''
        :param LaunchRequest request:
        '''
        return self._handle_launch_or_attach_request(py_db, request, start_reason='launch')

    def on_attach_request(self, py_db, request):
        '''
        :param AttachRequest request:
        '''
        return self._handle_launch_or_attach_request(py_db, request, start_reason='attach')

    def on_pause_request(self, py_db, request):
        '''
        :param PauseRequest request:
        '''
        arguments = request.arguments  # : :type arguments: PauseArguments
        thread_id = arguments.threadId

        self.api.request_suspend_thread(py_db, thread_id=thread_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_continue_request(self, py_db, request):
        '''
        :param ContinueRequest request:
        '''
        arguments = request.arguments  # : :type arguments: ContinueArguments
        thread_id = arguments.threadId

        def on_resumed():
            body = {'allThreadsContinued': thread_id == '*'}
            response = pydevd_base_schema.build_response(request, kwargs={'body': body})
            cmd = NetCommand(CMD_RETURN, 0, response, is_json=True)
            py_db.writer.add_command(cmd)

        # Only send resumed notification when it has actually resumed!
        # (otherwise the user could send a continue, receive the notification and then
        # request a new pause which would be paused without sending any notification as
        # it didn't really run in the first place).
        py_db.threads_suspended_single_notification.add_on_resumed_callback(on_resumed)
        self.api.request_resume_thread(thread_id)

    def on_next_request(self, py_db, request):
        '''
        :param NextRequest request:
        '''
        arguments = request.arguments  # : :type arguments: NextArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_OVER_MY_CODE
        else:
            step_cmd_id = CMD_STEP_OVER

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_stepin_request(self, py_db, request):
        '''
        :param StepInRequest request:
        '''
        arguments = request.arguments  # : :type arguments: StepInArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_INTO_MY_CODE
        else:
            step_cmd_id = CMD_STEP_INTO

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_stepout_request(self, py_db, request):
        '''
        :param StepOutRequest request:
        '''
        arguments = request.arguments  # : :type arguments: StepOutArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_RETURN_MY_CODE
        else:
            step_cmd_id = CMD_STEP_RETURN

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def _get_hit_condition_expression(self, hit_condition):
        '''Following hit condition values are supported

        * x or == x when breakpoint is hit x times
        * >= x when breakpoint is hit more than or equal to x times
        * % x when breakpoint is hit multiple of x times

        Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits
        '''
        if not hit_condition:
            return None

        expr = hit_condition.strip()
        try:
            int(expr)
            return '@HIT@ == {}'.format(expr)
        except ValueError:
            pass

        if expr.startswith('%'):
            return '@HIT@ {} == 0'.format(expr)

        if expr.startswith('==') or \
            expr.startswith('>') or \
            expr.startswith('<'):
            return '@HIT@ {}'.format(expr)

        return hit_condition

    def on_disconnect_request(self, py_db, request):
        '''
        :param DisconnectRequest request:
        '''
        self._launch_or_attach_request_done = False
        py_db.enable_output_redirection(False, False)
        self.api.request_disconnect(py_db, resume_threads=True)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_setbreakpoints_request(self, py_db, request):
        '''
        :param SetBreakpointsRequest request:
        '''
        if not self._launch_or_attach_request_done:
            # Note that to validate the breakpoints we need the launch request to be done already
            # (otherwise the filters wouldn't be set for the breakpoint validation).
            body = SetBreakpointsResponseBody([])
            response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body': body,
                    'success': False,
                    'message': 'Breakpoints may only be set after the launch request is received.'
                })
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

        arguments = request.arguments  # : :type arguments: SetBreakpointsArguments
        # TODO: Path is optional here it could be source reference.
        filename = arguments.source.path
        filename = self.api.filename_to_server(filename)
        func_name = 'None'

        self.api.remove_all_breakpoints(py_db, filename)

        # Validate breakpoints and adjust their positions.
        try:
            lines = sorted(_get_code_lines(filename))
        except Exception:
            pass
        else:
            for bp in arguments.breakpoints:
                line = bp['line']
                if line not in lines:
                    # Adjust to the first preceding valid line.
                    idx = bisect.bisect_left(lines, line)
                    if idx > 0:
                        bp['line'] = lines[idx - 1]

        btype = 'python-line'
        suspend_policy = 'ALL'

        if not filename.lower().endswith('.py'):
            if self._debug_options.get('DJANGO_DEBUG', False):
                btype = 'django-line'
            elif self._debug_options.get('FLASK_DEBUG', False):
                btype = 'jinja2-line'

        breakpoints_set = []

        for source_breakpoint in arguments.breakpoints:
            source_breakpoint = SourceBreakpoint(**source_breakpoint)
            line = source_breakpoint.line
            condition = source_breakpoint.condition
            breakpoint_id = line

            hit_condition = self._get_hit_condition_expression(source_breakpoint.hitCondition)
            log_message = source_breakpoint.logMessage
            if not log_message:
                is_logpoint = None
                expression = None
            else:
                is_logpoint = True
                expression = convert_dap_log_message_to_expression(log_message)

            error_code = self.api.add_breakpoint(
                py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint)

            if error_code:
                if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND:
                    error_msg = 'Breakpoint in file that does not exist.'

                elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS:
                    error_msg = 'Breakpoint in file excluded by filters.'
                    if py_db.get_use_libraries_filter():
                        error_msg += '\nNote: may be excluded because of "justMyCode" option (default == true).'

                else:
                    # Shouldn't get here.
                    error_msg = 'Breakpoint not validated (reason unknown -- please report as bug).'

                breakpoints_set.append(pydevd_schema.Breakpoint(
                    verified=False, line=line, message=error_msg).to_dict())
            else:
                # Note that the id is made up (the id for pydevd is unique only within a file, so, the
                # line is used for it).
                # Also, the id is currently not used afterwards, so, we don't even keep a mapping.
                breakpoints_set.append(pydevd_schema.Breakpoint(
                    verified=True, id=self._next_breakpoint_id(), line=line).to_dict())

        body = {'breakpoints': breakpoints_set}
        set_breakpoints_response = pydevd_base_schema.build_response(request, kwargs={'body': body})
        return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True)

    def on_setexceptionbreakpoints_request(self, py_db, request):
        '''
        :param SetExceptionBreakpointsRequest request:
        '''
        # : :type arguments: SetExceptionBreakpointsArguments
        arguments = request.arguments
        filters = arguments.filters
        exception_options = arguments.exceptionOptions
        self.api.remove_all_exception_breakpoints(py_db)

        # Can't set these in the DAP.
        condition = None
        expression = None
        notify_on_first_raise_only = False

        ignore_libraries = 1 if py_db.get_use_libraries_filter() else 0

        if exception_options:
            break_raised = True
            break_uncaught = True

            for option in exception_options:
                option = ExceptionOptions(**option)
                if not option.path:
                    continue

                notify_on_handled_exceptions = 1 if option.breakMode == 'always' else 0
                notify_on_unhandled_exceptions = 1 if option.breakMode in ('unhandled', 'userUnhandled') else 0
                exception_paths = option.path

                exception_names = []
                if len(exception_paths) == 0:
                    continue

                elif len(exception_paths) == 1:
                    if 'Python Exceptions' in exception_paths[0]['names']:
                        exception_names = ['BaseException']

                else:
                    path_iterator = iter(exception_paths)
                    if 'Python Exceptions' in next(path_iterator)['names']:
                        for path in path_iterator:
                            for ex_name in path['names']:
                                exception_names.append(ex_name)

                for exception_name in exception_names:
                    self.api.add_python_exception_breakpoint(
                        py_db,
                        exception_name,
                        condition,
                        expression,
                        notify_on_handled_exceptions,
                        notify_on_unhandled_exceptions,
                        notify_on_first_raise_only,
                        ignore_libraries
                    )

        else:
            break_raised = 'raised' in filters
            break_uncaught = 'uncaught' in filters
            if break_raised or break_uncaught:
                notify_on_handled_exceptions = 1 if break_raised else 0
                notify_on_unhandled_exceptions = 1 if break_uncaught else 0
                exception = 'BaseException'

                self.api.add_python_exception_breakpoint(
                    py_db,
                    exception,
                    condition,
                    expression,
                    notify_on_handled_exceptions,
                    notify_on_unhandled_exceptions,
                    notify_on_first_raise_only,
                    ignore_libraries
                )

        if break_raised or break_uncaught:
            btype = None
            if self._debug_options.get('DJANGO_DEBUG', False):
                btype = 'django'
            elif self._debug_options.get('FLASK_DEBUG', False):
                btype = 'jinja2'

            if btype:
                self.api.add_plugins_exception_breakpoint(
                    py_db, btype, 'BaseException')  # Note: Exception name could be anything here.

        # Note: no body required on success.
        set_breakpoints_response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True)

    def on_stacktrace_request(self, py_db, request):
        '''
        :param StackTraceRequest request:
        '''
        # : :type stack_trace_arguments: StackTraceArguments
        stack_trace_arguments = request.arguments
        thread_id = stack_trace_arguments.threadId
        start_frame = stack_trace_arguments.startFrame
        levels = stack_trace_arguments.levels

        fmt = stack_trace_arguments.format
        if hasattr(fmt, 'to_dict'):
            fmt = fmt.to_dict()
        self.api.request_stack(py_db, request.seq, thread_id, fmt=fmt, start_frame=start_frame, levels=levels)

    def on_exceptioninfo_request(self, py_db, request):
        '''
        :param ExceptionInfoRequest request:
        '''
        # : :type exception_into_arguments: ExceptionInfoArguments
        exception_into_arguments = request.arguments
        thread_id = exception_into_arguments.threadId
        max_frames = int(self._debug_options['args'].get('maxExceptionStackFrames', 0))
        self.api.request_exception_info_json(py_db, request, thread_id, max_frames)

    def on_scopes_request(self, py_db, request):
        '''
        Scopes are the top-level items which appear for a frame (so, we receive the frame id
        and provide the scopes it has).

        :param ScopesRequest request:
        '''
        frame_id = request.arguments.frameId

        variables_reference = frame_id
        scopes = [Scope('Locals', int(variables_reference), False).to_dict()]
        body = ScopesResponseBody(scopes)
        scopes_response = pydevd_base_schema.build_response(request, kwargs={'body': body})
        return NetCommand(CMD_RETURN, 0, scopes_response, is_json=True)

    def on_evaluate_request(self, py_db, request):
        '''
        :param EvaluateRequest request:
        '''
        # : :type arguments: EvaluateArguments
        arguments = request.arguments

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            arguments.frameId)

        if thread_id is not None:
            self.api.request_exec_or_evaluate_json(
                py_db, request, thread_id)
        else:
            body = EvaluateResponseBody('', 0)
            response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body': body,
                    'success': False,
                    'message': 'Unable to find thread for evaluation.'
                })
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_setexpression_request(self, py_db, request):
        # : :type arguments: SetExpressionArguments
        arguments = request.arguments

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            arguments.frameId)

        if thread_id is not None:
            self.api.request_set_expression_json(py_db, request, thread_id)
        else:
            body = SetExpressionResponseBody('')
            response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body': body,
                    'success': False,
                    'message': 'Unable to find thread to set expression.'
                })
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_variables_request(self, py_db, request):
        '''
        Variables can be asked whenever some place returned a variables reference (so, it
        can be a scope gotten from on_scopes_request, the result of some evaluation, etc.).

        Note that in the DAP the variables reference requires a unique int... the way this works for
        pydevd is that an instance is generated for that specific variable reference and we use its
        id(instance) to identify it to make sure all items are unique (and the actual {id->instance}
        is added to a dict which is only valid while the thread is suspended and later cleared when
        the related thread resumes execution).

        see: SuspendedFramesManager

        :param VariablesRequest request:
        '''
        arguments = request.arguments  # : :type arguments: VariablesArguments
        variables_reference = arguments.variablesReference

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            variables_reference)
        if thread_id is not None:
            self.api.request_get_variable_json(py_db, request, thread_id)
        else:
            variables = []
            body = VariablesResponseBody(variables)
            variables_response = pydevd_base_schema.build_response(request, kwargs={
                'body': body,
                'success': False,
                'message': 'Unable to find thread to evaluate variable reference.'
            })
            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

    def on_setvariable_request(self, py_db, request):
        arguments = request.arguments  # : :type arguments: SetVariableArguments
        variables_reference = arguments.variablesReference

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            variables_reference)
        if thread_id is not None:
            self.api.request_change_variable_json(py_db, request, thread_id)
        else:
            body = SetVariableResponseBody('')
            variables_response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body': body,
                    'success': False,
                    'message': 'Unable to find thread to evaluate variable reference.'
                })
            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

    def on_modules_request(self, py_db, request):
        modules_manager = py_db.cmd_factory.modules_manager  # : :type modules_manager: ModulesManager
        modules_info = modules_manager.get_modules_info()
        body = ModulesResponseBody(modules_info)
        variables_response = pydevd_base_schema.build_response(request, kwargs={'body': body})
        return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

    def on_source_request(self, py_db, request):
        '''
        :param SourceRequest request:
        '''
        source_reference = request.arguments.sourceReference
        server_filename = None
        content = None

        if source_reference != 0:
            server_filename = pydevd_file_utils.get_server_filename_from_source_reference(source_reference)
            if server_filename:
                # Try direct file access first - it's much faster when available.
                try:
                    with open(server_filename, 'r') as stream:
                        content = stream.read()
                except:
                    pass

                if content is None:
                    # File might not exist at all, or we might not have a permission to read it,
                    # but it might also be inside a zipfile, or an IPython cell. In this case,
                    # linecache might still be able to retrieve the source.
                    lines = (linecache.getline(server_filename, i) for i in itertools.count(1))
                    lines = itertools.takewhile(bool, lines)  # empty lines are '\n', EOF is ''

                    # If we didn't get at least one line back, reset it to None so that it's
                    # reported as error below, and not as an empty file.
                    content = ''.join(lines) or None

        body = SourceResponseBody(content or '')
        response_args = {'body': body}

        if content is None:
            if source_reference == 0:
                message = 'Source unavailable'
            elif server_filename:
                message = 'Unable to retrieve source for %s' % (server_filename,)
            else:
                message = 'Invalid sourceReference %d' % (source_reference,)
            response_args.update({'success': False, 'message': message})

        response = pydevd_base_schema.build_response(request, kwargs=response_args)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_gototargets_request(self, py_db, request):
        path = request.arguments.source.path
        line = request.arguments.line
        target_id = self._goto_targets_map.obtain_key((path, line))
        target = {
            'id': target_id,
            'label': '%s:%s' % (path, line),
            'line': line
        }
        body = GotoTargetsResponseBody(targets=[target])
        response_args = {'body': body}
        response = pydevd_base_schema.build_response(request, kwargs=response_args)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_goto_request(self, py_db, request):
        target_id = int(request.arguments.targetId)
        thread_id = request.arguments.threadId
        try:
            _, line = self._goto_targets_map.obtain_value(target_id)
        except KeyError:
            response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body': {},
                    'success': False,
                    'message': 'Unknown goto target id: %d' % (target_id,),
                })
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

        self.api.request_set_next(py_db, request.seq, thread_id, CMD_SET_NEXT_STATEMENT, line, '*')
        # See 'NetCommandFactoryJson.make_set_next_stmnt_status_message' for response
        return None

    def on_setdebuggerproperty_request(self, py_db, request):
        args = request.arguments
        if args.ideOS is not None:
            self.api.set_ide_os(args.ideOS)

        if args.dontTraceStartPatterns is not None and args.dontTraceEndPatterns is not None:
            start_patterns = tuple(args.dontTraceStartPatterns)
            end_patterns = tuple(args.dontTraceEndPatterns)
            self.api.set_dont_trace_start_end_patterns(py_db, start_patterns, end_patterns)

        if args.skipSuspendOnBreakpointException is not None:
            py_db.skip_suspend_on_breakpoint_exception = tuple(
                get_exception_class(x) for x in args.skipSuspendOnBreakpointException)

        if args.skipPrintBreakpointException is not None:
            py_db.skip_print_breakpoint_exception = tuple(
                get_exception_class(x) for x in args.skipPrintBreakpointException)

        if args.multiThreadsSingleNotification is not None:
            py_db.multi_threads_single_notification = args.multiThreadsSingleNotification

        # TODO: Support other common settings. Note that not all of these might be relevant to python.
        # JustMyCodeStepping: 0 or 1
        # AllowOutOfProcessSymbols: 0 or 1
        # DisableJITOptimization: 0 or 1
        # InterpreterOptions: 0 or 1
        # StopOnExceptionCrossingManagedBoundary: 0 or 1
        # WarnIfNoUserCodeOnLaunch: 0 or 1
        # EnableStepFiltering: true of false

        response = pydevd_base_schema.build_response(request, kwargs={'body': {}})
        return NetCommand(CMD_RETURN, 0, response, is_json=True)
示例#6
0
class _PyDevJsonCommandProcessor(object):

    def __init__(self, from_json):
        self.from_json = from_json
        self.api = PyDevdAPI()
        self._debug_options = {}
        self._next_breakpoint_id = partial(next, itertools.count(0))

    def process_net_command_json(self, py_db, json_contents):
        '''
        Processes a debug adapter protocol json command.
        '''

        DEBUG = False

        request = self.from_json(json_contents)

        if DEBUG:
            print('Process %s: %s\n' % (
                request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True),))

        assert request.type == 'request'
        method_name = 'on_%s_request' % (request.command.lower(),)
        on_request = getattr(self, method_name, None)
        if on_request is None:
            print('Unhandled: %s not available in _PyDevJsonCommandProcessor.\n' % (method_name,))
            return

        if DEBUG:
            print('Handled in pydevd: %s (in _PyDevJsonCommandProcessor).\n' % (method_name,))

        py_db._main_lock.acquire()
        try:

            cmd = on_request(py_db, request)
            if cmd is not None:
                py_db.writer.add_command(cmd)
        finally:
            py_db._main_lock.release()

    def on_configurationdone_request(self, py_db, request):
        '''
        :param ConfigurationDoneRequest request:
        '''
        self.api.run(py_db)
        configuration_done_response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, configuration_done_response.to_dict(), is_json=True)

    def on_threads_request(self, py_db, request):
        '''
        :param ThreadsRequest request:
        '''
        return self.api.list_threads(py_db, request.seq)

    def on_completions_request(self, py_db, request):
        '''
        :param CompletionsRequest request:
        '''
        arguments = request.arguments  # : :type arguments: CompletionsArguments
        seq = request.seq
        text = arguments.text
        thread_id, frame_id = arguments.frameId

        # Note: line and column are 1-based (convert to 0-based for pydevd).
        column = arguments.column - 1

        if arguments.line is None:
            # line is optional
            line = -1
        else:
            line = arguments.line - 1

        self.api.request_completions(py_db, seq, thread_id, frame_id, text, line=line, column=column)

    def _set_debug_options(self, py_db, args):
        rules = args.get('rules')
        exclude_filters = []

        if rules is not None:
            exclude_filters = _convert_rules_to_exclude_filters(
                rules, self.api.filename_to_server, lambda msg:self.api.send_error_message(py_db, msg))

        self.api.set_exclude_filters(py_db, exclude_filters)

        self._debug_options = _extract_debug_options(
            args.get('options'),
            args.get('debugOptions'),
        )
        debug_stdlib = self._debug_options.get('DEBUG_STDLIB', False)
        self.api.set_use_libraries_filter(py_db, not debug_stdlib)

    def on_launch_request(self, py_db, request):
        '''
        :param LaunchRequest request:
        '''
        self._set_debug_options(py_db, request.arguments.kwargs)
        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True)

    def on_attach_request(self, py_db, request):
        '''
        :param AttachRequest request:
        '''
        self._set_debug_options(py_db, request.arguments.kwargs)
        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True)

    def _get_hit_condition_expression(self, hit_condition):
        '''Following hit condition values are supported

        * x or == x when breakpoint is hit x times
        * >= x when breakpoint is hit more than or equal to x times
        * % x when breakpoint is hit multiple of x times

        Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits
        '''
        if not hit_condition:
            return None

        expr = hit_condition.strip()
        try:
            int(expr)
            return '@HIT@ == {}'.format(expr)
        except ValueError:
            pass

        if expr.startswith('%'):
            return '@HIT@ {} == 0'.format(expr)

        if expr.startswith('==') or \
            expr.startswith('>') or \
            expr.startswith('<'):
            return '@HIT@ {}'.format(expr)

        return hit_condition

    def on_disconnect_request(self, py_db, request):
        '''
        :param DisconnectRequest request:
        '''
        self.api.remove_all_breakpoints(py_db, filename='*')
        self.api.request_resume_thread(thread_id='*')

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True)

    def on_setbreakpoints_request(self, py_db, request):
        '''
        :param SetBreakpointsRequest request:
        '''
        arguments = request.arguments  # : :type arguments: SetBreakpointsArguments
        filename = arguments.source.path
        filename = self.api.filename_to_server(filename)
        func_name = 'None'

        self.api.remove_all_breakpoints(py_db, filename)

        btype = 'python-line'
        suspend_policy = 'ALL'

        if not filename.lower().endswith('.py'):
            if self._debug_options.get('DJANGO_DEBUG', False):
                btype = 'django-line'
            elif self._debug_options.get('FLASK_DEBUG', False):
                btype = 'jinja2-line'

        breakpoints_set = []

        for source_breakpoint in arguments.breakpoints:
            source_breakpoint = SourceBreakpoint(**source_breakpoint)
            line = source_breakpoint.line
            condition = source_breakpoint.condition
            breakpoint_id = line

            hit_condition = self._get_hit_condition_expression(source_breakpoint.hitCondition)
            log_message = source_breakpoint.logMessage
            if not log_message:
                is_logpoint = None
                expression = None
            else:
                is_logpoint = True
                expression = convert_dap_log_message_to_expression(log_message)

            self.api.add_breakpoint(
                py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint)

            # Note that the id is made up (the id for pydevd is unique only within a file, so, the
            # line is used for it).
            # Also, the id is currently not used afterwards, so, we don't even keep a mapping.
            breakpoints_set.append({'id':self._next_breakpoint_id(), 'verified': True, 'line': line})

        body = {'breakpoints': breakpoints_set}
        set_breakpoints_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
        return NetCommand(CMD_RETURN, 0, set_breakpoints_response.to_dict(), is_json=True)
class _PyDevJsonCommandProcessor(object):

    def __init__(self, from_json):
        self.from_json = from_json
        self.api = PyDevdAPI()
        self._debug_options = {}
        self._next_breakpoint_id = partial(next, itertools.count(0))
        self._goto_targets_map = IDMap()

    def process_net_command_json(self, py_db, json_contents):
        '''
        Processes a debug adapter protocol json command.
        '''

        DEBUG = False

        request = self.from_json(json_contents, update_ids_from_dap=True)

        if DEBUG:
            print('Process %s: %s\n' % (
                request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True),))

        assert request.type == 'request'
        method_name = 'on_%s_request' % (request.command.lower(),)
        on_request = getattr(self, method_name, None)
        if on_request is None:
            print('Unhandled: %s not available in _PyDevJsonCommandProcessor.\n' % (method_name,))
            return

        if DEBUG:
            print('Handled in pydevd: %s (in _PyDevJsonCommandProcessor).\n' % (method_name,))

        py_db._main_lock.acquire()
        try:

            cmd = on_request(py_db, request)
            if cmd is not None:
                py_db.writer.add_command(cmd)
        finally:
            py_db._main_lock.release()

    def on_configurationdone_request(self, py_db, request):
        '''
        :param ConfigurationDoneRequest request:
        '''
        self.api.run(py_db)
        configuration_done_response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, configuration_done_response, is_json=True)

    def on_threads_request(self, py_db, request):
        '''
        :param ThreadsRequest request:
        '''
        return self.api.list_threads(py_db, request.seq)

    def on_completions_request(self, py_db, request):
        '''
        :param CompletionsRequest request:
        '''
        arguments = request.arguments  # : :type arguments: CompletionsArguments
        seq = request.seq
        text = arguments.text
        frame_id = arguments.frameId
        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            frame_id)

        # Note: line and column are 1-based (convert to 0-based for pydevd).
        column = arguments.column - 1

        if arguments.line is None:
            # line is optional
            line = -1
        else:
            line = arguments.line - 1

        self.api.request_completions(py_db, seq, thread_id, frame_id, text, line=line, column=column)

    def _set_debug_options(self, py_db, args):
        rules = args.get('rules')
        exclude_filters = []

        if rules is not None:
            exclude_filters = _convert_rules_to_exclude_filters(
                rules, self.api.filename_to_server, lambda msg:self.api.send_error_message(py_db, msg))

        self.api.set_exclude_filters(py_db, exclude_filters)

        self._debug_options = _extract_debug_options(
            args.get('options'),
            args.get('debugOptions'),
        )
        self._debug_options['args'] = args

        debug_stdlib = self._debug_options.get('DEBUG_STDLIB', False)
        self.api.set_use_libraries_filter(py_db, not debug_stdlib)

    def on_launch_request(self, py_db, request):
        '''
        :param LaunchRequest request:
        '''
        self._set_debug_options(py_db, request.arguments.kwargs)
        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_attach_request(self, py_db, request):
        '''
        :param AttachRequest request:
        '''
        self._set_debug_options(py_db, request.arguments.kwargs)
        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_pause_request(self, py_db, request):
        '''
        :param PauseRequest request:
        '''
        arguments = request.arguments  # : :type arguments: PauseArguments
        thread_id = arguments.threadId

        self.api.request_suspend_thread(py_db, thread_id=thread_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_continue_request(self, py_db, request):
        '''
        :param ContinueRequest request:
        '''
        arguments = request.arguments  # : :type arguments: ContinueArguments
        thread_id = arguments.threadId

        def on_resumed():
            body = {'allThreadsContinued': thread_id == '*'}
            response = pydevd_base_schema.build_response(request, kwargs={'body': body})
            cmd = NetCommand(CMD_RETURN, 0, response, is_json=True)
            py_db.writer.add_command(cmd)

        # Only send resumed notification when it has actually resumed!
        # (otherwise the user could send a continue, receive the notification and then
        # request a new pause which would be paused without sending any notification as
        # it didn't really run in the first place).
        py_db.threads_suspended_single_notification.add_on_resumed_callback(on_resumed)
        self.api.request_resume_thread(thread_id)

    def on_next_request(self, py_db, request):
        '''
        :param NextRequest request:
        '''
        arguments = request.arguments  # : :type arguments: NextArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_OVER_MY_CODE
        else:
            step_cmd_id = CMD_STEP_OVER

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_stepin_request(self, py_db, request):
        '''
        :param StepInRequest request:
        '''
        arguments = request.arguments  # : :type arguments: StepInArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_INTO_MY_CODE
        else:
            step_cmd_id = CMD_STEP_INTO

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_stepout_request(self, py_db, request):
        '''
        :param StepOutRequest request:
        '''
        arguments = request.arguments  # : :type arguments: StepOutArguments
        thread_id = arguments.threadId

        if py_db.get_use_libraries_filter():
            step_cmd_id = CMD_STEP_RETURN_MY_CODE
        else:
            step_cmd_id = CMD_STEP_RETURN

        self.api.request_step(py_db, thread_id, step_cmd_id)

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def _get_hit_condition_expression(self, hit_condition):
        '''Following hit condition values are supported

        * x or == x when breakpoint is hit x times
        * >= x when breakpoint is hit more than or equal to x times
        * % x when breakpoint is hit multiple of x times

        Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits
        '''
        if not hit_condition:
            return None

        expr = hit_condition.strip()
        try:
            int(expr)
            return '@HIT@ == {}'.format(expr)
        except ValueError:
            pass

        if expr.startswith('%'):
            return '@HIT@ {} == 0'.format(expr)

        if expr.startswith('==') or \
            expr.startswith('>') or \
            expr.startswith('<'):
            return '@HIT@ {}'.format(expr)

        return hit_condition

    def on_disconnect_request(self, py_db, request):
        '''
        :param DisconnectRequest request:
        '''
        self.api.remove_all_breakpoints(py_db, filename='*')
        self.api.remove_all_exception_breakpoints(py_db)
        self.api.request_resume_thread(thread_id='*')

        response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_setbreakpoints_request(self, py_db, request):
        '''
        :param SetBreakpointsRequest request:
        '''
        arguments = request.arguments  # : :type arguments: SetBreakpointsArguments
        filename = arguments.source.path
        filename = self.api.filename_to_server(filename)
        func_name = 'None'

        self.api.remove_all_breakpoints(py_db, filename)

        btype = 'python-line'
        suspend_policy = 'ALL'

        if not filename.lower().endswith('.py'):
            if self._debug_options.get('DJANGO_DEBUG', False):
                btype = 'django-line'
            elif self._debug_options.get('FLASK_DEBUG', False):
                btype = 'jinja2-line'

        breakpoints_set = []

        for source_breakpoint in arguments.breakpoints:
            source_breakpoint = SourceBreakpoint(**source_breakpoint)
            line = source_breakpoint.line
            condition = source_breakpoint.condition
            breakpoint_id = line

            hit_condition = self._get_hit_condition_expression(source_breakpoint.hitCondition)
            log_message = source_breakpoint.logMessage
            if not log_message:
                is_logpoint = None
                expression = None
            else:
                is_logpoint = True
                expression = convert_dap_log_message_to_expression(log_message)

            self.api.add_breakpoint(
                py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint)

            # Note that the id is made up (the id for pydevd is unique only within a file, so, the
            # line is used for it).
            # Also, the id is currently not used afterwards, so, we don't even keep a mapping.
            breakpoints_set.append({'id':self._next_breakpoint_id(), 'verified': True, 'line': line})

        body = {'breakpoints': breakpoints_set}
        set_breakpoints_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
        return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True)

    def on_setexceptionbreakpoints_request(self, py_db, request):
        '''
        :param SetExceptionBreakpointsRequest request:
        '''
        # : :type arguments: SetExceptionBreakpointsArguments
        arguments = request.arguments
        filters = arguments.filters
        exception_options = arguments.exceptionOptions
        self.api.remove_all_exception_breakpoints(py_db)

        # Can't set these in the DAP.
        condition = None
        expression = None
        notify_on_first_raise_only = False

        ignore_libraries = 1 if py_db.get_use_libraries_filter() else 0

        if exception_options:
            break_raised = True
            break_uncaught = True

            for option in exception_options:
                option = ExceptionOptions(**option)
                if not option.path:
                    continue

                notify_on_handled_exceptions = 1 if option.breakMode == 'always' else 0
                notify_on_unhandled_exceptions = 1 if option.breakMode in ('unhandled', 'userUnhandled') else 0
                exception_paths = option.path

                exception_names = []
                if len(exception_paths) == 0:
                    continue

                elif len(exception_paths) == 1:
                    if 'Python Exceptions' in exception_paths[0]['names']:
                        exception_names = ['BaseException']

                else:
                    path_iterator = iter(exception_paths)
                    if 'Python Exceptions' in next(path_iterator)['names']:
                        for path in path_iterator:
                            for ex_name in path['names']:
                                exception_names.append(ex_name)

                for exception_name in exception_names:
                    self.api.add_python_exception_breakpoint(
                        py_db,
                        exception_name,
                        condition,
                        expression,
                        notify_on_handled_exceptions,
                        notify_on_unhandled_exceptions,
                        notify_on_first_raise_only,
                        ignore_libraries
                    )

        else:
            break_raised = 'raised' in filters
            break_uncaught = 'uncaught' in filters
            if break_raised or break_uncaught:
                notify_on_handled_exceptions = 1 if break_raised else 0
                notify_on_unhandled_exceptions = 1 if break_uncaught else 0
                exception = 'BaseException'

                self.api.add_python_exception_breakpoint(
                    py_db,
                    exception,
                    condition,
                    expression,
                    notify_on_handled_exceptions,
                    notify_on_unhandled_exceptions,
                    notify_on_first_raise_only,
                    ignore_libraries
                )

        if break_raised or break_uncaught:
            btype = None
            if self._debug_options.get('DJANGO_DEBUG', False):
                btype = 'django'
            elif self._debug_options.get('FLASK_DEBUG', False):
                btype = 'jinja2'

            if btype:
                self.api.add_plugins_exception_breakpoint(
                    py_db, btype, 'BaseException')  # Note: Exception name could be anything here.

        # Note: no body required on success.
        set_breakpoints_response = pydevd_base_schema.build_response(request)
        return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True)

    def on_stacktrace_request(self, py_db, request):
        '''
        :param StackTraceRequest request:
        '''
        # : :type stack_trace_arguments: StackTraceArguments
        stack_trace_arguments = request.arguments
        thread_id = stack_trace_arguments.threadId

        fmt = stack_trace_arguments.format
        if hasattr(fmt, 'to_dict'):
            fmt = fmt.to_dict()
        self.api.request_stack(py_db, request.seq, thread_id, fmt)

    def on_exceptioninfo_request(self, py_db, request):
        '''
        :param ExceptionInfoRequest request:
        '''
        # : :type exception_into_arguments: ExceptionInfoArguments
        exception_into_arguments = request.arguments
        thread_id = exception_into_arguments.threadId            
        max_frames = int(self._debug_options['args'].get('maxExceptionStackFrames', 0))
        self.api.request_exception_info_json(py_db, request, thread_id, max_frames)

    def on_scopes_request(self, py_db, request):
        '''
        Scopes are the top-level items which appear for a frame (so, we receive the frame id
        and provide the scopes it has).

        :param ScopesRequest request:
        '''
        frame_id = request.arguments.frameId

        variables_reference = frame_id
        scopes = [Scope('Locals', int(variables_reference), False).to_dict()]
        body = ScopesResponseBody(scopes)
        scopes_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
        return NetCommand(CMD_RETURN, 0, scopes_response, is_json=True)

    def on_evaluate_request(self, py_db, request):
        '''
        :param EvaluateRequest request:
        '''
        # : :type arguments: EvaluateArguments
        arguments = request.arguments

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            arguments.frameId)

        self.api.request_exec_or_evaluate_json(
            py_db, request, thread_id)

    def on_setexpression_request(self, py_db, request):
        # : :type arguments: SetExpressionArguments
        arguments = request.arguments

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            arguments.frameId)

        self.api.request_set_expression_json(
            py_db, request, thread_id)

    def on_variables_request(self, py_db, request):
        '''
        Variables can be asked whenever some place returned a variables reference (so, it
        can be a scope gotten from on_scopes_request, the result of some evaluation, etc.).

        Note that in the DAP the variables reference requires a unique int... the way this works for
        pydevd is that an instance is generated for that specific variable reference and we use its
        id(instance) to identify it to make sure all items are unique (and the actual {id->instance}
        is added to a dict which is only valid while the thread is suspended and later cleared when
        the related thread resumes execution).

        see: SuspendedFramesManager

        :param VariablesRequest request:
        '''
        arguments = request.arguments  # : :type arguments: VariablesArguments
        variables_reference = arguments.variablesReference

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            variables_reference)
        if thread_id is not None:
            self.api.request_get_variable_json(py_db, request, thread_id)
        else:
            variables = []
            body = VariablesResponseBody(variables)
            variables_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

    def on_setvariable_request(self, py_db, request):
        arguments = request.arguments  # : :type arguments: SetVariableArguments
        variables_reference = arguments.variablesReference

        thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
            variables_reference)
        if thread_id is not None:
            self.api.request_change_variable_json(py_db, request, thread_id)
        else:
            body = SetVariableResponseBody('')
            variables_response = pydevd_base_schema.build_response(
                request,
                kwargs={
                    'body':body,
                    'success': False,
                    'message': 'Unable to find thread to evaluate variable reference.'
            })
            return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

    def on_modules_request(self, py_db, request):
        modules_manager = py_db.cmd_factory.modules_manager  # : :type modules_manager: ModulesManager
        modules_info = modules_manager.get_modules_info()
        body = ModulesResponseBody(modules_info)
        variables_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
        return NetCommand(CMD_RETURN, 0, variables_response, is_json=True)

    def on_source_request(self, py_db, request):
        '''
        :param SourceRequest request:
        '''
        source_reference = request.arguments.sourceReference
        server_filename = None
        content = None

        if source_reference != 0:
            server_filename = pydevd_file_utils.get_server_filename_from_source_reference(source_reference)
            if server_filename:
                # Try direct file access first - it's much faster when available.
                try:
                    with open(server_filename, 'r') as stream:
                        content = stream.read()
                except:
                    pass

                if content is None:
                    # File might not exist at all, or we might not have a permission to read it,
                    # but it might also be inside a zipfile, or an IPython cell. In this case,
                    # linecache might still be able to retrieve the source.
                    lines = (linecache.getline(server_filename, i) for i in itertools.count(1))
                    lines = itertools.takewhile(bool, lines)  # empty lines are '\n', EOF is ''

                    # If we didn't get at least one line back, reset it to None so that it's
                    # reported as error below, and not as an empty file.
                    content = ''.join(lines) or None

        body = SourceResponseBody(content or '')
        response_args = {'body': body}

        if content is None:
            if server_filename:
                message = 'Unable to retrieve source for %s' % (server_filename,)
            else:
                message = 'Invalid sourceReference %d' % (source_reference,)
            response_args.update({'success': False, 'message': message})

        response = pydevd_base_schema.build_response(request, kwargs=response_args)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_gototargets_request(self, py_db, request):
        path = request.arguments.source.path
        line = request.arguments.line
        target_id = self._goto_targets_map.obtain_key((path, line))
        target = {
            'id': target_id,
            'label': '%s:%s' % (path, line),
            'line': line
        }
        body = GotoTargetsResponseBody(targets=[target])
        response_args = {'body': body}
        response = pydevd_base_schema.build_response(request, kwargs=response_args)
        return NetCommand(CMD_RETURN, 0, response, is_json=True)

    def on_goto_request(self, py_db, request):
        target_id = int(request.arguments.targetId)
        thread_id = request.arguments.threadId
        try:
            _, line = self._goto_targets_map.obtain_value(target_id)
        except KeyError:
            response = pydevd_base_schema.build_response(request,
                kwargs={
                    'body': {},
                    'success': False,
                    'message': 'Unknown goto target id: %d' % (target_id,),
                })
            return NetCommand(CMD_RETURN, 0, response, is_json=True)

        self.api.request_set_next(py_db, thread_id, CMD_SET_NEXT_STATEMENT, line, '*')
        response = pydevd_base_schema.build_response(request, kwargs={'body': {}})
        return NetCommand(CMD_RETURN, 0, response, is_json=True)