Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Adds debug console completion #772

Merged
merged 10 commits into from
Aug 29, 2018
1 change: 1 addition & 0 deletions debugger_protocol/messages/_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class Capabilities(FieldsNamespace):
Field('supportsSetExpression', bool),
Field('supportsModulesRequest', bool),
Field('supportsDebuggerProperties', bool),
Field('supportsCompletionsRequest', bool),
]


Expand Down
46 changes: 46 additions & 0 deletions ptvsd/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@
# print(s)
#ipcjson._TRACE = ipcjson_trace

#completion types.
TYPE_IMPORT = '0'
TYPE_CLASS = '1'
TYPE_FUNCTION = '2'
TYPE_ATTR = '3'
TYPE_BUILTIN = '4'
TYPE_PARAM = '5'


def NOOP(*args, **kwargs):
pass
Expand Down Expand Up @@ -952,6 +960,7 @@ def _stop_event_loop(self):


INITIALIZE_RESPONSE = dict(
supportsCompletionsRequest=True,
supportsConditionalBreakpoints=True,
supportsConfigurationDoneRequest=True,
supportsDebuggerProperties=True,
Expand Down Expand Up @@ -2222,6 +2231,43 @@ def on_exceptionInfo(self, request, args):
'source': source},
)

@async_handler
def on_completions(self, request, args):
type_look_up = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be outside of the function, so that the dict is not re-created for every new request.

TYPE_IMPORT: 'module',
TYPE_CLASS: 'class',
TYPE_FUNCTION: 'function',
TYPE_ATTR: 'field',
TYPE_BUILTIN: 'keyword',
TYPE_PARAM: 'variable',
}

text = args['text'] # required argument
vsc_fid = args.get('frameId', None)

try:
pyd_tid, pyd_fid = self.frame_map.to_pydevd(vsc_fid)
except KeyError:
self.send_error_response(request)

cmd_args = '{}\t{}\t{}\t{}'.format(pyd_tid, pyd_fid, 'LOCAL', text)
_, _, resp_args = yield self.pydevd_request(
pydevd_comm.CMD_GET_COMPLETIONS,
cmd_args)

xml = self.parse_xml_response(resp_args)
targets = []
for item in list(xml.comp):
target = {}
target['label'] = unquote(item['p0'])
try:
target['type'] = type_look_up[item['p3']]
except KeyError:
pass
targets.append(target)

self.send_response(request, targets=targets)

# Custom ptvsd message
def on_ptvsd_systemInfo(self, request, args):
try:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import sys
import ptvsd

ptvsd.enable_attach((sys.argv[1], sys.argv[2]))
ptvsd.wait_for_attach()


class SomeClass():
def __init__(self, someVar):
self.some_var = someVar

def do_someting(self):
someVariable = self.some_var
return someVariable


def someFunction(someVar):
someVariable = someVar
return SomeClass(someVariable).do_someting()


someFunction('value')
print('done')
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class SomeClass():
def __init__(self, someVar):
self.some_var = someVar

def do_someting(self):
someVariable = self.some_var
return someVariable


def someFunction(someVar):
someVariable = someVar
return SomeClass(someVariable).do_someting()


someFunction('value')
print('done')
280 changes: 280 additions & 0 deletions tests/system_tests/test_completions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
import os
import os.path

from tests.helpers.resource import TestResources
from . import (
lifecycle_handshake, LifecycleTestsBase, DebugInfo, PORT,
)


TEST_FILES = TestResources.from_module(__name__)


class CompletionsTests(LifecycleTestsBase):

def run_test_completions(self, debug_info, bp_filename, bp_line, expected):
pathMappings = []
# Required to ensure sourceReference = 0
if (debug_info.starttype == 'attach'):
pathMappings.append({
'localRoot': debug_info.cwd,
'remoteRoot': debug_info.cwd
})
options = {
'debugOptions': ['RedirectOutput'],
'pathMappings': pathMappings
}
breakpoints = [{
'source': {
'path': bp_filename
},
'breakpoints': [{
'line': bp_line
}]
}]

with self.start_debugging(debug_info) as dbg:
session = dbg.session
with session.wait_for_event('stopped') as result:
(_, req_launch_attach, _, _, _, _,
) = lifecycle_handshake(session, debug_info.starttype,
options=options,
breakpoints=breakpoints)
req_launch_attach.wait()

event = result['msg']
tid = event.body['threadId']

req_stacktrace = session.send_request(
'stackTrace',
threadId=tid,
)
req_stacktrace.wait()
frames = req_stacktrace.resp.body['stackFrames']
frame_id = frames[0]['id']

req_completions = session.send_request(
'completions',
text='some',
frameId=int(frame_id)
)
req_completions.wait(timeout=2.0)
targets = req_completions.resp.body['targets']

session.send_request(
'continue',
threadId=tid,
)

self.assertEqual(targets, expected)

def run_test_outermost_scope(self, debug_info, filename, line):
self.run_test_completions(
debug_info,
bp_filename=filename,
bp_line=line,
expected=[
{
'label': 'SomeClass',
'type': 'class'
},
{
'label': 'someFunction',
'type': 'function'
}
]
)

