diff --git a/doc/Vdebug.txt b/doc/Vdebug.txt index f7f30f0a..495d8830 100644 --- a/doc/Vdebug.txt +++ b/doc/Vdebug.txt @@ -206,7 +206,7 @@ it as a zend extension. This can be done in the PHP INI file, but in Ubuntu I can add a new file to /etc/php5/conf.d/ that contains all the configuration options and gets loaded automatically by PHP. -Add these options to the INI file: > +For Xdebug v2, add these options to the INI file: > zend_extension=/path/to/xdebug.so xdebug.remote_enable=on @@ -214,6 +214,16 @@ Add these options to the INI file: > xdebug.remote_host=localhost xdebug.remote_port=9000 < + +For Xdebug v3, add these options to the INI file: > + + zend_extension=/path/to/xdebug.so + xdebug.mode=debug + xdebug.client_host=localhost + xdebug.client_port=9000 +< +Please refer to https://xdebug.org/docs/upgrade_guide for instructions on how to upgrade from v2 to v3. + If using Apache, restart it to enable the new library. The command line interface should be ready to go - type "php -v" and you should see the line > with Xdebug v2.2.0, Copyright (c) 2002-2012, by Derick Rethans diff --git a/plugin/vdebug.vim b/plugin/vdebug.vim index 7c605d5a..216fe681 100644 --- a/plugin/vdebug.vim +++ b/plugin/vdebug.vim @@ -79,6 +79,8 @@ let g:vdebug_options_defaults = { \ 'port' : 9000, \ 'timeout' : 20, \ 'server' : '', +\ "proxy_host" : '', +\ "proxy_port" : 9001, \ 'on_close' : 'stop', \ 'break_on_open' : 1, \ 'ide_key' : '', diff --git a/python3/vdebug/connection.py b/python3/vdebug/connection.py index 89b34dad..f7494e23 100644 --- a/python3/vdebug/connection.py +++ b/python3/vdebug/connection.py @@ -5,6 +5,7 @@ import threading import time import asyncio +import xml.etree.ElementTree as ET from . import log @@ -103,13 +104,17 @@ def __init__(self, input_stream=None): """ self.__sock = None self.input_stream = input_stream + self.proxy_success = False - def start(self, host='', port=9000, timeout=30): + def start(self, host='', proxy_host = '', proxy_port = 9001, idekey = None, port=9000, timeout=30): """Listen for a connection from the debugger. Listening for the actual connection is handled by self.listen() host -- host name where debugger is running (default '') port -- port number which debugger is listening on (default 9000) + proxy_host -- If using a DBGp Proxy, host name where the proxy is running (default None to disable) + proxy_port -- If using a DBGp Proxy, port where the proxy is listening for debugger connections (default 9001) + idekey -- The idekey that our Api() wrapper is expecting. Only required if using a proxy timeout -- time in seconds to wait for a debugger connection before giving up (default 30) """ print('Waiting for a connection (Ctrl-C to cancel, this message will ' @@ -120,13 +125,17 @@ def start(self, host='', port=9000, timeout=30): serv.setblocking(1) serv.bind((host, port)) serv.listen(5) - self.__sock = self.listen(serv, timeout) + if proxy_host and proxy_port: + # Register ourselves with the proxy server + self.proxyinit(proxy_host, proxy_port, port, idekey) + self.__sock = self.accept(serv, timeout) except socket.timeout: raise TimeoutError("Timeout waiting for connection") finally: + self.proxystop(proxy_host, proxy_port, idekey) serv.close() - def listen(self, serv, timeout): + def accept(self, serv, timeout): """Non-blocking listener. Provides support for keyboard interrupts from the user. Although it's non-blocking, the user interface will still block until the timeout is reached. @@ -155,13 +164,50 @@ def socket(self): def has_socket(self): return self.__sock is not None + def proxyinit(self, proxy_host, proxy_port, port, idekey): + """Register ourselves with the proxy.""" + if not proxy_host or not proxy_port: + return + + self.log("Connecting to DBGp proxy [%s:%d]" % (proxy_host, proxy_port)) + proxy_conn = socket.create_connection((proxy_host, proxy_port), 30) + + self.log("Sending proxyinit command") + msg = 'proxyinit -p %d -k %s -m 0' % (port, idekey) + proxy_conn.send(msg.encode()) + proxy_conn.shutdown(socket.SHUT_WR) + + # Parse proxy response + response = proxy_conn.recv(8192) + proxy_conn.close() + response = ET.fromstring(response) + self.proxy_success = bool(response.get("success")) + + def proxystop(self, proxy_host, proxy_port, idekey): + """De-register ourselves from the proxy.""" + if not self.proxy_success: + return + + proxy_conn = socket.create_connection((proxy_host, proxy_port), 30) + + self.log("Sending proxystop command") + msg = 'proxystop -k %s' % str(idekey) + proxy_conn.send(msg.encode()) + proxy_conn.close() + self.proxy_success = False + + class BackgroundSocketCreator(threading.Thread): - def __init__(self, host, port, output_q): + def __init__(self, host, port, proxy_host, proxy_port, idekey, output_q): self.__output_q = output_q self.__host = host self.__port = port + self.__proxy_host = proxy_host + self.__proxy_port = proxy_port + self.__idekey = idekey + self.proxy_success = False self.__socket_task = None self.__loop = None threading.Thread.__init__(self) @@ -189,6 +235,9 @@ async def run_async(self): try: # using ensure_future here since before 3.7, this is not a coroutine, but returns a future self.__socket_task = asyncio.ensure_future(self.__loop.sock_accept(s)) + if self.__proxy_host and self.__proxy_port: + # Register ourselves with the proxy server + await self.proxyinit() client, address = await self.__socket_task # set resulting socket to blocking client.setblocking(True) @@ -197,6 +246,7 @@ async def run_async(self): self.__output_q.put((client, address)) break except socket.error: + await self.proxystop() # No connection pass except socket.error as socket_error: @@ -214,9 +264,44 @@ async def run_async(self): self.log("Error: %s" % str(sys.exc_info())) self.log("Stopping server") finally: + await self.proxystop() self.log("Finishing socket server") s.close() + async def proxyinit(self): + """Register ourselves with the proxy.""" + if not self.__proxy_host or not self.__proxy_port: + return + + self.log("Connecting to DBGp proxy [%s:%d]" % (self.__proxy_host, self.__proxy_port)) + proxy_conn = socket.create_connection((self.__proxy_host, self.__proxy_port), 30) + + self.log("Sending proxyinit command") + msg = 'proxyinit -p %d -k %s -m 0' % (self.__port, self.__idekey) + proxy_conn.send(msg.encode()) + proxy_conn.shutdown(socket.SHUT_WR) + + # Parse proxy response + response = proxy_conn.recv(8192) + proxy_conn.close() + response = ET.fromstring(response) + self.proxy_success = bool(response.get("success")) + + async def proxystop(self): + """De-register ourselves from the proxy.""" + if not self.proxy_success: + return + + proxy_conn = socket.create_connection((self.__proxy_host, self.__proxy_port), 30) + + self.log("Sending proxystop command") + msg = 'proxystop -k %s' % str(self.__idekey) + proxy_conn.send(msg.encode()) + proxy_conn.close() + self.proxy_success = False + + + def _exit(self): if self.__socket_task: # this will raise asyncio.CancelledError @@ -236,10 +321,10 @@ def __init__(self): def __del__(self): self.stop() - def start(self, host, port): + def start(self, host, port, proxy_host, proxy_port, ide_key): if not self.is_alive(): self.__thread = BackgroundSocketCreator( - host, port, self.__socket_q) + host, port, proxy_host, proxy_port, ide_key, self.__socket_q) self.__thread.start() def is_alive(self): diff --git a/python3/vdebug/dbgp.py b/python3/vdebug/dbgp.py index b8f1a108..053f0186 100644 --- a/python3/vdebug/dbgp.py +++ b/python3/vdebug/dbgp.py @@ -181,7 +181,7 @@ def is_supported(self): def __str__(self): if self.is_supported(): xml = self.as_xml() - return xml.text + return xml.text if xml.text else "" return "* Feature not supported *" diff --git a/python3/vdebug/event.py b/python3/vdebug/event.py index 7e4acb3a..87842e4c 100644 --- a/python3/vdebug/event.py +++ b/python3/vdebug/event.py @@ -44,16 +44,16 @@ class CursorEvalEvent(Event): """Evaluate the variable currently under the cursor. """ char_regex = { - "default": "a-zA-Z0-9_.\[\]'\"", - "ruby": "$@a-zA-Z0-9_.\[\]'\"", + "default": "a-zA-Z0-9_.\\[\\]'\"", + "ruby": "$@a-zA-Z0-9_.\\[\\]'\"", "perl": "$a-zA-Z0-9_{}'\"", - "php": "$@%a-zA-Z0-9_\[\]'\"\->" + "php": "$@%a-zA-Z0-9_\\[\\]'\">-" } var_regex = { "default": "^[a-zA-Z_]", "ruby": "^[$@a-zA-Z_]", - "php": "^[\$A-Z]", + "php": r"^[\$A-Z]", "perl": "^[$@%]" } @@ -559,7 +559,7 @@ def run(self): line = self.ui.windows.breakpoints().line_at(lineno - 1) # Match on ID - id = re.findall('^[\s][0-9]*[\s]', line) + id = re.findall('^[\\s][0-9]*[\\s]', line) if not id: return False @@ -877,9 +877,9 @@ def _get_window_name(): @staticmethod def _get_breakpoint_id_breakpoint_window(line): # Match on ID - id = re.findall('^[\s][0-9]*[\s]', line) + id = re.findall(r'^[\s][0-9]*[\s]', line) if not id: - log.Log("No breakpoint founr at current cursor position", + log.Log("No breakpoint found at current cursor position", log.Logger.DEBUG) return False diff --git a/python3/vdebug/listener.py b/python3/vdebug/listener.py index 15775904..0c39ad2d 100644 --- a/python3/vdebug/listener.py +++ b/python3/vdebug/listener.py @@ -22,6 +22,9 @@ def __init__(self): def start(self): self.__server.start(opts.Options.get('server'), opts.Options.get('port', int), + opts.Options.get('proxy_host'), + opts.Options.get('proxy_port', int), + opts.Options.get('ide_key'), opts.Options.get('timeout', int)) def stop(self): @@ -51,7 +54,10 @@ def start(self): if opts.Options.get("auto_start", int): vim.command('autocmd Vdebug CursorHold,CursorHoldI,CursorMoved,CursorMovedI,FocusGained,FocusLost * python3 debugger.start_if_ready()') self.__server.start(opts.Options.get('server'), - opts.Options.get('port', int)) + opts.Options.get('port', int), + opts.Options.get('proxy_host'), + opts.Options.get('proxy_port', int), + opts.Options.get('ide_key')) def stop(self): if opts.Options.get("auto_start", bool): diff --git a/tests/test_dbgp_api.py b/tests/test_dbgp_api.py index 26382d57..4e998059 100644 --- a/tests/test_dbgp_api.py +++ b/tests/test_dbgp_api.py @@ -127,10 +127,10 @@ def test_stop_retval(self): assert str(status_res) == "stopping" def test_detatch_retval(self): - """Test that the detatch command receives a message from the api.""" + """Test that the detach command receives a message from the api.""" self.p.conn.recv_msg.return_value = """\n -