diff --git a/pytests/func/test_completions.py b/pytests/func/test_completions.py index da00256a5..92a795351 100644 --- a/pytests/func/test_completions.py +++ b/pytests/func/test_completions.py @@ -12,19 +12,19 @@ expected_at_line = { 8: [ - {'label': 'SomeClass', 'type': 'class'}, - {'label': 'someFunction', 'type': 'function'}, - {'label': 'someVariable', 'type': 'field'}, + {'label': 'SomeClass', 'type': 'class', 'start': 0, 'length': 4}, + {'label': 'someFunction', 'type': 'function', 'start': 0, 'length': 4}, + {'label': 'someVariable', 'type': 'field', 'start': 0, 'length': 4}, ], 11: [ - {'label': 'SomeClass', 'type': 'class'}, - {'label': 'someFunction', 'type': 'function'}, - {'label': 'someVar', 'type': 'field'}, - {'label': 'someVariable', 'type': 'field'}, + {'label': 'SomeClass', 'type': 'class', 'start': 0, 'length': 4}, + {'label': 'someFunction', 'type': 'function', 'start': 0, 'length': 4}, + {'label': 'someVar', 'type': 'field', 'start': 0, 'length': 4}, + {'label': 'someVariable', 'type': 'field', 'start': 0, 'length': 4}, ], 13: [ - {'label': 'SomeClass', 'type': 'class'}, - {'label': 'someFunction', 'type': 'function'}, + {'label': 'SomeClass', 'type': 'class', 'start': 0, 'length': 4}, + {'label': 'someFunction', 'type': 'function', 'start': 0, 'length': 4}, ], } @@ -72,7 +72,7 @@ def someFunction(someVar): resp_completions = session.send_request('completions', arguments={ 'text': 'some', 'frameId': fid, - 'column': 1 + 'column': 5, }).wait_for_response() targets = resp_completions.body['targets'] @@ -85,7 +85,7 @@ def someFunction(someVar): session.wait_for_exit() -def test_completions(pyfile, run_as, start_method): +def test_completions_cases(pyfile, run_as, start_method): @pyfile def code_to_debug(): from dbgimporter import import_and_enable_debugger @@ -110,7 +110,8 @@ def code_to_debug(): response = session.send_request('completions', arguments={ 'frameId': hit.frame_id, - 'text': 'b.' + 'text': 'b.', + 'column': 3, }).wait_for_response() labels = set(target['label'] for target in response.body['targets']) @@ -118,7 +119,17 @@ def code_to_debug(): response = session.send_request('completions', arguments={ 'frameId': hit.frame_id, - 'text': 'not_there' + 'text': 'x = b.setdefault', + 'column': 13, + }).wait_for_response() + + assert response.body['targets'] == [ + {'label': 'setdefault', 'length': 6, 'start': 6, 'type': 'function'}] + + response = session.send_request('completions', arguments={ + 'frameId': hit.frame_id, + 'text': 'not_there', + 'column': 10, }).wait_for_response() assert not response.body['targets'] diff --git a/src/ptvsd/_vendored/pydevd/_pydev_bundle/_pydev_completer.py b/src/ptvsd/_vendored/pydevd/_pydev_bundle/_pydev_completer.py index 4b51342c3..9dbbf6680 100644 --- a/src/ptvsd/_vendored/pydevd/_pydev_bundle/_pydev_completer.py +++ b/src/ptvsd/_vendored/pydevd/_pydev_bundle/_pydev_completer.py @@ -1,20 +1,23 @@ +from collections import namedtuple +from string import ascii_letters, digits + +from _pydevd_bundle import pydevd_xml +from _pydevd_bundle.pydevd_constants import IS_PY2 import pydevconsole -import sys -if sys.version_info[0] >= 3: - import builtins as __builtin__ # Py3 -else: +if IS_PY2: import __builtin__ +else: + import builtins as __builtin__ # Py3 try: - import java.lang #@UnusedImport + import java.lang # @UnusedImport from _pydev_bundle import _pydev_jy_imports_tipper _pydev_imports_tipper = _pydev_jy_imports_tipper except ImportError: IS_JYTHON = False from _pydev_bundle import _pydev_imports_tipper -from _pydevd_bundle import pydevd_xml dir2 = _pydev_imports_tipper.generate_imports_tip_for_module @@ -26,13 +29,13 @@ class _StartsWithFilter: Used because we can't create a lambda that'll use an outer scope in jython 2.1 ''' - def __init__(self, start_with): self.start_with = start_with.lower() def __call__(self, name): return name.lower().startswith(self.start_with) + #======================================================================================================================= # Completer # @@ -82,9 +85,9 @@ def complete(self, text): """ if self.use_main_ns: - #In pydev this option should never be used + # In pydev this option should never be used raise RuntimeError('Namespace must be provided!') - self.namespace = __main__.__dict__ #@UndefinedVariable + self.namespace = __main__.__dict__ # @UndefinedVariable if "." in text: return self.attr_matches(text) @@ -99,13 +102,12 @@ def global_matches(self, text): """ - def get_item(obj, attr): return obj[attr] a = {} - for dict_with_comps in [__builtin__.__dict__, self.namespace, self.global_namespace]: #@UndefinedVariable + for dict_with_comps in [__builtin__.__dict__, self.namespace, self.global_namespace]: # @UndefinedVariable a.update(dict_with_comps) filter = _StartsWithFilter(text) @@ -128,7 +130,7 @@ def attr_matches(self, text): import re # Another option, seems to work great. Catches things like ''. - m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) #@UndefinedVariable + m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) # @UndefinedVariable if not m: return [] @@ -149,30 +151,44 @@ def attr_matches(self, text): return words -#======================================================================================================================= -# generate_completions_as_xml -#======================================================================================================================= -def generate_completions_as_xml(frame, act_tok): +def generate_completions(frame, act_tok): + ''' + :return list(tuple(method_name, docstring, parameters, completion_type)) + + method_name: str + docstring: str + parameters: str -- i.e.: "(a, b)" + completion_type is an int + See: _pydev_bundle._pydev_imports_tipper for TYPE_ constants + ''' if frame is None: - return '' + return [] - #Not using frame.f_globals because of https://sourceforge.net/tracker2/?func=detail&aid=2541355&group_id=85796&atid=577329 - #(Names not resolved in generator expression in method) - #See message: http://mail.python.org/pipermail/python-list/2009-January/526522.html + # Not using frame.f_globals because of https://sourceforge.net/tracker2/?func=detail&aid=2541355&group_id=85796&atid=577329 + # (Names not resolved in generator expression in method) + # See message: http://mail.python.org/pipermail/python-list/2009-January/526522.html updated_globals = {} updated_globals.update(frame.f_globals) - updated_globals.update(frame.f_locals) #locals later because it has precedence over the actual globals + updated_globals.update(frame.f_locals) # locals later because it has precedence over the actual globals if pydevconsole.IPYTHON: completions = pydevconsole.get_completions(act_tok, act_tok, updated_globals, frame.f_locals) else: completer = Completer(updated_globals, None) - #list(tuple(name, descr, parameters, type)) + # list(tuple(name, descr, parameters, type)) completions = completer.complete(act_tok) + return completions + + +def generate_completions_as_xml(frame, act_tok): + completions = generate_completions(frame, act_tok) + return completions_to_xml(completions) + + +def completions_to_xml(completions): valid_xml = pydevd_xml.make_valid_xml_value quote = pydevd_xml.quote - msg = [""] for comp in completions: @@ -189,3 +205,85 @@ def generate_completions_as_xml(frame, act_tok): return ''.join(msg) + +identifier_start = ascii_letters + '_' +identifier_part = ascii_letters + '_' + digits + +if IS_PY2: + identifier_start = identifier_start.decode('utf-8') + identifier_part = identifier_part.decode('utf-8') + +identifier_start = set(identifier_start) +identifier_part = set(identifier_part) + +if IS_PY2: + + # There's no string.isidentifier() on py2. + def isidentifier(s): + if not s: + return False + if s[0] not in identifier_start: + return False + + for c in s[1:]: + if c not in identifier_part: + return False + return True + +else: + + def isidentifier(s): + return s.isidentifier() + +TokenAndQualifier = namedtuple('TokenAndQualifier', 'token, qualifier') + + +def extract_token_and_qualifier(text, line=0, column=0): + ''' + Extracts the token a qualifier from the text given the line/colum + (see test_extract_token_and_qualifier for examples). + + :param unicode text: + :param int line: 0-based + :param int column: 0-based + ''' + # Note: not using the tokenize module because text should be unicode and + # line/column refer to the unicode text (otherwise we'd have to know + # those ranges after converted to bytes). + if line < 0: + line = 0 + if column < 0: + column = 0 + + if isinstance(text, bytes): + text = text.decode('utf-8') + + lines = text.splitlines() + try: + text = lines[line] + except IndexError: + return TokenAndQualifier(u'', u'') + + if column >= len(text): + column = len(text) + + text = text[:column] + token = u'' + qualifier = u'' + + temp_token = [] + for i in range(column - 1, -1, -1): + c = text[i] + if c in identifier_part or isidentifier(c) or c == u'.': + temp_token.append(c) + else: + break + temp_token = u''.join(reversed(temp_token)) + if u'.' in temp_token: + temp_token = temp_token.split(u'.') + token = u'.'.join(temp_token[:-1]) + qualifier = temp_token[-1] + else: + qualifier = temp_token + + return TokenAndQualifier(token, qualifier) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/__main__pydevd_gen_debug_adapter_protocol.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/__main__pydevd_gen_debug_adapter_protocol.py index cbd56bd03..76ab010fc 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/__main__pydevd_gen_debug_adapter_protocol.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/__main__pydevd_gen_debug_adapter_protocol.py @@ -47,8 +47,9 @@ def set_repr(self): class Ref(object): - def __init__(self, ref): + def __init__(self, ref, ref_data): self.ref = ref + self.ref_data = ref_data def __str__(self): return self.ref @@ -79,6 +80,10 @@ def create_classes_to_generate_structure(json_schema_data): for name, definition in definitions.items(): all_of = definition.get('allOf') description = definition.get('description') + is_enum = definition.get('type') == 'string' and 'enum' in definition + enum_values = None + if is_enum: + enum_values = definition['enum'] properties = {} properties.update(definition.get('properties', {})) required = _OrderedSet(definition.get('required', _OrderedSet())) @@ -103,6 +108,8 @@ def create_classes_to_generate_structure(json_schema_data): base_definitions=base_definitions, description=description, required=required, + is_enum=is_enum, + enum_values=enum_values ) return class_to_generatees @@ -157,7 +164,7 @@ def update_class_to_generate_description(class_to_generate): class_to_generate['description'] = ' ' + ('\n '.join(lines)) -def update_class_to_generate_type(class_to_generate): +def update_class_to_generate_type(classes_to_generate, class_to_generate): properties = class_to_generate.get('properties') for _prop_name, prop_val in properties.items(): prop_type = prop_val.get('type', '') @@ -166,7 +173,7 @@ def update_class_to_generate_type(class_to_generate): if prop_type: assert prop_type.startswith('#/definitions/') prop_type = prop_type[len('#/definitions/'):] - prop_val['type'] = Ref(prop_type) + prop_val['type'] = Ref(prop_type, classes_to_generate[prop_type]) def update_class_to_generate_register_dec(classes_to_generate, class_to_generate): @@ -237,9 +244,9 @@ def update_class_to_generate_to_json(class_to_generate): for prop_name, prop in prop_name_and_prop: namespace = dict(prop_name=prop_name) - is_ref = prop['type'].__class__ == Ref + use_to_dict = prop['type'].__class__ == Ref and not prop['type'].ref_data.get('is_enum', False) if prop_name in required: - if is_ref: + if use_to_dict: to_dict_body.append(' %(prop_name)r: self.%(prop_name)s.to_dict(),' % namespace) else: to_dict_body.append(' %(prop_name)r: self.%(prop_name)s,' % namespace) @@ -249,7 +256,7 @@ def update_class_to_generate_to_json(class_to_generate): to_dict_body.append(' }') to_dict_body.append(' if self.%(prop_name)s is not None:' % namespace) - if is_ref: + if use_to_dict: to_dict_body.append(' dct[%(prop_name)r] = self.%(prop_name)s.to_dict()' % namespace) else: to_dict_body.append(' dct[%(prop_name)r] = self.%(prop_name)s' % namespace) @@ -286,15 +293,22 @@ def update_class_to_generate_init(class_to_generate): args.append(prop_name + '=None') if prop['type'].__class__ == Ref: - namespace = dict( - prop_name=prop_name, - ref_name=str(prop['type']) - ) - init_body.append(' if %(prop_name)s is None:' % namespace) - init_body.append(' self.%(prop_name)s = %(ref_name)s()' % namespace) - init_body.append(' else:') - init_body.append(' self.%(prop_name)s = %(ref_name)s(**%(prop_name)s) if %(prop_name)s.__class__ != %(ref_name)s else %(prop_name)s' % namespace - ) + ref = prop['type'] + ref_data = ref.ref_data + if ref_data.get('is_enum', False): + init_body.append(' assert %s in %s.VALID_VALUES' % (prop_name, str(ref))) + init_body.append(' self.%(prop_name)s = %(prop_name)s' % dict( + prop_name=prop_name)) + else: + namespace = dict( + prop_name=prop_name, + ref_name=str(ref) + ) + init_body.append(' if %(prop_name)s is None:' % namespace) + init_body.append(' self.%(prop_name)s = %(ref_name)s()' % namespace) + init_body.append(' else:') + init_body.append(' self.%(prop_name)s = %(ref_name)s(**%(prop_name)s) if %(prop_name)s.__class__ != %(ref_name)s else %(prop_name)s' % namespace + ) else: init_body.append(' self.%(prop_name)s = %(prop_name)s' % dict( @@ -343,7 +357,19 @@ def default(o): def update_class_to_generate_refs(class_to_generate): properties = class_to_generate['properties'] - class_to_generate['refs'] = ' __refs__ = %s' % _OrderedSet(key for (key, val) in properties.items() if val['type'].__class__ == Ref).set_repr() + class_to_generate['refs'] = ' __refs__ = %s' % _OrderedSet( + key for (key, val) in properties.items() if val['type'].__class__ == Ref).set_repr() + + +def update_class_to_generate_enums(class_to_generate): + class_to_generate['enums'] = '' + if class_to_generate.get('is_enum', False): + enums = '' + for enum in class_to_generate['enum_values']: + enums += ' %s = %r\n' % (enum.upper(), enum) + enums += '\n' + enums += ' VALID_VALUES = %s\n\n' % _OrderedSet(class_to_generate['enum_values']).set_repr() + class_to_generate['enums'] = enums def update_class_to_generate_objects(classes_to_generate, class_to_generate): @@ -361,13 +387,13 @@ def update_class_to_generate_objects(classes_to_generate, class_to_generate): assert create_new['name'] not in classes_to_generate classes_to_generate[create_new['name']] = create_new - update_class_to_generate_type(create_new) + update_class_to_generate_type(classes_to_generate, create_new) update_class_to_generate_props(create_new) # Update nested object types update_class_to_generate_objects(classes_to_generate, create_new) - val['type'] = Ref(create_new['name']) + val['type'] = Ref(create_new['name'], classes_to_generate[create_new['name']]) val.pop('properties', None) @@ -384,13 +410,14 @@ def gen_debugger_protocol(): for class_to_generate in list(classes_to_generate.values()): update_class_to_generate_description(class_to_generate) - update_class_to_generate_type(class_to_generate) + update_class_to_generate_type(classes_to_generate, class_to_generate) update_class_to_generate_props(class_to_generate) update_class_to_generate_objects(classes_to_generate, class_to_generate) for class_to_generate in classes_to_generate.values(): update_class_to_generate_refs(class_to_generate) update_class_to_generate_init(class_to_generate) + update_class_to_generate_enums(class_to_generate) update_class_to_generate_to_json(class_to_generate) update_class_to_generate_register_dec(classes_to_generate, class_to_generate) @@ -403,7 +430,7 @@ class %(name)s(BaseSchema): Note: automatically generated code. Do not edit manually. """ -%(props)s +%(enums)s%(props)s %(refs)s __slots__ = list(__props__.keys()) + ['kwargs'] diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_base_schema.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_base_schema.py index ad8f273eb..fda0fc349 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_base_schema.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_base_schema.py @@ -77,6 +77,8 @@ def from_dict(dct): def from_json(json_msg): + if isinstance(json_msg, bytes): + json_msg = json_msg.decode('utf-8') import json return from_dict(json.loads(json_msg)) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_schema.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_schema.py index 6e41d1255..80cea6c9c 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_schema.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_schema.py @@ -10093,10 +10093,8 @@ def __init__(self, label, text=None, type=None, start=None, length=None, **kwarg """ self.label = label self.text = text - if type is None: - self.type = CompletionItemType() - else: - self.type = CompletionItemType(**type) if type.__class__ != CompletionItemType else type + assert type in CompletionItemType.VALID_VALUES + self.type = type self.start = start self.length = length self.kwargs = kwargs @@ -10109,7 +10107,7 @@ def to_dict(self): if self.text is not None: dct['text'] = self.text if self.type is not None: - dct['type'] = self.type.to_dict() + dct['type'] = self.type if self.start is not None: dct['start'] = self.start if self.length is not None: @@ -10127,6 +10125,28 @@ class CompletionItemType(BaseSchema): Note: automatically generated code. Do not edit manually. """ + METHOD = 'method' + FUNCTION = 'function' + CONSTRUCTOR = 'constructor' + FIELD = 'field' + VARIABLE = 'variable' + CLASS = 'class' + INTERFACE = 'interface' + MODULE = 'module' + PROPERTY = 'property' + UNIT = 'unit' + VALUE = 'value' + ENUM = 'enum' + KEYWORD = 'keyword' + SNIPPET = 'snippet' + TEXT = 'text' + COLOR = 'color' + FILE = 'file' + REFERENCE = 'reference' + CUSTOMCOLOR = 'customcolor' + + VALID_VALUES = set(['method', 'function', 'constructor', 'field', 'variable', 'class', 'interface', 'module', 'property', 'unit', 'value', 'enum', 'keyword', 'snippet', 'text', 'color', 'file', 'reference', 'customcolor']) + __props__ = {} __refs__ = set() @@ -10155,6 +10175,13 @@ class ChecksumAlgorithm(BaseSchema): Note: automatically generated code. Do not edit manually. """ + MD5 = 'MD5' + SHA1 = 'SHA1' + SHA256 = 'SHA256' + TIMESTAMP = 'timestamp' + + VALID_VALUES = set(['MD5', 'SHA1', 'SHA256', 'timestamp']) + __props__ = {} __refs__ = set() @@ -10202,17 +10229,15 @@ def __init__(self, algorithm, checksum, **kwargs): :param ChecksumAlgorithm algorithm: The algorithm used to calculate this checksum. :param string checksum: Value of the checksum. """ - if algorithm is None: - self.algorithm = ChecksumAlgorithm() - else: - self.algorithm = ChecksumAlgorithm(**algorithm) if algorithm.__class__ != ChecksumAlgorithm else algorithm + assert algorithm in ChecksumAlgorithm.VALID_VALUES + self.algorithm = algorithm self.checksum = checksum self.kwargs = kwargs def to_dict(self): dct = { - 'algorithm': self.algorithm.to_dict(), + 'algorithm': self.algorithm, 'checksum': self.checksum, } dct.update(self.kwargs) @@ -10375,17 +10400,15 @@ def __init__(self, breakMode, path=None, **kwargs): :param ExceptionBreakMode breakMode: Condition when a thrown exception should result in a break. :param array path: A path that selects a single or multiple exceptions in a tree. If 'path' is missing, the whole tree is selected. By convention the first segment of the path is a category that is used to group exceptions in the UI. """ - if breakMode is None: - self.breakMode = ExceptionBreakMode() - else: - self.breakMode = ExceptionBreakMode(**breakMode) if breakMode.__class__ != ExceptionBreakMode else breakMode + assert breakMode in ExceptionBreakMode.VALID_VALUES + self.breakMode = breakMode self.path = path self.kwargs = kwargs def to_dict(self): dct = { - 'breakMode': self.breakMode.to_dict(), + 'breakMode': self.breakMode, } if self.path is not None: dct['path'] = self.path @@ -10409,6 +10432,13 @@ class ExceptionBreakMode(BaseSchema): Note: automatically generated code. Do not edit manually. """ + NEVER = 'never' + ALWAYS = 'always' + UNHANDLED = 'unhandled' + USERUNHANDLED = 'userUnhandled' + + VALID_VALUES = set(['never', 'always', 'unhandled', 'userUnhandled']) + __props__ = {} __refs__ = set() @@ -12028,10 +12058,8 @@ def __init__(self, exceptionId, breakMode, description=None, details=None, **kwa :param ExceptionDetails details: Detailed information about the exception. """ self.exceptionId = exceptionId - if breakMode is None: - self.breakMode = ExceptionBreakMode() - else: - self.breakMode = ExceptionBreakMode(**breakMode) if breakMode.__class__ != ExceptionBreakMode else breakMode + assert breakMode in ExceptionBreakMode.VALID_VALUES + self.breakMode = breakMode self.description = description if details is None: self.details = ExceptionDetails() @@ -12043,7 +12071,7 @@ def __init__(self, exceptionId, breakMode, description=None, details=None, **kwa def to_dict(self): dct = { 'exceptionId': self.exceptionId, - 'breakMode': self.breakMode.to_dict(), + 'breakMode': self.breakMode, } if self.description is not None: dct['description'] = self.description diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py index 9b17ae0fe..a2685792e 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py @@ -47,7 +47,7 @@ 119 CMD_RELOAD_CODE 120 CMD_GET_COMPLETIONS JAVA - 200 CMD_REDIRECT_OUTPUT JAVA streams to redirect as string - + 200 CMD_REDIRECT_OUTPUT JAVA streams to redirect as string - 'STDOUT' (redirect only STDOUT) 'STDERR' (redirect only STDERR) 'STDOUT STDERR' (redirect both streams) @@ -70,11 +70,12 @@ from _pydev_imps._pydev_saved_modules import thread from _pydev_imps._pydev_saved_modules import threading from socket import AF_INET, SOCK_STREAM, SHUT_RD, SHUT_WR, SOL_SOCKET, SO_REUSEADDR, SHUT_RDWR -from _pydevd_bundle.pydevd_constants import DebugInfoHolder, get_thread_id, IS_JYTHON, IS_PY2, \ - IS_PY36_OR_GREATER, STATE_RUN, dict_keys, ASYNC_EVAL_TIMEOUT_SEC, GlobalDebuggerHolder, \ - get_global_debugger, GetGlobalDebugger, set_global_debugger # Keep for backward compatibility @UnusedImport +from _pydevd_bundle.pydevd_constants import (DebugInfoHolder, get_thread_id, IS_JYTHON, IS_PY2, + IS_PY36_OR_GREATER, STATE_RUN, dict_keys, ASYNC_EVAL_TIMEOUT_SEC, GlobalDebuggerHolder, + get_global_debugger, GetGlobalDebugger, set_global_debugger) # Keep for backward compatibility @UnusedImport from _pydev_bundle.pydev_override import overrides import weakref +from _pydev_bundle._pydev_completer import extract_token_and_qualifier try: from urllib import quote_plus, unquote_plus except: @@ -96,21 +97,25 @@ from _pydev_bundle.pydev_monkey import disable_trace_thread_modules, enable_trace_thread_modules from socket import socket try: - import cStringIO as StringIO #may not always be available @UnusedImport + import cStringIO as StringIO # may not always be available @UnusedImport except: try: - import StringIO #@Reimport + import StringIO # @Reimport except: import io as StringIO - + from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE get_file_type = DONT_TRACE.get # CMD_XXX constants imported for backward compatibility from _pydevd_bundle.pydevd_comm_constants import * # @UnusedWildImport +if IS_JYTHON: + import org.python.core as JyCore # @UnresolvedImport + #--------------------------------------------------------------------------------------------------- UTILITIES + #======================================================================================================================= # pydevd_log #======================================================================================================================= @@ -121,15 +126,15 @@ def pydevd_log(level, *args): 2 informational trace """ if level <= DebugInfoHolder.DEBUG_TRACE_LEVEL: - #yes, we can have errors printing if the console of the program has been finished (and we're still trying to print something) + # yes, we can have errors printing if the console of the program has been finished (and we're still trying to print something) try: sys.stderr.write('%s\n' % (args,)) except: pass - #------------------------------------------------------------------- ACTUAL COMM + #======================================================================================================================= # PyDBDaemonThread #======================================================================================================================= @@ -155,10 +160,9 @@ def run(self): if IS_JYTHON and not isinstance(threading.currentThread(), threading._MainThread): # we shouldn't update sys.modules for the main thread, cause it leads to the second importing 'threading' # module, and the new instance of main thread is created - import org.python.core as PyCore #@UnresolvedImport - ss = PyCore.PySystemState() + ss = JyCore.PySystemState() # Note: Py.setSystemState() affects only the current thread. - PyCore.Py.setSystemState(ss) + JyCore.Py.setSystemState(ss) self._stop_trace() self._on_run() @@ -206,70 +210,134 @@ class ReaderThread(PyDBDaemonThread): """ reader thread reads and dispatches commands in an infinite loop """ def __init__(self, sock): + from _pydevd_bundle.pydevd_process_net_command_json import process_net_command_json + from _pydevd_bundle.pydevd_process_net_command import process_net_command PyDBDaemonThread.__init__(self) + self.sock = sock + self._buffer = b'' self.setName("pydevd.Reader") - from _pydevd_bundle.pydevd_process_net_command import process_net_command self.process_net_command = process_net_command + self.process_net_command_json = process_net_command_json self.global_debugger_holder = GlobalDebuggerHolder - - def do_kill_pydev_thread(self): - #We must close the socket so that it doesn't stay halted there. + # We must close the socket so that it doesn't stay halted there. self.killReceived = True try: - self.sock.shutdown(SHUT_RD) #shutdown the socket for read + self.sock.shutdown(SHUT_RD) # shutdown the socket for read except: - #just ignore that + # just ignore that pass + def _read(self, size): + while True: + buffer_len = len(self._buffer) + if buffer_len == size: + ret = self._buffer + self._buffer = b'' + return ret + + if buffer_len > size: + ret = self._buffer[:size] + self._buffer = self._buffer[size:] + return ret + + r = self.sock.recv(max(size - buffer_len, 1024)) + if not r: + return b'' + self._buffer += r + + def _read_line(self): + while True: + i = self._buffer.find(b'\n') + if i != -1: + i += 1 # Add the newline to the return + ret = self._buffer[:i] + self._buffer = self._buffer[i:] + return ret + else: + r = self.sock.recv(1024) + if not r: + return b'' + self._buffer += r + @overrides(PyDBDaemonThread._on_run) def _on_run(self): - read_buffer = "" try: + content_len = -1 while not self.killReceived: try: - r = self.sock.recv(1024) + line = self._read_line() + + if len(line) == 0: + self.handle_except() + return # Finished communication. + + if line.startswith(b'Content-Length:'): + content_len = int(line.strip().split(b':', 1)[1]) + continue + + if content_len != -1: + # If we previously received a content length, read until a '\r\n'. + if line == b'\r\n': + json_contents = self._read(content_len) + content_len = -1 + + if len(json_contents) == 0: + self.handle_except() + return # Finished communication. + + # We just received a json message, let's process it. + self.process_net_command_json(self.global_debugger_holder.global_dbg, json_contents) + + continue + else: + # No content len, regular line-based protocol message (remove trailing new-line). + if line.endswith(b'\n\n'): + line = line[:-2] + + elif line.endswith(b'\n'): + line = line[:-1] + + elif line.endswith(b'\r'): + line = line[:-1] except: if not self.killReceived: traceback.print_exc() self.handle_except() - return #Finished communication. + return # Finished communication. - #Note: the java backend is always expected to pass utf-8 encoded strings. We now work with unicode - #internally and thus, we may need to convert to the actual encoding where needed (i.e.: filenames - #on python 2 may need to be converted to the filesystem encoding). - if hasattr(r, 'decode'): - r = r.decode('utf-8') - read_buffer += r + # Note: the java backend is always expected to pass utf-8 encoded strings. We now work with unicode + # internally and thus, we may need to convert to the actual encoding where needed (i.e.: filenames + # on python 2 may need to be converted to the filesystem encoding). + if hasattr(line, 'decode'): + line = line.decode('utf-8') + if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS: - sys.stderr.write(u'debugger: received >>%s<<\n' % (read_buffer,)) + sys.stderr.write(u'debugger: received >>%s<<\n' % (line,)) sys.stderr.flush() - if len(read_buffer) == 0: - self.handle_except() - break - while read_buffer.find(u'\n') != -1: - command, read_buffer = read_buffer.split(u'\n', 1) - args = command.split(u'\t', 2) - try: - cmd_id = int(args[0]) - pydev_log.debug('Received command: %s %s\n' % (ID_TO_MEANING.get(str(cmd_id), '???'), command,)) - self.process_command(cmd_id, int(args[1]), args[2]) - except: + args = line.split(u'\t', 2) + try: + cmd_id = int(args[0]) + pydev_log.debug('Received command: %s %s\n' % (ID_TO_MEANING.get(str(cmd_id), '???'), line,)) + self.process_command(cmd_id, int(args[1]), args[2]) + except: + if traceback is not None and sys is not None: # Could happen at interpreter shutdown traceback.print_exc() - sys.stderr.write("Can't process net command: %s\n" % command) + sys.stderr.write("Can't process net command: %s\n" % line) sys.stderr.flush() except: - traceback.print_exc() + if not self.killReceived: + if traceback is not None: # Could happen at interpreter shutdown + traceback.print_exc() self.handle_except() - def handle_except(self): self.global_debugger_holder.global_dbg.finish_debugging_session() @@ -283,6 +351,7 @@ def process_command(self, cmd_id, seq, text): #======================================================================================================================= class WriterThread(PyDBDaemonThread): """ writer thread writes out the commands in an infinite loop """ + def __init__(self, sock): PyDBDaemonThread.__init__(self) self.sock = sock @@ -295,7 +364,7 @@ def __init__(self, sock): def add_command(self, cmd): """ cmd is NetCommand """ - if not self.killReceived: #we don't take new data after everybody die + if not self.killReceived: # we don't take new data after everybody die self.cmdQueue.put(cmd) @overrides(PyDBDaemonThread._on_run) @@ -315,33 +384,32 @@ def _on_run(self): except: pass - return #break if queue is empty and killReceived + return # break if queue is empty and killReceived else: continue except: - #pydevd_log(0, 'Finishing debug communication...(1)') - #when liberating the thread here, we could have errors because we were shutting down - #but the thread was still not liberated + # pydevd_log(0, 'Finishing debug communication...(1)') + # when liberating the thread here, we could have errors because we were shutting down + # but the thread was still not liberated return cmd.send(self.sock) - + if cmd.id == CMD_EXIT: break if time is None: - break #interpreter shutdown + break # interpreter shutdown time.sleep(self.timeout) except Exception: GlobalDebuggerHolder.global_dbg.finish_debugging_session() if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 0: traceback.print_exc() - + def empty(self): return self.cmdQueue.empty() - - #--------------------------------------------------- CREATING THE SOCKET THREADS + #======================================================================================================================= # start_server #======================================================================================================================= @@ -373,6 +441,7 @@ def start_server(port): sys.stderr.flush() traceback.print_exc() + #======================================================================================================================= # start_client #======================================================================================================================= @@ -407,9 +476,6 @@ def start_client(host, port): traceback.print_exc() raise - - - #------------------------------------------------------------------------------------ MANY COMMUNICATION STUFF @@ -442,21 +508,18 @@ def do_it(self, dbg): class ReloadCodeCommand(InternalThreadCommand): - def __init__(self, module_name, thread_id): self.thread_id = thread_id self.module_name = module_name self.executed = False self.lock = thread.allocate_lock() - def can_be_executed_by(self, thread_id): if self.thread_id == '*': - return True #Any thread can execute it! + return True # Any thread can execute it! return InternalThreadCommand.can_be_executed_by(self, thread_id) - def do_it(self, dbg): self.lock.acquire() try: @@ -512,7 +575,7 @@ def __init__(self, seq, thread_id, py_db, set_additional_thread_info, timeout=.5 @overrides(InternalThreadCommand.can_be_executed_by) def can_be_executed_by(self, _thread_id): timed_out = time.time() >= self._timeout - + py_db = self._py_db() t = pydevd_find_thread_by_id(self.thread_id) frame = None @@ -525,7 +588,7 @@ def can_be_executed_by(self, _thread_id): finally: frame = None t = None - + return self._cmd is not None or timed_out @overrides(InternalThreadCommand.do_it) @@ -552,6 +615,7 @@ def do_it(self, dbg): # InternalStepThread #======================================================================================================================= class InternalStepThread(InternalThreadCommand): + def __init__(self, thread_id, cmd_id): self.thread_id = thread_id self.cmd_id = cmd_id @@ -567,6 +631,7 @@ def do_it(self, dbg): # InternalSetNextStatementThread #======================================================================================================================= class InternalSetNextStatementThread(InternalThreadCommand): + def __init__(self, thread_id, cmd_id, line, func_name, seq=0): self.thread_id = thread_id self.cmd_id = cmd_id @@ -595,6 +660,7 @@ def do_it(self, dbg): #======================================================================================================================= class InternalGetVariable(InternalThreadCommand): """ gets the value of a variable """ + def __init__(self, seq, thread_id, frame_id, scope, attrs): self.sequence = seq self.thread_id = thread_id @@ -636,6 +702,7 @@ def do_it(self, dbg): # InternalGetArray #======================================================================================================================= class InternalGetArray(InternalThreadCommand): + def __init__(self, seq, roffset, coffset, rows, cols, format, thread_id, frame_id, scope, attrs): self.sequence = seq self.thread_id = thread_id @@ -653,18 +720,20 @@ def do_it(self, dbg): try: frame = pydevd_vars.find_frame(self.thread_id, self.frame_id) var = pydevd_vars.eval_in_context(self.name, frame.f_globals, frame.f_locals) - xml = pydevd_vars.table_like_struct_to_xml(var, self.name, self.roffset, self.coffset, self.rows, self.cols, self.format ) + xml = pydevd_vars.table_like_struct_to_xml(var, self.name, self.roffset, self.coffset, self.rows, self.cols, self.format) cmd = dbg.cmd_factory.make_get_array_message(self.sequence, xml) dbg.writer.add_command(cmd) except: cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error resolving array: " + get_exception_traceback_str()) dbg.writer.add_command(cmd) + #======================================================================================================================= # InternalChangeVariable #======================================================================================================================= class InternalChangeVariable(InternalThreadCommand): """ changes the value of a variable """ + def __init__(self, seq, thread_id, frame_id, scope, attr, expression): self.sequence = seq self.thread_id = thread_id @@ -692,6 +761,7 @@ def do_it(self, dbg): #======================================================================================================================= class InternalGetFrame(InternalThreadCommand): """ gets the value of a variable """ + def __init__(self, seq, thread_id, frame_id): self.sequence = seq self.thread_id = thread_id @@ -710,19 +780,21 @@ def do_it(self, dbg): cmd = dbg.cmd_factory.make_get_frame_message(self.sequence, xml) dbg.writer.add_command(cmd) else: - #pydevd_vars.dump_frames(self.thread_id) - #don't print this error: frame not found: means that the client is not synchronized (but that's ok) + # pydevd_vars.dump_frames(self.thread_id) + # don't print this error: frame not found: means that the client is not synchronized (but that's ok) cmd = dbg.cmd_factory.make_error_message(self.sequence, "Frame not found: %s from thread: %s" % (self.frame_id, self.thread_id)) dbg.writer.add_command(cmd) except: cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error resolving frame: %s from thread: %s" % (self.frame_id, self.thread_id)) dbg.writer.add_command(cmd) + #======================================================================================================================= # InternalGetNextStatementTargets #======================================================================================================================= class InternalGetNextStatementTargets(InternalThreadCommand): """ gets the valid line numbers for use with set next statement """ + def __init__(self, seq, thread_id, frame_id): self.sequence = seq self.thread_id = thread_id @@ -758,6 +830,7 @@ def do_it(self, dbg): cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error resolving frame: %s from thread: %s" % (self.frame_id, self.thread_id)) dbg.writer.add_command(cmd) + #======================================================================================================================= # InternalEvaluateExpression #======================================================================================================================= @@ -789,37 +862,59 @@ def do_it(self, dbg): cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error evaluating expression " + exc) dbg.writer.add_command(cmd) + #======================================================================================================================= # InternalGetCompletions #======================================================================================================================= class InternalGetCompletions(InternalThreadCommand): """ Gets the completions in a given scope """ - def __init__(self, seq, thread_id, frame_id, act_tok): + def __init__(self, seq, thread_id, frame_id, act_tok, line=-1, column=-1): + ''' + Note that if the column is >= 0, the act_tok is considered text and the actual + activation token/qualifier is computed in this command. + ''' self.sequence = seq self.thread_id = thread_id self.frame_id = frame_id self.act_tok = act_tok - + self.line = line + self.column = column def do_it(self, dbg): """ Converts request into completions """ try: remove_path = None try: + act_tok = self.act_tok + qualifier = u'' + if self.column >= 0: + token_and_qualifier = extract_token_and_qualifier(act_tok, self.line, self.column) + act_tok = token_and_qualifier[0] + if act_tok: + act_tok += u'.' + qualifier = token_and_qualifier[1] frame = pydevd_vars.find_frame(self.thread_id, self.frame_id) if frame is not None: - - msg = _pydev_completer.generate_completions_as_xml(frame, self.act_tok) - - cmd = dbg.cmd_factory.make_get_completions_message(self.sequence, msg) + if IS_PY2: + if not isinstance(act_tok, bytes): + act_tok = act_tok.encode('utf-8') + if not isinstance(qualifier, bytes): + qualifier = qualifier.encode('utf-8') + + completions = _pydev_completer.generate_completions(frame, act_tok) + + # Note that qualifier and start are only actually valid for the + # Debug Adapter Protocol (for the line-based protocol, the IDE + # is required to filter the completions returned). + cmd = dbg.cmd_factory.make_get_completions_message( + self.sequence, completions, qualifier, start=self.column-len(qualifier)) dbg.writer.add_command(cmd) else: cmd = dbg.cmd_factory.make_error_message(self.sequence, "InternalGetCompletions: Frame not found: %s from thread: %s" % (self.frame_id, self.thread_id)) dbg.writer.add_command(cmd) - finally: if remove_path is not None: sys.path.remove(remove_path) @@ -865,6 +960,7 @@ def do_it(self, dbg): #======================================================================================================================= class InternalGetBreakpointException(InternalThreadCommand): """ Send details of exception raised while evaluating conditional breakpoint """ + def __init__(self, thread_id, exc_type, stacktrace): self.sequence = 0 self.thread_id = thread_id @@ -902,6 +998,7 @@ def do_it(self, dbg): class InternalSendCurrExceptionTrace(InternalThreadCommand): """ Send details of the exception that was caught and where we've broken in. """ + def __init__(self, thread_id, arg, curr_frame_id): ''' :param arg: exception type, description, traceback object @@ -922,12 +1019,14 @@ def do_it(self, dbg): cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error Sending Current Exception Trace: " + exc) dbg.writer.add_command(cmd) + #======================================================================================================================= # InternalSendCurrExceptionTraceProceeded #======================================================================================================================= class InternalSendCurrExceptionTraceProceeded(InternalThreadCommand): """ Send details of the exception that was caught and where we've broken in. """ + def __init__(self, thread_id): self.sequence = 0 self.thread_id = thread_id @@ -991,6 +1090,7 @@ def do_it(self, dbg): class InternalRunCustomOperation(InternalThreadCommand): """ Run a custom command on an expression """ + def __init__(self, seq, thread_id, frame_id, scope, attrs, style, encoded_code_or_file, fnname): self.sequence = seq self.thread_id = thread_id @@ -1020,6 +1120,7 @@ def do_it(self, dbg): class InternalConsoleGetCompletions(InternalThreadCommand): """ Fetch the completions in the debug console """ + def __init__(self, seq, thread_id, frame_id, act_tok): self.sequence = seq self.thread_id = thread_id @@ -1056,7 +1157,7 @@ def do_it(self, dbg): """ Converts request into python variable """ try: try: - #don't trace new threads created by console command + # don't trace new threads created by console command disable_trace_thread_modules() result = pydevconsole.console_exec(self.thread_id, self.frame_id, self.expression, dbg) @@ -1084,6 +1185,7 @@ class InternalLoadFullValue(InternalThreadCommand): """ Loads values asynchronously """ + def __init__(self, seq, thread_id, frame_id, vars): self.sequence = seq self.thread_id = thread_id @@ -1119,6 +1221,7 @@ class AbstractGetValueAsyncThread(PyDBDaemonThread): """ Abstract class for a thread, which evaluates values for async variables """ + def __init__(self, frame_accessor, seq, var_objects): PyDBDaemonThread.__init__(self) self.frame_accessor = frame_accessor @@ -1149,6 +1252,7 @@ class GetValueAsyncThreadDebug(AbstractGetValueAsyncThread): A thread for evaluation async values, which returns result for debugger Create message and send it via writer thread """ + def send_result(self, xml): if self.frame_accessor is not None: cmd = self.frame_accessor.cmd_factory.make_load_full_value_message(self.seq, xml.getvalue()) @@ -1160,6 +1264,7 @@ class GetValueAsyncThreadConsole(AbstractGetValueAsyncThread): A thread for evaluation async values, which returns result for Console Send result directly to Console's server """ + def send_result(self, xml): if self.frame_accessor is not None: self.frame_accessor.ReturnFullValue(self.seq, xml.getvalue()) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py index b07e61fb4..465ccd67f 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py @@ -455,6 +455,10 @@ def get_protocol(): return _GlobalSettings.protocol +def is_json_protocol(): + return _GlobalSettings.protocol in (JSON_PROTOCOL, HTTP_JSON_PROTOCOL) + + class GlobalDebuggerHolder: ''' Holder for the global debugger. diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace_files.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace_files.py index d8da35dc4..8236dcea6 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace_files.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace_files.py @@ -99,6 +99,7 @@ 'pydevd_plugin_utils.py': PYDEV_FILE, 'pydevd_plugins_django_form_str.py': PYDEV_FILE, 'pydevd_process_net_command.py': PYDEV_FILE, + 'pydevd_process_net_command_json.py': PYDEV_FILE, 'pydevd_referrers.py': PYDEV_FILE, 'pydevd_reload.py': PYDEV_FILE, 'pydevd_resolver.py': PYDEV_FILE, diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py index b5c0f4187..54d0f5cc8 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py @@ -1,11 +1,13 @@ -from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory -from _pydevd_bundle.pydevd_comm_constants import CMD_THREAD_CREATE, CMD_RETURN -from _pydevd_bundle.pydevd_net_command import NetCommand +from _pydev_bundle._pydev_imports_tipper import TYPE_IMPORT, TYPE_CLASS, TYPE_FUNCTION, TYPE_ATTR, \ + TYPE_BUILTIN, TYPE_PARAM +from _pydev_bundle.pydev_is_thread_alive import is_thread_alive +from _pydev_bundle.pydev_override import overrides from _pydevd_bundle._debug_adapter import pydevd_schema +from _pydevd_bundle.pydevd_comm_constants import CMD_THREAD_CREATE, CMD_RETURN from _pydevd_bundle.pydevd_constants import get_thread_id -from _pydev_bundle.pydev_override import overrides +from _pydevd_bundle.pydevd_net_command import NetCommand +from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory from _pydevd_bundle.pydevd_utils import get_non_pydevd_threads -from _pydev_bundle.pydev_is_thread_alive import is_thread_alive class NetCommandFactoryJson(NetCommandFactory): @@ -44,3 +46,31 @@ def make_list_threads_message(self, seq): request_seq=seq, success=True, command='threads', body=body) return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True) + + @overrides(NetCommandFactory.make_get_completions_message) + def make_get_completions_message(self, seq, completions, qualifier, start): + COMPLETION_TYPE_LOOK_UP = { + TYPE_IMPORT: pydevd_schema.CompletionItemType.MODULE, + TYPE_CLASS: pydevd_schema.CompletionItemType.CLASS, + TYPE_FUNCTION: pydevd_schema.CompletionItemType.FUNCTION, + TYPE_ATTR: pydevd_schema.CompletionItemType.FIELD, + TYPE_BUILTIN: pydevd_schema.CompletionItemType.KEYWORD, + TYPE_PARAM: pydevd_schema.CompletionItemType.VARIABLE, + } + + qualifier = qualifier.lower() + qualifier_len = len(qualifier) + targets = [] + for completion in completions: + label = completion[0] + if label.lower().startswith(qualifier): + completion = pydevd_schema.CompletionItem( + label=label, type=COMPLETION_TYPE_LOOK_UP[completion[3]], start=start, length=qualifier_len) + targets.append(completion.to_dict()) + + + body = pydevd_schema.CompletionsResponseBody(targets) + response = pydevd_schema.CompletionsResponse( + request_seq=seq, success=True, command='completions', body=body) + return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True) + diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py index 98515ecc7..3ec624364 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py @@ -27,6 +27,7 @@ from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame import pydevd_file_utils from pydevd_tracing import get_exception_traceback_str +from _pydev_bundle._pydev_completer import completions_to_xml if IS_IRONPYTHON: @@ -325,8 +326,9 @@ def make_evaluate_expression_message(self, seq, payload): except Exception: return self.make_error_message(seq, get_exception_traceback_str()) - def make_get_completions_message(self, seq, payload): + def make_get_completions_message(self, seq, completions, qualifier, start): try: + payload = completions_to_xml(completions) return NetCommand(CMD_GET_COMPLETIONS, seq, payload) except Exception: return self.make_error_message(seq, get_exception_traceback_str()) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py new file mode 100644 index 000000000..6011f1dd3 --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py @@ -0,0 +1,65 @@ +from _pydevd_bundle._debug_adapter import pydevd_base_schema +import json +from _pydevd_bundle.pydevd_comm import InternalGetCompletions + + +class _PyDevJsonCommandProcessor(object): + + def __init__(self, from_json): + self.from_json = from_json + + 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 not None: + if DEBUG: + print('Handled in pydevd: %s (in _PyDevdCommandProcessor).\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() + + else: + print('Unhandled: %s not available in _PyDevdCommandProcessor.\n' % (method_name,)) + + 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 + + int_cmd = InternalGetCompletions(seq, thread_id, frame_id, text, line=line, column=column) + py_db.post_internal_command(int_cmd, thread_id) + + +process_net_command_json = _PyDevJsonCommandProcessor(pydevd_base_schema.from_json).process_net_command_json diff --git a/src/ptvsd/_vendored/pydevd/pydevconsole.py b/src/ptvsd/_vendored/pydevd/pydevconsole.py index 7731781a9..df2456d2b 100644 --- a/src/ptvsd/_vendored/pydevd/pydevconsole.py +++ b/src/ptvsd/_vendored/pydevd/pydevconsole.py @@ -310,7 +310,9 @@ def process_exec_queue(interpreter): if 'IPYTHONENABLE' in os.environ: IPYTHON = os.environ['IPYTHONENABLE'] == 'True' else: - IPYTHON = True + # By default, don't use IPython because occasionally changes + # in IPython break pydevd. + IPYTHON = False try: try: diff --git a/src/ptvsd/_vendored/pydevd/tests_python/debugger_unittest.py b/src/ptvsd/_vendored/pydevd/tests_python/debugger_unittest.py index c3fac9656..dcbca3305 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/debugger_unittest.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/debugger_unittest.py @@ -494,6 +494,13 @@ def _ignore_stderr_line(self, line): )): return True + for expected in ( + 'PyDev console: using IPython', + 'Attempting to work in a virtualenv. If you encounter problems, please', + ): + if expected in line: + return True + if re.match(r'^(\d+)\t(\d)+', line): return True @@ -509,6 +516,8 @@ def _ignore_stderr_line(self, line): 'from _pydevd_bundle.pydevd_additional_thread_info_regular import _current_frames', 'import org.python.core as PyCore #@UnresolvedImport', 'from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info', + "RuntimeWarning: Parent module '_pydevd_bundle._debug_adapter' not found while handling absolute import", + 'import json', ): if expected in line: return True @@ -573,6 +582,22 @@ def do_kill(self): if hasattr(self, 'sock'): self.sock.close() + def write_with_content_len(self, msg): + self.log.append('write: %s' % (msg,)) + + if SHOW_WRITES_AND_READS: + print('Test Writer Thread Written %s' % (msg,)) + + if not hasattr(self, 'sock'): + print('%s.sock not available when sending: %s' % (self, msg)) + return + + if not isinstance(msg, bytes): + msg = msg.encode('utf-8') + + self.sock.sendall((u'Content-Length: %s\r\n\r\n' % len(msg)).encode('ascii')) + self.sock.sendall(msg) + def write(self, s): from _pydevd_bundle.pydevd_comm import ID_TO_MEANING meaning = ID_TO_MEANING.get(re.search(r'\d+', s).group(), '') diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py index 16f3d7717..b6e6513a7 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py @@ -26,6 +26,11 @@ def accept_json_message(msg): msg = self.writer.wait_for_message(accept_json_message, unquote_msg=False, expect_xml=False) return from_json(msg) + def write_request(self, request): + seq = self.writer.next_seq() + request.seq = seq + self.writer.write_with_content_len(request.to_json()) + @pytest.mark.skipif(IS_JYTHON, reason='Must check why it is failing in Jython.') def test_case_json_protocol(case_setup): @@ -51,3 +56,41 @@ def test_case_json_protocol(case_setup): writer.finished_ok = True + +def test_case_completions_json(case_setup): + from _pydevd_bundle._debug_adapter import pydevd_schema + with case_setup.test_file('_debugger_case_print.py') as writer: + json_facade = JsonFacade(writer) + + writer.write_set_protocol('http_json') + writer.write_add_breakpoint(writer.get_line_index_with_content('Break here')) + writer.write_make_initial_run() + + hit = writer.wait_for_breakpoint_hit() + thread_id = hit.thread_id + frame_id = hit.frame_id + + completions_arguments = pydevd_schema.CompletionsArguments( + 'dict.', 6, frameId=(thread_id, frame_id), line=0) + completions_request = pydevd_schema.CompletionsRequest(completions_arguments) + json_facade.write_request(completions_request) + + response = json_facade.wait_for_json_message(pydevd_schema.CompletionsResponse) + labels = [x['label'] for x in response.body.targets] + assert set(labels).issuperset(set(['__contains__', 'items', 'keys', 'values'])) + + completions_arguments = pydevd_schema.CompletionsArguments( + 'dict.item', 10, frameId=(thread_id, frame_id)) + completions_request = pydevd_schema.CompletionsRequest(completions_arguments) + json_facade.write_request(completions_request) + + response = json_facade.wait_for_json_message(pydevd_schema.CompletionsResponse) + labels = [x['label'] for x in response.body.targets] + assert labels == ['items'] + + texts = [x['text'] for x in response.body.targets] + assert texts == ['s'] + + writer.write_run_thread(thread_id) + + writer.finished_ok = True diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_extract_token.py b/src/ptvsd/_vendored/pydevd/tests_python/test_extract_token.py new file mode 100644 index 000000000..9f5509adf --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_extract_token.py @@ -0,0 +1,54 @@ +# coding: utf-8 +from __future__ import unicode_literals +from _pydev_bundle._pydev_completer import (isidentifier, extract_token_and_qualifier, + TokenAndQualifier) +from _pydevd_bundle.pydevd_constants import IS_PY2 + + +def test_isidentifier(): + assert isidentifier('abc') + assert not isidentifier('<') + assert not isidentifier('') + if IS_PY2: + # Py3 accepts unicode identifiers + assert not isidentifier('áéíóú') + else: + assert isidentifier('áéíóú') + + +def test_extract_token_and_qualifier(): + + assert extract_token_and_qualifier('tok', 0, 0) == TokenAndQualifier('', '') + assert extract_token_and_qualifier('tok', 0, 1) == TokenAndQualifier('', 't') + assert extract_token_and_qualifier('tok', 0, 2) == TokenAndQualifier('', 'to') + assert extract_token_and_qualifier('tok', 0, 3) == TokenAndQualifier('', 'tok') + assert extract_token_and_qualifier('tok', 0, 4) == TokenAndQualifier('', 'tok') + + assert extract_token_and_qualifier('tok.qual', 0, 0) == TokenAndQualifier('', '') + assert extract_token_and_qualifier('tok.qual', 0, 1) == TokenAndQualifier('', 't') + assert extract_token_and_qualifier('tok.qual', 0, 2) == TokenAndQualifier('', 'to') + assert extract_token_and_qualifier('tok.qual', 0, 3) == TokenAndQualifier('', 'tok') + + assert extract_token_and_qualifier('tok.qual', 0, 4) == TokenAndQualifier('tok', '') + assert extract_token_and_qualifier('tok.qual', 0, 5) == TokenAndQualifier('tok', 'q') + assert extract_token_and_qualifier('tok.qual', 0, 6) == TokenAndQualifier('tok', 'qu') + assert extract_token_and_qualifier('tok.qual', 0, 7) == TokenAndQualifier('tok', 'qua') + assert extract_token_and_qualifier('tok.qual', 0, 8) == TokenAndQualifier('tok', 'qual') + + # out of range (column) + assert extract_token_and_qualifier('tok.qual.qual2', 0, 100) == TokenAndQualifier('tok.qual', 'qual2') + + assert extract_token_and_qualifier('t