def run_test_in_function(self, debug_info, filename, line):
self.run_test_completions(
debug_info,
bp_filename=filename,
bp_line=line,
expected=[
{
'label': 'SomeClass',
'type': 'class'
},
{
'label': 'someFunction',
'type': 'function'
},
{
'label': 'someVar',
'type': 'field'
},
{
'label': 'someVariable',
'type': 'field'
}
]
)

def run_test_in_method(self, debug_info, filename, line):
self.run_test_completions(
debug_info,
bp_filename=filename,
bp_line=line,
expected=[
{
'label': 'SomeClass',
'type': 'class'
},
{
'label': 'someFunction',
'type': 'function'
},
{
'label': 'someVariable',
'type': 'field'
}
]
)


class LaunchFileTests(CompletionsTests):
def _get_debug_info(self):
filename = TEST_FILES.resolve('launch_completions.py')
cwd = os.path.dirname(filename)
return DebugInfo(filename=filename, cwd=cwd)

def test_outermost_scope(self):
debug_info = self._get_debug_info()
self.run_test_outermost_scope(debug_info, debug_info.filename, 16)

def test_in_function(self):
debug_info = self._get_debug_info()
self.run_test_in_function(debug_info, debug_info.filename, 12)

def test_in_method(self):
debug_info = self._get_debug_info()
self.run_test_in_method(debug_info, debug_info.filename, 7)


class LaunchModuleTests(CompletionsTests):
def _get_debug_info(self):
module_name = 'launch_completions'
filename = TEST_FILES.resolve('launch_completions.py')
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
return DebugInfo(modulename=module_name, cwd=cwd, env=env), filename

def test_outermost_scope(self):
debug_info, filename = self._get_debug_info()
self.run_test_outermost_scope(debug_info, filename, 16)

def test_in_function(self):
debug_info, filename = self._get_debug_info()
self.run_test_in_function(debug_info, filename, 12)

def test_in_method(self):
debug_info, filename = self._get_debug_info()
self.run_test_in_method(debug_info, filename, 7)


class ServerAttachTests(CompletionsTests):
def _get_debug_info(self):
filename = TEST_FILES.resolve('launch_completions.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
return DebugInfo(
filename=filename,
cwd=cwd,
starttype='attach',
argv=argv,
), filename

def test_outermost_scope(self):
debug_info, filename = self._get_debug_info()
self.run_test_outermost_scope(debug_info, filename, 16)

def test_in_function(self):
debug_info, filename = self._get_debug_info()
self.run_test_in_function(debug_info, filename, 12)

def test_in_method(self):
debug_info, filename = self._get_debug_info()
self.run_test_in_method(debug_info, filename, 7)


class ServerAttachModuleTests(CompletionsTests):
def _get_debug_info(self):
module_name = 'launch_completions'
filename = TEST_FILES.resolve('launch_completions.py')
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
argv = ['localhost', str(PORT)]
return DebugInfo(
modulename=module_name,
env=env,
cwd=cwd,
argv=argv,
starttype='attach',
), filename

def test_outermost_scope(self):
debug_info, filename = self._get_debug_info()
self.run_test_outermost_scope(debug_info, filename, 16)

def test_in_function(self):
debug_info, filename = self._get_debug_info()
self.run_test_in_function(debug_info, filename, 12)

def test_in_method(self):
debug_info, filename = self._get_debug_info()
self.run_test_in_method(debug_info, filename, 7)


class PTVSDAttachTests(CompletionsTests):
def _get_debug_info(self):
filename = TEST_FILES.resolve('attach_completions.py')
cwd = os.path.dirname(filename)
argv = ['localhost', str(PORT)]
return DebugInfo(
filename=filename,
attachtype='import',
cwd=cwd,
starttype='attach',
argv=argv,
), filename

def test_outermost_scope(self):
debug_info, filename = self._get_debug_info()
self.run_test_outermost_scope(debug_info, filename, 23)

def test_in_function(self):
debug_info, filename = self._get_debug_info()
self.run_test_in_function(debug_info, filename, 19)

def test_in_method(self):
debug_info, filename = self._get_debug_info()
self.run_test_in_method(debug_info, filename, 14)


class PTVSDAttachModuleTests(CompletionsTests):
def _get_debug_info(self):
filename = TEST_FILES.resolve('attach_completions.py')
module_name = 'attach_completions'
env = TEST_FILES.env_with_py_path()
cwd = TEST_FILES.root
argv = ['localhost', str(PORT)]
return DebugInfo(
modulename=module_name,
env=env,
cwd=cwd,
argv=argv,
attachtype='import',
starttype='attach',
), filename

def test_outermost_scope(self):
debug_info, filename = self._get_debug_info()
self.run_test_outermost_scope(debug_info, filename, 23)

def test_in_function(self):
debug_info, filename = self._get_debug_info()
self.run_test_in_function(debug_info, filename, 19)

def test_in_method(self):
debug_info, filename = self._get_debug_info()
self.run_test_in_method(debug_info, filename, 14)