import hashlib import functools import glob import sublime import sublime_plugin import websocket import urllib2 import threading import json import types import os import re import wip import time from wip import utils from wip import Console from wip import Runtime from wip import Debugger from wip import Network from wip import Page import sys reload(sys.modules['wip.utils']) reload(sys.modules['wip.Console']) reload(sys.modules['wip.Runtime']) reload(sys.modules['wip.Debugger']) reload(sys.modules['wip.Network']) reload(sys.modules['wip.Page']) brk_object = {} buffers = {} protocol = None original_layout = None window = None debug_view = None debug_url = None file_to_scriptId = [] project_folders = [] last_clicked = None paused = False current_line = None reload_on_start = False reload_on_save = False set_script_source = False current_call_frame = None current_call_frame_position = None open_stack_current_in_new_tab = True timing = time.time() # scriptId_fileName = {} breakpoint_active_icon = '../Web Inspector/icons/breakpoint_active' breakpoint_inactive_icon = '../Web Inspector/icons/breakpoint_inactive' breakpoint_current_icon = '../Web Inspector/icons/breakpoint_current' #################################################################################### # PROTOCOL #################################################################################### # Define protocol to communicate with remote debugger by web sockets class Protocol(object): def __init__(self): self.next_id = 0 self.commands = {} self.notifications = {} self.last_log_object = None def connect(self, url, on_open=None, on_close=None): print 'SWI: Connecting to ' + url websocket.enableTrace(False) self.last_break = None self.last_log_object = None self.url = url self.on_open = on_open self.on_close = on_close thread = threading.Thread(target=self.thread_callback) thread.start() # start connect with new thread def thread_callback(self): print 'SWI: Thread started' self.socket = websocket.WebSocketApp(self.url, on_message=self.message_callback, on_open=self.open_callback, on_close=self.close_callback) self.socket.run_forever() print 'SWI: Thread stoped' # send command and increment command counter def send(self, command, callback=None, options=None): command.id = self.next_id command.callback = callback command.options = options self.commands[command.id] = command self.next_id += 1 # print 'SWI: ->> ' + json.dumps(command.request) self.socket.send(json.dumps(command.request)) # subscribe to notification with callback def subscribe(self, notification, callback): notification.callback = callback self.notifications[notification.name] = notification # unsubscribe def unsubscribe(self, notification): del self.notifications[notification.name] # unsubscribe def message_callback(self, ws, message): parsed = json.loads(message) # print 'SWI: <<- ' + message # print '' if 'method' in parsed: if parsed['method'] in self.notifications: notification = self.notifications[parsed['method']] if 'params' in parsed: data = notification.parser(parsed['params']) else: data = None notification.callback(data, notification) # else: # print 'SWI: New unsubscrib notification --- ' + parsed['method'] else: if parsed['id'] in self.commands: command = self.commands[parsed['id']] if 'error' in parsed: sublime.set_timeout(lambda: sublime.error_message(parsed['error']['message']), 0) else: if 'result' in parsed: command.data = command.parser(parsed['result']) else: command.data = None if command.callback: command.callback(command) # print 'SWI: Command response with ID ' + str(parsed['id']) def open_callback(self, ws): if self.on_open: self.on_open() print 'SWI: WebSocket opened' def close_callback(self, ws): if self.on_close: self.on_close() print 'SWI: WebSocket closed' #################################################################################### # COMMANDS #################################################################################### class SwiDebugCommand(sublime_plugin.TextCommand): ''' The SWIdebug main quick panel menu ''' def run(self, editswi): mapping = {} try: urllib2.urlopen('http://127.0.0.1:' + get_setting('chrome_remote_port') + '/json') mapping = {} if paused: mapping['swi_debug_resume'] = 'Resume execution' mapping['swi_debug_evaluate_on_call_frame'] = 'Evaluate selection' #mapping['swi_debug_step_into'] = 'Step into' #mapping['swi_debug_step_out'] = 'Step out' #mapping['swi_debug_step_over'] = 'Step over' else: #mapping['swi_debug_clear_all_breakpoint'] = 'Clear all Breakpoints' mapping['swi_debug_breakpoint'] = 'Add/Remove Breakpoint' if protocol: mapping['swi_debug_clear_console'] = 'Clear console' mapping['swi_debug_stop'] = 'Stop debugging' mapping['swi_debug_reload'] = 'Reload page' else: mapping['swi_debug_start'] = 'Start debugging' except: mapping['swi_debug_start_chrome'] = 'Start Google Chrome with remote debug port ' + get_setting('chrome_remote_port') self.cmds = mapping.keys() self.items = mapping.values() self.view.window().show_quick_panel(self.items, self.command_selected) def command_selected(self, index): if index == -1: return command = self.cmds[index] if command == 'swi_debug_start': response = urllib2.urlopen('http://127.0.0.1:' + get_setting('chrome_remote_port') + '/json') pages = json.loads(response.read()) mapping = {} for page in pages: if 'webSocketDebuggerUrl' in page: if page['url'].find('chrome-extension://') == -1: mapping[page['webSocketDebuggerUrl']] = page['url'] self.urls = mapping.keys() items = mapping.values() self.view.window().show_quick_panel(items, self.remote_debug_url_selected) return self.view.run_command(command) def remote_debug_url_selected(self, index): if index == -1: return url = self.urls[index] global window window = sublime.active_window() global original_layout original_layout = window.get_layout() global debug_view debug_view = window.active_view() window.set_layout(get_setting('console_layout')) load_breaks() self.view.run_command('swi_debug_start', {'url': url}) class SwiDebugStartChromeCommand(sublime_plugin.TextCommand): def run(self, edit): window = sublime.active_window() window.run_command('exec', { "cmd": [os.getenv('GOOGLE_CHROME_PATH', '')+get_setting('chrome_path')[sublime.platform()], '--remote-debugging-port=' + get_setting('chrome_remote_port')] }) class SwiDebugStartCommand(sublime_plugin.TextCommand): def run(self, edit, url): global file_to_scriptId file_to_scriptId = [] window = sublime.active_window() global project_folders project_folders = window.folders() print 'Starting SWI' self.url = url global protocol if(protocol): print 'SWI: Socket closed' protocol.socket.close() else: print 'SWI: Creating protocol' protocol = Protocol() protocol.connect(self.url, self.connected, self.disconnected) global reload_on_start reload_on_start = get_setting('reload_on_start') global reload_on_save reload_on_save = get_setting('reload_on_save') global set_script_source set_script_source = get_setting('set_script_source') global open_stack_current_in_new_tab open_stack_current_in_new_tab = get_setting('open_stack_current_in_new_tab') def connected(self): protocol.subscribe(wip.Console.messageAdded(), self.messageAdded) protocol.subscribe(wip.Console.messageRepeatCountUpdated(), self.messageRepeatCountUpdated) protocol.subscribe(wip.Console.messagesCleared(), self.messagesCleared) protocol.subscribe(wip.Debugger.scriptParsed(), self.scriptParsed) protocol.subscribe(wip.Debugger.paused(), self.paused) protocol.subscribe(wip.Debugger.resumed(), self.resumed) protocol.send(wip.Debugger.enable()) protocol.send(wip.Console.enable()) protocol.send(wip.Debugger.canSetScriptSource(), self.canSetScriptSource) if reload_on_start: protocol.send(wip.Network.clearBrowserCache()) protocol.send(wip.Page.reload(), on_reload) def disconnected(self): sublime.set_timeout(lambda: debug_view.run_command('swi_debug_stop'), 0) def messageAdded(self, data, notification): sublime.set_timeout(lambda: console_add_message(data), 0) def messageRepeatCountUpdated(self, data, notification): sublime.set_timeout(lambda: console_repeat_message(data['count']), 0) def messagesCleared(self, data, notification): sublime.set_timeout(lambda: clear_view('console'), 0) def scriptParsed(self, data, notification): url = data['url'] if url != '': url_parts = url.split("/") scriptId = str(data['scriptId']) file_name = '' script = get_script(data['url']) if script: script['scriptId'] = str(scriptId) file_name = script['file'] else: del url_parts[0:3] while len(url_parts) > 0: for folder in project_folders: if sublime.platform() == "windows": files = glob.glob(folder + "\\" + "\\".join(url_parts)) else: files = glob.glob(folder + "/" + "/".join(url_parts)) if len(files) > 0 and files[0] != '': file_name = files[0] file_to_scriptId.append({'file': file_name, 'scriptId': str(scriptId), 'sha1': hashlib.sha1(data['url']).hexdigest()}) del url_parts[0] if get_breakpoints_by_full_path(file_name): for line in get_breakpoints_by_full_path(file_name).keys(): location = wip.Debugger.Location({'lineNumber': int(line), 'scriptId': scriptId}) protocol.send(wip.Debugger.setBreakpoint(location), self.breakpointAdded) def paused(self, data, notification): sublime.set_timeout(lambda: window.set_layout(get_setting('stack_layout')), 0) sublime.set_timeout(lambda: console_show_stack(data['callFrames']), 0) scriptId = data['callFrames'][0].location.scriptId line_number = data['callFrames'][0].location.lineNumber file_name = find_script(str(scriptId)) first_scope = data['callFrames'][0].scopeChain[0] if open_stack_current_in_new_tab: title = {'objectId': first_scope.object.objectId, 'name': "%s:%s (%s)" % (file_name, line_number, first_scope.type)} else: title = {'objectId': first_scope.object.objectId, 'name': "Breakpoint Local"} global current_call_frame current_call_frame = data['callFrames'][0].callFrameId global current_call_frame_position current_call_frame_position = "%s:%s" % (file_name, line_number) sublime.set_timeout(lambda: protocol.send(wip.Runtime.getProperties(first_scope.object.objectId, True), console_add_properties, title), 30) sublime.set_timeout(lambda: open_script_and_focus_line(scriptId, line_number), 100) global paused paused = True def resumed(self, data, notification): sublime.set_timeout(lambda: clear_view('stack'), 0) global current_line current_line = None global current_call_frame current_call_frame = None global current_call_frame_position current_call_frame_position = None sublime.set_timeout(lambda: lookup_view(self.view).view_breakpoints(), 50) global paused paused = False def breakpointAdded(self, command): breakpointId = command.data['breakpointId'] scriptId = command.data['actualLocation'].scriptId lineNumber = command.data['actualLocation'].lineNumber try: breakpoint = get_breakpoints_by_scriptId(str(scriptId))[str(lineNumber)] breakpoint['status'] = 'enabled' breakpoint['breakpointId'] = str(breakpointId) except: pass try: breaks = get_breakpoints_by_scriptId(str(scriptId))[str(lineNumber)] lineNumber = str(lineNumber) lineNumberSend = str(command.params['lineNumber']) if lineNumberSend in breaks and lineNumber != lineNumberSend: breaks[lineNumber] = breaks[lineNumberSend].copy() del breaks[lineNumberSend] breaks[lineNumber]['status'] = 'enabled' breaks[lineNumber]['breakpointId'] = str(breakpointId) except: pass sublime.set_timeout(lambda: save_breaks(), 0) sublime.set_timeout(lambda: lookup_view(self.view).view_breakpoints(), 0) def canSetScriptSource(self, command): global set_script_source if set_script_source: set_script_source = command.data['result'] class SwiDebugResumeCommand(sublime_plugin.TextCommand): def run(self, edit): protocol.send(wip.Debugger.resume()) class SwiDebugStepIntoCommand(sublime_plugin.TextCommand): def run(self, edit): protocol.send(wip.Debugger.stepInto()) class SwiDebugStepOutCommand(sublime_plugin.TextCommand): def run(self, edit): protocol.send(wip.Debugger.stepOut()) class SwiDebugStepOverCommand(sublime_plugin.TextCommand): def run(self, edit): protocol.send(wip.Debugger.stepOver()) class SwiDebugClearConsoleCommand(sublime_plugin.TextCommand): def run(self, edit): sublime.set_timeout(lambda: clear_view('console'), 0) class SwiDebugEvaluateOnCallFrameCommand(sublime_plugin.TextCommand): def run(self, edit): for region in self.view.sel(): title = self.view.substr(region) if current_call_frame_position: title = "%s on %s" % (self.view.substr(region), current_call_frame_position) protocol.send(wip.Debugger.evaluateOnCallFrame(current_call_frame, self.view.substr(region)), self.evaluated, {'name': title}) def evaluated(self, command): if command.data.type == 'object': protocol.send(wip.Runtime.getProperties(command.data.objectId, True), console_add_properties, command.options) else: sublime.set_timeout(lambda: console_add_evaluate(command.data), 0) class SwiDebugBreakpointCommand(sublime_plugin.TextCommand): ''' Toggle a breakpoint ''' def run(self, edit): view = lookup_view(self.view) row = str(view.rows(view.lines())[0]) init_breakpoint_for_file(view.file_name()) breaks = get_breakpoints_by_full_path(view.file_name()) if row in breaks: if protocol: if row in breaks: protocol.send(wip.Debugger.removeBreakpoint(breaks[row]['breakpointId'])) del_breakpoint_by_full_path(view.file_name(), row) else: if protocol: scriptId = find_script(view.file_name()) if scriptId: location = wip.Debugger.Location({'lineNumber': int(row), 'scriptId': scriptId}) protocol.send(wip.Debugger.setBreakpoint(location), self.breakpointAdded, view.file_name()) else: set_breakpoint_by_full_path(view.file_name(), row) view.view_breakpoints() def breakpointAdded(self, command): breakpointId = command.data['breakpointId'] scriptId = command.data['actualLocation'].scriptId lineNumber = command.data['actualLocation'].lineNumber init_breakpoint_for_file(command.options) sublime.set_timeout(lambda: set_breakpoint_by_scriptId(str(scriptId), str(lineNumber), 'enabled', breakpointId), 0) # Scroll to position where breakpoints have resolved sublime.set_timeout(lambda: lookup_view(self.view).view_breakpoints(), 0) class SwiDebugStopCommand(sublime_plugin.TextCommand): def run(self, edit): global window window.focus_group(1) for view in window.views_in_group(1): window.run_command("close") window.focus_group(2) for view in window.views_in_group(2): window.run_command("close") window.set_layout(original_layout) disable_all_breakpoints() lookup_view(self.view).view_breakpoints() global paused paused = False global current_line current_line = None sublime.set_timeout(lambda: lookup_view(self.view).view_breakpoints(), 0) global protocol if protocol: try: protocol.socket.close() except: print 'SWI: Can\'t close soket' finally: protocol = None class SwiDebugReloadCommand(sublime_plugin.TextCommand): def run(self, view): if(protocol): protocol.send(wip.Network.clearBrowserCache()) protocol.send(wip.Page.reload(), on_reload) #################################################################################### # VIEW #################################################################################### class SwiDebugView(object): ''' The SWIDebugView is sort of a normal view with some convenience methods. See lookup_view. ''' def __init__(self, view): self.view = view self.context_data = {} self.clicks = [] self.prev_click_position = 0 def __getattr__(self, attr): if hasattr(self.view, attr): return getattr(self.view, attr) if attr.startswith('on_'): return self raise(AttributeError, "%s does not exist" % attr) def __call__(self, *args, **kwargs): pass def uri(self): return 'file://' + os.path.realpath(self.view.file_name()) def lines(self, data=None): lines = [] if data is None: regions = self.view.sel() else: if type(data) != types.ListType: data = [data] regions = [] for item in data: if type(item) == types.IntType 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)) return [self.view.line(line) for line in lines] def rows(self, lines): if not type(lines) == types.ListType: lines = [lines] return [self.view.rowcol(line.begin())[0] + 1 for line in lines] def insert_click(self, a, b, click_type, data): insert_before = 0 new_region = sublime.Region(a, b) regions = self.view.get_regions('swi_log_clicks') for region in regions: if new_region.b < region.a: break insert_before += 1 self.clicks.insert(insert_before, {'click_type': click_type, 'data': data}) regions.append(new_region) self.view.add_regions('swi_log_clicks', regions, get_setting('interactive_scope'), sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED) def print_click(self, edit, position, text, click_type, data): insert_length = self.insert(edit, position, text) self.insert_click(position, position + insert_length, click_type, data) def remove_click(self, index): regions = self.view.get_regions('swi_log_clicks') del regions[index] self.view.add_regions('swi_log_clicks', regions, get_setting('interactive_scope'), sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED) def clear_clicks(self): self.clicks = [] def view_breakpoints(self): self.view.erase_regions('swi_breakpoint_inactive') self.view.erase_regions('swi_breakpoint_active') self.view.erase_regions('swi_breakpoint_current') if not self.view.file_name(): return breaks = get_breakpoints_by_full_path(self.view.file_name()) if not breaks: return enabled = [] disabled = [] for key in breaks.keys(): if breaks[key]['status'] == 'enabled' and str(current_line) != key: enabled.append(key) if breaks[key]['status'] == 'disabled' and str(current_line) != key: disabled.append(key) self.view.add_regions('swi_breakpoint_active', self.lines(enabled), get_setting('breakpoint_scope'), breakpoint_active_icon, sublime.HIDDEN) self.view.add_regions('swi_breakpoint_inactive', self.lines(disabled), get_setting('breakpoint_scope'), breakpoint_inactive_icon, sublime.HIDDEN) if current_line: self.view.add_regions('swi_breakpoint_current', self.lines([current_line]), get_setting('current_line_scope'), breakpoint_current_icon, sublime.DRAW_EMPTY) def check_click(self): if not self.name().startswith('SWI'): return cursor = self.sel()[0].a if cursor == self.prev_click_position: return self.prev_click_position = cursor click_counter = 0 click_regions = self.get_regions('swi_log_clicks') for click in click_regions: if cursor > click.a and cursor < click.b: if click_counter < len(self.clicks): click = self.clicks[click_counter] if click['click_type'] == 'goto_file_line': open_script_and_focus_line(click['data']['scriptId'], click['data']['line']) if click['click_type'] == 'goto_call_frame': callFrame = click['data']['callFrame'] scriptId = callFrame.location.scriptId line_number = callFrame.location.lineNumber file_name = find_script(str(scriptId)) open_script_and_focus_line(scriptId, line_number) first_scope = callFrame.scopeChain[0] if open_stack_current_in_new_tab: title = {'objectId': first_scope.object.objectId, 'name': "%s:%s (%s)" % (file_name.split('/')[-1], line_number, first_scope.type)} else: title = {'objectId': first_scope.object.objectId, 'name': "Breakpoint Local"} sublime.set_timeout(lambda: protocol.send(wip.Runtime.getProperties(first_scope.object.objectId, True), console_add_properties, title), 30) global current_call_frame current_call_frame = callFrame.callFrameId global current_call_frame_position current_call_frame_position = "%s:%s" % (file_name.split('/')[-1], line_number) if click['click_type'] == 'get_params': if protocol: protocol.send(wip.Runtime.getProperties(click['data']['objectId'], True), console_add_properties, click['data']) if click['click_type'] == 'command': self.remove_click(click_counter) self.run_command(click['data']) click_counter += 1 def lookup_view(v): ''' Convert a Sublime View into an SWIDebugView ''' if isinstance(v, SwiDebugView): return v if isinstance(v, sublime.View): id = v.buffer_id() if id in buffers: buffers[id].view = v else: buffers[id] = SwiDebugView(v) return buffers[id] return None #################################################################################### # EventListener #################################################################################### class EventListener(sublime_plugin.EventListener): def on_new(self, view): lookup_view(view).on_new() def on_clone(self, view): lookup_view(view).on_clone() def on_load(self, view): lookup_view(view).view_breakpoints() lookup_view(view).on_load() def on_close(self, view): lookup_view(view).on_close() def on_pre_save(self, view): lookup_view(view).on_pre_save() def on_post_save(self, view): print view.file_name().find('.js') if protocol and reload_on_save: protocol.send(wip.Network.clearBrowserCache()) if view.file_name().find('.css') > 0 or view.file_name().find('.less') > 0 or view.file_name().find('.sass') > 0 or view.file_name().find('.scss') > 0: protocol.send(wip.Runtime.evaluate("var files = document.getElementsByTagName('link');var links = [];for (var a = 0, l = files.length; a < l; a++) {var elem = files[a];var rel = elem.rel;if (typeof rel != 'string' || rel.length === 0 || rel === 'stylesheet') {links.push({'elem': elem,'href': elem.getAttribute('href').split('?')[0],'last': false});}}for ( a = 0, l = links.length; a < l; a++) {var link = links[a];link.elem.setAttribute('href', (link.href + '?x=' + Math.random()));}")) elif view.file_name().find('.js') > 0: scriptId = find_script(view.file_name()) if scriptId and set_script_source: scriptSource = view.substr(sublime.Region(0, view.size())) protocol.send(wip.Debugger.setScriptSource(scriptId, scriptSource), self.paused) else: protocol.send(wip.Page.reload(), on_reload) else: protocol.send(wip.Page.reload(), on_reload) lookup_view(view).on_post_save() def on_modified(self, view): lookup_view(view).on_modified() lookup_view(view).view_breakpoints() def on_selection_modified(self, view): #lookup_view(view).on_selection_modified() global timing now = time.time() if now - timing > 0.08: timing = now sublime.set_timeout(lambda: lookup_view(view).check_click(), 0) else: timing = now def on_activated(self, view): lookup_view(view).on_activated() lookup_view(view).view_breakpoints() def on_deactivated(self, view): lookup_view(view).on_deactivated() def on_query_context(self, view, key, operator, operand, match_all): lookup_view(view).on_query_context(key, operator, operand, match_all) def paused(self, command): global paused if not paused: return data = command.data sublime.set_timeout(lambda: window.set_layout(get_setting('stack_layout')), 0) sublime.set_timeout(lambda: console_show_stack(data['callFrames']), 0) scriptId = data['callFrames'][0].location.scriptId line_number = data['callFrames'][0].location.lineNumber file_name = find_script(str(scriptId)) first_scope = data['callFrames'][0].scopeChain[0] if open_stack_current_in_new_tab: title = {'objectId': first_scope.object.objectId, 'name': "%s:%s (%s)" % (file_name, line_number, first_scope.type)} else: title = {'objectId': first_scope.object.objectId, 'name': "Breakpoint Local"} sublime.set_timeout(lambda: protocol.send(wip.Runtime.getProperties(first_scope.object.objectId, True), console_add_properties, title), 30) sublime.set_timeout(lambda: open_script_and_focus_line(scriptId, line_number), 100) #################################################################################### # GLOBAL HANDLERS #################################################################################### def on_reload(command): global file_to_scriptId file_to_scriptId = [] #################################################################################### # Console #################################################################################### def find_view(console_type, title=''): found = False v = None window = sublime.active_window() if console_type.startswith('console'): group = 1 fullName = "SWI Console" if console_type == 'stack': group = 2 fullName = "SWI Breakpoint stack" if console_type.startswith('eval'): group = 1 fullName = "SWI Object evaluate" fullName = fullName + ' ' + title for v in window.views(): if v.name() == fullName: found = True break if not found: v = window.new_file() v.set_scratch(True) v.set_read_only(False) v.set_name(fullName) v.settings().set('word_wrap', False) window.set_view_index(v, group, 0) if console_type.startswith('console'): v.set_syntax_file('Packages/Web Inspector/swi_log.tmLanguage') if console_type == 'stack': v.set_syntax_file('Packages/Web Inspector/swi_stack.tmLanguage') if console_type.startswith('eval'): v.set_syntax_file('Packages/Web Inspector/swi_log.tmLanguage') window.focus_view(v) v.set_read_only(False) return lookup_view(v) def clear_view(view): v = find_view(view) edit = v.begin_edit() v.erase(edit, sublime.Region(0, v.size())) v.end_edit(edit) v.show(v.size()) window.focus_group(0) lookup_view(v).clear_clicks() def console_repeat_message(count): v = find_view('console') edit = v.begin_edit() if count > 2: erase_to = v.size() - len(u' \u21AA Repeat:' + str(count - 1) + '\n') v.erase(edit, sublime.Region(erase_to, v.size())) v.insert(edit, v.size(), u' \u21AA Repeat:' + str(count) + '\n') v.end_edit(edit) v.show(v.size()) window.focus_group(0) def console_add_evaluate(eval_object): v = find_view('console') edit = v.begin_edit() insert_position = v.size() v.insert(edit, insert_position, str(eval_object) + ' ') v.insert(edit, v.size(), "\n") v.end_edit(edit) v.show(v.size()) window.focus_group(0) def console_add_message(message): v = find_view('console') edit = v.begin_edit() if message.level == 'debug': level = "D" if message.level == 'error': level = "E" if message.level == 'log': level = "L" if message.level == 'tip': level = "T" if message.level == 'warning': level = "W" v.insert(edit, v.size(), "[%s] " % (level)) # Add file and line scriptId = None if message.url: scriptId = find_script(message.url) if scriptId: url = message.url.split("/")[-1] else: url = message.url else: url = '---' if message.line: line = message.line else: line = 0 insert_position = v.size() insert_length = v.insert(edit, insert_position, "%s:%d" % (url, line)) if scriptId and line > 0: v.insert_click(insert_position, insert_position + insert_length, 'goto_file_line', {'scriptId': scriptId, 'line': str(line)}) v.insert(edit, v.size(), " ") # Add text if len(message.parameters) > 0: for param in message.parameters: insert_position = v.size() insert_length = v.insert(edit, insert_position, str(param) + ' ') if param.type == 'object': v.insert_click(insert_position, insert_position + insert_length - 1, 'get_params', {'objectId': param.objectId}) else: v.insert(edit, v.size(), message.text) v.insert(edit, v.size(), "\n") if level == "E" and message.stackTrace: stack_start = v.size() for callFrame in message.stackTrace: scriptId = find_script(callFrame.url) file_name = callFrame.url.split('/')[-1] v.insert(edit, v.size(), u'\t\u21E1 ') if scriptId: v.print_click(edit, v.size(), "%s:%s %s" % (file_name, callFrame.lineNumber, callFrame.functionName), 'goto_file_line', {'scriptId': scriptId, 'line': str(callFrame.lineNumber)}) else: v.insert(edit, v.size(), "%s:%s %s" % (file_name, callFrame.lineNumber, callFrame.functionName)) v.insert(edit, v.size(), "\n") v.fold(sublime.Region(stack_start-1, v.size()-1)) v.end_edit(edit) v.show(v.size()) window.focus_group(0) def console_add_properties(command): sublime.set_timeout(lambda: console_print_properties(command), 0) def console_print_properties(command): if 'name' in command.options: name = command.options['name'] else: name = str(command.options['objectId']) if 'prev' in command.options: prev = command.options['prev'] + ' -> ' + name else: prev = name v = find_view('eval', name) edit = v.begin_edit() v.erase(edit, sublime.Region(0, v.size())) v.insert(edit, v.size(), prev) v.insert(edit, v.size(), "\n\n") for prop in command.data: v.insert(edit, v.size(), prop.name + ': ') insert_position = v.size() if(prop.value): insert_length = v.insert(edit, insert_position, str(prop.value) + '\n') if prop.value.type == 'object': v.insert_click(insert_position, insert_position + insert_length - 1, 'get_params', {'objectId': prop.value.objectId, 'name': prop.name, 'prev': prev}) v.end_edit(edit) v.show(0) window.focus_group(0) def console_show_stack(callFrames): v = find_view('stack') edit = v.begin_edit() v.erase(edit, sublime.Region(0, v.size())) v.insert(edit, v.size(), "\n") v.print_click(edit, v.size(), "\tResume\t", 'command', 'swi_debug_resume') v.print_click(edit, v.size(), "\tStep Over\t", 'command', 'swi_debug_step_over') v.print_click(edit, v.size(), "\tStep Into\t", 'command', 'swi_debug_step_into') v.print_click(edit, v.size(), "\tStep Out\t", 'command', 'swi_debug_step_out') v.insert(edit, v.size(), "\n\n") for callFrame in callFrames: line = str(callFrame.location.lineNumber) file_name = find_script(str(callFrame.location.scriptId)) if file_name: file_name = file_name.split('/')[-1] else: file_name = '-' insert_position = v.size() insert_length = v.insert(edit, insert_position, "%s:%s" % (file_name, line)) if file_name != '-': v.insert_click(insert_position, insert_position + insert_length, 'goto_call_frame', {'callFrame': callFrame}) v.insert(edit, v.size(), " %s\n" % (callFrame.functionName)) for scope in callFrame.scopeChain: v.insert(edit, v.size(), "\t") insert_position = v.size() insert_length = v.insert(edit, v.size(), "%s\n" % (scope.type)) if scope.object.type == 'object': v.insert_click(insert_position, insert_position + insert_length - 1, 'get_params', {'objectId': scope.object.objectId, 'name': "%s:%s (%s)" % (file_name, line, scope.type)}) v.end_edit(edit) v.show(0) window.focus_group(0) #################################################################################### # All about breaks #################################################################################### def get_project(): if not sublime.active_window(): return None win_id = sublime.active_window().id() project = None reg_session = os.path.join(sublime.packages_path(), "..", "Settings", "Session.sublime_session") auto_save = os.path.join(sublime.packages_path(), "..", "Settings", "Auto Save Session.sublime_session") session = auto_save if os.path.exists(auto_save) else reg_session if not os.path.exists(session) or win_id == None: return project try: with open(session, 'r') as f: # Tabs in strings messes things up for some reason j = json.JSONDecoder(strict=False).decode(f.read()) for w in j['windows']: if w['window_id'] == win_id: if "workspace_name" in w: if sublime.platform() == "windows": # Account for windows specific formatting project = os.path.normpath(w["workspace_name"].lstrip("/").replace("/", ":/", 1)) else: project = w["workspace_name"] break except: pass # Throw out empty project names if project == None or re.match(".*\\.sublime-project", project) == None or not os.path.exists(project): project = None return project def load_breaks(): # if not get_project(): # sublime.error_message('Can\' load breaks') # brk_object = {} # return # breaks_file = os.path.splitext(get_project())[0] + '-breaks.json' # global brk_object # if not os.path.exists(breaks_file): # with open(breaks_file, 'w') as f: # f.write('{}') # try: # with open(breaks_file, 'r') as f: # brk_object = json.loads(f.read()) # except: # brk_object = {} global brk_object brk_object = get_setting('breaks') def save_breaks(): # try: # breaks_file = os.path.splitext(get_project())[0] + '-breaks.json' # with open(breaks_file, 'w') as f: # f.write(json.dumps(brk_object, sort_keys=True, indent=4, separators=(',', ': '))) # except: # pass s = sublime.load_settings("swi.sublime-settings") s.set('breaks', brk_object) sublime.save_settings("swi.sublime-settings") #print breaks def full_path_to_file_name(path): return os.path.basename(os.path.realpath(path)) def set_breakpoint_by_full_path(file_name, line, status='disabled', breakpointId=None): breaks = get_breakpoints_by_full_path(file_name) if not line in breaks: breaks[line] = {} breaks[line]['status'] = status breaks[line]['breakpointId'] = str(breakpointId) else: breaks[line]['status'] = status breaks[line]['breakpointId'] = str(breakpointId) save_breaks() def del_breakpoint_by_full_path(file_name, line): breaks = get_breakpoints_by_full_path(file_name) if line in breaks: del breaks[line] save_breaks() def get_breakpoints_by_full_path(file_name): if file_name in brk_object: return brk_object[file_name] return None def set_breakpoint_by_scriptId(scriptId, line, status='disabled', breakpointId=None): file_name = find_script(str(scriptId)) if file_name: set_breakpoint_by_full_path(file_name, line, status, breakpointId) def del_breakpoint_by_scriptId(scriptId, line): file_name = find_script(str(scriptId)) if file_name: del_breakpoint_by_full_path(file_name, line) def get_breakpoints_by_scriptId(scriptId): file_name = find_script(str(scriptId)) if file_name: return get_breakpoints_by_full_path(file_name) return None def init_breakpoint_for_file(file_path): if not file_path in brk_object: brk_object[file_path] = {} def disable_all_breakpoints(): for file_name in brk_object: for line in brk_object[file_name]: brk_object[file_name][line]['status'] = 'disabled' if 'breakpointId' in brk_object[file_name][line]: del brk_object[file_name][line]['breakpointId'] save_breaks() #################################################################################### # Utils #################################################################################### def get_setting(key): s = sublime.load_settings("swi.sublime-settings") if s and s.has(key): return s.get(key) def find_script(scriptId_or_file_or_url): sha = hashlib.sha1(scriptId_or_file_or_url).hexdigest() for item in file_to_scriptId: if item['scriptId'] == scriptId_or_file_or_url: return item['file'] if item['file'] == scriptId_or_file_or_url: return item['scriptId'] if item['sha1'] == sha: return item['scriptId'] return None def get_script(scriptId_or_file_or_url): sha = hashlib.sha1(scriptId_or_file_or_url).hexdigest() for item in file_to_scriptId: if item['scriptId'] == scriptId_or_file_or_url: return item if item['file'] == scriptId_or_file_or_url: return item if item['sha1'] == sha: return item return None def do_when(conditional, callback, *args, **kwargs): if conditional(): return callback(*args, **kwargs) sublime.set_timeout(functools.partial(do_when, conditional, callback, *args, **kwargs), 50) def open_script_and_focus_line(scriptId, line_number): file_name = find_script(str(scriptId)) window = sublime.active_window() window.focus_group(0) view = window.open_file(file_name, sublime.TRANSIENT) do_when(lambda: not view.is_loading(), lambda: view.run_command("goto_line", {"line": line_number})) def open_script_and_show_current_breakpoint(scriptId, line_number): file_name = find_script(str(scriptId)) window.focus_group(0) view = window.open_file(file_name, sublime.TRANSIENT) do_when(lambda: not view.is_loading(), lambda: view.run_command("goto_line", {"line": line_number})) #do_when(lambda: not view.is_loading(), lambda: focus_line_and_highlight(view, line_number)) def focus_line_and_highlight(view, line_number): view.run_command("goto_line", {"line": line_number}) global current_line current_line = line_number lookup_view(view).view_breakpoints() sublime.set_timeout(lambda: load_breaks(), 1000)