1264 lines
No EOL
42 KiB
Python
1264 lines
No EOL
42 KiB
Python
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) |