├── .gitignore ├── .no-sublime-package ├── .travis.yml ├── CoffeescriptPlugin.py ├── CommandAPI.py ├── CompassPlugin.py ├── CompassPlugin.sublime-settings ├── Default.sublime-commands ├── LESSPlugin.py ├── LiveReload.py ├── LiveReload.sublime-settings ├── Main.sublime-menu ├── Makefile ├── README.md ├── SassPlugin.py ├── SassPlugin.sublime-settings ├── SimpleReloadCallback.py ├── SimpleReloadPlugin.py ├── SimpleReloadPluginDelay.py ├── SimpleWSCallback.py ├── __init__.py ├── docs ├── Makefile ├── _build │ ├── doctrees │ │ ├── environment.pickle │ │ └── index.doctree │ └── html │ │ ├── .buildinfo │ │ ├── _modules │ │ ├── LiveReload.html │ │ ├── PluginAPI.html │ │ └── index.html │ │ ├── _sources │ │ └── index.txt │ │ ├── _static │ │ ├── ajax-loader.gif │ │ ├── basic.css │ │ ├── comment-bright.png │ │ ├── comment-close.png │ │ ├── comment.png │ │ ├── default.css │ │ ├── doctools.js │ │ ├── down-pressed.png │ │ ├── down.png │ │ ├── file.png │ │ ├── jquery.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── sidebar.js │ │ ├── underscore.js │ │ ├── up-pressed.png │ │ ├── up.png │ │ └── websupport.js │ │ ├── genindex.html │ │ ├── index.html │ │ ├── objects.inv │ │ ├── py-modindex.html │ │ ├── search.html │ │ └── searchindex.js ├── conf.py ├── index.rst └── sublime.py ├── messages.json ├── messages └── install_upgrade.txt ├── pylint.rc ├── requirements.txt ├── server ├── LiveReloadAPI.py ├── PluginAPI.py ├── Settings.py ├── SimpleCallbackServer.py ├── SimpleResourceServer.py ├── SimpleWSServer.py ├── WSRequestHandler.py ├── WebSocketClient.py ├── WebSocketServer.py └── __init__.py ├── snippets ├── livereload_html.sublime-snippet ├── livereload_jade.sublime-snippet └── livereload_plugin.sublime-snippet ├── test ├── __init__.py └── server_vows.py └── web ├── Gruntfile.coffee ├── README.md ├── dist ├── livereloadjs-sm2.js └── livereloadjs-sm2.min.js ├── package.json ├── src ├── connector.coffee ├── customevents.coffee ├── less.coffee ├── livereload.coffee ├── options.coffee ├── protocol.coffee ├── reloader.coffee ├── socketPluginAPI.coffee ├── startup.coffee └── timer.coffee ├── test.html └── test ├── connector_test.coffee ├── html ├── css-import │ ├── bug-test-case.html │ ├── imported.css │ ├── test.css │ └── test.html ├── delayed │ ├── test.css │ ├── test.html │ └── testcss.php ├── images │ ├── .gitignore │ ├── imported.css │ ├── pic1.jpg │ ├── pic2.jpg │ ├── run.sh │ ├── test.css │ └── test.html ├── lessjs │ ├── .gitignore │ ├── test.html │ └── test.less ├── prefixfree │ ├── test.css │ └── test.html └── trivial │ ├── test.css │ └── test.html ├── mocha.opts ├── options_test.coffee ├── protocol_test.coffee └── timer_test.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-workspace 2 | *.pyc 3 | package-metadata.json 4 | /web/node_modules 5 | *.sublime-project 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.no-sublime-package: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/.no-sublime-package -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | before_install: 5 | sudo apt-get install -y libevent-dev python-gevent python-lxml 6 | install: 7 | pip install -r requirements.txt --use-mirrors 8 | script: 9 | make test 10 | notifications: 11 | irc: "irc.freenode.org#lr-sublime" -------------------------------------------------------------------------------- /CoffeescriptPlugin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import threading 6 | import subprocess 7 | import sys 8 | import sublime 9 | import sublime_plugin 10 | 11 | # fix for import order 12 | 13 | sys.path.append(os.path.join(sublime.packages_path(), 'LiveReload')) 14 | LiveReload = __import__('LiveReload') 15 | sys.path.remove(os.path.join(sublime.packages_path(), 'LiveReload')) 16 | 17 | 18 | class CoffeeThread(threading.Thread): 19 | 20 | def getLocalOverride(self): 21 | """ 22 | You can override defaults in sublime-project file 23 | 24 | Discussion: https://github.com/dz0ny/LiveReload-sublimetext2/issues/43 25 | 26 | Example: 27 | 28 | "settings": { 29 | "coffee": { 30 | "command": "coffee -cv --maps" 31 | } 32 | } 33 | """ 34 | try: 35 | view_settings = sublime.active_window().active_view().settings() 36 | view_settings = view_settings.get('lrcoffee') 37 | if view_settings: 38 | return view_settings 39 | else: 40 | return {} 41 | except Exception: 42 | return {} 43 | 44 | def __init__(self, dirname, on_compile, filename): 45 | 46 | self.filename = filename 47 | 48 | ##TODO: Proper handler for this 49 | try: 50 | self.dirname = self.getLocalOverride.get('dirname') \ 51 | or dirname.replace('\\', '/') 52 | except Exception as e: 53 | self.dirname = dirname.replace('\\', '/') 54 | try: 55 | self.command = self.getLocalOverride.get('command') or 'coffee -c' 56 | except Exception as e: 57 | self.command = 'coffee -c' 58 | 59 | self.stdout = None 60 | self.stderr = None 61 | self.on_compile = on_compile 62 | threading.Thread.__init__(self) 63 | 64 | def run(self): 65 | cmd = self.command + " " + self.filename + " " + self.filename.replace('.coffee','.js') 66 | 67 | p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, 68 | stdout=subprocess.PIPE, 69 | stderr=subprocess.STDOUT) 70 | test = p.stdout.read() 71 | # if there is no result, everything worked Great! 72 | if not test: 73 | self.on_compile() 74 | else: 75 | # something went wrong... 76 | err = test.split('\n') 77 | sublime.error_message(err[0]) 78 | 79 | class coffeePreprocessor(LiveReload.Plugin, sublime_plugin.EventListener): 80 | 81 | title = 'CoffeeScript Preprocessor' 82 | description = 'Coffeescript Compile and refresh page, when file is compiled' 83 | file_types = '.coffee' 84 | this_session_only = True 85 | file_name = '' 86 | 87 | def on_post_save(self, view): 88 | self.original_filename = os.path.basename(view.file_name()) 89 | 90 | if self.should_run(self.original_filename): 91 | self.file_name_to_refresh = \ 92 | self.original_filename.replace('.coffee', '.js') 93 | dirname = os.path.dirname(view.file_name()) 94 | CoffeeThread(dirname, self.on_compile, self.original_filename).start() 95 | 96 | def on_compile(self): 97 | print(self.file_name_to_refresh) 98 | settings = { 99 | 'path': self.file_name_to_refresh, 100 | 'apply_js_live': False, 101 | 'apply_css_live': False, 102 | 'apply_images_live': False, 103 | } 104 | self.sendCommand('refresh', settings, self.original_filename) -------------------------------------------------------------------------------- /CommandAPI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sublime 5 | import sublime_plugin 6 | import LiveReload 7 | import webbrowser 8 | import os 9 | 10 | 11 | class LiveReloadTest(sublime_plugin.ApplicationCommand): 12 | 13 | def run(self): 14 | path = os.path.join(sublime.packages_path(), 'LiveReload', 'web') 15 | file_name = os.path.join(path, 'test.html') 16 | webbrowser.open_new_tab("file://"+file_name) 17 | 18 | 19 | class LiveReloadHelp(sublime_plugin.ApplicationCommand): 20 | 21 | def run(self): 22 | webbrowser.open_new_tab('https://github.com/alepez/LiveReload-sublimetext3#using' 23 | ) 24 | 25 | 26 | class LiveReloadEnablePluginCommand(sublime_plugin.ApplicationCommand): 27 | 28 | def on_done(self, index): 29 | if index != -1: 30 | LiveReload.Plugin.togglePlugin(index) 31 | 32 | def run(self): 33 | sublime.active_window().show_quick_panel(LiveReload.Plugin.listPlugins(), 34 | self.on_done) 35 | -------------------------------------------------------------------------------- /CompassPlugin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .server.Settings import read_sublime_settings 5 | import os 6 | import threading 7 | import subprocess 8 | import sys 9 | import sublime 10 | import sublime_plugin 11 | import shlex 12 | import re 13 | import json 14 | 15 | # fix for import order 16 | 17 | sys.path.append(os.path.join(sublime.packages_path(), 'LiveReload')) 18 | LiveReload = __import__('LiveReload') 19 | sys.path.remove(os.path.join(sublime.packages_path(), 'LiveReload')) 20 | 21 | 22 | class CompassThread(threading.Thread): 23 | 24 | def getLocalOverride(self): 25 | """ 26 | You can override defaults in sublime-project file 27 | 28 | Discussion: https://github.com/dz0ny/LiveReload-sublimetext2/issues/43 29 | 30 | Example: 31 | 32 | "settings": { 33 | "lrcompass": { 34 | "dirname": "/path/to/directory/which/contains/config.rb", 35 | "command": "compass compile -e production --force" 36 | } 37 | } 38 | """ 39 | try: 40 | view_settings = sublime.active_window().active_view().settings() 41 | view_settings = view_settings.get('lrcompass') 42 | if view_settings: 43 | return view_settings 44 | else: 45 | return {} 46 | except Exception: 47 | return {} 48 | 49 | def __init__(self, dirname, on_compile): 50 | ##TODO: Proper handler for this 51 | try: 52 | self.dirname = self.getLocalOverride.get('dirname') \ 53 | or dirname.replace('\\', '/') 54 | except Exception as e: 55 | self.dirname = dirname.replace('\\', '/') 56 | 57 | try: 58 | self.command = self.getLocalOverride.get('command') or 'compass compile' 59 | except Exception as e: 60 | self.command = 'compass compile' 61 | 62 | self.stdout = None 63 | self.stderr = None 64 | self.on_compile = on_compile 65 | threading.Thread.__init__(self) 66 | 67 | # Check if a config.rb file exists in the current directory 68 | # If no config.rb is found then check the parent and stop when root directory is reached 69 | def check_for_compass_config(self): 70 | if os.path.isfile(os.path.join(self.dirname, "config.rb")): 71 | return True 72 | dirname = os.path.abspath(os.path.join(self.dirname, os.pardir)).replace('\\', '/') 73 | if self.dirname == dirname: 74 | return False 75 | else: 76 | self.dirname = dirname 77 | return self.check_for_compass_config() 78 | 79 | # Generate config.rb file 80 | def generate_conf_rb(self, dirname): 81 | config_rb = """http_path = "/" 82 | css_dir = "." 83 | sass_dir = "." 84 | images_dir = "img" 85 | javascripts_dir = "javascripts" 86 | output_style = :nested 87 | relative_assets=true 88 | line_comments = false 89 | """ 90 | with open(os.path.join(dirname, "config.rb"), 'w') as f: 91 | f.write(config_rb) 92 | self.dirname = dirname 93 | return 94 | 95 | def run(self): 96 | dirname = self.dirname 97 | if not self.check_for_compass_config(): 98 | if read_sublime_settings(os.path.join(sublime.packages_path(),'LiveReload','CompassPlugin.sublime-settings'))["create_configrb"]: 99 | self.generate_conf_rb(dirname) 100 | else: 101 | sublime.error_message("Could not find Compass config.rb. Please check your sublime-project file and adjust settings accordingly!") 102 | return 103 | # cmd = shlex.split(self.command) 104 | # cmd.append(self.dirname) 105 | # Mac doesn't compile array 106 | cmd = self.command + ' "' + self.dirname + '"' 107 | p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, 108 | stdout=subprocess.PIPE, 109 | stderr=subprocess.STDOUT) 110 | compiled = p.stdout.read() 111 | 112 | # Find the file to refresh from the console output 113 | if compiled: 114 | print("Compass : " + compiled.decode("utf-8")); 115 | matches = re.findall('\S+\.css', compiled.decode("utf-8")) 116 | if len(matches) > 0: 117 | for match in matches: 118 | self.on_compile(match) 119 | 120 | 121 | class CompassPreprocessor(LiveReload.Plugin, 122 | sublime_plugin.EventListener): 123 | 124 | title = 'Compass Preprocessor' 125 | description = 'Compile and refresh page, when file is compiled' 126 | file_types = '.scss,.sass' 127 | this_session_only = True 128 | file_name = '' 129 | 130 | def on_post_save(self, view): 131 | self.original_filename = os.path.basename(view.file_name()) 132 | if self.should_run(self.original_filename): 133 | dirname = os.path.dirname(view.file_name()) 134 | CompassThread(dirname, self.on_compile).start() 135 | 136 | def on_compile(self, file_to_refresh): 137 | settings = { 138 | 'path': file_to_refresh, 139 | 'apply_js_live': False, 140 | 'apply_css_live': True, 141 | 'apply_images_live': True, 142 | } 143 | self.sendCommand('refresh', settings, self.original_filename) 144 | -------------------------------------------------------------------------------- /CompassPlugin.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "create_configrb": 1 3 | } -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "LiveReload: Self test", 4 | "command": "live_reload_test" 5 | }, 6 | { 7 | "caption": "LiveReload: Help", 8 | "command": "live_reload_help" 9 | }, 10 | { 11 | "caption": "LiveReload: Enable/disable plug-ins", 12 | "command": "live_reload_enable_plugin" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /LESSPlugin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import threading 6 | import subprocess 7 | import sys 8 | import sublime 9 | import sublime_plugin 10 | 11 | # fix for import order 12 | 13 | sys.path.append(os.path.join(sublime.packages_path(), 'LiveReload')) 14 | LiveReload = __import__('LiveReload') 15 | sys.path.remove(os.path.join(sublime.packages_path(), 'LiveReload')) 16 | 17 | 18 | class LessThread(threading.Thread): 19 | 20 | def getLocalOverride(self): 21 | """ 22 | You can override defaults in sublime-project file 23 | 24 | Discussion: https://github.com/dz0ny/LiveReload-sublimetext2/issues/43 25 | 26 | Example: 27 | 28 | "settings": { 29 | "lesscompass": { 30 | "command": "compass compile -e production --force" 31 | } 32 | } 33 | """ 34 | try: 35 | view_settings = sublime.active_window().active_view().settings() 36 | view_settings = view_settings.get('lrless') 37 | if view_settings: 38 | return view_settings 39 | else: 40 | return {} 41 | except Exception: 42 | return {} 43 | 44 | def __init__(self, dirname, on_compile, filename): 45 | 46 | self.filename = filename 47 | 48 | ##TODO: Proper handler for this 49 | try: 50 | self.dirname = self.getLocalOverride.get('dirname') \ 51 | or dirname.replace('\\', '/') 52 | except Exception as e: 53 | self.dirname = dirname.replace('\\', '/') 54 | # print(e) 55 | try: 56 | self.command = self.getLocalOverride.get('command') or 'lessc --verbose' 57 | except Exception as e: 58 | self.command = 'lessc --verbose' 59 | # print(e) 60 | 61 | self.stdout = None 62 | self.stderr = None 63 | self.on_compile = on_compile 64 | threading.Thread.__init__(self) 65 | 66 | def run(self): 67 | cmd = self.command + " " + self.filename + " " + self.filename.replace('.less','.css') 68 | 69 | p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, 70 | stdout=subprocess.PIPE, 71 | stderr=subprocess.STDOUT) 72 | test = p.stdout.read() 73 | if test: 74 | print("ATSDFASDFASDFASD!!") 75 | print(test) 76 | self.on_compile() 77 | 78 | class lessPreprocessor(LiveReload.Plugin, sublime_plugin.EventListener): 79 | 80 | title = 'Less Preprocessor' 81 | description = 'Less Compile and refresh page, when file is compiled' 82 | file_types = '.less' 83 | this_session_only = True 84 | file_name = '' 85 | 86 | def on_post_save(self, view): 87 | self.original_filename = os.path.basename(view.file_name()) 88 | 89 | if self.should_run(self.original_filename): 90 | self.file_name_to_refresh = \ 91 | self.original_filename.replace('.less', '.css') 92 | dirname = os.path.dirname(view.file_name()) 93 | LessThread(dirname, self.on_compile, self.original_filename).start() 94 | 95 | def on_compile(self): 96 | print(self.file_name_to_refresh) 97 | settings = { 98 | 'path': self.file_name_to_refresh, 99 | 'apply_js_live': False, 100 | 'apply_css_live': True, 101 | 'apply_images_live': True, 102 | } 103 | self.sendCommand('refresh', settings, self.original_filename) -------------------------------------------------------------------------------- /LiveReload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sublime 5 | import os 6 | import sys 7 | import threading 8 | import atexit 9 | import time 10 | 11 | try: 12 | 13 | # Python 3 14 | 15 | from .server.WebSocketServer import WebSocketServer 16 | from .server.SimpleResourceServer import SimpleResourceServer 17 | from .server.SimpleCallbackServer import SimpleCallbackServer 18 | from .server.SimpleWSServer import SimpleWSServer 19 | from .server.LiveReloadAPI import LiveReloadAPI 20 | from .server.PluginAPI import PluginInterface as Plugin 21 | from .server.Settings import Settings 22 | except ValueError: 23 | 24 | # Python 2 25 | 26 | from server.WebSocketServer import WebSocketServer 27 | from server.SimpleResourceServer import SimpleResourceServer 28 | from server.SimpleCallbackServer import SimpleCallbackServer 29 | from server.SimpleWSServer import SimpleWSServer 30 | from server.LiveReloadAPI import LiveReloadAPI 31 | from server.PluginAPI import PluginInterface as Plugin 32 | from server.Settings import Settings 33 | 34 | 35 | def singleton(cls): 36 | instances = {} 37 | 38 | def getinstance(): 39 | if cls not in instances: 40 | instances[cls] = cls() 41 | return instances[cls] 42 | 43 | return getinstance 44 | 45 | 46 | class LiveReload(threading.Thread, SimpleCallbackServer, 47 | SimpleWSServer, SimpleResourceServer, LiveReloadAPI): 48 | 49 | """ 50 | Start the LiveReload, which exposes public api. 51 | """ 52 | 53 | def __init__(self): 54 | 55 | threading.Thread.__init__(self) 56 | SimpleCallbackServer.__init__(self) 57 | SimpleWSServer.__init__(self) 58 | SimpleResourceServer.__init__(self) 59 | LiveReloadAPI.__init__(self) 60 | 61 | def run(self): 62 | """ 63 | Start LiveReload 64 | """ 65 | 66 | path = os.path.join(sublime.packages_path(), 'LiveReload', 'web' 67 | , 'dist', 'livereloadjs-sm2.js') 68 | local = open(path, 'rU') 69 | self.add_static_file('/livereload.js', local.read(), 70 | 'text/javascript') 71 | 72 | settings = Settings() 73 | self.port = settings.get('port', 35729) 74 | self.version = settings.get('version', '2.0') 75 | 76 | try: 77 | self.start_server(self.port) 78 | except Exception: 79 | sublime.error_message('Port(' + str(self.port) 80 | + ') is already using, trying (' 81 | + str(self.port + 1) + ')') 82 | time.sleep(3) 83 | self.start_server(self.port + 1) 84 | 85 | def start_server(self, port): 86 | """ 87 | Start the server. 88 | """ 89 | 90 | self.ws_server = WebSocketServer(port, self.version) 91 | self.ws_server.start() 92 | 93 | @atexit.register 94 | def clean(self): 95 | """ 96 | Stop the server. 97 | """ 98 | 99 | self.ws_server.stop() 100 | 101 | 102 | if sublime.platform != 'build': 103 | try: 104 | sys.modules['LiveReload'].API 105 | except Exception: 106 | API = LiveReload() 107 | API.start() 108 | 109 | 110 | def http_callback(callback_f): 111 | """ 112 | Add http callback to plugin defined function. For example request to GET /callback/plugin_name/log_me 113 | would trigger log_me function in plugin 114 | 115 | Example: 116 | :: 117 | 118 | @LiveReload.http_callback 119 | def compiled(self, req): 120 | print req # urlparse object 121 | return "cool" #to http client 122 | 123 | """ 124 | 125 | callback_f.path = 'http://localhost:35729/callback/%s/%s' \ 126 | % (callback_f.__module__.lower(), callback_f.__name__) 127 | sys.modules['LiveReload' 128 | ].API.callbacks.append({'path': callback_f.path, 129 | 'name': callback_f.__name__, 'cls': callback_f.__module__}) 130 | return callback_f 131 | 132 | 133 | def websocket_callback(callback_f): 134 | """ 135 | Add websocket callback to plugin defined function. For example on function call in browser 136 | LiveReload.SM2.plugin_name.definedfunction(data) would trigger definedfunction function in plugin or vice verse. 137 | Shortly you can call client functions from the server and server functions from client. Everything is JSON encoded 138 | by default. 139 | 140 | Example: 141 | :: 142 | 143 | @LiveReload.websocket_callback 144 | def compiled(self, json): 145 | print json # json object 146 | return "cool" #to http client {msg: "cool"} 147 | 148 | """ 149 | 150 | callback_f.path = 'SM2.%s.%s' % (callback_f.__module__.lower(), 151 | callback_f.__name__) 152 | sys.modules['LiveReload' 153 | ].API.ws_callbacks.append({'path': callback_f.path, 154 | 'name': callback_f.__name__, 'cls': callback_f.__module__}) 155 | return callback_f 156 | -------------------------------------------------------------------------------- /LiveReload.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "enabled_plugins": [] 3 | } -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Preferences", 4 | "mnemonic": "n", 5 | "id": "preferences", 6 | "children": [ 7 | { 8 | "caption": "Package Settings", 9 | "mnemonic": "P", 10 | "id": "package-settings", 11 | "children": [ 12 | { 13 | "caption": "LiveReload", 14 | "children": [ 15 | { 16 | "caption": "Plugins", 17 | "children": [ 18 | { 19 | "caption": "Enable/disable plugins", 20 | "command": "show_overlay", 21 | "args": { 22 | "overlay": "command_palette", 23 | "text": "LiveReload: Enable/disable plugins" 24 | } 25 | } 26 | ] 27 | }, 28 | { 29 | "caption": "-" 30 | }, 31 | { 32 | "command": "open_file", 33 | "args": { 34 | "file": "${packages}/LiveReload/LiveReload.sublime-settings" 35 | }, 36 | "caption": "Settings – Default" 37 | }, 38 | { 39 | "command": "open_file", 40 | "args": { 41 | "file": "${packages}/User/LiveReload.sublime-settings" 42 | }, 43 | "caption": "Settings – User" 44 | } 45 | ] 46 | } 47 | ] 48 | } 49 | ] 50 | } 51 | ] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @env PYTHONPATH=. pyvows test/ 3 | 4 | .PHONY: test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LiveReload for Sublime Text 3 2 | 3 | A web browser page reloading plugin for the [Sublime Text 3](http://sublimetext.com "Sublime Text 3") editor. 4 | 5 | ## Installing 6 | 7 | With [Package Control](http://wbond.net/sublime_packages/package_control): 8 | 9 | 1. Run “Package Control: Install Package” command, find and install `LiveReload` plugin. 10 | 2. Restart ST editor (if required) 11 | 12 | ### Manual install, Linux users 13 | 14 | ``` 15 | cd ~/.config/sublime-text-3/Packages 16 | rm -rf LiveReload 17 | git clone https://github.com/alepez/LiveReload-sublimetext3 LiveReload 18 | ``` 19 | 20 | ### Manual install, OSX users 21 | 22 | ``` 23 | cd ~/Library/Application\ Support/Sublime\ Text\ 3/Packages/ 24 | rm -rf LiveReload 25 | git clone https://github.com/alepez/LiveReload-sublimetext3 LiveReload 26 | ``` 27 | 28 | # Using 29 | 30 | Enable desired plug-ins via Command Palette (Ctrl+Shift+P) add livereload.js to you html document. 31 | 32 | ``` 33 | 34 | ``` 35 | 36 | You can also use one of the extensions listed here http://livereload.com/extensions/ 37 | 38 | ## Available plug-ins: 39 | 40 | - Compass Preprocessor, compiles .scss, .sass and refreshes page when file is compiled 41 | - Less Preprocessor, compiles .less and refreshes page when file is compiled 42 | - Sass Preprocessor, compiles .scss, .sass with the latest installed sass version and refreshes page when file is compiled 43 | - CoffeeScript Preprocessor, compiles .coffee and refreshes page when file is compiled 44 | - Simple Reload, refresh page when file is saved 45 | - Simple Reload with delay(400ms), wait 400ms then refresh page, when file is saved 46 | 47 | ## Examples 48 | 49 | - Simple Reload from http GET request, reloads page on visit to http://localhost:35729/callback/simplereloadplugincallback/on_post_compile 50 | - Send content on change, sends file content to browser console 51 | 52 | ## Sass Preprocessor usage 53 | 54 | First, install latest version of sass 55 | ```bash 56 | sudo gem install sass 57 | ``` 58 | Activate the plugin in SublimeText3 via the `package settings -> livereload -> plugins -> enable/disable plugins` menu 59 | 60 | By default, the plugin save the compiled css in same dir of sources. 61 | You can change this by creating a `sass_config.json` file near your sources: 62 | ```json 63 | { 64 | "destination_dir": "../../webroot/css" 65 | } 66 | ``` 67 | 68 | # Plug-in api 69 | 70 | https://livereload-for-sublime-text.readthedocs.org/en/latest/ 71 | 72 | # Thanks 73 | 74 | The original plugin was written by [Janez Troha](https://github.com/dz0ny) 75 | 76 | -------------------------------------------------------------------------------- /SassPlugin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .server.Settings import read_sublime_settings 5 | import os 6 | import threading 7 | import subprocess 8 | import sys 9 | import sublime 10 | import sublime_plugin 11 | import shlex 12 | import re 13 | import json 14 | 15 | # fix for import order 16 | 17 | sys.path.append(os.path.join(sublime.packages_path(), 'LiveReload')) 18 | LiveReload = __import__('LiveReload') 19 | sys.path.remove(os.path.join(sublime.packages_path(), 'LiveReload')) 20 | 21 | 22 | class SassThread(threading.Thread): 23 | # init class 24 | def __init__(self, dirname, on_compile, filename): 25 | # filename 26 | self.filename = filename 27 | 28 | # dirname 29 | try: 30 | self.dirname = self.getLocalOverride.get('dirname') or dirname.replace('\\', '/') 31 | except Exception as e: 32 | self.dirname = dirname.replace('\\', '/') 33 | 34 | # default config 35 | self.config = read_sublime_settings(os.path.join(sublime.packages_path(),'LiveReload','SassPlugin.sublime-settings')) or {} 36 | 37 | # check for local config 38 | localConfigFile = os.path.join(self.dirname, "sass_config.json"); 39 | if os.path.isfile(localConfigFile): 40 | localConfig = read_sublime_settings(localConfigFile) 41 | self.config.update(localConfig) 42 | 43 | try: 44 | self.command = self.getLocalOverride.get('command') or 'sass --update --stop-on-error --no-cache --sourcemap=none' 45 | except Exception as e: 46 | self.command = 'sass --update --stop-on-error --no-cache --sourcemap=none' 47 | 48 | self.stdout = None 49 | self.stderr = None 50 | self.on_compile = on_compile 51 | threading.Thread.__init__(self) 52 | 53 | # get settings from user settings with prefix lrsass 54 | def getLocalOverride(self): 55 | try: 56 | view_settings = sublime.active_window().active_view().settings() 57 | view_settings = view_settings.get('lrsass') 58 | if view_settings: 59 | return view_settings 60 | else: 61 | return {} 62 | except Exception: 63 | return {} 64 | 65 | def run(self): 66 | 67 | source = os.path.join(self.dirname, self.filename) 68 | destinationDir = self.dirname if self.config['destination_dir'] is None else self.config['destination_dir'] 69 | destination = os.path.abspath(os.path.join(self.dirname, destinationDir, re.sub("\.(sass|scss)", '.css', self.filename))) 70 | 71 | cmd = self.command + ' "' + source + '":"' + destination + '"' 72 | 73 | print("[LiveReload Sass] source : " + source) 74 | print("[LiveReload Sass] destination : " + destination) 75 | print("[LiveReload Sass] Cmd : " + cmd) 76 | 77 | p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, 78 | stdout=subprocess.PIPE, 79 | stderr=subprocess.STDOUT) 80 | compiled = p.stdout.read() 81 | 82 | # Find the file to refresh from the console output 83 | if compiled: 84 | print("[LiveReload Sass] reloading : " + compiled.decode("utf-8")); 85 | matches = re.findall('\S+\.css', compiled.decode("utf-8")) 86 | if len(matches) > 0: 87 | for match in matches: 88 | self.on_compile(match) 89 | 90 | 91 | class SassPreprocessor(LiveReload.Plugin, sublime_plugin.EventListener): 92 | title = 'Sass Preprocessor' 93 | description = 'Compile and refresh page, when file is compiled' 94 | file_types = '.scss,.sass' 95 | this_session_only = True 96 | file_name = '' 97 | 98 | def on_post_save(self, view): 99 | self.original_filename = os.path.basename(view.file_name()) 100 | if self.should_run(self.original_filename): 101 | dirname = os.path.dirname(view.file_name()) 102 | SassThread(dirname, self.on_compile, self.original_filename).start() 103 | 104 | def on_compile(self, file_to_refresh): 105 | settings = { 106 | 'path': file_to_refresh, 107 | 'apply_js_live': False, 108 | 'apply_css_live': True, 109 | 'apply_images_live': True, 110 | } 111 | self.sendCommand('refresh', settings, self.original_filename) 112 | -------------------------------------------------------------------------------- /SassPlugin.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "destination_dir": null 3 | } -------------------------------------------------------------------------------- /SimpleReloadCallback.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import sublime 7 | import sublime_plugin 8 | 9 | # fix for import order 10 | 11 | sys.path.append(os.path.join(sublime.packages_path(), 'LiveReload')) 12 | LiveReload = __import__('LiveReload') 13 | sys.path.remove(os.path.join(sublime.packages_path(), 'LiveReload')) 14 | 15 | ##Modlue name must be the same as class or else callbacks won't work 16 | class SimpleReloadCallback(LiveReload.Plugin): 17 | 18 | title = 'Simple Reload from http GET request' 19 | description = \ 20 | 'Refresh page, on http://localhost:35729/callback/simplereloadplugincallback/on_post_compile' 21 | file_types = '*' 22 | this_session_only = True 23 | 24 | @LiveReload.http_callback 25 | def on_post_compile(self, req): 26 | self.refresh(os.path.basename(sublime.active_window().active_view().file_name())) 27 | return 'All ok from SimpleRefreshCallBack!' 28 | -------------------------------------------------------------------------------- /SimpleReloadPlugin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import sublime 7 | import sublime_plugin 8 | 9 | # fix for import order 10 | 11 | sys.path.append(os.path.join(sublime.packages_path(), 'LiveReload')) 12 | LiveReload = __import__('LiveReload') 13 | sys.path.remove(os.path.join(sublime.packages_path(), 'LiveReload')) 14 | 15 | 16 | class SimpleRefresh(LiveReload.Plugin, sublime_plugin.EventListener): 17 | 18 | title = 'Simple Reload' 19 | description = 'Refresh page, when file is saved' 20 | file_types = '*' 21 | 22 | def on_post_save(self, view): 23 | self.refresh(os.path.basename(view.file_name())) 24 | -------------------------------------------------------------------------------- /SimpleReloadPluginDelay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import sublime 7 | import sublime_plugin 8 | 9 | # fix for import order 10 | 11 | sys.path.append(os.path.join(sublime.packages_path(), 'LiveReload')) 12 | LiveReload = __import__('LiveReload') 13 | sys.path.remove(os.path.join(sublime.packages_path(), 'LiveReload')) 14 | 15 | 16 | class SimpleRefreshDelay(LiveReload.Plugin, sublime_plugin.EventListener): 17 | 18 | title = 'Simple Reload with delay(400ms)' 19 | description = 'Wait 400ms then refresh page, when file is saved' 20 | file_types = '*' 21 | 22 | def on_post_save(self, view): 23 | ref = self 24 | sublime.set_timeout(lambda : ref.refresh(os.path.basename(view.file_name())), 400) 25 | -------------------------------------------------------------------------------- /SimpleWSCallback.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import sublime 7 | import sublime_plugin 8 | 9 | # fix for import order 10 | 11 | sys.path.append(os.path.join(sublime.packages_path(), 'LiveReload')) 12 | LiveReload = __import__('LiveReload') 13 | sys.path.remove(os.path.join(sublime.packages_path(), 'LiveReload')) 14 | 15 | ##Modlue name must be the same as class or else callbacks won't work 16 | class SimpleWSCallback(LiveReload.Plugin, sublime_plugin.EventListener): 17 | 18 | title = 'Send content on change' 19 | description = \ 20 | 'Send file content to browser console' 21 | file_types = '*' 22 | this_session_only = True 23 | 24 | def on_modified_async(self, view): 25 | if self.isEnabled: 26 | region = sublime.Region(0, view.size()) 27 | source = view.substr(region) 28 | self.sendRaw("socket", source) 29 | 30 | def onReceive(self, data, origin): 31 | print(data) 32 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | try: 5 | from .LiveReload import * 6 | from .server import * 7 | except ValueError: 8 | from LiveReload import * 9 | from server import * -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/LiveReloadforSublimeText2.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/LiveReloadforSublimeText2.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/LiveReloadforSublimeText2" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/LiveReloadforSublimeText2" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/_build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/_build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/_build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 507d892113ce68dec4790f5f4fa382bf 4 | tags: a205e9ed8462ae86fdd2f73488852ba9 5 | -------------------------------------------------------------------------------- /docs/_build/html/_modules/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Overview: module code — LiveReload for Sublime Text 2 2.0.9 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 41 | 42 |
43 |
44 |
45 |
46 | 47 |

