From 8e02a66989b7a8b29b72fc597ecfb1ebe36b50cb Mon Sep 17 00:00:00 2001 From: Henson Date: Sun, 7 Apr 2013 12:11:09 +0800 Subject: [PATCH] Tested on sublime3 --- Context.sublime-menu | 21 ++ Default (Windows).sublime-keymap | 6 +- Main.sublime-menu | 57 ++++++ Xdebug.py | 330 ++++++++++++++++--------------- Xdebug.sublime-settings | 3 +- 5 files changed, 257 insertions(+), 160 deletions(-) create mode 100644 Context.sublime-menu create mode 100644 Main.sublime-menu diff --git a/Context.sublime-menu b/Context.sublime-menu new file mode 100644 index 0000000..701b18f --- /dev/null +++ b/Context.sublime-menu @@ -0,0 +1,21 @@ +[ + { + "caption": "设置断点", + "command": "xdebug_breakpoint" + }, + { + "caption": "跳进断点", + "command": "xdebug_continue", + "args": {"state": "step_into"} + }, + { + "caption": "跳出断点", + "command": "xdebug_continue", + "args": {"state": "step_over"} + }, + { + "caption": "查看变量", + "command": "xdebug_watch" + }, + { "caption": "-", "id": "debugend" } +] diff --git a/Default (Windows).sublime-keymap b/Default (Windows).sublime-keymap index bfc1e98..a92d234 100644 --- a/Default (Windows).sublime-keymap +++ b/Default (Windows).sublime-keymap @@ -1,9 +1,7 @@ [ - {"keys": ["shift+f8"], "command": "xdebug" }, - {"keys": ["f8"], "command": "xdebug_continue" }, {"keys": ["ctrl+f8"], "command": "xdebug_breakpoint"}, {"keys": ["ctrl+shift+f5"], "command": "xdebug_continue", "args": {"state": "run"}}, - {"keys": ["ctrl+shift+f6"], "command": "xdebug_continue", "args": {"state": "step_over"}}, - {"keys": ["ctrl+shift+f7"], "command": "xdebug_continue", "args": {"state": "step_into"}}, + {"keys": ["f8"], "command": "xdebug_continue", "args": {"state": "step_over"}}, + {"keys": ["shift+f8"], "command": "xdebug_continue", "args": {"state": "step_into"}}, {"keys": ["ctrl+shift+f8"], "command": "xdebug_continue", "args": {"state": "step_out"}} ] diff --git a/Main.sublime-menu b/Main.sublime-menu new file mode 100644 index 0000000..faf3faf --- /dev/null +++ b/Main.sublime-menu @@ -0,0 +1,57 @@ +[ + { + "caption": "调试", + "mnemonic": "z", + "id": "Debug", + "children": + [ + { + "command": "xdebug_run_with_debug", + "caption": "运行并调试" + }, + { + "command": "xdebug_close_listen", + "caption": "停止调试" + }, + { + "id": "debugstart", + "caption": "-" + }, + { + "caption": "设置断点", + "command": "xdebug_breakpoint" + }, + { + "caption": "跳进断点", + "command": "xdebug_continue", + "args": {"state": "step_into"} + }, + { + "caption": "跳出断点", + "command": "xdebug_continue", + "args": {"state": "step_over"} + }, + { + "caption": "跳过断点", + "command": "xdebug_continue", + "args": {"state": "step_out"} + }, + { + "id": "debugend", + "caption": "-" + }, + { + "caption": "查看调试状态", + "command": "xdebug_status" + }, + { + "caption": "输入调试命令", + "command": "xdebug_execute" + }, + { + "caption": "清除所有断点", + "command": "xdebug_clear_all_breakpoints" + }, + ] + } +] diff --git a/Xdebug.py b/Xdebug.py index 5bf9fe8..791fbb4 100644 --- a/Xdebug.py +++ b/Xdebug.py @@ -9,16 +9,14 @@ import webbrowser from xml.dom.minidom import parseString - xdebug_current = None original_layout = None debug_view = None protocol = None buffers = {} -breakpoint_icon = '../Xdebug/icons/breakpoint' -current_icon = '../Xdebug/icons/current' -current_breakpoint_icon = '../Xdebug/icons/current_breakpoint' - +breakpoint_icon = 'Packages/SublimeXdebug/icons/breakpoint.png' +current_icon = 'Packages/SublimeXdebug/icons/current.png' +current_breakpoint_icon = 'Packages/SublimeXdebug/icons/current_breakpoint.png' class DebuggerException(Exception): pass @@ -76,12 +74,13 @@ def fdel(self): def read_until_null(self): if self.connected: - while not '\x00' in self.buffer: - self.buffer += self.sock.recv(self.read_rate) + while '\x00' not in self.buffer and self.sock: + bytes= self.sock.recv(self.read_rate) + self.buffer += bytes.decode("utf-8") data, self.buffer = self.buffer.split('\x00', 1) return data else: - raise(ProtocolConnectionException, "Not Connected") + raise ProtocolConnectionException("Not Connected") def read_data(self): length = self.read_until_null() @@ -89,11 +88,11 @@ def read_data(self): if int(length) == len(message): return message else: - raise(ProtocolException, "Length mismatch") + raise ProtocolException("Length mismatch") def read(self): data = self.read_data() - #print '<---', data + print('<---', data) document = parseString(data) return document @@ -115,49 +114,34 @@ def send(self, command, *args, **kwargs): command = ' '.join(parts) if data: command += ' -- ' + base64.b64encode(data) - try: - self.sock.send(command + '\x00') - #print '--->', command - except Exception, x: - raise(ProtocolConnectionException, x) + self.sock.send(command.encode('utf-8') + b'\x00') + print('--->', command) + except Exception as x: + raise ProtocolConnectionException(str(x)) def accept(self): - serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - if serv: - try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as serv: + if serv: serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - serv.settimeout(1) + serv.settimeout(10) serv.bind(('', self.port)) - serv.listen(1) + serv.listen(10) self.listening = True self.sock = None - except Exception, x: - raise(ProtocolConnectionException, x) - - while self.listening: - try: - self.sock, address = serv.accept() - self.listening = False - except socket.timeout: - pass - - if self.sock: - self.connected = True - self.sock.settimeout(None) - else: self.connected = False - self.listening = False - - try: - serv.close() - serv = None - except: - pass - return self.sock - else: - raise ProtocolConnectionException('Could not create socket') + while self.listening: + try: + self.sock, address = serv.accept() + self.listening = False + if self.sock: + print(address) + self.connected = True + self.sock.settimeout(None) + serv.close() + except socket.timeout: + pass + return self.sock class XdebugView(object): @@ -177,7 +161,7 @@ def __getattr__(self, attr): return getattr(self.view, attr) if attr.startswith('on_'): return self - raise(AttributeError, "%s does not exist" % attr) + raise AttributeError("%s does not exist" % attr) def __call__(self, *args, **kwargs): pass @@ -187,7 +171,7 @@ def center(self, lineno): self.view.show_at_center(line) def add_breakpoint(self, row): - if not row in self.breaks: + if row not in self.breaks: self.breaks[row] = {} if protocol and protocol.connected: protocol.send('breakpoint_set', t='line', f=self.uri(), n=row) @@ -204,8 +188,17 @@ def view_breakpoints(self): self.view.add_regions('xdebug_breakpoint', self.lines(self.breaks.keys()), get_setting('breakpoint_scope'), breakpoint_icon, sublime.HIDDEN) def breakpoint_init(self): + regions_key = ["xdebug_breakpoint", "xdebug_current_line"] + for key in regions_key: + _regions = self.view.get_regions(key) + for _region in _regions: + line = self.view.rowcol(_region.begin())[0] + 1 + print(line) + self.add_breakpoint(line) + if not self.breaks: return + uri = self.uri() for row in self.breaks: protocol.send('breakpoint_set', t='line', f=uri, n=row) @@ -215,37 +208,42 @@ def breakpoint_init(self): def breakpoint_clear(self): if not self.breaks: return - for row in self.breaks.keys(): + for row in list(self.breaks.keys()): self.del_breakpoint(row) + self.view.erase_regions("xdebug_breakpoint") + self.view.erase_regions("xdebug_current_line") def uri(self): return 'file://' + os.path.realpath(self.view.file_name()) def lines(self, data=None): + if (type(data) == type(1)) or (type(data) == type('')): + data = [data] + lines = [] if data is None: - regions = self.view.sel() + regions = list(self.view.sel()) else: - if type(data) != types.ListType: - data = [data] regions = [] for item in data: - if type(item) == types.IntType or item.isdigit(): + if type(item) == type(1) or item.isdigit(): regions.append(self.view.line(self.view.text_point(int(item) - 1, 0))) else: regions.append(item) + for region in regions: lines.extend(self.view.split_by_newlines(region)) + print("226 Lines: %s" % str([self.view.rowcol(line.begin())[0] + 1 for line in lines])) return [self.view.line(line) for line in lines] def rows(self, lines): - if not type(lines) == types.ListType: + if type(lines) != type([]): lines = [lines] return [self.view.rowcol(line.begin())[0] + 1 for line in lines] def append(self, content, edit=None, end=False): if not edit: - edit = self.view.begin_edit() + edit = self.view.begin_edit(0, "") end = True self.view.insert(edit, self.view.size(), content + "\n") if end: @@ -262,12 +260,12 @@ def current(self, line): self.current_line = line return region = self.lines(line) - icon = current_icon - + print("Line:%s" % line) + print(self.breaks.keys()) if line in self.breaks.keys(): - icon = current_breakpoint_icon - - self.add_regions('xdebug_current_line', region, get_setting('current_line_scope'), icon, sublime.HIDDEN) + self.add_regions('xdebug_current_line', region, get_setting('current_line_scope'), current_breakpoint_icon, sublime.DRAW_EMPTY_AS_OVERWRITE) + else: + self.add_regions('xdebug_current_line', region, get_setting('current_line_scope'), current_icon, sublime.HIDDEN) self.center(line) def add_context_data(self, propName, propType, propData): @@ -280,6 +278,8 @@ def on_selection_modified(self): ''' Show selected variable in an output panel when clicked ''' + + # if protocol and protocol.connected and self.context_data: data = '' point = self.view.sel()[0].a @@ -300,7 +300,7 @@ def on_selection_modified(self): window = self.view.window() if window: output = window.get_output_panel('xdebug_inspect') - edit = output.begin_edit() + edit = output.begin_edit(0, "") output.erase(edit, sublime.Region(0, output.size())) output.insert(edit, 0, data) output.end_edit(edit) @@ -326,11 +326,10 @@ def gui_callback(self): sublime.status_message('Xdebug: Connected') init = protocol.read().firstChild uri = init.getAttribute('fileuri') - #show_file(self.view.window(), uri) + show_file(self.view.window(), uri) for view in buffers.values(): view.breakpoint_init() - self.view.run_command('xdebug_continue', {'state': 'run'}) def is_enabled(self): @@ -338,6 +337,52 @@ def is_enabled(self): return False return True +class XdebugRunWithDebugCommand(sublime_plugin.TextCommand): + def run(self, edit): + self.view.run_command("xdebug_listen") + # url = get_project_setting('url') + # sublime.status_message('URL: {0}'. format(url)) + # if url and url.strip() != "": + # webbrowser.open(url + '?XDEBUG_SESSION_START=sublime.xdebug') + # else: + # sublime.status_message('Xdebug: No URL defined in project settings file.') + + global original_layout + global debug_view + self.view.window().run_command('hide_panel', {"panel": 'output.xdebug_inspect'}) + original_layout = self.view.window().get_layout() + debug_view = self.view.window().active_view() + self.view.window().set_layout({ + "cols": [0.0, 0.5, 1.0], + "rows": [0.0, 0.7, 1.0], + "cells": [[0, 0, 2, 1], [0, 1, 1, 2], [1, 1, 2, 2]] + }) + + def is_enabled(self): + if self.view.file_name().split(".")[-1] != "php" or protocol: + return False + return True + +class XdebugCloseListenCommand(sublime_plugin.TextCommand): + def run(self, edit): + self.view.run_command("xdebug_clear") + # url = get_project_setting('url') + # if url and url.strip() != "": + # webbrowser.open(url + '?XDEBUG_SESSION_STOP=sublime.xdebug') + # else: + # sublime.status_message('Xdebug: No URL defined in project settings file.') + self.view.window().run_command('hide_panel', {"panel": 'output.xdebug_inspect'}) + self.view.window().set_layout(original_layout) + self.view.window().run_command('set_layout', {"cols": [0.0, 1.0], "rows": [0.0, 1.0], "cells": [[0, 0, 1, 1]] }) + for v in self.view.window().views(): + if v.name() == "Xdebug Context" or v.name() == "Xdebug Stack": + self.view.window().focus_view(v) + self.view.window().run_command('close') + + def is_enabled(self): + if self.view.file_name().split(".")[-1] == "php" and protocol: + return True + return False class XdebugClearAllBreakpointsCommand(sublime_plugin.TextCommand): ''' @@ -362,67 +407,10 @@ def run(self, edit): view.add_breakpoint(row) view.view_breakpoints() - -class XdebugCommand(sublime_plugin.TextCommand): - ''' - The Xdebug main quick panel menu - ''' - def run(self, edit): - mapping = { - 'xdebug_breakpoint': 'Add/Remove Breakpoint', - 'xdebug_clear_all_breakpoints': 'Clear all Breakpoints', - } - - if protocol: - mapping['xdebug_clear'] = 'Stop debugging' - else: - mapping['xdebug_listen'] = 'Start debugging' - - if protocol and protocol.connected: - mapping.update({ - 'xdebug_status': 'Status', - 'xdebug_execute': 'Execute', - }) - - self.cmds = mapping.keys() - self.items = mapping.values() - self.view.window().show_quick_panel(self.items, self.callback) - - def callback(self, index): - if index == -1: - return - - command = self.cmds[index] - self.view.run_command(command) - - if protocol and command == 'xdebug_listen': - url = get_project_setting('url') - if url: - webbrowser.open(url + '?XDEBUG_SESSION_START=sublime.xdebug') - else: - sublime.status_message('Xdebug: No URL defined in project settings file.') - - global original_layout - global debug_view - window = sublime.active_window() - original_layout = window.get_layout() - debug_view = window.active_view() - window.set_layout({ - "cols": [0.0, 0.5, 1.0], - "rows": [0.0, 0.7, 1.0], - "cells": [[0, 0, 2, 1], [0, 1, 1, 2], [1, 1, 2, 2]] - }) - - if command == 'xdebug_clear': - url = get_project_setting('url') - if url: - webbrowser.open(url + '?XDEBUG_SESSION_STOP=sublime.xdebug') - else: - sublime.status_message('Xdebug: No URL defined in project settings file.') - window = sublime.active_window() - window.run_command('hide_panel', {"panel": 'output.xdebug_inspect'}) - window.set_layout(original_layout) - + def is_visible(self): + if self.view.file_name().split(".")[-1] == "php": + return True + return False class XdebugContinueCommand(sublime_plugin.TextCommand): ''' @@ -440,64 +428,55 @@ class XdebugContinueCommand(sublime_plugin.TextCommand): } def run(self, edit, state=None): - if not state or not state in self.states: - self.view.window().show_quick_panel(self.states.values(), self.callback) - else: - self.callback(state) - - def callback(self, state): - if state == -1: - return - if type(state) == int: - state = self.states.keys()[state] + self.callback(edit, state) + def callback(self, edit, state): global xdebug_current reset_current() protocol.send(state) res = protocol.read().firstChild - for child in res.childNodes: if child.nodeName == 'xdebug:message': - #print '>>>break ' + child.getAttribute('filename') + ':' + child.getAttribute('lineno') + print('>>>break ' + child.getAttribute('filename') + ':' + child.getAttribute('lineno')) sublime.status_message('Xdebug: breakpoint') xdebug_current = show_file(self.view.window(), child.getAttribute('filename')) xdebug_current.current(int(child.getAttribute('lineno'))) if (res.getAttribute('status') == 'break'): - # TODO stack_get protocol.send('context_get') res = protocol.read().firstChild result = '' def getValues(node): - result = unicode('') + result = '' for child in node.childNodes: if child.nodeName == 'property': - propName = unicode(child.getAttribute('fullname')) - propType = unicode(child.getAttribute('type')) + propName = child.getAttribute('fullname') + propType = child.getAttribute('type') propValue = None try: - propValue = unicode(' '.join(base64.b64decode(t.data) for t in child.childNodes if t.nodeType == t.TEXT_NODE or t.nodeType == t.CDATA_SECTION_NODE)) - except: - propValue = unicode(' '.join(t.data for t in child.childNodes if t.nodeType == t.TEXT_NODE or t.nodeType == t.CDATA_SECTION_NODE)) + propValue = ' '.join(base64.b64decode(t.data.encode("iso-8859-1")).decode("iso-8859-1") for t in child.childNodes if t.nodeType == t.TEXT_NODE or t.nodeType == t.CDATA_SECTION_NODE) + propValue += '\n' + except Exception as e: + propValue = ' '.join(t.data for t in child.childNodes if t.nodeType == t.TEXT_NODE or t.nodeType == t.CDATA_SECTION_NODE) if propName: if propName.lower().find('password') != -1: - propValue = unicode('*****') - result = result + unicode(propName + ' [' + propType + '] = ' + str(propValue) + '\n') + propValue = '*****' + result = result + propName + ' [' + propType + '] = ' + str(propValue) + '\n' result = result + getValues(child) if xdebug_current: xdebug_current.add_context_data(propName, propType, propValue) return result result = getValues(res) - add_debug_info('context', result) + add_debug_info(edit, 'context', result) if xdebug_current: xdebug_current.on_selection_modified() protocol.send('stack_get') res = protocol.read().firstChild - result = unicode('') + result = '' for child in res.childNodes: if child.nodeName == 'stack': propWhere = child.getAttribute('where') @@ -505,19 +484,20 @@ def getValues(node): propType = child.getAttribute('type') propFile = child.getAttribute('filename') propLine = child.getAttribute('lineno') - result = result + unicode('{level:>3}: {type:<10} {where:<10} {filename}:{lineno}\n' \ - .format(level=propLevel, type=propType, where=propWhere, lineno=propLine, filename=propFile)) - add_debug_info('stack', result) + result = result + '{level:>3}: {type:<10} {where:<10} {filename}:{lineno}\n' \ + .format(level=propLevel, type=propType, where=propWhere, lineno=propLine, filename=propFile) + add_debug_info(edit, 'stack', result) if res.getAttribute('status') == 'stopping' or res.getAttribute('status') == 'stopped': self.view.run_command('xdebug_clear') self.view.run_command('xdebug_listen') sublime.status_message('Xdebug: Page finished executing. Reload to continue debugging.') - def is_enabled(self): + def is_visible(self): if protocol and protocol.connected: + sublime.status_message('Xdebug Is running') return True - if protocol: + elif protocol: sublime.status_message('Xdebug: Waiting for executing to start') return False sublime.status_message('Xdebug: Not running') @@ -543,6 +523,47 @@ def is_enabled(self): return True return False +class XdebugWatchCommand(sublime_plugin.TextCommand): + ''' + DBGp watch command + ''' + def run(self, edit): + point = self.view.sel()[0].a + var_name = self.view.substr(self.view.word(point)) + if not var_name.startswith('$'): + var_name = '$' + var_name + is_variable = sublime.score_selector(self.view.scope_name(point), 'variable') + + if is_variable: + protocol.send('property_get', n=var_name) + res = protocol.read().firstChild + + def getValues(node): + result = '' + for child in node.childNodes: + if child.nodeName == 'property': + propName = child.getAttribute('fullname') + propType = child.getAttribute('type') + propValue = None + try: + propValue = ' '.join(base64.b64decode(t.data.encode("iso-8859-1")).decode("iso-8859-1") for t in child.childNodes if t.nodeType == t.TEXT_NODE or t.nodeType == t.CDATA_SECTION_NODE) + propValue += '\n' + except Exception as e: + propValue = ' '.join(t.data for t in child.childNodes if t.nodeType == t.TEXT_NODE or t.nodeType == t.CDATA_SECTION_NODE) + if propName: + if propName.lower().find('password') != -1: + propValue = '*****' + result = result + propName + ' [' + propType + '] = ' + str(propValue) + '\n' + result = result + getValues(child) + return result + + result = getValues(res) + sublime.message_dialog(result) + + def is_visible(self): + if protocol and protocol.connected: + return True + return False class XdebugStatus(sublime_plugin.TextCommand): ''' @@ -582,7 +603,7 @@ def on_done(self, line): window = self.view.window() output = window.get_output_panel('xdebug_execute') - edit = output.begin_edit() + edit = output.begin_edit(0, "") output.erase(edit, sublime.Region(0, output.size())) output.insert(edit, 0, res.toprettyxml()) output.end_edit(edit) @@ -715,7 +736,7 @@ def get_setting(key): return s.get(key) -def add_debug_info(name, data): +def add_debug_info(edit, name, data): ''' Adds data to the debug output windows ''' @@ -746,10 +767,9 @@ def add_debug_info(name, data): if found: v.set_read_only(False) window.set_view_index(v, group, 0) - edit = v.begin_edit() v.erase(edit, sublime.Region(0, v.size())) v.insert(edit, 0, data) v.end_edit(edit) v.set_read_only(True) - window.focus_group(0) + window.focus_group(0) \ No newline at end of file diff --git a/Xdebug.sublime-settings b/Xdebug.sublime-settings index 545d710..85aea8e 100644 --- a/Xdebug.sublime-settings +++ b/Xdebug.sublime-settings @@ -1,5 +1,6 @@ { "breakpoint_scope": "xdebug.breakpoint", "current_line_scope": "xdebug.current", - "port": 9000 + "port": 9333, + "url": "" } \ No newline at end of file