All modules for which code is available

48 | 51 | 52 |
53 |
54 |
55 |
56 |
57 | 69 | 70 |
71 |
72 |
73 |
74 | 86 | 90 | 91 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/index.txt: -------------------------------------------------------------------------------- 1 | .. LiveReload for Sublime Text 2 documentation master file, created by 2 | sphinx-quickstart on Mon Feb 18 09:38:07 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | LiveReload for Sublime Text 7 | ========================================================= 8 | 9 | Plugins api 10 | **************************** 11 | .. autoclass:: PluginAPI.PluginClass 12 | :members: 13 | :show-inheritance: 14 | 15 | Decorators: 16 | 17 | .. automodule:: LiveReload 18 | :members: 19 | :show-inheritance: 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | 28 | -------------------------------------------------------------------------------- /docs/_build/html/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/html/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/_build/html/_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | /* -- relbar ---------------------------------------------------------------- */ 19 | 20 | div.related { 21 | width: 100%; 22 | font-size: 90%; 23 | } 24 | 25 | div.related h3 { 26 | display: none; 27 | } 28 | 29 | div.related ul { 30 | margin: 0; 31 | padding: 0 0 0 10px; 32 | list-style: none; 33 | } 34 | 35 | div.related li { 36 | display: inline; 37 | } 38 | 39 | div.related li.right { 40 | float: right; 41 | margin-right: 5px; 42 | } 43 | 44 | /* -- sidebar --------------------------------------------------------------- */ 45 | 46 | div.sphinxsidebarwrapper { 47 | padding: 10px 5px 0 10px; 48 | } 49 | 50 | div.sphinxsidebar { 51 | float: left; 52 | width: 230px; 53 | margin-left: -100%; 54 | font-size: 90%; 55 | } 56 | 57 | div.sphinxsidebar ul { 58 | list-style: none; 59 | } 60 | 61 | div.sphinxsidebar ul ul, 62 | div.sphinxsidebar ul.want-points { 63 | margin-left: 20px; 64 | list-style: square; 65 | } 66 | 67 | div.sphinxsidebar ul ul { 68 | margin-top: 0; 69 | margin-bottom: 0; 70 | } 71 | 72 | div.sphinxsidebar form { 73 | margin-top: 10px; 74 | } 75 | 76 | div.sphinxsidebar input { 77 | border: 1px solid #98dbcc; 78 | font-family: sans-serif; 79 | font-size: 1em; 80 | } 81 | 82 | div.sphinxsidebar #searchbox input[type="text"] { 83 | width: 170px; 84 | } 85 | 86 | div.sphinxsidebar #searchbox input[type="submit"] { 87 | width: 30px; 88 | } 89 | 90 | img { 91 | border: 0; 92 | } 93 | 94 | /* -- search page ----------------------------------------------------------- */ 95 | 96 | ul.search { 97 | margin: 10px 0 0 20px; 98 | padding: 0; 99 | } 100 | 101 | ul.search li { 102 | padding: 5px 0 5px 20px; 103 | background-image: url(file.png); 104 | background-repeat: no-repeat; 105 | background-position: 0 7px; 106 | } 107 | 108 | ul.search li a { 109 | font-weight: bold; 110 | } 111 | 112 | ul.search li div.context { 113 | color: #888; 114 | margin: 2px 0 0 30px; 115 | text-align: left; 116 | } 117 | 118 | ul.keywordmatches li.goodmatch a { 119 | font-weight: bold; 120 | } 121 | 122 | /* -- index page ------------------------------------------------------------ */ 123 | 124 | table.contentstable { 125 | width: 90%; 126 | } 127 | 128 | table.contentstable p.biglink { 129 | line-height: 150%; 130 | } 131 | 132 | a.biglink { 133 | font-size: 1.3em; 134 | } 135 | 136 | span.linkdescr { 137 | font-style: italic; 138 | padding-top: 5px; 139 | font-size: 90%; 140 | } 141 | 142 | /* -- general index --------------------------------------------------------- */ 143 | 144 | table.indextable { 145 | width: 100%; 146 | } 147 | 148 | table.indextable td { 149 | text-align: left; 150 | vertical-align: top; 151 | } 152 | 153 | table.indextable dl, table.indextable dd { 154 | margin-top: 0; 155 | margin-bottom: 0; 156 | } 157 | 158 | table.indextable tr.pcap { 159 | height: 10px; 160 | } 161 | 162 | table.indextable tr.cap { 163 | margin-top: 10px; 164 | background-color: #f2f2f2; 165 | } 166 | 167 | img.toggler { 168 | margin-right: 3px; 169 | margin-top: 3px; 170 | cursor: pointer; 171 | } 172 | 173 | div.modindex-jumpbox { 174 | border-top: 1px solid #ddd; 175 | border-bottom: 1px solid #ddd; 176 | margin: 1em 0 1em 0; 177 | padding: 0.4em; 178 | } 179 | 180 | div.genindex-jumpbox { 181 | border-top: 1px solid #ddd; 182 | border-bottom: 1px solid #ddd; 183 | margin: 1em 0 1em 0; 184 | padding: 0.4em; 185 | } 186 | 187 | /* -- general body styles --------------------------------------------------- */ 188 | 189 | a.headerlink { 190 | visibility: hidden; 191 | } 192 | 193 | h1:hover > a.headerlink, 194 | h2:hover > a.headerlink, 195 | h3:hover > a.headerlink, 196 | h4:hover > a.headerlink, 197 | h5:hover > a.headerlink, 198 | h6:hover > a.headerlink, 199 | dt:hover > a.headerlink { 200 | visibility: visible; 201 | } 202 | 203 | div.body p.caption { 204 | text-align: inherit; 205 | } 206 | 207 | div.body td { 208 | text-align: left; 209 | } 210 | 211 | .field-list ul { 212 | padding-left: 1em; 213 | } 214 | 215 | .first { 216 | margin-top: 0 !important; 217 | } 218 | 219 | p.rubric { 220 | margin-top: 30px; 221 | font-weight: bold; 222 | } 223 | 224 | img.align-left, .figure.align-left, object.align-left { 225 | clear: left; 226 | float: left; 227 | margin-right: 1em; 228 | } 229 | 230 | img.align-right, .figure.align-right, object.align-right { 231 | clear: right; 232 | float: right; 233 | margin-left: 1em; 234 | } 235 | 236 | img.align-center, .figure.align-center, object.align-center { 237 | display: block; 238 | margin-left: auto; 239 | margin-right: auto; 240 | } 241 | 242 | .align-left { 243 | text-align: left; 244 | } 245 | 246 | .align-center { 247 | text-align: center; 248 | } 249 | 250 | .align-right { 251 | text-align: right; 252 | } 253 | 254 | /* -- sidebars -------------------------------------------------------------- */ 255 | 256 | div.sidebar { 257 | margin: 0 0 0.5em 1em; 258 | border: 1px solid #ddb; 259 | padding: 7px 7px 0 7px; 260 | background-color: #ffe; 261 | width: 40%; 262 | float: right; 263 | } 264 | 265 | p.sidebar-title { 266 | font-weight: bold; 267 | } 268 | 269 | /* -- topics ---------------------------------------------------------------- */ 270 | 271 | div.topic { 272 | border: 1px solid #ccc; 273 | padding: 7px 7px 0 7px; 274 | margin: 10px 0 10px 0; 275 | } 276 | 277 | p.topic-title { 278 | font-size: 1.1em; 279 | font-weight: bold; 280 | margin-top: 10px; 281 | } 282 | 283 | /* -- admonitions ----------------------------------------------------------- */ 284 | 285 | div.admonition { 286 | margin-top: 10px; 287 | margin-bottom: 10px; 288 | padding: 7px; 289 | } 290 | 291 | div.admonition dt { 292 | font-weight: bold; 293 | } 294 | 295 | div.admonition dl { 296 | margin-bottom: 0; 297 | } 298 | 299 | p.admonition-title { 300 | margin: 0px 10px 5px 0px; 301 | font-weight: bold; 302 | } 303 | 304 | div.body p.centered { 305 | text-align: center; 306 | margin-top: 25px; 307 | } 308 | 309 | /* -- tables ---------------------------------------------------------------- */ 310 | 311 | table.docutils { 312 | border: 0; 313 | border-collapse: collapse; 314 | } 315 | 316 | table.docutils td, table.docutils th { 317 | padding: 1px 8px 1px 5px; 318 | border-top: 0; 319 | border-left: 0; 320 | border-right: 0; 321 | border-bottom: 1px solid #aaa; 322 | } 323 | 324 | table.field-list td, table.field-list th { 325 | border: 0 !important; 326 | } 327 | 328 | table.footnote td, table.footnote th { 329 | border: 0 !important; 330 | } 331 | 332 | th { 333 | text-align: left; 334 | padding-right: 5px; 335 | } 336 | 337 | table.citation { 338 | border-left: solid 1px gray; 339 | margin-left: 1px; 340 | } 341 | 342 | table.citation td { 343 | border-bottom: none; 344 | } 345 | 346 | /* -- other body styles ----------------------------------------------------- */ 347 | 348 | ol.arabic { 349 | list-style: decimal; 350 | } 351 | 352 | ol.loweralpha { 353 | list-style: lower-alpha; 354 | } 355 | 356 | ol.upperalpha { 357 | list-style: upper-alpha; 358 | } 359 | 360 | ol.lowerroman { 361 | list-style: lower-roman; 362 | } 363 | 364 | ol.upperroman { 365 | list-style: upper-roman; 366 | } 367 | 368 | dl { 369 | margin-bottom: 15px; 370 | } 371 | 372 | dd p { 373 | margin-top: 0px; 374 | } 375 | 376 | dd ul, dd table { 377 | margin-bottom: 10px; 378 | } 379 | 380 | dd { 381 | margin-top: 3px; 382 | margin-bottom: 10px; 383 | margin-left: 30px; 384 | } 385 | 386 | dt:target, .highlighted { 387 | background-color: #fbe54e; 388 | } 389 | 390 | dl.glossary dt { 391 | font-weight: bold; 392 | font-size: 1.1em; 393 | } 394 | 395 | .field-list ul { 396 | margin: 0; 397 | padding-left: 1em; 398 | } 399 | 400 | .field-list p { 401 | margin: 0; 402 | } 403 | 404 | .refcount { 405 | color: #060; 406 | } 407 | 408 | .optional { 409 | font-size: 1.3em; 410 | } 411 | 412 | .versionmodified { 413 | font-style: italic; 414 | } 415 | 416 | .system-message { 417 | background-color: #fda; 418 | padding: 5px; 419 | border: 3px solid red; 420 | } 421 | 422 | .footnote:target { 423 | background-color: #ffa; 424 | } 425 | 426 | .line-block { 427 | display: block; 428 | margin-top: 1em; 429 | margin-bottom: 1em; 430 | } 431 | 432 | .line-block .line-block { 433 | margin-top: 0; 434 | margin-bottom: 0; 435 | margin-left: 1.5em; 436 | } 437 | 438 | .guilabel, .menuselection { 439 | font-family: sans-serif; 440 | } 441 | 442 | .accelerator { 443 | text-decoration: underline; 444 | } 445 | 446 | .classifier { 447 | font-style: oblique; 448 | } 449 | 450 | abbr, acronym { 451 | border-bottom: dotted 1px; 452 | cursor: help; 453 | } 454 | 455 | /* -- code displays --------------------------------------------------------- */ 456 | 457 | pre { 458 | overflow: auto; 459 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 460 | } 461 | 462 | td.linenos pre { 463 | padding: 5px 0px; 464 | border: 0; 465 | background-color: transparent; 466 | color: #aaa; 467 | } 468 | 469 | table.highlighttable { 470 | margin-left: 0.5em; 471 | } 472 | 473 | table.highlighttable td { 474 | padding: 0 0.5em 0 0.5em; 475 | } 476 | 477 | tt.descname { 478 | background-color: transparent; 479 | font-weight: bold; 480 | font-size: 1.2em; 481 | } 482 | 483 | tt.descclassname { 484 | background-color: transparent; 485 | } 486 | 487 | tt.xref, a tt { 488 | background-color: transparent; 489 | font-weight: bold; 490 | } 491 | 492 | h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { 493 | background-color: transparent; 494 | } 495 | 496 | .viewcode-link { 497 | float: right; 498 | } 499 | 500 | .viewcode-back { 501 | float: right; 502 | font-family: sans-serif; 503 | } 504 | 505 | div.viewcode-block:target { 506 | margin: -1px -10px; 507 | padding: 0 10px; 508 | } 509 | 510 | /* -- math display ---------------------------------------------------------- */ 511 | 512 | img.math { 513 | vertical-align: middle; 514 | } 515 | 516 | div.body div.math p { 517 | text-align: center; 518 | } 519 | 520 | span.eqno { 521 | float: right; 522 | } 523 | 524 | /* -- printout stylesheet --------------------------------------------------- */ 525 | 526 | @media print { 527 | div.document, 528 | div.documentwrapper, 529 | div.bodywrapper { 530 | margin: 0 !important; 531 | width: 100%; 532 | } 533 | 534 | div.sphinxsidebar, 535 | div.related, 536 | div.footer, 537 | #top-link { 538 | display: none; 539 | } 540 | } -------------------------------------------------------------------------------- /docs/_build/html/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/html/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/_build/html/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/html/_static/comment-close.png -------------------------------------------------------------------------------- /docs/_build/html/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/html/_static/comment.png -------------------------------------------------------------------------------- /docs/_build/html/_static/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | * default.css_t 3 | * ~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- default theme. 6 | * 7 | * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: sans-serif; 18 | font-size: 100%; 19 | background-color: #11303d; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | background-color: #1c4e63; 27 | } 28 | 29 | div.documentwrapper { 30 | float: left; 31 | width: 100%; 32 | } 33 | 34 | div.bodywrapper { 35 | margin: 0 0 0 230px; 36 | } 37 | 38 | div.body { 39 | background-color: #ffffff; 40 | color: #000000; 41 | padding: 0 20px 30px 20px; 42 | } 43 | 44 | div.footer { 45 | color: #ffffff; 46 | width: 100%; 47 | padding: 9px 0 9px 0; 48 | text-align: center; 49 | font-size: 75%; 50 | } 51 | 52 | div.footer a { 53 | color: #ffffff; 54 | text-decoration: underline; 55 | } 56 | 57 | div.related { 58 | background-color: #133f52; 59 | line-height: 30px; 60 | color: #ffffff; 61 | } 62 | 63 | div.related a { 64 | color: #ffffff; 65 | } 66 | 67 | div.sphinxsidebar { 68 | } 69 | 70 | div.sphinxsidebar h3 { 71 | font-family: 'Trebuchet MS', sans-serif; 72 | color: #ffffff; 73 | font-size: 1.4em; 74 | font-weight: normal; 75 | margin: 0; 76 | padding: 0; 77 | } 78 | 79 | div.sphinxsidebar h3 a { 80 | color: #ffffff; 81 | } 82 | 83 | div.sphinxsidebar h4 { 84 | font-family: 'Trebuchet MS', sans-serif; 85 | color: #ffffff; 86 | font-size: 1.3em; 87 | font-weight: normal; 88 | margin: 5px 0 0 0; 89 | padding: 0; 90 | } 91 | 92 | div.sphinxsidebar p { 93 | color: #ffffff; 94 | } 95 | 96 | div.sphinxsidebar p.topless { 97 | margin: 5px 10px 10px 10px; 98 | } 99 | 100 | div.sphinxsidebar ul { 101 | margin: 10px; 102 | padding: 0; 103 | color: #ffffff; 104 | } 105 | 106 | div.sphinxsidebar a { 107 | color: #98dbcc; 108 | } 109 | 110 | div.sphinxsidebar input { 111 | border: 1px solid #98dbcc; 112 | font-family: sans-serif; 113 | font-size: 1em; 114 | } 115 | 116 | 117 | 118 | /* -- hyperlink styles ------------------------------------------------------ */ 119 | 120 | a { 121 | color: #355f7c; 122 | text-decoration: none; 123 | } 124 | 125 | a:visited { 126 | color: #355f7c; 127 | text-decoration: none; 128 | } 129 | 130 | a:hover { 131 | text-decoration: underline; 132 | } 133 | 134 | 135 | 136 | /* -- body styles ----------------------------------------------------------- */ 137 | 138 | div.body h1, 139 | div.body h2, 140 | div.body h3, 141 | div.body h4, 142 | div.body h5, 143 | div.body h6 { 144 | font-family: 'Trebuchet MS', sans-serif; 145 | background-color: #f2f2f2; 146 | font-weight: normal; 147 | color: #20435c; 148 | border-bottom: 1px solid #ccc; 149 | margin: 20px -20px 10px -20px; 150 | padding: 3px 0 3px 10px; 151 | } 152 | 153 | div.body h1 { margin-top: 0; font-size: 200%; } 154 | div.body h2 { font-size: 160%; } 155 | div.body h3 { font-size: 140%; } 156 | div.body h4 { font-size: 120%; } 157 | div.body h5 { font-size: 110%; } 158 | div.body h6 { font-size: 100%; } 159 | 160 | a.headerlink { 161 | color: #c60f0f; 162 | font-size: 0.8em; 163 | padding: 0 4px 0 4px; 164 | text-decoration: none; 165 | } 166 | 167 | a.headerlink:hover { 168 | background-color: #c60f0f; 169 | color: white; 170 | } 171 | 172 | div.body p, div.body dd, div.body li { 173 | text-align: justify; 174 | line-height: 130%; 175 | } 176 | 177 | div.admonition p.admonition-title + p { 178 | display: inline; 179 | } 180 | 181 | div.admonition p { 182 | margin-bottom: 5px; 183 | } 184 | 185 | div.admonition pre { 186 | margin-bottom: 5px; 187 | } 188 | 189 | div.admonition ul, div.admonition ol { 190 | margin-bottom: 5px; 191 | } 192 | 193 | div.note { 194 | background-color: #eee; 195 | border: 1px solid #ccc; 196 | } 197 | 198 | div.seealso { 199 | background-color: #ffc; 200 | border: 1px solid #ff6; 201 | } 202 | 203 | div.topic { 204 | background-color: #eee; 205 | } 206 | 207 | div.warning { 208 | background-color: #ffe4e4; 209 | border: 1px solid #f66; 210 | } 211 | 212 | p.admonition-title { 213 | display: inline; 214 | } 215 | 216 | p.admonition-title:after { 217 | content: ":"; 218 | } 219 | 220 | pre { 221 | padding: 5px; 222 | background-color: #eeffcc; 223 | color: #333333; 224 | line-height: 120%; 225 | border: 1px solid #ac9; 226 | border-left: none; 227 | border-right: none; 228 | } 229 | 230 | tt { 231 | background-color: #ecf0f3; 232 | padding: 0 1px 0 1px; 233 | font-size: 0.95em; 234 | } 235 | 236 | th { 237 | background-color: #ede; 238 | } 239 | 240 | .warning tt { 241 | background: #efc2c2; 242 | } 243 | 244 | .note tt { 245 | background: #d6d6d6; 246 | } 247 | 248 | .viewcode-back { 249 | font-family: sans-serif; 250 | } 251 | 252 | div.viewcode-block:target { 253 | background-color: #f4debf; 254 | border-top: 1px solid #ac9; 255 | border-bottom: 1px solid #ac9; 256 | } -------------------------------------------------------------------------------- /docs/_build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node) { 70 | if (node.nodeType == 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 74 | var span = document.createElement("span"); 75 | span.className = className; 76 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 77 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 78 | document.createTextNode(val.substr(pos + text.length)), 79 | node.nextSibling)); 80 | node.nodeValue = val.substr(0, pos); 81 | } 82 | } 83 | else if (!jQuery(node).is("button, select, textarea")) { 84 | jQuery.each(node.childNodes, function() { 85 | highlight(this); 86 | }); 87 | } 88 | } 89 | return this.each(function() { 90 | highlight(this); 91 | }); 92 | }; 93 | 94 | /** 95 | * Small JavaScript module for the documentation. 96 | */ 97 | var Documentation = { 98 | 99 | init : function() { 100 | this.fixFirefoxAnchorBug(); 101 | this.highlightSearchWords(); 102 | this.initIndexTable(); 103 | }, 104 | 105 | /** 106 | * i18n support 107 | */ 108 | TRANSLATIONS : {}, 109 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 110 | LOCALE : 'unknown', 111 | 112 | // gettext and ngettext don't access this so that the functions 113 | // can safely bound to a different name (_ = Documentation.gettext) 114 | gettext : function(string) { 115 | var translated = Documentation.TRANSLATIONS[string]; 116 | if (typeof translated == 'undefined') 117 | return string; 118 | return (typeof translated == 'string') ? translated : translated[0]; 119 | }, 120 | 121 | ngettext : function(singular, plural, n) { 122 | var translated = Documentation.TRANSLATIONS[singular]; 123 | if (typeof translated == 'undefined') 124 | return (n == 1) ? singular : plural; 125 | return translated[Documentation.PLURALEXPR(n)]; 126 | }, 127 | 128 | addTranslations : function(catalog) { 129 | for (var key in catalog.messages) 130 | this.TRANSLATIONS[key] = catalog.messages[key]; 131 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 132 | this.LOCALE = catalog.locale; 133 | }, 134 | 135 | /** 136 | * add context elements like header anchor links 137 | */ 138 | addContextElements : function() { 139 | $('div[id] > :header:first').each(function() { 140 | $('\u00B6'). 141 | attr('href', '#' + this.id). 142 | attr('title', _('Permalink to this headline')). 143 | appendTo(this); 144 | }); 145 | $('dt[id]').each(function() { 146 | $('\u00B6'). 147 | attr('href', '#' + this.id). 148 | attr('title', _('Permalink to this definition')). 149 | appendTo(this); 150 | }); 151 | }, 152 | 153 | /** 154 | * workaround a firefox stupidity 155 | */ 156 | fixFirefoxAnchorBug : function() { 157 | if (document.location.hash && $.browser.mozilla) 158 | window.setTimeout(function() { 159 | document.location.href += ''; 160 | }, 10); 161 | }, 162 | 163 | /** 164 | * highlight the search words provided in the url in the text 165 | */ 166 | highlightSearchWords : function() { 167 | var params = $.getQueryParameters(); 168 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 169 | if (terms.length) { 170 | var body = $('div.body'); 171 | window.setTimeout(function() { 172 | $.each(terms, function() { 173 | body.highlightText(this.toLowerCase(), 'highlighted'); 174 | }); 175 | }, 10); 176 | $('') 178 | .appendTo($('#searchbox')); 179 | } 180 | }, 181 | 182 | /** 183 | * init the domain index toggle buttons 184 | */ 185 | initIndexTable : function() { 186 | var togglers = $('img.toggler').click(function() { 187 | var src = $(this).attr('src'); 188 | var idnum = $(this).attr('id').substr(7); 189 | $('tr.cg-' + idnum).toggle(); 190 | if (src.substr(-9) == 'minus.png') 191 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 192 | else 193 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 194 | }).css('display', ''); 195 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 196 | togglers.click(); 197 | } 198 | }, 199 | 200 | /** 201 | * helper function to hide the search marks again 202 | */ 203 | hideSearchWords : function() { 204 | $('#searchbox .highlight-link').fadeOut(300); 205 | $('span.highlighted').removeClass('highlighted'); 206 | }, 207 | 208 | /** 209 | * make the url absolute 210 | */ 211 | makeURL : function(relativeURL) { 212 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 213 | }, 214 | 215 | /** 216 | * get the current relative url 217 | */ 218 | getCurrentURL : function() { 219 | var path = document.location.pathname; 220 | var parts = path.split(/\//); 221 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 222 | if (this == '..') 223 | parts.pop(); 224 | }); 225 | var url = parts.join('/'); 226 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 227 | } 228 | }; 229 | 230 | // quick alias for translations 231 | _ = Documentation.gettext; 232 | 233 | $(document).ready(function() { 234 | Documentation.init(); 235 | }); 236 | -------------------------------------------------------------------------------- /docs/_build/html/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/html/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/_build/html/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/html/_static/down.png -------------------------------------------------------------------------------- /docs/_build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/html/_static/file.png -------------------------------------------------------------------------------- /docs/_build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 9 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 10 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 15 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 16 | .highlight .go { color: #333333 } /* Generic.Output */ 17 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 20 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 21 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 25 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #902000 } /* Keyword.Type */ 27 | .highlight .m { color: #208050 } /* Literal.Number */ 28 | .highlight .s { color: #4070a0 } /* Literal.String */ 29 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 30 | .highlight .nb { color: #007020 } /* Name.Builtin */ 31 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #60add5 } /* Name.Constant */ 33 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 34 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 35 | .highlight .ne { color: #007020 } /* Name.Exception */ 36 | .highlight .nf { color: #06287e } /* Name.Function */ 37 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 38 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 39 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 40 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 41 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 44 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 45 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 46 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 47 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 48 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 49 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 50 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 51 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 52 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 53 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 54 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 55 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 56 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 57 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 58 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 59 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 60 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 61 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 62 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/_build/html/_static/sidebar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * sidebar.js 3 | * ~~~~~~~~~~ 4 | * 5 | * This script makes the Sphinx sidebar collapsible. 6 | * 7 | * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds 8 | * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton 9 | * used to collapse and expand the sidebar. 10 | * 11 | * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden 12 | * and the width of the sidebar and the margin-left of the document 13 | * are decreased. When the sidebar is expanded the opposite happens. 14 | * This script saves a per-browser/per-session cookie used to 15 | * remember the position of the sidebar among the pages. 16 | * Once the browser is closed the cookie is deleted and the position 17 | * reset to the default (expanded). 18 | * 19 | * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. 20 | * :license: BSD, see LICENSE for details. 21 | * 22 | */ 23 | 24 | $(function() { 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | // global elements used by the functions. 34 | // the 'sidebarbutton' element is defined as global after its 35 | // creation, in the add_sidebar_button function 36 | var bodywrapper = $('.bodywrapper'); 37 | var sidebar = $('.sphinxsidebar'); 38 | var sidebarwrapper = $('.sphinxsidebarwrapper'); 39 | 40 | // for some reason, the document has no sidebar; do not run into errors 41 | if (!sidebar.length) return; 42 | 43 | // original margin-left of the bodywrapper and width of the sidebar 44 | // with the sidebar expanded 45 | var bw_margin_expanded = bodywrapper.css('margin-left'); 46 | var ssb_width_expanded = sidebar.width(); 47 | 48 | // margin-left of the bodywrapper and width of the sidebar 49 | // with the sidebar collapsed 50 | var bw_margin_collapsed = '.8em'; 51 | var ssb_width_collapsed = '.8em'; 52 | 53 | // colors used by the current theme 54 | var dark_color = $('.related').css('background-color'); 55 | var light_color = $('.document').css('background-color'); 56 | 57 | function sidebar_is_collapsed() { 58 | return sidebarwrapper.is(':not(:visible)'); 59 | } 60 | 61 | function toggle_sidebar() { 62 | if (sidebar_is_collapsed()) 63 | expand_sidebar(); 64 | else 65 | collapse_sidebar(); 66 | } 67 | 68 | function collapse_sidebar() { 69 | sidebarwrapper.hide(); 70 | sidebar.css('width', ssb_width_collapsed); 71 | bodywrapper.css('margin-left', bw_margin_collapsed); 72 | sidebarbutton.css({ 73 | 'margin-left': '0', 74 | 'height': bodywrapper.height() 75 | }); 76 | sidebarbutton.find('span').text('»'); 77 | sidebarbutton.attr('title', _('Expand sidebar')); 78 | document.cookie = 'sidebar=collapsed'; 79 | } 80 | 81 | function expand_sidebar() { 82 | bodywrapper.css('margin-left', bw_margin_expanded); 83 | sidebar.css('width', ssb_width_expanded); 84 | sidebarwrapper.show(); 85 | sidebarbutton.css({ 86 | 'margin-left': ssb_width_expanded-12, 87 | 'height': bodywrapper.height() 88 | }); 89 | sidebarbutton.find('span').text('«'); 90 | sidebarbutton.attr('title', _('Collapse sidebar')); 91 | document.cookie = 'sidebar=expanded'; 92 | } 93 | 94 | function add_sidebar_button() { 95 | sidebarwrapper.css({ 96 | 'float': 'left', 97 | 'margin-right': '0', 98 | 'width': ssb_width_expanded - 28 99 | }); 100 | // create the button 101 | sidebar.append( 102 | '
«
' 103 | ); 104 | var sidebarbutton = $('#sidebarbutton'); 105 | light_color = sidebarbutton.css('background-color'); 106 | // find the height of the viewport to center the '<<' in the page 107 | var viewport_height; 108 | if (window.innerHeight) 109 | viewport_height = window.innerHeight; 110 | else 111 | viewport_height = $(window).height(); 112 | sidebarbutton.find('span').css({ 113 | 'display': 'block', 114 | 'margin-top': (viewport_height - sidebar.position().top - 20) / 2 115 | }); 116 | 117 | sidebarbutton.click(toggle_sidebar); 118 | sidebarbutton.attr('title', _('Collapse sidebar')); 119 | sidebarbutton.css({ 120 | 'color': '#FFFFFF', 121 | 'border-left': '1px solid ' + dark_color, 122 | 'font-size': '1.2em', 123 | 'cursor': 'pointer', 124 | 'height': bodywrapper.height(), 125 | 'padding-top': '1px', 126 | 'margin-left': ssb_width_expanded - 12 127 | }); 128 | 129 | sidebarbutton.hover( 130 | function () { 131 | $(this).css('background-color', dark_color); 132 | }, 133 | function () { 134 | $(this).css('background-color', light_color); 135 | } 136 | ); 137 | } 138 | 139 | function set_position_from_cookie() { 140 | if (!document.cookie) 141 | return; 142 | var items = document.cookie.split(';'); 143 | for(var k=0; k2;a== 12 | null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= 13 | function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= 14 | e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, 17 | c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; 24 | b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, 25 | 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; 26 | b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; 27 | b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), 28 | function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ 29 | u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= 30 | function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= 31 | true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); 32 | -------------------------------------------------------------------------------- /docs/_build/html/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/html/_static/up-pressed.png -------------------------------------------------------------------------------- /docs/_build/html/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/html/_static/up.png -------------------------------------------------------------------------------- /docs/_build/html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Index — LiveReload for Sublime Text 2 2.0.9 documentation 11 | 12 | 13 | 14 | 15 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 42 | 43 |
44 |
45 |
46 |
47 | 48 | 49 |

Index

50 | 51 |
52 | A 53 | | F 54 | | H 55 | | L 56 | | O 57 | | P 58 | | R 59 | | S 60 | | T 61 | 62 |
63 |

A

64 | 65 | 71 |
66 | 67 |
addResource() (PluginAPI.PluginClass method) 68 |
69 | 70 |
72 | 73 |

F

74 | 75 | 81 |
76 | 77 |
file_types (PluginAPI.PluginClass attribute) 78 |
79 | 80 |
82 | 83 |

H

84 | 85 | 91 |
86 | 87 |
http_callback() (in module LiveReload) 88 |
89 | 90 |
92 | 93 |

L

94 | 95 | 101 | 107 |
96 | 97 |
listClients() (PluginAPI.PluginClass method) 98 |
99 | 100 |
102 | 103 |
LiveReload (module) 104 |
105 | 106 |
108 | 109 |

O

110 | 111 | 121 | 127 |
112 | 113 |
onDisabled() (PluginAPI.PluginClass method) 114 |
115 | 116 | 117 |
onEnabled() (PluginAPI.PluginClass method) 118 |
119 | 120 |
122 | 123 |
onReceive() (PluginAPI.PluginClass method) 124 |
125 | 126 |
128 | 129 |

P

130 | 131 | 137 |
132 | 133 |
PluginClass (class in PluginAPI) 134 |
135 | 136 |
138 | 139 |

R

140 | 141 | 147 |
142 | 143 |
refresh() (PluginAPI.PluginClass method) 144 |
145 | 146 |
148 | 149 |

S

150 | 151 | 161 | 167 |
152 | 153 |
sendCommand() (PluginAPI.PluginClass method) 154 |
155 | 156 | 157 |
sendRaw() (PluginAPI.PluginClass method) 158 |
159 | 160 |
162 | 163 |
should_run() (PluginAPI.PluginClass method) 164 |
165 | 166 |
168 | 169 |

T

170 | 171 | 177 |
172 | 173 |
this_session_only (PluginAPI.PluginClass attribute) 174 |
175 | 176 |
178 | 179 | 180 | 181 |
182 |
183 |
184 |
185 |
186 | 187 | 188 | 189 | 201 | 202 |
203 |
204 |
205 |
206 | 218 | 222 | 223 | -------------------------------------------------------------------------------- /docs/_build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/docs/_build/html/objects.inv -------------------------------------------------------------------------------- /docs/_build/html/py-modindex.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Python Module Index — LiveReload for Sublime Text 2 2.0.9 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 48 | 49 |
50 |
51 |
52 |
53 | 54 | 55 |

Python Module Index

56 | 57 |
58 | l 59 |
60 | 61 | 62 | 63 | 65 | 66 | 67 | 70 |
 
64 | l
68 | LiveReload 69 |
71 | 72 | 73 |
74 |
75 |
76 |
77 |
78 | 90 | 91 |
92 |
93 |
94 |
95 | 107 | 111 | 112 | -------------------------------------------------------------------------------- /docs/_build/html/search.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Search — LiveReload for Sublime Text 2 2.0.9 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 49 | 50 |
51 |
52 |
53 |
54 | 55 |

Search

56 |
57 | 58 |

59 | Please activate JavaScript to enable the search 60 | functionality. 61 |

62 |
63 |

64 | From here you can search these documents. Enter your search 65 | words into the box below and click "search". Note that the search 66 | function will automatically search for all of the words. Pages 67 | containing fewer words won't appear in the result list. 68 |

69 |
70 | 71 | 72 | 73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | 100 | 104 | 105 | -------------------------------------------------------------------------------- /docs/_build/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({objects:{"":{LiveReload:[0,0,1,""]},PluginAPI:{PluginClass:[0,1,1,""]},"PluginAPI.PluginClass":{onDisabled:[0,4,1,""],this_session_only:[0,3,1,""],refresh:[0,4,1,""],onEnabled:[0,4,1,""],onReceive:[0,4,1,""],addResource:[0,4,1,""],should_run:[0,4,1,""],file_types:[0,3,1,""],sendCommand:[0,4,1,""],listClients:[0,4,1,""]},LiveReload:{websocket_callback:[0,2,1,""],http_callback:[0,2,1,""]}},terms:{all:0,just:0,show:[],text:0,session:0,follow:0,this_session_onli:0,onli:0,menu:0,forev:0,send:0,should:0,jan:[],add:0,onrec:0,main:[],sent:0,serv:0,vers:0,sourc:0,"return":0,string:0,get:0,fals:0,onen:0,framework:[],base:[],isen:[],cool:0,name:0,should_run:0,list:0,refresh:0,"public":[],reload:0,refer:0,page:0,compil:0,pluginapi:0,set:0,definedfunct:0,request:0,connect:0,pass:0,urlpars:0,file_typ:0,server:0,event:0,compat:0,index:0,req:0,content:[],sublim:0,version:[],print:0,method:[],attribut:0,run:0,livereload:0,gener:0,extens:0,entir:0,mime:0,path:0,classnam:[],addit:0,plug:0,search:0,plugin:0,plugin_nam:0,sm2:0,instanc:0,implement:0,com:[],disabl:0,origin:0,client:0,via:0,listclient:0,enabled_plugin:[],modul:0,encod:0,filenam:0,api:0,log_m:0,your:0,sendcommand:0,callback_f:0,from:0,describ:0,would:0,data:0,addresourc:0,custom:0,json:0,trigger:0,call:0,msg:0,type:0,"function":0,fire:0,handler:0,specifi:0,provid:0,part:0,indic:0,req_path:0,sublime_plugin:0,"true":0,content_typ:0,info:0,none:0,attr:[],"default":0,plain:0,autoclass:[],pluginclass:0,defin:0,itself:0,can:0,toggleplugin:[],req_url:0,def:0,browser:0,websocket:0,want:0,getplugin:[],jpg:0,packag:[],dispatch_onrec:[],listplugin:[],file:0,tabl:0,parsabl:0,yourfil:0,decor:0,welcom:[],titl:0,self:0,when:0,martyalchin:[],simplerefreshdelai:[],port:[],member:[],how:0,websocket_callback:0,which:0,pluginfactori:[],shortli:0,you:0,document:[],simpl:[],css:0,enabl:0,http:0,allow:0,http_callback:0,buffer:0,object:0,automodul:[],ondis:0,"class":0,stai:0,vice:0,descript:0,inherit:[],callback:0,exampl:0,command:0,thi:0,everyth:0},objtypes:{"0":"py:module","1":"py:class","2":"py:function","3":"py:attribute","4":"py:method"},titles:["LiveReload for Sublime Text"],objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","function","Python function"],"3":["py","attribute","Python attribute"],"4":["py","method","Python method"]},filenames:["index"]}) -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # LiveReload for Sublime Text 2 documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Feb 18 09:38:07 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.append(os.path.join(os.path.abspath('../'), 'server')) 20 | sys.path.append(os.path.abspath('../')) 21 | sys.path.append(os.path.abspath('.')) 22 | 23 | # -- General configuration ----------------------------------------------------- 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be extensions 29 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 30 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] 31 | 32 | # Add any paths that contain templates here, relative to this directory. 33 | templates_path = ['_templates'] 34 | 35 | # The suffix of source filenames. 36 | source_suffix = '.rst' 37 | 38 | # The encoding of source files. 39 | #source_encoding = 'utf-8-sig' 40 | 41 | # The master toctree document. 42 | master_doc = 'index' 43 | 44 | # General information about the project. 45 | project = u'LiveReload for Sublime Text 2' 46 | copyright = u'2013, Janez Troha' 47 | 48 | # The version info for the project you're documenting, acts as replacement for 49 | # |version| and |release|, also used in various other places throughout the 50 | # built documents. 51 | # 52 | # The short X.Y version. 53 | version = '2.0.9' 54 | # The full version, including alpha/beta/rc tags. 55 | release = '2.0.9' 56 | 57 | # The language for content autogenerated by Sphinx. Refer to documentation 58 | # for a list of supported languages. 59 | #language = None 60 | 61 | # There are two options for replacing |today|: either, you set today to some 62 | # non-false value, then it is used: 63 | #today = '' 64 | # Else, today_fmt is used as the format for a strftime call. 65 | #today_fmt = '%B %d, %Y' 66 | 67 | # List of patterns, relative to source directory, that match files and 68 | # directories to ignore when looking for source files. 69 | exclude_patterns = ['_build'] 70 | 71 | # The reST default role (used for this markup: `text`) to use for all documents. 72 | #default_role = None 73 | 74 | # If true, '()' will be appended to :func: etc. cross-reference text. 75 | #add_function_parentheses = True 76 | 77 | # If true, the current module name will be prepended to all description 78 | # unit titles (such as .. function::). 79 | #add_module_names = True 80 | 81 | # If true, sectionauthor and moduleauthor directives will be shown in the 82 | # output. They are ignored by default. 83 | #show_authors = False 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = 'sphinx' 87 | 88 | # A list of ignored prefixes for module index sorting. 89 | #modindex_common_prefix = [] 90 | 91 | 92 | # -- Options for HTML output --------------------------------------------------- 93 | 94 | # The theme to use for HTML and HTML Help pages. See the documentation for 95 | # a list of builtin themes. 96 | html_theme = 'default' 97 | 98 | # Theme options are theme-specific and customize the look and feel of a theme 99 | # further. For a list of options available for each theme, see the 100 | # documentation. 101 | #html_theme_options = {} 102 | 103 | # Add any paths that contain custom themes here, relative to this directory. 104 | #html_theme_path = [] 105 | 106 | # The name for this set of Sphinx documents. If None, it defaults to 107 | # " v documentation". 108 | #html_title = None 109 | 110 | # A shorter title for the navigation bar. Default is the same as html_title. 111 | #html_short_title = None 112 | 113 | # The name of an image file (relative to this directory) to place at the top 114 | # of the sidebar. 115 | #html_logo = None 116 | 117 | # The name of an image file (within the static path) to use as favicon of the 118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 119 | # pixels large. 120 | #html_favicon = None 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ['_static'] 126 | 127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 128 | # using the given strftime format. 129 | #html_last_updated_fmt = '%b %d, %Y' 130 | 131 | # If true, SmartyPants will be used to convert quotes and dashes to 132 | # typographically correct entities. 133 | #html_use_smartypants = True 134 | 135 | # Custom sidebar templates, maps document names to template names. 136 | #html_sidebars = {} 137 | 138 | # Additional templates that should be rendered to pages, maps page names to 139 | # template names. 140 | #html_additional_pages = {} 141 | 142 | # If false, no module index is generated. 143 | #html_domain_indices = True 144 | 145 | # If false, no index is generated. 146 | #html_use_index = True 147 | 148 | # If true, the index is split into individual pages for each letter. 149 | #html_split_index = False 150 | 151 | # If true, links to the reST sources are added to the pages. 152 | #html_show_sourcelink = True 153 | 154 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 155 | #html_show_sphinx = True 156 | 157 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 158 | #html_show_copyright = True 159 | 160 | # If true, an OpenSearch description file will be output, and all pages will 161 | # contain a tag referring to it. The value of this option must be the 162 | # base URL from which the finished HTML is served. 163 | #html_use_opensearch = '' 164 | 165 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 166 | #html_file_suffix = None 167 | 168 | # Output file base name for HTML help builder. 169 | htmlhelp_basename = 'LiveReloadforSublimeText2doc' 170 | 171 | 172 | # -- Options for LaTeX output -------------------------------------------------- 173 | 174 | latex_elements = { 175 | # The paper size ('letterpaper' or 'a4paper'). 176 | #'papersize': 'letterpaper', 177 | 178 | # The font size ('10pt', '11pt' or '12pt'). 179 | #'pointsize': '10pt', 180 | 181 | # Additional stuff for the LaTeX preamble. 182 | #'preamble': '', 183 | } 184 | 185 | # Grouping the document tree into LaTeX files. List of tuples 186 | # (source start file, target name, title, author, documentclass [howto/manual]). 187 | latex_documents = [ 188 | ('index', 'LiveReloadforSublimeText2.tex', u'LiveReload for Sublime Text 2 Documentation', 189 | u'Janez Troha', 'manual'), 190 | ] 191 | 192 | # The name of an image file (relative to this directory) to place at the top of 193 | # the title page. 194 | #latex_logo = None 195 | 196 | # For "manual" documents, if this is true, then toplevel headings are parts, 197 | # not chapters. 198 | #latex_use_parts = False 199 | 200 | # If true, show page references after internal links. 201 | #latex_show_pagerefs = False 202 | 203 | # If true, show URL addresses after external links. 204 | #latex_show_urls = False 205 | 206 | # Documents to append as an appendix to all manuals. 207 | #latex_appendices = [] 208 | 209 | # If false, no module index is generated. 210 | #latex_domain_indices = True 211 | 212 | 213 | # -- Options for manual page output -------------------------------------------- 214 | 215 | # One entry per manual page. List of tuples 216 | # (source start file, name, description, authors, manual section). 217 | man_pages = [ 218 | ('index', 'livereloadforsublimetext2', u'LiveReload for Sublime Text 2 Documentation', 219 | [u'Janez Troha'], 1) 220 | ] 221 | 222 | # If true, show URL addresses after external links. 223 | #man_show_urls = False 224 | 225 | 226 | # -- Options for Texinfo output ------------------------------------------------ 227 | 228 | # Grouping the document tree into Texinfo files. List of tuples 229 | # (source start file, target name, title, author, 230 | # dir menu entry, description, category) 231 | texinfo_documents = [ 232 | ('index', 'LiveReloadforSublimeText2', u'LiveReload for Sublime Text 2 Documentation', 233 | u'Janez Troha', 'LiveReloadforSublimeText2', 'One line description of project.', 234 | 'Miscellaneous'), 235 | ] 236 | 237 | # Documents to append as an appendix to all manuals. 238 | #texinfo_appendices = [] 239 | 240 | # If false, no module index is generated. 241 | #texinfo_domain_indices = True 242 | 243 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 244 | #texinfo_show_urls = 'footnote' 245 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. LiveReload for Sublime Text 2 documentation master file, created by 2 | sphinx-quickstart on Mon Feb 18 09:38:07 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | LiveReload for Sublime Text 7 | ========================================================= 8 | 9 | Plugins api 10 | **************************** 11 | .. autoclass:: PluginAPI.PluginClass 12 | :members: 13 | :show-inheritance: 14 | 15 | Decorators: 16 | 17 | .. automodule:: LiveReload 18 | :members: 19 | :show-inheritance: 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | 28 | -------------------------------------------------------------------------------- /docs/sublime.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | 4 | def packages_path(): 5 | return os.path.abspath('../../') 6 | 7 | def error_message(string): 8 | pass 9 | 10 | platform = "build" -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "messages/install_upgrade.txt", 3 | "upgrade": "messages/install_upgrade.txt" 4 | } -------------------------------------------------------------------------------- /messages/install_upgrade.txt: -------------------------------------------------------------------------------- 1 | Changelog: 2 | 3 | Please be sure to restart Sublime Text to start using this new version. For more information or how to use this plugin visit https://github.com/alepez/LiveReload-sublimetext3 4 | -------------------------------------------------------------------------------- /pylint.rc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | 25 | [MESSAGES CONTROL] 26 | 27 | # Enable the message, report, category or checker with the given id(s). You can 28 | # either give multiple identifier separated by comma (,) or put this option 29 | # multiple time. 30 | #enable= 31 | 32 | # Disable the message, report, category or checker with the given id(s). You 33 | # can either give multiple identifier separated by comma (,) or put this option 34 | # multiple time (only on the command line, not in the configuration file where 35 | # it should appear only once). 36 | # C0111 Missing docstring 37 | # I0011 Warning locally suppressed using disable-msg 38 | # I0012 Warning locally suppressed using disable-msg 39 | # W0704 Except doesn't do anything Used when an except clause does nothing but "pass" and there is no "else" clause 40 | # W0142 Used * or * magic* Used when a function or method is called using *args or **kwargs to dispatch arguments. 41 | # W0212 Access to a protected member %s of a client class 42 | # W0232 Class has no __init__ method Used when a class has no __init__ method, neither its parent classes. 43 | # W0613 Unused argument %r Used when a function or method argument is not used. 44 | # W0702 No exception's type specified Used when an except clause doesn't specify exceptions type to catch. 45 | # R0201 Method could be a function 46 | # W0614 Unused import XYZ from wildcard import 47 | # R0914 Too many local variables 48 | # R0912 Too many branches 49 | # R0915 Too many statements 50 | # R0913 Too many arguments 51 | # R0904 Too many public methods 52 | disable=C0111,R0901,C0103,W0201,I0011,I0012,W0704,W0142,W0212,W0232,W0613,W0702,R0201,W0614,R0914,R0912,R0915,R0913,R0904,R0801,W0703 53 | 54 | 55 | [REPORTS] 56 | 57 | # Set the output format. Available formats are text, parseable, colorized, msvs 58 | # (visual studio) and html 59 | output-format=text 60 | 61 | # Include message's id in output 62 | include-ids=no 63 | 64 | # Put messages in a separate file for each module / package specified on the 65 | # command line instead of printing them on stdout. Reports (if any) will be 66 | # written in a file name "pylint_global.[txt|html]". 67 | files-output=no 68 | 69 | # Tells whether to display a full report or only the messages 70 | reports=yes 71 | 72 | # Python expression which should return a note less than 10 (10 is the highest 73 | # note). You have access to the variables errors warning, statement which 74 | # respectively contain the number of errors / warnings messages and the total 75 | # number of statements analyzed. This is used by the global evaluation report 76 | # (RP0004). 77 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 78 | 79 | # Add a comment according to your evaluation note. This is used by the global 80 | # evaluation report (RP0004). 81 | comment=no 82 | 83 | 84 | [BASIC] 85 | 86 | # Required attributes for module, separated by a comma 87 | required-attributes= 88 | 89 | # List of builtins function names that should not be used, separated by a comma 90 | bad-functions=map,filter,apply,input 91 | 92 | # Regular expression which should only match correct module names 93 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 94 | 95 | # Regular expression which should only match correct module level names 96 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 97 | 98 | # Regular expression which should only match correct class names 99 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 100 | 101 | # Regular expression which should only match correct function names 102 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 103 | 104 | # Regular expression which should only match correct method names 105 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 106 | 107 | # Regular expression which should only match correct instance attribute names 108 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 109 | 110 | # Regular expression which should only match correct argument names 111 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 112 | 113 | # Regular expression which should only match correct variable names 114 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 115 | 116 | # Regular expression which should only match correct list comprehension / 117 | # generator expression variable names 118 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 119 | 120 | # Good variable names which should always be accepted, separated by a comma 121 | good-names=i,j,k,ex,Run,_ 122 | 123 | # Bad variable names which should always be refused, separated by a comma 124 | bad-names=foo,bar,baz,toto,tutu,tata 125 | 126 | # Regular expression which should only match functions or classes name which do 127 | # not require a docstring 128 | no-docstring-rgx=__.*__ 129 | 130 | 131 | [SIMILARITIES] 132 | 133 | # Minimum lines number of a similarity. 134 | min-similarity-lines=4 135 | 136 | # Ignore comments when computing similarities. 137 | ignore-comments=yes 138 | 139 | # Ignore docstrings when computing similarities. 140 | ignore-docstrings=yes 141 | 142 | 143 | [MISCELLANEOUS] 144 | 145 | # List of note tags to take in consideration, separated by a comma. 146 | notes=FIXME,XXX,TODO 147 | 148 | 149 | [TYPECHECK] 150 | 151 | # Tells whether missing members accessed in mixin class should be ignored. A 152 | # mixin class is detected if its name ends with "mixin" (case insensitive). 153 | ignore-mixin-members=yes 154 | 155 | # List of classes names for which member attributes should not be checked 156 | # (useful for classes with attributes dynamically set). 157 | ignored-classes=SQLObject 158 | 159 | # When zope mode is activated, add a predefined set of Zope acquired attributes 160 | # to generated-members. 161 | zope=no 162 | 163 | # List of members which are set dynamically and missed by pylint inference 164 | # system, and so shouldn't trigger E0201 when accessed. Python regular 165 | # expressions are accepted. 166 | generated-members=REQUEST,acl_users,aq_parent 167 | 168 | 169 | [VARIABLES] 170 | 171 | # Tells whether we should check for unused import in __init__ files. 172 | init-import=yes 173 | 174 | # A regular expression matching the beginning of the name of dummy variables 175 | # (i.e. not used). 176 | dummy-variables-rgx=_|dummy 177 | 178 | # List of additional names supposed to be defined in builtins. Remember that 179 | # you should avoid to define new builtins when possible. 180 | additional-builtins=sublime 181 | 182 | 183 | [FORMAT] 184 | 185 | # Maximum number of characters on a single line. 186 | max-line-length=100 187 | 188 | # Maximum number of lines in a module 189 | max-module-lines=1000 190 | 191 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 192 | # tab). 193 | indent-string=' ' 194 | 195 | 196 | [CLASSES] 197 | 198 | # List of interface methods to ignore, separated by a comma. This is used for 199 | # instance to not check methods defines in Zope's Interface base class. 200 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by,sublime 201 | 202 | # List of method names used to declare (i.e. assign) instance attributes. 203 | defining-attr-methods=__init__,__new__,setUp 204 | 205 | # List of valid names for the first argument in a class method. 206 | valid-classmethod-first-arg=cls 207 | 208 | 209 | [DESIGN] 210 | 211 | # Maximum number of arguments for function / method 212 | max-args=5 213 | 214 | # Argument names that match this expression will be ignored. Default to name 215 | # with leading underscore 216 | ignored-argument-names=_.* 217 | 218 | # Maximum number of locals for function / method body 219 | max-locals=25 220 | 221 | # Maximum number of return / yield for function / method body 222 | max-returns=6 223 | 224 | # Maximum number of branch for function / method body 225 | max-branchs=12 226 | 227 | # Maximum number of statements in function / method body 228 | max-statements=50 229 | 230 | # Maximum number of parents for a class (see R0901). 231 | max-parents=7 232 | 233 | # Maximum number of attributes for a class (see R0902). 234 | max-attributes=17 235 | 236 | # Minimum number of public methods for a class (see R0903). 237 | min-public-methods=0 238 | 239 | # Maximum number of public methods for a class (see R0904). 240 | max-public-methods=40 241 | 242 | 243 | [IMPORTS] 244 | 245 | # Deprecated modules which should not be used, separated by a comma 246 | deprecated-modules=regsub,string,TERMIOS,Bastion,rexec 247 | 248 | # Create a graph of every (i.e. internal and external) dependencies in the 249 | # given file (report RP0402 must not be disabled) 250 | import-graph= 251 | 252 | # Create a graph of external dependencies in the given file (report RP0402 must 253 | # not be disabled) 254 | ext-import-graph= 255 | 256 | # Create a graph of internal dependencies in the given file (report RP0402 must 257 | # not be disabled) 258 | int-import-graph= 259 | 260 | 261 | [EXCEPTIONS] 262 | 263 | # Exceptions that will emit a warning when being caught. Defaults to 264 | # "Exception" 265 | overgeneral-exceptions=Exception -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyvows -------------------------------------------------------------------------------- /server/LiveReloadAPI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sublime 5 | 6 | try: 7 | from .Settings import Settings 8 | except ValueError: 9 | from Settings import Settings 10 | 11 | from functools import wraps 12 | 13 | def log(msg): 14 | print(msg) 15 | 16 | class LiveReloadAPI(object): 17 | 18 | """Official LiveReloadAPI for SM2""" 19 | 20 | def __init__(self): 21 | super(LiveReloadAPI, self).__init__() 22 | self.callbacks = [] 23 | 24 | def add_static_file( 25 | self, 26 | path, 27 | buffer, 28 | content_type, 29 | ): 30 | """ 31 | Adds resource to embedded http server 32 | path (string) request uri 33 | buffer (string) or (file) object 34 | content_type (string) mime type of the object to be served 35 | Example: 36 | 37 | LiveReload.API.add_static_file('/helloworld.js', "alert('Helloworld!')", 38 | 'text/javascript') 39 | """ 40 | 41 | log('LiveReload: added file ' + path + ' with content-type: ' + str(content_type)) 42 | self.static_files.append({'path': path, 'buffer': buffer, 'content_type': content_type}) 43 | 44 | def send(self, data): 45 | """ 46 | Send json encoded command to all clients 47 | Example: 48 | 49 | data = json.dumps(["refresh", { 50 | "path": filename, 51 | "apply_js_live": False, 52 | "apply_css_live": False, 53 | "apply_images_live": False 54 | }]) 55 | 56 | LiveReload.API.send(data) 57 | """ 58 | try: 59 | log(data) 60 | self.ws_server.send(data) 61 | except Exception as e: 62 | log(e) 63 | 64 | 65 | def list_clients(self): 66 | """ 67 | Return list with connected clients with origin and url 68 | Example: 69 | 70 | [ 71 | { 72 | origin: "chrome-extension://jnihajbhpnppcggbcgedagnkighmdlei", 73 | url: "https://chrome.google.com/webstore/search/livereload" 74 | } 75 | ] 76 | """ 77 | 78 | return self.ws_server.server.list_clients() -------------------------------------------------------------------------------- /server/PluginAPI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import LiveReload 5 | import json 6 | import sublime 7 | try: 8 | from .Settings import Settings 9 | except ValueError: 10 | from Settings import Settings 11 | 12 | 13 | def log(msg): 14 | pass 15 | 16 | 17 | class PluginFactory(type): 18 | 19 | """ 20 | Based on example from http://martyalchin.com/2008/jan/10/simple-plugin-framework/ 21 | """ 22 | 23 | def __init__( 24 | mcs, 25 | name, 26 | bases, 27 | attrs, 28 | ): 29 | 30 | if not hasattr(mcs, 'plugins'): 31 | mcs.settings = Settings() 32 | mcs.plugins = [] 33 | mcs.enabled_plugins = mcs.settings.get('enabled_plugins', 34 | []) 35 | else: 36 | log('LiveReload new plugin: ' + mcs.__name__) 37 | 38 | # remove old plug-in 39 | 40 | for plugin in mcs.plugins: 41 | if plugin.__name__ == mcs.__name__: 42 | mcs.plugins.remove(plugin) 43 | mcs.plugins.append(mcs) 44 | 45 | def togglePlugin(mcs, index): 46 | 47 | plugin = mcs.plugins[index]() 48 | 49 | if plugin.name in mcs.enabled_plugins: 50 | mcs.enabled_plugins.remove(plugin.name) 51 | sublime.set_timeout(lambda : \ 52 | sublime.status_message('"%s" the LiveReload plug-in has been disabled!' 53 | % plugin.title), 100) 54 | plugin.onDisabled() 55 | else: 56 | mcs.enabled_plugins.append(plugin.name) 57 | sublime.set_timeout(lambda : \ 58 | sublime.status_message('"%s" the LiveReload plug-in has been enabled!' 59 | % plugin.title), 100) 60 | plugin.onEnabled() 61 | 62 | # should only save permanent plug-ins 63 | 64 | p_enabled_plugins = [] 65 | for p in mcs.enabled_plugins: 66 | try: 67 | if mcs.getPlugin(p).this_session_only is not True: 68 | p_enabled_plugins.append(p) 69 | except Exception: 70 | pass 71 | mcs.settings.set('enabled_plugins', p_enabled_plugins) 72 | 73 | def getPlugin(mcs, className): 74 | for p in mcs.plugins: 75 | if p.__name__ == className: 76 | return p() # instance 77 | return False 78 | 79 | def listAllDefinedFilters(mcs): 80 | file_types = [] 81 | for plugin in mcs.plugins: 82 | if plugin.__name__ in mcs.enabled_plugins: 83 | if plugin.file_types != '*': 84 | for ext in plugin.file_types.split(','): 85 | file_types.append(ext) 86 | return file_types 87 | 88 | def listPlugins(mcs): 89 | plist = [] 90 | for plugin in mcs.plugins: 91 | p = [] 92 | if plugin.__name__ in mcs.enabled_plugins: 93 | p.append('Disable - ' + str(plugin.title)) 94 | else: 95 | if plugin.this_session_only is not True: 96 | p.append('Enable - ' + str(plugin.title)) 97 | else: 98 | p.append('Enable - ' + str(plugin.title) 99 | + ' (this session)') 100 | if plugin.description: 101 | p.append(str(plugin.description) + ' (' 102 | + str(plugin.file_types) + ')') 103 | plist.append(p) 104 | return plist 105 | 106 | def dispatch_OnReceive(mcs, data, origin): 107 | log(data) 108 | for plugin in mcs.plugins: 109 | try: 110 | plugin().onReceive(data, origin) 111 | except Exception as e: 112 | log(e) 113 | try: 114 | _wscallback = LiveReload.API.has_callback(data.path) 115 | if _wscallback: 116 | try: 117 | func = getattr(sys.modules['LiveReload' 118 | ].Plugin.getPlugin(_wscallback['mcs' 119 | ]), _wscallback['name'], None) 120 | if func: 121 | func(data) 122 | except Exception as e: 123 | log(e) 124 | except Exception: 125 | 126 | log('no WS handler') 127 | 128 | 129 | class PluginClass: 130 | 131 | """ 132 | Class for implementing your custom plug-ins, sublime_plugins compatible 133 | 134 | Plug-ins implementing this reference should provide the following attributes: 135 | 136 | - description (string) describing your plug-in 137 | - title (string) naming your plug-in 138 | - file_types (string) file_types which should trigger refresh for this plug-in 139 | 140 | """ 141 | 142 | @property 143 | def name(self): 144 | return str(self.__class__).split('.')[1].rstrip("'>") 145 | 146 | @property 147 | def isEnabled(self): 148 | return self.name in self.enabled_plugins 149 | 150 | def should_run(self, filename=False): 151 | """ Returns True if specified filename is allowed for plug-in, and plug-in itself is enabled """ 152 | if self.isEnabled: 153 | all_filters = LiveReload.Plugin.listAllDefinedFilters() 154 | 155 | def otherPluginsWithFilter(): 156 | for f in all_filters: 157 | if filename.endswith(f): 158 | return False 159 | return True 160 | 161 | this_plugin = self.file_types.split(',') 162 | 163 | if [f for f in this_plugin if filename.endswith(f)]: 164 | return True 165 | elif self.file_types == '*' and otherPluginsWithFilter(): 166 | return True 167 | else: 168 | return False 169 | else: 170 | return False 171 | 172 | def addResource( 173 | self, 174 | req_path, 175 | blob, 176 | content_type='text/plain', 177 | ): 178 | """ 179 | - (string) req_path; browser path to file you want to serve. Ex: /yourfile.js 180 | - (string/file) buffer; string or file instance to file you want to serve 181 | - (string) content_type; Mime-type of file you want to serve 182 | """ 183 | 184 | LiveReload.API.add_static_file(req_path, blob, content_type) 185 | 186 | def sendCommand( 187 | self, 188 | command, 189 | settings, 190 | filename=False, 191 | ): 192 | """ 193 | - (instance) plug-in; instance 194 | - (string) command; to trigger in livereload.js (refresh, info, or one of the plugins) 195 | - (object) settings; additional data that gets passed to command (should be json parsable) 196 | - (string) original name of file 197 | """ 198 | 199 | if self.isEnabled: 200 | if command == 'refresh': # to support new protocol 201 | settings['command'] = 'reload' 202 | try: 203 | if not filename: 204 | filename = settings['path'].strip(' ') 205 | except Exception: 206 | log('Missing path definition') 207 | 208 | if self.should_run(filename): 209 | sublime.set_timeout(lambda : \ 210 | sublime.status_message('LiveReload refresh from %s' 211 | % self.name), 100) 212 | 213 | # if we have defined filter 214 | 215 | LiveReload.API.send(json.dumps(settings)) 216 | else: 217 | log('Skipping '+ self.name) 218 | 219 | def refresh(self, filename, settings=None): 220 | """ 221 | Generic refresh command 222 | 223 | - (string) filename; file to refresh (.css, .js, jpg ...) 224 | - (object) settings; how to reload(entire page or just parts) 225 | """ 226 | 227 | if not settings: 228 | settings = { 229 | 'path': filename, 230 | 'apply_js_live': self.settings.get('apply_js_live'), 231 | 'apply_css_live': self.settings.get('apply_css_live'), 232 | 'apply_images_live': self.settings.get('apply_images_live' 233 | ), 234 | } 235 | 236 | self.sendCommand('refresh', settings) 237 | 238 | def listClients(self): 239 | """ returns list with all connected clients with their req_url and origin""" 240 | 241 | return LiveReload.API.list_clients() 242 | 243 | def onReceive(self, data, origin): 244 | """ 245 | Event handler which fires when browser plug-ins sends data 246 | - (string) data sent by browser 247 | - (string) origin of data 248 | """ 249 | 250 | pass 251 | 252 | def onEnabled(self): 253 | """ Runs when plug-in is enabled via menu""" 254 | 255 | pass 256 | 257 | def onDisabled(self): 258 | """ Runs when plug-in is disabled via menu""" 259 | 260 | pass 261 | 262 | @property 263 | def this_session_only(self): 264 | """ Should it stay enabled forever or this session only """ 265 | 266 | return False 267 | 268 | @property 269 | def file_types(self): 270 | """ Run plug-in only with this file extensions, defaults to all extensions""" 271 | 272 | return '*' 273 | 274 | 275 | ##black magic, python2 vs python3 276 | 277 | try: 278 | PluginInterface = PluginFactory('PluginInterface', (object, 279 | PluginClass), {}) 280 | except TypeError: 281 | PluginInterface = PluginFactory('PluginInterface', (PluginClass, ), 282 | {}) 283 | -------------------------------------------------------------------------------- /server/Settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import json 6 | 7 | def log(msg): 8 | pass 9 | 10 | try: 11 | import sublime 12 | except Exception as e: 13 | log(e) 14 | 15 | 16 | def read_sublime_settings(file_path): 17 | try: 18 | with open(file_path, 'r', encoding='utf-8') as f: 19 | return sublime.decode_value(f.read()) 20 | except (IOError, ValueError): 21 | return None 22 | 23 | 24 | class Settings(dict): 25 | 26 | def __init__(self): 27 | try: 28 | log('---- SETTING -------') 29 | cdir = os.path.dirname(os.path.abspath(__file__)) 30 | if not "LiveReload" in cdir: 31 | cdir = os.path.join(sublime.packages_path(), 'LiveReload') 32 | self.file_name = os.path.join(cdir, '..', 'LiveReload.sublime-settings') 33 | data = read_sublime_settings(self.file_name) or {} 34 | 35 | # Merging user settings 36 | user = os.path.join(cdir, '..', '..', 'User', 'LiveReload.sublime-settings') 37 | data_user = read_sublime_settings(user) or {} 38 | 39 | for i in data: 40 | self[i] = data[i] 41 | 42 | for i in data_user: 43 | log(i) 44 | self[i] = data_user[i] 45 | log('LiveReload: Settings loaded') 46 | log('-----------') 47 | except Exception as e: 48 | log(e) 49 | 50 | def save(self): 51 | file_object = open(self.file_name, 'w') 52 | json.dump(self, file_object, indent=5) 53 | file_object.close() 54 | log('LiveReload: Settings saved') 55 | 56 | def get(self, key, default=None): 57 | try: 58 | return self[key] 59 | except Exception: 60 | return default 61 | 62 | def set(self, key, value): 63 | self[key] = value 64 | self.save() 65 | 66 | def reload(self): 67 | self.__init__(self.file_name) 68 | -------------------------------------------------------------------------------- /server/SimpleCallbackServer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | class SimpleCallbackServer(object): 6 | 7 | """SimpleCallbackServer""" 8 | 9 | def __init__(self): 10 | try: 11 | if not self.callbacks: 12 | self.callbacks = [] 13 | except Exception: 14 | self.callbacks = [] 15 | 16 | def has_callback(self, path): 17 | """Traverse added static_files return object""" 18 | 19 | for callback in self.callbacks: 20 | if path in callback['path']: 21 | print(self.callbacks) 22 | print(path) 23 | return callback 24 | return False -------------------------------------------------------------------------------- /server/SimpleResourceServer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | class SimpleResourceServer(object): 6 | 7 | """SimpleResourceServer""" 8 | 9 | def __init__(self): 10 | self.static_files = [] 11 | 12 | def has_file(self, path): 13 | """Traverse added static_files return object""" 14 | 15 | for l_file in self.static_files: 16 | if path == l_file['path']: 17 | return l_file 18 | return False 19 | -------------------------------------------------------------------------------- /server/SimpleWSServer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | class SimpleWSServer(object): 6 | 7 | """SimpleWSServer""" 8 | 9 | def __init__(self): 10 | try: 11 | if not self.ws_callbacks: 12 | self.ws_callbacks = [] 13 | except Exception: 14 | self.ws_callbacks = [] 15 | 16 | def has_ws_callback(self, path): 17 | """Traverse added static_files return object""" 18 | 19 | for callback in self.ws_callbacks: 20 | if path in callback['path']: 21 | return callback 22 | return False -------------------------------------------------------------------------------- /server/WSRequestHandler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | try: 4 | from SimpleHTTPServer import SimpleHTTPRequestHandler 5 | except ImportError: 6 | from http.server import SimpleHTTPRequestHandler 7 | 8 | import LiveReload 9 | 10 | try: 11 | from urlparse import urlparse 12 | except ImportError: 13 | from urllib.parse import urlparse 14 | 15 | import sys 16 | 17 | # HTTP handler with WebSocket upgrade support 18 | 19 | class WSRequestHandler(SimpleHTTPRequestHandler): 20 | 21 | def __init__( 22 | self, 23 | req, 24 | addr, 25 | ): 26 | 27 | SimpleHTTPRequestHandler.__init__(self, req, addr, object()) 28 | 29 | self.server_version = 'LiveReload/1.0' 30 | 31 | def do_GET(self): 32 | if self.headers.get('upgrade') and self.headers.get('upgrade').lower() == 'websocket': 33 | 34 | if self.headers.get('sec-websocket-key1') or self.headers.get('websocket-key1'): 35 | 36 | # For Hixie-76 read out the key hash 37 | 38 | self.headers.__setitem__('key3', self.rfile.read(8)) 39 | 40 | # Just indicate that an WebSocket upgrade is needed 41 | 42 | self.last_code = 101 43 | self.last_message = '101 Switching Protocols' 44 | else: 45 | req = urlparse(self.path) 46 | _file = LiveReload.API.has_file(req.path) 47 | _httpcallback = LiveReload.API.has_callback(req.path) 48 | if _httpcallback: 49 | try: 50 | plugin = sys.modules['LiveReload'].PluginAPI.PluginFactory.getPlugin(LiveReload.Plugin, _httpcallback['cls']) 51 | func = getattr(plugin, _httpcallback['name'], None) 52 | if func: 53 | res = func(req) 54 | self.send_response(200, res) 55 | else: 56 | res = "Callback method not found" 57 | self.send_response(404, 'Not Found') 58 | except Exception as e: 59 | self.send_response(500, 'Error') 60 | res = e 61 | 62 | self.send_header('Content-type', 'text/plain') 63 | self.send_header('Content-Length', len(res)) 64 | self.end_headers() 65 | self.wfile.write(bytes(res.encode("UTF-8"))) 66 | return 67 | elif _file: 68 | if hasattr(_file['buffer'], 'read'): 69 | _buffer = _file['buffer'].read() 70 | else: 71 | _buffer = _file['buffer'] 72 | 73 | self.send_response(200, 'OK') 74 | self.send_header('Content-type', _file['content_type']) 75 | self.send_header('Content-Length', len(_buffer)) 76 | self.end_headers() 77 | self.wfile.write(bytes(_buffer.encode("UTF-8"))) 78 | return 79 | else: 80 | 81 | # Disable other requests 82 | notallowed = "Method not allowed" 83 | 84 | self.send_response(405, notallowed) 85 | self.send_header('Content-type', 'text/plain') 86 | self.send_header('Content-Length', len(notallowed)) 87 | self.end_headers() 88 | self.wfile.write(bytes(notallowed.encode("utf-8"))) 89 | return 90 | 91 | def send_response(self, code, message=None): 92 | 93 | # Save the status code 94 | 95 | self.last_code = code 96 | SimpleHTTPRequestHandler.send_response(self, code, message) 97 | 98 | def log_message(self, f, *args): 99 | 100 | # Save instead of printing 101 | 102 | self.last_message = f % args 103 | -------------------------------------------------------------------------------- /server/WebSocketClient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | try: 5 | from .WSRequestHandler import WSRequestHandler 6 | except ValueError: 7 | from WSRequestHandler import WSRequestHandler 8 | 9 | from base64 import b64encode, b64decode, encodestring 10 | import sublime 11 | import LiveReload 12 | from struct import pack, unpack_from 13 | import array 14 | import sys 15 | import json 16 | import logging 17 | try: 18 | from hashlib import md5, sha1 19 | except: 20 | from md5 import md5 21 | from sha import sha as sha1 22 | 23 | s2a = lambda s: [c for c in s] 24 | 25 | 26 | logging.basicConfig(level=logging.INFO) 27 | log = logging.getLogger('WebSocketClient') 28 | 29 | 30 | class WebSocketClient(object): 31 | 32 | """ 33 | A single connection (client) of the program 34 | """ 35 | 36 | # Handshaking, create the WebSocket connection 37 | 38 | server_handshake_hybi = \ 39 | """HTTP/1.1 101 Switching Protocols\r 40 | Upgrade: websocket\r 41 | Connection: Upgrade\r 42 | Sec-WebSocket-Accept: %s\r 43 | """ 44 | GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 45 | 46 | def __init__(self, handler): 47 | self.handler = handler 48 | self.socket = handler.request 49 | self.addr = handler.client_address 50 | self.server = handler.server 51 | try: 52 | 53 | self.wsh = WSRequestHandler(self.socket, self.addr) 54 | if not hasattr(self.wsh, 'headers'): 55 | self.close() 56 | self.headers = self.wsh.headers 57 | self.ver = self.headers.get('Sec-WebSocket-Version') 58 | self.handshaken = False 59 | if self.ver: 60 | 61 | # HyBi/IETF version of the protocol 62 | 63 | # HyBi-07 report version 7 64 | # HyBi-08 - HyBi-12 report version 8 65 | # HyBi-13 reports version 13 66 | 67 | if self.ver in ['7', '8', '13']: 68 | self.version = 'hybi-%02d' % int(self.ver) 69 | else: 70 | raise Exception('Unsupported protocol version %s' 71 | % self.ver) 72 | 73 | key = self.headers.get('Sec-WebSocket-Key') 74 | 75 | # Generate the hash value for the accept header 76 | accept = b64encode(sha1(key.encode('utf-8') + self.GUID.encode('utf-8')).digest()) 77 | 78 | response = self.server_handshake_hybi % accept.decode("UTF-8") 79 | response += '\r\n' 80 | self.socket.send(response.encode("UTF-8")) 81 | self.handler.addClient(self) 82 | while 0x1: 83 | try: 84 | data = self.socket.recv(1024) 85 | except Exception as e: 86 | log.exception('WebSocket error') 87 | break 88 | if not data: 89 | break 90 | dec = WebSocketClient.decode_hybi(data) 91 | if dec['opcode'] == 0x08: 92 | self.close() 93 | else: 94 | self.onreceive(dec) 95 | 96 | # Close the client connection 97 | 98 | self.close() 99 | except Exception as e: 100 | log.exception('Decoding error') 101 | self.close() 102 | 103 | @staticmethod 104 | def unmask(buf, f): 105 | pstart = f['hlen'] + 4 106 | pend = pstart + f['length'] 107 | 108 | # Slower fallback 109 | 110 | data = array.array('B') 111 | mask = s2a(f['mask']) 112 | data.fromstring(buf[pstart:pend]) 113 | for i in range(len(data)): 114 | data[i] ^= mask[i % 4] 115 | return data.tostring() 116 | 117 | @staticmethod 118 | def encode_hybi(buf, opcode, base64=False): 119 | """ Encode a HyBi style WebSocket frame. 120 | Optional opcode: 121 | 0x0 - continuation 122 | 0x1 - text frame (base64 encode buf) 123 | 0x2 - binary frame (use raw buf) 124 | 0x8 - connection close 125 | 0x9 - ping 126 | 0xA - pong 127 | """ 128 | 129 | if base64: 130 | buf = b64encode(buf) 131 | 132 | b1 = 0x80 | opcode & 0x0f # FIN + opcode 133 | payload_len = len(buf) 134 | if payload_len <= 125: 135 | header = pack('>BB', b1, payload_len) 136 | elif payload_len > 125 and payload_len < 65536: 137 | header = pack('>BBH', b1, 126, payload_len) 138 | elif payload_len >= 65536: 139 | header = pack('>BBQ', b1, 0x7f, payload_len) 140 | 141 | return (header + buf.encode("utf-8"), len(header), 0) 142 | 143 | @staticmethod 144 | def decode_hybi(buf, base64=False): 145 | """ Decode HyBi style WebSocket packets. 146 | Returns: 147 | {'fin' : 0_or_1, 148 | 'opcode' : number, 149 | 'mask' : 32_bit_number, 150 | 'hlen' : header_bytes_number, 151 | 'length' : payload_bytes_number, 152 | 'payload' : decoded_buffer, 153 | 'left' : bytes_left_number, 154 | 'close_code' : number, 155 | 'close_reason' : string} 156 | """ 157 | 158 | f = { 159 | 'fin': 0, 160 | 'opcode': 0, 161 | 'mask': 0, 162 | 'hlen': 2, 163 | 'length': 0, 164 | 'payload': None, 165 | 'left': 0, 166 | 'close_code': None, 167 | 'close_reason': None, 168 | } 169 | 170 | blen = len(buf) 171 | f['left'] = blen 172 | 173 | if blen < f['hlen']: 174 | return f # Incomplete frame header 175 | 176 | (b1, b2) = unpack_from('>BB', buf) 177 | f['opcode'] = b1 & 0x0f 178 | f['fin'] = (b1 & 0x80) >> 7 179 | has_mask = (b2 & 0x80) >> 7 180 | 181 | f['length'] = b2 & 0x7f 182 | 183 | if f['length'] == 126: 184 | f['hlen'] = 4 185 | if blen < f['hlen']: 186 | return f # Incomplete frame header 187 | (f['length'], ) = unpack_from('>xxH', buf) 188 | elif f['length'] == 0x7f: 189 | f['hlen'] = 10 190 | if blen < f['hlen']: 191 | return f # Incomplete frame header 192 | (f['length'], ) = unpack_from('>xxQ', buf) 193 | 194 | full_len = f['hlen'] + has_mask * 4 + f['length'] 195 | 196 | if blen < full_len: # Incomplete frame 197 | return f # Incomplete frame header 198 | 199 | # Number of bytes that are part of the next frame(s) 200 | 201 | f['left'] = blen - full_len 202 | 203 | # Process 1 frame 204 | 205 | if has_mask: 206 | 207 | # unmask payload 208 | 209 | f['mask'] = buf[f['hlen']:f['hlen'] + 4] 210 | f['payload'] = WebSocketClient.unmask(buf, f) 211 | else: 212 | log.info('Unmasked frame: %s' % repr(buf)) 213 | f['payload'] = buf[f['hlen'] + has_mask * 4:full_len] 214 | 215 | if base64 and f['opcode'] in [0x1, 2]: 216 | try: 217 | f['payload'] = b64decode(f['payload']) 218 | except: 219 | log.exception('Exception while b64decoding buffer: %s' 220 | % repr(buf)) 221 | raise 222 | 223 | if f['opcode'] == 0x08: 224 | if f['length'] >= 2: 225 | f['close_code'] = unpack_from('>H', f['payload']) 226 | if f['length'] > 3: 227 | f['close_reason'] = (f['payload'])[2:] 228 | 229 | return f 230 | 231 | def close(self): 232 | """ 233 | Close this connection 234 | """ 235 | 236 | self.handler.removeClient(self) 237 | self.socket.close() 238 | 239 | def send(self, msg): 240 | """ 241 | Send a message to this client 242 | """ 243 | 244 | msg = WebSocketClient.encode_hybi(msg, 0x1, False) 245 | self.socket.send(msg[0]) 246 | 247 | def onreceive(self, data): 248 | """ 249 | Event called when a message is received from this client 250 | """ 251 | 252 | try: 253 | log.info(data) 254 | 255 | if 'payload' in data: 256 | req = json.loads(data.get('payload').decode("UTF-8")) 257 | log.info('Command: %s' % req.get('command')) 258 | if not self.handshaken: 259 | if req.get('command') == 'hello': 260 | sublime.set_timeout(lambda : \ 261 | sublime.status_message('New LiveReload v2 client connected' 262 | ), 100) 263 | self.send('{"command":"hello","protocols":["http://livereload.com/protocols/connection-check-1","http://livereload.com/protocols/official-6","http://livereload.com/protocols/official-7","http://dz0ny.info/sm2-plugin"]}' 264 | ) 265 | else: 266 | sublime.set_timeout(lambda : \ 267 | sublime.status_message('New LiveReload v1 client connected' 268 | ), 100) 269 | self.send('!!ver:' + str(self.server.version)) 270 | 271 | self.handshaken = True 272 | self.info = {'origin': self.headers.get('Origin'), 273 | 'url': data.get('payload')} 274 | self.handler.updateInfo() 275 | else: 276 | try: 277 | sys.modules['LiveReload'].PluginAPI.PluginFactory.dispatch_OnReceive(LiveReload.Plugin, data.get('payload'), 278 | self.headers.get('Origin')) 279 | except Exception as e: 280 | log.exception('API error') 281 | 282 | except Exception as e: 283 | log.exception('receive error') 284 | 285 | def _clean(self, msg): 286 | """ 287 | Remove special chars used for the transmission 288 | """ 289 | 290 | msg = msg.replace('\x00', '', 0x1) 291 | msg = msg.replace('\xff', '', 0x1) 292 | return msg 293 | -------------------------------------------------------------------------------- /server/WebSocketServer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | 6 | try: 7 | from .WebSocketClient import WebSocketClient 8 | except ValueError: 9 | from WebSocketClient import WebSocketClient 10 | 11 | try: 12 | import SocketServer 13 | except ImportError: 14 | import socketserver as SocketServer 15 | 16 | logging.basicConfig(level=logging.INFO) 17 | log = logging.getLogger('WebSocketClient') 18 | 19 | 20 | class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler): 21 | 22 | def updateInfo(self): 23 | self.server.clients_info = [] 24 | for client in self.server.clients: 25 | self.server.clients_info.append(client.info) 26 | 27 | def addClient(self, client): 28 | log.info('Add to clients table ' + str(client)) 29 | self.server.clients.append(client) 30 | 31 | def handle(self): 32 | log.info('new client ' + str(self.client_address)) 33 | WebSocketClient(self) 34 | 35 | def removeClient(self, client): 36 | log.info('Remove from clients table ' + str(client)) 37 | try: 38 | self.server.clients.remove(client) 39 | self.updateInfo() 40 | except Exception: 41 | 42 | # this is normal because we support both connection protocols 43 | 44 | pass 45 | 46 | 47 | class ThreadedTCPServer(SocketServer.ThreadingMixIn, 48 | SocketServer.TCPServer): 49 | 50 | daemon_threads = True 51 | allow_reuse_address = True 52 | 53 | def __init__( 54 | self, 55 | server_address, 56 | RequestHandlerClass, 57 | version, 58 | ): 59 | 60 | SocketServer.TCPServer.__init__(self, server_address, 61 | RequestHandlerClass) 62 | self.clients = [] 63 | self.clients_info = [] 64 | self.version = version 65 | 66 | def send_all(self, data): 67 | """ 68 | Send a message to all the currently connected clients. 69 | """ 70 | 71 | for client in self.clients: 72 | try: 73 | client.send(data) 74 | except Exception: 75 | self.clients.remove(client) 76 | 77 | def list_clients(self): 78 | """ 79 | List all the currently connected clients. 80 | contains request URL, headers 81 | """ 82 | 83 | return self.clients_info 84 | 85 | 86 | class WebSocketServer: 87 | 88 | """ 89 | Handle the Server, bind and accept new connections, open and close 90 | clients connections. 91 | """ 92 | 93 | def __init__(self, port, version): 94 | self.server = ThreadedTCPServer((u'', port), 95 | ThreadedTCPRequestHandler, version) 96 | 97 | def send(self, data): 98 | self.server.send_all(data) 99 | 100 | def stop(self): 101 | """ 102 | Stop the server. 103 | """ 104 | log.info("Stopping server") 105 | self.server.shutdown() 106 | 107 | def start(self): 108 | """ 109 | Start the server. 110 | """ 111 | log.info("Starting server") 112 | self.server.serve_forever() 113 | -------------------------------------------------------------------------------- /server/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | -------------------------------------------------------------------------------- /snippets/livereload_html.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | Insert livereload.js script 3 | document.write(' 5 | ]]> 6 | 7 | livereload 8 | 9 | text.html 10 | 11 | -------------------------------------------------------------------------------- /snippets/livereload_jade.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | Insert livereload.js script 3 | ') 6 | ]]> 7 | 8 | livereload 9 | 10 | source.jade 11 | 12 | -------------------------------------------------------------------------------- /snippets/livereload_plugin.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | Create new LiveReload plugin 3 | 60 | 61 | livereload_plugin 62 | 63 | source.python 64 | 65 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/test/__init__.py -------------------------------------------------------------------------------- /test/server_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyvows import Vows, expect 5 | 6 | @Vows.batch 7 | class WebsocketServer(Vows.Context): 8 | 9 | def setup(self): 10 | pass 11 | 12 | def teardown(self): 13 | pass 14 | 15 | def topic(self): 16 | return 'ok' 17 | 18 | def tests_are_working(self, topic): 19 | expect(topic).Not.to_be_null() 20 | -------------------------------------------------------------------------------- /web/Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | Snockets = require('snockets') 2 | fs = require('fs') 3 | path = require('path') 4 | 5 | #global module:false 6 | module.exports = (grunt) -> 7 | 8 | # Project configuration. 9 | grunt.initConfig 10 | 11 | # Metadata. 12 | pkg: grunt.file.readJSON("package.json") 13 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + 14 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 15 | '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + 16 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;' + 17 | ' Licensed <%= pkg.license %> */\n', 18 | # Task configuration. 19 | uglify: 20 | options: 21 | banner: "<%= banner %>" 22 | 23 | dist: 24 | src: "<%= snockets.dist.dest %>" 25 | dest: "dist/<%= pkg.name %>.min.js" 26 | 27 | snockets: 28 | dist: 29 | src: 'src/startup.coffee' 30 | dest: "dist/<%= pkg.name %>.js" 31 | 32 | qunit: 33 | files: ["test/**/*.html"] 34 | 35 | grunt.registerMultiTask "snockets", "Building js files with snockets.js.", -> 36 | 37 | snockets = new Snockets 38 | 39 | # It doesn't run with empty src and dest parameters. 40 | if typeof @data.src is "undefined" or typeof @data.dest is "undefined" 41 | grunt.log.error "Missing Options: src and dest options necessary" 42 | return false 43 | if fs.existsSync(path.resolve(@data.src)) 44 | try 45 | js = snockets.getConcatenation(@data.src, 46 | async: false 47 | ) 48 | js = """ 49 | (function() { 50 | #{js} 51 | }).call(this); 52 | """ 53 | fs.writeFileSync path.resolve(@data.dest), js 54 | grunt.log.write @data.src + " snocket to " + @data.dest 55 | return true 56 | catch e 57 | grunt.log.error e 58 | return false 59 | else 60 | grunt.log.error "Missing File: " + @data.src 61 | false 62 | 63 | # These plugins provide necessary tasks. 64 | grunt.loadNpmTasks "grunt-contrib-uglify" 65 | grunt.loadNpmTasks "grunt-contrib-qunit" 66 | grunt.loadNpmTasks "grunt-contrib-watch" 67 | 68 | # Default task. 69 | grunt.registerTask "default", ["snockets", "uglify"] 70 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alepez/LiveReload-sublimetext3/32cbba4a89a37c6ecd4dbe440bfa60662c84138f/web/README.md -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "livereloadjs-sm2", 3 | "version": "2.0.9", 4 | "description": "Livereload fork for Sublime Text", 5 | "main": "dist/livereload.min.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "grunt test" 11 | }, 12 | "repository": "", 13 | "keywords": [ 14 | "livereload", 15 | "sublimetext" 16 | ], 17 | "author": "dz0ny", 18 | "license": "MIT", 19 | "devDependencies": { 20 | "grunt": "~0.4.0rc8", 21 | "grunt-contrib-uglify": "~0.1.1rc6", 22 | "grunt-contrib-watch": "~0.2.0", 23 | "grunt-contrib-qunit": "~0.1.1", 24 | "snockets": "~1.3.8" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /web/src/connector.coffee: -------------------------------------------------------------------------------- 1 | # CoffeeScript: bare: true 2 | #= require protocol 3 | 4 | Version = '2.0.8' 5 | 6 | class Connector 7 | 8 | constructor: (@options, @WebSocket, @Timer, @handlers) -> 9 | @_uri = "ws://#{@options.host}:#{@options.port}/livereload" 10 | @_nextDelay = @options.mindelay 11 | @_connectionDesired = no 12 | @protocol = 0 13 | 14 | @protocolParser = new Parser 15 | connected: (@protocol) => 16 | @_handshakeTimeout.stop() 17 | @_nextDelay = @options.mindelay 18 | @_disconnectionReason = 'broken' 19 | @handlers.connected(protocol) 20 | error: (e) => 21 | @handlers.error(e) 22 | @_closeOnError() 23 | message: (message) => 24 | @handlers.message(message) 25 | 26 | @_handshakeTimeout = new Timer => 27 | return unless @_isSocketConnected() 28 | @_disconnectionReason = 'handshake-timeout' 29 | @socket.close() 30 | 31 | @_reconnectTimer = new Timer => 32 | return unless @_connectionDesired # shouldn't hit this, but just in case 33 | @connect() 34 | 35 | @connect() 36 | 37 | 38 | _isSocketConnected: -> 39 | @socket and @socket.readyState is @WebSocket.OPEN 40 | 41 | connect: -> 42 | @_connectionDesired = yes 43 | return if @_isSocketConnected() 44 | 45 | # prepare for a new connection 46 | @_reconnectTimer.stop() 47 | @_disconnectionReason = 'cannot-connect' 48 | @protocolParser.reset() 49 | 50 | @handlers.connecting() 51 | 52 | @socket = new @WebSocket(@_uri) 53 | @socket.onopen = (e) => @_onopen(e) 54 | @socket.onclose = (e) => @_onclose(e) 55 | @socket.onmessage = (e) => @_onmessage(e) 56 | @socket.onerror = (e) => @_onerror(e) 57 | 58 | disconnect: -> 59 | @_connectionDesired = no 60 | @_reconnectTimer.stop() # in case it was running 61 | return unless @_isSocketConnected() 62 | @_disconnectionReason = 'manual' 63 | @socket.close() 64 | 65 | 66 | _scheduleReconnection: -> 67 | return unless @_connectionDesired # don't reconnect after manual disconnection 68 | unless @_reconnectTimer.running 69 | @_reconnectTimer.start(@_nextDelay) 70 | @_nextDelay = Math.min(@options.maxdelay, @_nextDelay * 2) 71 | 72 | sendCommand: (command) -> 73 | return unless @protocol? 74 | @_sendCommand command 75 | 76 | _sendCommand: (command) -> 77 | @socket.send JSON.stringify(command) 78 | 79 | _closeOnError: -> 80 | @_handshakeTimeout.stop() 81 | @_disconnectionReason = 'error' 82 | @socket.close() 83 | 84 | _onopen: (e) -> 85 | @handlers.socketConnected() 86 | @_disconnectionReason = 'handshake-failed' 87 | 88 | # start handshake 89 | hello = { command: 'hello', protocols: [PROTOCOL_6, PROTOCOL_7] } 90 | hello.ver = Version 91 | hello.ext = @options.ext if @options.ext 92 | hello.extver = @options.extver if @options.extver 93 | hello.snipver = @options.snipver if @options.snipver 94 | @_sendCommand hello 95 | @_handshakeTimeout.start(@options.handshake_timeout) 96 | 97 | _onclose: (e) -> 98 | @protocol = 0 99 | @handlers.disconnected @_disconnectionReason, @_nextDelay 100 | @_scheduleReconnection() 101 | 102 | _onerror: (e) -> 103 | 104 | _onmessage: (e) -> 105 | @protocolParser.process(e.data) 106 | -------------------------------------------------------------------------------- /web/src/customevents.coffee: -------------------------------------------------------------------------------- 1 | # CoffeeScript: bare: true 2 | class CustomEvents 3 | @bind: (element, eventName, handler) -> 4 | if element.addEventListener 5 | element.addEventListener eventName, handler, false 6 | else if element.attachEvent 7 | element[eventName] = 1 8 | element.attachEvent 'onpropertychange', (event) -> 9 | if event.propertyName is eventName 10 | handler() 11 | else 12 | throw new Error("Attempt to attach custom event #{eventName} to something which isn't a DOMElement") 13 | 14 | @fire: (element, eventName) -> 15 | if element.addEventListener 16 | event = document.createEvent('HTMLEvents') 17 | event.initEvent(eventName, true, true) 18 | document.dispatchEvent(event) 19 | else if element.attachEvent 20 | if element[eventName] 21 | element[eventName]++ 22 | else 23 | throw new Error("Attempt to fire custom event #{eventName} on something which isn't a DOMElement") 24 | -------------------------------------------------------------------------------- /web/src/less.coffee: -------------------------------------------------------------------------------- 1 | # CoffeeScript: bare: true 2 | class LessPlugin 3 | @identifier = 'less' 4 | @version = '1.0' 5 | 6 | constructor: (@window, @host) -> 7 | 8 | reload: (path, options) -> 9 | if @window.less and @window.less.refresh 10 | if path.match(/\.less$/i) 11 | return @reloadLess(path) 12 | if options.originalPath.match(/\.less$/i) 13 | return @reloadLess(options.originalPath) 14 | no 15 | 16 | reloadLess: (path) -> 17 | links = (link for link in document.getElementsByTagName('link') when link.href and link.rel is 'stylesheet/less' or (link.rel.match(/stylesheet/) and link.type.match(/^text\/(x-)?less$/))) 18 | 19 | return no if links.length is 0 20 | 21 | for link in links 22 | link.href = @host.generateCacheBustUrl(link.href) 23 | 24 | @host.console.log "LiveReload is asking LESS to recompile all stylesheets" 25 | @window.less.refresh(true) 26 | return yes 27 | 28 | 29 | analyze: -> 30 | { disable: !!(@window.less and @window.less.refresh) } 31 | 32 | LiveReloadPluginLess = window.LiveReloadPluginLess = LessPlugin -------------------------------------------------------------------------------- /web/src/livereload.coffee: -------------------------------------------------------------------------------- 1 | # CoffeeScript: bare: true 2 | 3 | #= require connector 4 | #= require timer 5 | #= require options 6 | #= require reloader 7 | 8 | class LiveReload 9 | 10 | constructor: (@window) -> 11 | @listeners = {} 12 | @plugins = [] 13 | @pluginIdentifiers = {} 14 | 15 | livelog = (msg)-> 16 | console.log "livelog", msg 17 | b = document.getElementsByTagName 'body' 18 | p = document.createElement "p" 19 | text = document.createTextNode msg 20 | p.appendChild(text) 21 | b[0].appendChild(p) 22 | 23 | # i can haz console? 24 | @console = if @window.location.href.match(/LR-verbose/) && @window.console && @window.console.log && @window.console.error 25 | @window.console 26 | else if @window.location.href.match(/LiveTest/) 27 | log:(msg)-> 28 | livelog msg 29 | error:(msg)-> 30 | livelog msg 31 | else 32 | log: -> 33 | error: -> 34 | 35 | # i can haz sockets? 36 | unless @WebSocket = @window.WebSocket || @window.MozWebSocket 37 | console.error("LiveReload disabled because the browser does not seem to support web sockets") 38 | return 39 | 40 | # i can haz options? 41 | unless @options = Options.extract(@window.document) 42 | console.error("LiveReload disabled because it could not find its own 7 | 8 | Start testing 9 | 10 | -------------------------------------------------------------------------------- /web/test/connector_test.coffee: -------------------------------------------------------------------------------- 1 | { Options } = require 'options' 2 | { Connector } = require 'connector' 3 | { PROTOCOL_7 } = require 'protocol' 4 | 5 | HELLO = { command: 'hello', protocols: [PROTOCOL_7] } 6 | 7 | class MockHandlers 8 | constructor: -> 9 | @_log = [] 10 | 11 | obtainLog: -> result = @_log.join("\n"); @_log = []; result 12 | log: (message) -> @_log.push message 13 | 14 | connecting: -> @log "connecting" 15 | socketConnected: -> 16 | connected: (protocol) -> @log "connected(#{protocol})" 17 | disconnected: (reason) -> @log "disconnected(#{reason})" 18 | message: (message) -> @log "message(#{message.command})" 19 | 20 | newMockTimer = -> 21 | class MockTimer 22 | constructor: (@func) -> 23 | MockTimer.timers.push this 24 | @time = null 25 | 26 | start: (timeout) -> 27 | @time = MockTimer.now + timeout 28 | 29 | stop: -> 30 | @time = null 31 | 32 | fire: -> 33 | @time = null 34 | @func() 35 | 36 | MockTimer.timers = [] 37 | MockTimer.now = 0 38 | MockTimer.advance = (period) -> 39 | MockTimer.now += period 40 | for timer in MockTimer.timers 41 | timer.fire() if timer.time? and timer.time <= MockTimer.now 42 | 43 | return MockTimer 44 | 45 | newMockWebSocket = -> 46 | class MockWebSocket 47 | constructor: -> 48 | MockWebSocket._last = this 49 | @sent = [] 50 | @readyState = MockWebSocket.CONNECTING 51 | 52 | obtainSent: -> result = @sent; @sent = []; result 53 | log: (message) -> @_log.push message 54 | 55 | send: (message) -> @sent.push message 56 | 57 | close: -> 58 | @readyState = MockWebSocket.CLOSED 59 | @onclose({}) 60 | 61 | connected: -> 62 | @readyState = MockWebSocket.OPEN 63 | @onopen({}) 64 | 65 | disconnected: -> 66 | @readyState = MockWebSocket.CLOSED 67 | @onclose({}) 68 | 69 | receive: (message) -> 70 | @onmessage({ data: message }) 71 | 72 | assertMessages: (assert, messages) -> 73 | actual = [] 74 | expected = [] 75 | 76 | keys = [] 77 | for message in messages 78 | for own key, value of message 79 | keys.push key unless key in keys 80 | keys.sort() 81 | 82 | for payload in @sent 83 | message = JSON.parse(payload) 84 | actual.push ("#{key} = #{JSON.stringify(message[key])}" for key in keys when message.hasOwnProperty(key)) 85 | for message in messages 86 | expected.push ("#{key} = #{JSON.stringify(message[key])}" for key in keys when message.hasOwnProperty(key)) 87 | 88 | assert.equal expected.join("\n"), actual.join("\n") 89 | @sent = [] 90 | 91 | MockWebSocket.last = -> result = MockWebSocket._last; MockWebSocket._last = null; result 92 | 93 | MockWebSocket.CONNECTING = 0 94 | MockWebSocket.OPEN = 1 95 | MockWebSocket.CLOSED = 2 96 | 97 | return MockWebSocket 98 | 99 | 100 | shouldBeConnecting = (assert, handlers) -> 101 | assert.equal "connecting", handlers.obtainLog() 102 | 103 | shouldReconnect = (assert, handlers, timer, failed, code) -> 104 | if failed 105 | delays = [1000, 2000, 4000, 8000, 16000, 32000, 60000, 60000, 60000] 106 | else 107 | delays = [1000, 1000, 1000] 108 | for delay in delays 109 | timer.advance delay-100 110 | assert.equal "", handlers.obtainLog() 111 | 112 | timer.advance 100 113 | shouldBeConnecting assert, handlers 114 | 115 | code() 116 | 117 | cannotConnect = (assert, handlers, webSocket) -> 118 | assert.isNotNull (ws = webSocket.last()) 119 | ws.disconnected() 120 | assert.equal "disconnected(cannot-connect)", handlers.obtainLog() 121 | 122 | connectionBroken = (assert, handlers, ws) -> 123 | ws.disconnected() 124 | assert.equal "disconnected(broken)", handlers.obtainLog() 125 | 126 | connectAndPerformHandshake = (assert, handlers, webSocket, func) -> 127 | assert.isNotNull (ws = webSocket.last()) 128 | 129 | ws.connected() 130 | ws.assertMessages assert, [{ command: 'hello' }] 131 | assert.equal "", handlers.obtainLog() 132 | 133 | ws.receive JSON.stringify(HELLO) 134 | assert.equal "connected(7)", handlers.obtainLog() 135 | 136 | func?(ws) 137 | 138 | connectAndTimeoutHandshake = (assert, handlers, timer, webSocket, func) -> 139 | assert.isNotNull (ws = webSocket.last()) 140 | 141 | ws.connected() 142 | ws.assertMessages assert, [{ command: 'hello' }] 143 | assert.equal "", handlers.obtainLog() 144 | 145 | timer.advance 5000 146 | assert.equal "disconnected(handshake-timeout)", handlers.obtainLog() 147 | 148 | sendReload = (assert, handlers, ws) -> 149 | ws.receive JSON.stringify({ command: 'reload', path: 'foo.css' }) 150 | assert.equal "message(reload)", handlers.obtainLog() 151 | 152 | 153 | exports['should connect and perform handshake'] = (beforeExit, assert) -> 154 | handlers = new MockHandlers() 155 | options = new Options() 156 | timer = newMockTimer() 157 | webSocket = newMockWebSocket() 158 | connector = new Connector(options, webSocket, timer, handlers) 159 | 160 | shouldBeConnecting assert, handlers 161 | connectAndPerformHandshake assert, handlers, webSocket, (ws) -> 162 | sendReload assert, handlers, ws 163 | 164 | 165 | exports['should repeat connection attempts'] = (beforeExit, assert) -> 166 | handlers = new MockHandlers() 167 | options = new Options() 168 | timer = newMockTimer() 169 | webSocket = newMockWebSocket() 170 | connector = new Connector(options, webSocket, timer, handlers) 171 | 172 | shouldBeConnecting assert, handlers 173 | cannotConnect assert, handlers, webSocket 174 | 175 | shouldReconnect assert, handlers, timer, yes, -> 176 | cannotConnect assert, handlers, webSocket 177 | 178 | 179 | exports['should reconnect after disconnection'] = (beforeExit, assert) -> 180 | handlers = new MockHandlers() 181 | options = new Options() 182 | timer = newMockTimer() 183 | webSocket = newMockWebSocket() 184 | connector = new Connector(options, webSocket, timer, handlers) 185 | 186 | shouldBeConnecting assert, handlers 187 | connectAndPerformHandshake assert, handlers, webSocket, (ws) -> 188 | connectionBroken assert, handlers, ws 189 | 190 | shouldReconnect assert, handlers, timer, no, -> 191 | connectAndPerformHandshake assert, handlers, webSocket, (ws) -> 192 | connectionBroken assert, handlers, ws 193 | 194 | 195 | exports['should timeout handshake after 5 sec'] = (beforeExit, assert) -> 196 | handlers = new MockHandlers() 197 | options = new Options() 198 | timer = newMockTimer() 199 | webSocket = newMockWebSocket() 200 | connector = new Connector(options, webSocket, timer, handlers) 201 | 202 | shouldBeConnecting assert, handlers 203 | connectAndTimeoutHandshake assert, handlers, timer, webSocket 204 | 205 | shouldReconnect assert, handlers, timer, yes, -> 206 | connectAndTimeoutHandshake assert, handlers, timer, webSocket 207 | -------------------------------------------------------------------------------- /web/test/html/css-import/bug-test-case.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LiveReload Test 5 | 6 | 7 | 8 |

I'm Header

9 |

I'm too

10 | 11 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /web/test/html/css-import/imported.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: blue; 3 | color: green; 4 | } 5 | -------------------------------------------------------------------------------- /web/test/html/css-import/test.css: -------------------------------------------------------------------------------- 1 | @import url('imported.css'); 2 | 3 | 4 | body { 5 | background: green; 6 | background: #ccc; 7 | } 8 | 9 | h2 { 10 | background: yellow; 11 | } 12 | -------------------------------------------------------------------------------- /web/test/html/css-import/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LiveReload Test 5 | 6 | 7 | 8 |

I'm Header

9 |

I'm too

10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/test/html/delayed/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LiveReload Test 5 | 6 | 7 | 8 | 9 |
10 | Klaatu barada nikto 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /web/test/html/delayed/testcss.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LiveReload Test 5 | 6 | 7 | 8 |

Images Test

9 | 10 |
11 | 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /web/test/html/lessjs/.gitignore: -------------------------------------------------------------------------------- 1 | test.css 2 | -------------------------------------------------------------------------------- /web/test/html/lessjs/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LiveReload Test 5 | 6 | 7 | 8 | 9 |

Less.js In-Browser Test

10 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /web/test/html/lessjs/test.less: -------------------------------------------------------------------------------- 1 | body { 2 | background: #ccc; 3 | 4 | h1 { 5 | color: red; 6 | color: green; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /web/test/html/prefixfree/test.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: green; 3 | /*background: red;*/ 4 | font: 150% Times, serif; 5 | } 6 | 7 | div { 8 | background: #eee; 9 | padding: 10px; 10 | border-radius: 8px; 11 | width: 400px; 12 | box-sizing: border-box; 13 | transition: 0.5s width; 14 | } 15 | 16 | div:hover { 17 | width: 300px; 18 | } 19 | -------------------------------------------------------------------------------- /web/test/html/prefixfree/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LiveReload Test 5 | 6 | 7 | 8 | 9 | 10 |
11 | Klaatu barada nikto 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /web/test/html/trivial/test.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: green; 3 | } 4 | -------------------------------------------------------------------------------- /web/test/html/trivial/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LiveReload Test 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /web/test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers coffee:coffee-script 2 | --reporter spec 3 | --ui exports -------------------------------------------------------------------------------- /web/test/options_test.coffee: -------------------------------------------------------------------------------- 1 | { Options } = require 'options' 2 | jsdom = require 'jsdom' 3 | 4 | 5 | exports['should extract host and port from a SCRIPT tag'] = (beforeExit, assert) -> 6 | _loaded = no 7 | jsdom.env """ 8 | 9 | """, [], (errors, window) -> 10 | assert.isNull errors 11 | _loaded = yes 12 | 13 | options = Options.extract(window.document) 14 | assert.isNotNull options 15 | assert.equal 'somewhere.com', options.host 16 | assert.equal 9876, options.port 17 | 18 | beforeExit -> assert.ok _loaded 19 | 20 | 21 | exports['should recognize zlivereload.js as a valid SCRIPT tag for dev testing purposes'] = (beforeExit, assert) -> 22 | _loaded = no 23 | jsdom.env """ 24 | 25 | """, [], (errors, window) -> 26 | assert.isNull errors 27 | _loaded = yes 28 | 29 | options = Options.extract(window.document) 30 | assert.isNotNull options 31 | assert.equal 'somewhere.com', options.host 32 | assert.equal 9876, options.port 33 | 34 | beforeExit -> assert.ok _loaded 35 | 36 | 37 | exports['should pick the correct SCRIPT tag'] = (beforeExit, assert) -> 38 | _loaded = no 39 | jsdom.env """ 40 | 41 | 42 | 43 | """, [], (errors, window) -> 44 | assert.isNull errors 45 | _loaded = yes 46 | 47 | options = Options.extract(window.document) 48 | assert.isNotNull options 49 | assert.equal 'somewhere.com', options.host 50 | assert.equal 9876, options.port 51 | 52 | beforeExit -> assert.ok _loaded 53 | 54 | 55 | exports['should extract additional options'] = (beforeExit, assert) -> 56 | _loaded = no 57 | jsdom.env """ 58 | 59 | """, [], (errors, window) -> 60 | assert.isNull errors 61 | _loaded = yes 62 | 63 | options = Options.extract(window.document) 64 | assert.equal '1', options.snipver 65 | assert.equal 'Safari', options.ext 66 | assert.equal '2.0', options.extver 67 | 68 | beforeExit -> assert.ok _loaded 69 | 70 | 71 | exports['should be cool with a strange URL'] = (beforeExit, assert) -> 72 | _loaded = no 73 | jsdom.env """ 74 | 75 | """, [], (errors, window) -> 76 | assert.isNull errors 77 | _loaded = yes 78 | 79 | options = Options.extract(window.document) 80 | assert.equal 'somewhere.com', options.host 81 | assert.equal 35729, options.port 82 | 83 | beforeExit -> assert.ok _loaded 84 | -------------------------------------------------------------------------------- /web/test/protocol_test.coffee: -------------------------------------------------------------------------------- 1 | { Parser } = require 'protocol' 2 | 3 | class MockHandler 4 | constructor: -> 5 | @_log = [] 6 | @gotError = no 7 | 8 | obtainLog: -> result = @_log.join("\n"); @_log = []; result 9 | log: (message) -> @_log.push message 10 | 11 | connected: (@protocol) -> 12 | error: (@error) -> @gotError = yes 13 | 14 | message: (msg) -> 15 | switch msg.command 16 | when 'reload' then @log "reload(#{msg.path})" 17 | else @log msg.commmand 18 | 19 | 20 | exports['should reject a bogus handshake'] = (beforeExit, assert) -> 21 | handler = new MockHandler() 22 | parser = new Parser(handler) 23 | 24 | parser.process 'boo' 25 | assert.ok handler.gotError 26 | 27 | 28 | exports['should speak protocol 6'] = (beforeExit, assert) -> 29 | handler = new MockHandler() 30 | parser = new Parser(handler) 31 | 32 | parser.process '!!ver:1.6' 33 | assert.equal 6, parser.protocol 34 | 35 | parser.process '[ "refresh", { "path": "foo.css" } ]' 36 | assert.equal "reload(foo.css)", handler.obtainLog() 37 | 38 | 39 | exports['should speak protocol 7'] = (beforeExit, assert) -> 40 | handler = new MockHandler() 41 | parser = new Parser(handler) 42 | 43 | parser.process '{ "command": "hello", "protocols": [ "http://livereload.com/protocols/official/7" ] }' 44 | assert.equal null, handler.error?.message 45 | assert.equal 7, parser.protocol 46 | 47 | parser.process '{ "command": "reload", "path": "foo.css" }' 48 | assert.equal "reload(foo.css)", handler.obtainLog() 49 | -------------------------------------------------------------------------------- /web/test/timer_test.coffee: -------------------------------------------------------------------------------- 1 | { Timer } = require 'timer' 2 | 3 | 4 | exports['timer should fire an event once in due time'] = (beforeExit, assert) -> 5 | fired = 0 6 | timer = new Timer -> 7 | ++fired 8 | 9 | assert.equal no, timer.running 10 | timer.start(20) 11 | assert.equal yes, timer.running 12 | 13 | beforeExit -> 14 | assert.equal 1, fired 15 | 16 | 17 | exports['timer should not fire after it is stopped'] = (beforeExit, assert) -> 18 | fired = 0 19 | timer = new Timer -> 20 | ++fired 21 | 22 | timer.start(20) 23 | setTimeout((-> timer.stop()), 10) 24 | 25 | beforeExit -> 26 | assert.equal 0, fired 27 | 28 | 29 | exports['timer should restart interval on each start() call'] = (beforeExit, assert) -> 30 | okToFire = no 31 | fired = 0 32 | timer = new Timer -> 33 | assert.equal yes, okToFire 34 | ++fired 35 | 36 | timer.start(10) 37 | setTimeout((-> timer.start(100)), 5) 38 | setTimeout((-> okToFire = yes), 15) 39 | 40 | beforeExit -> 41 | assert.equal 1, fired 42 | --------------------------------------------------------------------------------