feat(SublimeText2.EditorPackages): cache packages
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"version" : "1.6"
|
||||
, "port" : 35729
|
||||
, "delay_ms" : 100
|
||||
, "apply_js_live" : false
|
||||
, "apply_css_live" : true
|
||||
, "apply_images_live" : true
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
[
|
||||
{
|
||||
"caption": "Preferences",
|
||||
"mnemonic": "n",
|
||||
"id": "preferences",
|
||||
"children":
|
||||
[
|
||||
{
|
||||
"caption": "Package Settings",
|
||||
"mnemonic": "P",
|
||||
"id": "package-settings",
|
||||
"children":
|
||||
[
|
||||
{
|
||||
"caption": "LiveReload",
|
||||
"children":
|
||||
[
|
||||
{
|
||||
"command": "open_file", "args":
|
||||
{
|
||||
"file": "${packages}/LiveReload/LiveReload.sublime-settings"
|
||||
},
|
||||
"caption": "Settings – Default"
|
||||
},
|
||||
{
|
||||
"command": "open_file", "args":
|
||||
{
|
||||
"file": "${packages}/User/LiveReload.sublime-settings"
|
||||
},
|
||||
"caption": "Settings – User"
|
||||
},
|
||||
{ "caption": "-" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@@ -0,0 +1,62 @@
|
||||
LiveReload for Sublime Text 2
|
||||
=========
|
||||
|
||||
A web browser page reloading plugin for the [Sublime Text 2](http://sublimetext.com "Sublime Text 2") editor.
|
||||
|
||||
Installing
|
||||
-----
|
||||
|
||||
Install with [Sublime Package Control](http://wbond.net/sublime_packages/package_control "Sublime Package Control"), search for LiveReload and install.
|
||||
|
||||
Devel branch
|
||||
-----
|
||||
Have a look at [devel version](https://github.com/dz0ny/LiveReload-sublimetext2/tree/devel). Which is total rewrite of plugin, supporting SublimeText 3, plugins and much more.
|
||||
|
||||
Browser extensions
|
||||
-----
|
||||
You can use both major LiveReload versions. For old one you can find instructions bellow, for new ones please visit [New browser extensions](http://help.livereload.com/kb/general-use/browser-extensions "New browser extensions") or try [self loading version](http://help.livereload.com/kb/general-use/using-livereload-without-browser-extensions "self loading version").
|
||||
|
||||
|
||||
### [Google Chrome extension](https://chrome.google.com/extensions/detail/jnihajbhpnppcggbcgedagnkighmdlei)
|
||||
|
||||

|
||||
|
||||
Click “Install”. Actually, LiveReload does not access your browser history. The warning is misleading.
|
||||
|
||||

|
||||
|
||||
If you want to use it with local files, be sure to enable “Allow access to file URLs” checkbox in Tools > Extensions > LiveReload after installation.
|
||||
|
||||
### Safari extension
|
||||
|
||||
For now it only works with self loading version:
|
||||
|
||||
<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')</script>
|
||||
|
||||
|
||||
### [Firefox 4 extension](http://feedback.livereload.com/knowledgebase/articles/86242-how-do-i-install-and-use-the-browser-extensions-)
|
||||
|
||||

|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Now, if you are using Safari, right-click the page you want to be livereload'ed and choose “Enable LiveReload”:
|
||||
|
||||

|
||||
|
||||
If you are using Chrome, just click the toolbar button (it will turn green to indicate that LiveReload is active).
|
||||
|
||||
----
|
||||
|
||||
You can also use the Preferences menu to change port, version and type of reloading(full, js,css).
|
||||
|
||||
## Compass
|
||||
|
||||

|
||||
|
||||
Want to use Livereload with compass ? Now you can !
|
||||
|
||||
.scss and .css have to be in the same directory , if no config.rb file is found one will automatically generated !
|
||||
|
||||
So if you want to start using compass, install compass gem, edit a xxx.scss file and voila ! A .css file would be automatically generated.
|
@@ -0,0 +1,8 @@
|
||||
http_path = "/"
|
||||
css_dir = "."
|
||||
sass_dir = "."
|
||||
images_dir = "img"
|
||||
javascripts_dir = "js"
|
||||
output_style = :compressed
|
||||
relative_assets=true
|
||||
line_comments = false
|
@@ -0,0 +1,942 @@
|
||||
(function() {
|
||||
var __customevents = {}, __protocol = {}, __connector = {}, __timer = {}, __options = {}, __reloader = {}, __livereload = {}, __less = {}, __startup = {};
|
||||
|
||||
// customevents
|
||||
var CustomEvents;
|
||||
CustomEvents = {
|
||||
bind: function(element, eventName, handler) {
|
||||
if (element.addEventListener) {
|
||||
return element.addEventListener(eventName, handler, false);
|
||||
} else if (element.attachEvent) {
|
||||
element[eventName] = 1;
|
||||
return element.attachEvent('onpropertychange', function(event) {
|
||||
if (event.propertyName === eventName) {
|
||||
return handler();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Error("Attempt to attach custom event " + eventName + " to something which isn't a DOMElement");
|
||||
}
|
||||
},
|
||||
fire: function(element, eventName) {
|
||||
var event;
|
||||
if (element.addEventListener) {
|
||||
event = document.createEvent('HTMLEvents');
|
||||
event.initEvent(eventName, true, true);
|
||||
return document.dispatchEvent(event);
|
||||
} else if (element.attachEvent) {
|
||||
if (element[eventName]) {
|
||||
return element[eventName]++;
|
||||
}
|
||||
} else {
|
||||
throw new Error("Attempt to fire custom event " + eventName + " on something which isn't a DOMElement");
|
||||
}
|
||||
}
|
||||
};
|
||||
__customevents.bind = CustomEvents.bind;
|
||||
__customevents.fire = CustomEvents.fire;
|
||||
|
||||
// protocol
|
||||
var PROTOCOL_6, PROTOCOL_7, Parser, ProtocolError;
|
||||
var __indexOf = Array.prototype.indexOf || function(item) {
|
||||
for (var i = 0, l = this.length; i < l; i++) {
|
||||
if (this[i] === item) return i;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
__protocol.PROTOCOL_6 = PROTOCOL_6 = 'http://livereload.com/protocols/official-6';
|
||||
__protocol.PROTOCOL_7 = PROTOCOL_7 = 'http://livereload.com/protocols/official-7';
|
||||
__protocol.ProtocolError = ProtocolError = (function() {
|
||||
function ProtocolError(reason, data) {
|
||||
this.message = "LiveReload protocol error (" + reason + ") after receiving data: \"" + data + "\".";
|
||||
}
|
||||
return ProtocolError;
|
||||
})();
|
||||
__protocol.Parser = Parser = (function() {
|
||||
function Parser(handlers) {
|
||||
this.handlers = handlers;
|
||||
this.reset();
|
||||
}
|
||||
Parser.prototype.reset = function() {
|
||||
return this.protocol = null;
|
||||
};
|
||||
Parser.prototype.process = function(data) {
|
||||
var command, message, options, _ref;
|
||||
try {
|
||||
if (!(this.protocol != null)) {
|
||||
if (data.match(/^!!ver:([\d.]+)$/)) {
|
||||
this.protocol = 6;
|
||||
} else if (message = this._parseMessage(data, ['hello'])) {
|
||||
if (!message.protocols.length) {
|
||||
throw new ProtocolError("no protocols specified in handshake message");
|
||||
} else if (__indexOf.call(message.protocols, PROTOCOL_7) >= 0) {
|
||||
this.protocol = 7;
|
||||
} else if (__indexOf.call(message.protocols, PROTOCOL_6) >= 0) {
|
||||
this.protocol = 6;
|
||||
} else {
|
||||
throw new ProtocolError("no supported protocols found");
|
||||
}
|
||||
}
|
||||
return this.handlers.connected(this.protocol);
|
||||
} else if (this.protocol === 6) {
|
||||
message = JSON.parse(data);
|
||||
if (!message.length) {
|
||||
throw new ProtocolError("protocol 6 messages must be arrays");
|
||||
}
|
||||
command = message[0], options = message[1];
|
||||
if (command !== 'refresh') {
|
||||
throw new ProtocolError("unknown protocol 6 command");
|
||||
}
|
||||
return this.handlers.message({
|
||||
command: 'reload',
|
||||
path: options.path,
|
||||
liveCSS: (_ref = options.apply_css_live) != null ? _ref : true
|
||||
});
|
||||
} else {
|
||||
message = this._parseMessage(data, ['reload', 'alert']);
|
||||
return this.handlers.message(message);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof ProtocolError) {
|
||||
return this.handlers.error(e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
Parser.prototype._parseMessage = function(data, validCommands) {
|
||||
var message, _ref;
|
||||
try {
|
||||
message = JSON.parse(data);
|
||||
} catch (e) {
|
||||
throw new ProtocolError('unparsable JSON', data);
|
||||
}
|
||||
if (!message.command) {
|
||||
throw new ProtocolError('missing "command" key', data);
|
||||
}
|
||||
if (_ref = message.command, __indexOf.call(validCommands, _ref) < 0) {
|
||||
throw new ProtocolError("invalid command '" + message.command + "', only valid commands are: " + (validCommands.join(', ')) + ")", data);
|
||||
}
|
||||
return message;
|
||||
};
|
||||
return Parser;
|
||||
})();
|
||||
|
||||
// connector
|
||||
var Connector, PROTOCOL_6, PROTOCOL_7, Parser, Version, _ref;
|
||||
|
||||
_ref = __protocol, Parser = _ref.Parser, PROTOCOL_6 = _ref.PROTOCOL_6, PROTOCOL_7 = _ref.PROTOCOL_7;
|
||||
|
||||
Version = '2.0.7';
|
||||
|
||||
__connector.Connector = Connector = (function() {
|
||||
|
||||
function Connector(options, WebSocket, Timer, handlers) {
|
||||
var _this = this;
|
||||
this.options = options;
|
||||
this.WebSocket = WebSocket;
|
||||
this.Timer = Timer;
|
||||
this.handlers = handlers;
|
||||
this._uri = "ws://" + this.options.host + ":" + this.options.port + "/livereload";
|
||||
this._nextDelay = this.options.mindelay;
|
||||
this._connectionDesired = false;
|
||||
this.protocol = 0;
|
||||
this.protocolParser = new Parser({
|
||||
connected: function(protocol) {
|
||||
_this.protocol = protocol;
|
||||
_this._handshakeTimeout.stop();
|
||||
_this._nextDelay = _this.options.mindelay;
|
||||
_this._disconnectionReason = 'broken';
|
||||
return _this.handlers.connected(protocol);
|
||||
},
|
||||
error: function(e) {
|
||||
_this.handlers.error(e);
|
||||
return _this._closeOnError();
|
||||
},
|
||||
message: function(message) {
|
||||
return _this.handlers.message(message);
|
||||
}
|
||||
});
|
||||
this._handshakeTimeout = new Timer(function() {
|
||||
if (!_this._isSocketConnected()) return;
|
||||
_this._disconnectionReason = 'handshake-timeout';
|
||||
return _this.socket.close();
|
||||
});
|
||||
this._reconnectTimer = new Timer(function() {
|
||||
if (!_this._connectionDesired) return;
|
||||
return _this.connect();
|
||||
});
|
||||
this.connect();
|
||||
}
|
||||
|
||||
Connector.prototype._isSocketConnected = function() {
|
||||
return this.socket && this.socket.readyState === this.WebSocket.OPEN;
|
||||
};
|
||||
|
||||
Connector.prototype.connect = function() {
|
||||
var _this = this;
|
||||
this._connectionDesired = true;
|
||||
if (this._isSocketConnected()) return;
|
||||
this._reconnectTimer.stop();
|
||||
this._disconnectionReason = 'cannot-connect';
|
||||
this.protocolParser.reset();
|
||||
this.handlers.connecting();
|
||||
this.socket = new this.WebSocket(this._uri);
|
||||
this.socket.onopen = function(e) {
|
||||
return _this._onopen(e);
|
||||
};
|
||||
this.socket.onclose = function(e) {
|
||||
return _this._onclose(e);
|
||||
};
|
||||
this.socket.onmessage = function(e) {
|
||||
return _this._onmessage(e);
|
||||
};
|
||||
return this.socket.onerror = function(e) {
|
||||
return _this._onerror(e);
|
||||
};
|
||||
};
|
||||
|
||||
Connector.prototype.disconnect = function() {
|
||||
this._connectionDesired = false;
|
||||
this._reconnectTimer.stop();
|
||||
if (!this._isSocketConnected()) return;
|
||||
this._disconnectionReason = 'manual';
|
||||
return this.socket.close();
|
||||
};
|
||||
|
||||
Connector.prototype._scheduleReconnection = function() {
|
||||
if (!this._connectionDesired) return;
|
||||
if (!this._reconnectTimer.running) {
|
||||
this._reconnectTimer.start(this._nextDelay);
|
||||
return this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2);
|
||||
}
|
||||
};
|
||||
|
||||
Connector.prototype.sendCommand = function(command) {
|
||||
if (this.protocol == null) return;
|
||||
return this._sendCommand(command);
|
||||
};
|
||||
|
||||
Connector.prototype._sendCommand = function(command) {
|
||||
return this.socket.send(JSON.stringify(command));
|
||||
};
|
||||
|
||||
Connector.prototype._closeOnError = function() {
|
||||
this._handshakeTimeout.stop();
|
||||
this._disconnectionReason = 'error';
|
||||
return this.socket.close();
|
||||
};
|
||||
|
||||
Connector.prototype._onopen = function(e) {
|
||||
var hello;
|
||||
this.handlers.socketConnected();
|
||||
this._disconnectionReason = 'handshake-failed';
|
||||
hello = {
|
||||
command: 'hello',
|
||||
protocols: [PROTOCOL_6, PROTOCOL_7]
|
||||
};
|
||||
hello.ver = Version;
|
||||
if (this.options.ext) hello.ext = this.options.ext;
|
||||
if (this.options.extver) hello.extver = this.options.extver;
|
||||
if (this.options.snipver) hello.snipver = this.options.snipver;
|
||||
this._sendCommand(hello);
|
||||
return this._handshakeTimeout.start(this.options.handshake_timeout);
|
||||
};
|
||||
|
||||
Connector.prototype._onclose = function(e) {
|
||||
this.protocol = 0;
|
||||
this.handlers.disconnected(this._disconnectionReason, this._nextDelay);
|
||||
return this._scheduleReconnection();
|
||||
};
|
||||
|
||||
Connector.prototype._onerror = function(e) {};
|
||||
|
||||
Connector.prototype._onmessage = function(e) {
|
||||
return this.protocolParser.process(e.data);
|
||||
};
|
||||
|
||||
return Connector;
|
||||
|
||||
})();
|
||||
|
||||
// timer
|
||||
var Timer;
|
||||
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
__timer.Timer = Timer = (function() {
|
||||
function Timer(func) {
|
||||
this.func = func;
|
||||
this.running = false;
|
||||
this.id = null;
|
||||
this._handler = __bind(function() {
|
||||
this.running = false;
|
||||
this.id = null;
|
||||
return this.func();
|
||||
}, this);
|
||||
}
|
||||
Timer.prototype.start = function(timeout) {
|
||||
if (this.running) {
|
||||
clearTimeout(this.id);
|
||||
}
|
||||
this.id = setTimeout(this._handler, timeout);
|
||||
return this.running = true;
|
||||
};
|
||||
Timer.prototype.stop = function() {
|
||||
if (this.running) {
|
||||
clearTimeout(this.id);
|
||||
this.running = false;
|
||||
return this.id = null;
|
||||
}
|
||||
};
|
||||
return Timer;
|
||||
})();
|
||||
Timer.start = function(timeout, func) {
|
||||
return setTimeout(func, timeout);
|
||||
};
|
||||
|
||||
// options
|
||||
var Options;
|
||||
__options.Options = Options = (function() {
|
||||
function Options() {
|
||||
this.host = null;
|
||||
this.port = 35729;
|
||||
this.snipver = null;
|
||||
this.ext = null;
|
||||
this.extver = null;
|
||||
this.mindelay = 1000;
|
||||
this.maxdelay = 60000;
|
||||
this.handshake_timeout = 5000;
|
||||
}
|
||||
Options.prototype.set = function(name, value) {
|
||||
switch (typeof this[name]) {
|
||||
case 'undefined':
|
||||
break;
|
||||
case 'number':
|
||||
return this[name] = +value;
|
||||
default:
|
||||
return this[name] = value;
|
||||
}
|
||||
};
|
||||
return Options;
|
||||
})();
|
||||
Options.extract = function(document) {
|
||||
var element, keyAndValue, m, mm, options, pair, src, _i, _j, _len, _len2, _ref, _ref2;
|
||||
_ref = document.getElementsByTagName('script');
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
element = _ref[_i];
|
||||
if ((src = element.src) && (m = src.match(/^[^:]+:\/\/(.*)\/z?livereload\.js(?:\?(.*))?$/))) {
|
||||
options = new Options();
|
||||
if (mm = m[1].match(/^([^\/:]+)(?::(\d+))?$/)) {
|
||||
options.host = mm[1];
|
||||
if (mm[2]) {
|
||||
options.port = parseInt(mm[2], 10);
|
||||
}
|
||||
}
|
||||
if (m[2]) {
|
||||
_ref2 = m[2].split('&');
|
||||
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
|
||||
pair = _ref2[_j];
|
||||
if ((keyAndValue = pair.split('=')).length > 1) {
|
||||
options.set(keyAndValue[0].replace(/-/g, '_'), keyAndValue.slice(1).join('='));
|
||||
}
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// reloader
|
||||
var IMAGE_STYLES, Reloader, numberOfMatchingSegments, pathFromUrl, pathsMatch, pickBestMatch, splitUrl;
|
||||
|
||||
splitUrl = function(url) {
|
||||
var hash, index, params;
|
||||
if ((index = url.indexOf('#')) >= 0) {
|
||||
hash = url.slice(index);
|
||||
url = url.slice(0, index);
|
||||
} else {
|
||||
hash = '';
|
||||
}
|
||||
if ((index = url.indexOf('?')) >= 0) {
|
||||
params = url.slice(index);
|
||||
url = url.slice(0, index);
|
||||
} else {
|
||||
params = '';
|
||||
}
|
||||
return {
|
||||
url: url,
|
||||
params: params,
|
||||
hash: hash
|
||||
};
|
||||
};
|
||||
|
||||
pathFromUrl = function(url) {
|
||||
var path;
|
||||
url = splitUrl(url).url;
|
||||
if (url.indexOf('file://') === 0) {
|
||||
path = url.replace(/^file:\/\/(localhost)?/, '');
|
||||
} else {
|
||||
path = url.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//, '/');
|
||||
}
|
||||
return decodeURIComponent(path);
|
||||
};
|
||||
|
||||
pickBestMatch = function(path, objects, pathFunc) {
|
||||
var bestMatch, object, score, _i, _len;
|
||||
bestMatch = {
|
||||
score: 0
|
||||
};
|
||||
for (_i = 0, _len = objects.length; _i < _len; _i++) {
|
||||
object = objects[_i];
|
||||
score = numberOfMatchingSegments(path, pathFunc(object));
|
||||
if (score > bestMatch.score) {
|
||||
bestMatch = {
|
||||
object: object,
|
||||
score: score
|
||||
};
|
||||
}
|
||||
}
|
||||
if (bestMatch.score > 0) {
|
||||
return bestMatch;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
numberOfMatchingSegments = function(path1, path2) {
|
||||
var comps1, comps2, eqCount, len;
|
||||
path1 = path1.replace(/^\/+/, '').toLowerCase();
|
||||
path2 = path2.replace(/^\/+/, '').toLowerCase();
|
||||
if (path1 === path2) return 10000;
|
||||
comps1 = path1.split('/').reverse();
|
||||
comps2 = path2.split('/').reverse();
|
||||
len = Math.min(comps1.length, comps2.length);
|
||||
eqCount = 0;
|
||||
while (eqCount < len && comps1[eqCount] === comps2[eqCount]) {
|
||||
++eqCount;
|
||||
}
|
||||
return eqCount;
|
||||
};
|
||||
|
||||
pathsMatch = function(path1, path2) {
|
||||
return numberOfMatchingSegments(path1, path2) > 0;
|
||||
};
|
||||
|
||||
IMAGE_STYLES = [
|
||||
{
|
||||
selector: 'background',
|
||||
styleNames: ['backgroundImage']
|
||||
}, {
|
||||
selector: 'border',
|
||||
styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage']
|
||||
}
|
||||
];
|
||||
|
||||
__reloader.Reloader = Reloader = (function() {
|
||||
|
||||
function Reloader(window, console, Timer) {
|
||||
this.window = window;
|
||||
this.console = console;
|
||||
this.Timer = Timer;
|
||||
this.document = this.window.document;
|
||||
this.stylesheetGracePeriod = 200;
|
||||
this.importCacheWaitPeriod = 200;
|
||||
this.plugins = [];
|
||||
}
|
||||
|
||||
Reloader.prototype.addPlugin = function(plugin) {
|
||||
return this.plugins.push(plugin);
|
||||
};
|
||||
|
||||
Reloader.prototype.analyze = function(callback) {
|
||||
return results;
|
||||
};
|
||||
|
||||
Reloader.prototype.reload = function(path, options) {
|
||||
var plugin, _i, _len, _ref;
|
||||
this.options = options;
|
||||
_ref = this.plugins;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
plugin = _ref[_i];
|
||||
if (plugin.reload && plugin.reload(path, options)) return;
|
||||
}
|
||||
if (options.liveCSS) {
|
||||
if (path.match(/\.css$/i)) if (this.reloadStylesheet(path)) return;
|
||||
}
|
||||
if (options.liveImg) {
|
||||
if (path.match(/\.(jpe?g|png|gif)$/i)) {
|
||||
this.reloadImages(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return this.reloadPage();
|
||||
};
|
||||
|
||||
Reloader.prototype.reloadPage = function() {
|
||||
return this.window.document.location.reload();
|
||||
};
|
||||
|
||||
Reloader.prototype.reloadImages = function(path) {
|
||||
var expando, img, selector, styleNames, styleSheet, _i, _j, _k, _l, _len, _len2, _len3, _len4, _ref, _ref2, _ref3, _ref4, _results;
|
||||
expando = this.generateUniqueString();
|
||||
_ref = this.document.images;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
img = _ref[_i];
|
||||
if (pathsMatch(path, pathFromUrl(img.src))) {
|
||||
img.src = this.generateCacheBustUrl(img.src, expando);
|
||||
}
|
||||
}
|
||||
if (this.document.querySelectorAll) {
|
||||
for (_j = 0, _len2 = IMAGE_STYLES.length; _j < _len2; _j++) {
|
||||
_ref2 = IMAGE_STYLES[_j], selector = _ref2.selector, styleNames = _ref2.styleNames;
|
||||
_ref3 = this.document.querySelectorAll("[style*=" + selector + "]");
|
||||
for (_k = 0, _len3 = _ref3.length; _k < _len3; _k++) {
|
||||
img = _ref3[_k];
|
||||
this.reloadStyleImages(img.style, styleNames, path, expando);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.document.styleSheets) {
|
||||
_ref4 = this.document.styleSheets;
|
||||
_results = [];
|
||||
for (_l = 0, _len4 = _ref4.length; _l < _len4; _l++) {
|
||||
styleSheet = _ref4[_l];
|
||||
_results.push(this.reloadStylesheetImages(styleSheet, path, expando));
|
||||
}
|
||||
return _results;
|
||||
}
|
||||
};
|
||||
|
||||
Reloader.prototype.reloadStylesheetImages = function(styleSheet, path, expando) {
|
||||
var rule, rules, styleNames, _i, _j, _len, _len2;
|
||||
try {
|
||||
rules = styleSheet != null ? styleSheet.cssRules : void 0;
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
if (!rules) return;
|
||||
for (_i = 0, _len = rules.length; _i < _len; _i++) {
|
||||
rule = rules[_i];
|
||||
switch (rule.type) {
|
||||
case CSSRule.IMPORT_RULE:
|
||||
this.reloadStylesheetImages(rule.styleSheet, path, expando);
|
||||
break;
|
||||
case CSSRule.STYLE_RULE:
|
||||
for (_j = 0, _len2 = IMAGE_STYLES.length; _j < _len2; _j++) {
|
||||
styleNames = IMAGE_STYLES[_j].styleNames;
|
||||
this.reloadStyleImages(rule.style, styleNames, path, expando);
|
||||
}
|
||||
break;
|
||||
case CSSRule.MEDIA_RULE:
|
||||
this.reloadStylesheetImages(rule, path, expando);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reloader.prototype.reloadStyleImages = function(style, styleNames, path, expando) {
|
||||
var newValue, styleName, value, _i, _len;
|
||||
var _this = this;
|
||||
for (_i = 0, _len = styleNames.length; _i < _len; _i++) {
|
||||
styleName = styleNames[_i];
|
||||
value = style[styleName];
|
||||
if (typeof value === 'string') {
|
||||
newValue = value.replace(/\burl\s*\(([^)]*)\)/, function(match, src) {
|
||||
if (pathsMatch(path, pathFromUrl(src))) {
|
||||
return "url(" + (_this.generateCacheBustUrl(src, expando)) + ")";
|
||||
} else {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
if (newValue !== value) style[styleName] = newValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reloader.prototype.reloadStylesheet = function(path) {
|
||||
var imported, link, links, match, style, _i, _j, _k, _len, _len2, _len3, _ref;
|
||||
links = (function() {
|
||||
var _i, _len, _ref, _results;
|
||||
_ref = this.document.getElementsByTagName('link');
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
link = _ref[_i];
|
||||
if (link.rel === 'stylesheet' && !link.__LiveReload_pendingRemoval) {
|
||||
_results.push(link);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
}).call(this);
|
||||
imported = [];
|
||||
_ref = this.document.getElementsByTagName('style');
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
style = _ref[_i];
|
||||
if (style.sheet) {
|
||||
this.collectImportedStylesheets(style, style.sheet, imported);
|
||||
}
|
||||
}
|
||||
for (_j = 0, _len2 = links.length; _j < _len2; _j++) {
|
||||
link = links[_j];
|
||||
this.collectImportedStylesheets(link, link.sheet, imported);
|
||||
}
|
||||
this.console.log("LiveReload found " + links.length + " LINKed stylesheets, " + imported.length + " @imported stylesheets");
|
||||
match = pickBestMatch(path, links.concat(imported), function(l) {
|
||||
return pathFromUrl(l.href);
|
||||
});
|
||||
if (match) {
|
||||
if (match.object.rule) {
|
||||
this.console.log("LiveReload is reloading imported stylesheet: " + match.object.href);
|
||||
this.reattachImportedRule(match.object);
|
||||
} else {
|
||||
this.console.log("LiveReload is reloading stylesheet: " + match.object.href);
|
||||
this.reattachStylesheetLink(match.object);
|
||||
}
|
||||
} else {
|
||||
this.console.log("LiveReload will reload all stylesheets because path '" + path + "' did not match any specific one");
|
||||
for (_k = 0, _len3 = links.length; _k < _len3; _k++) {
|
||||
link = links[_k];
|
||||
this.reattachStylesheetLink(link);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
Reloader.prototype.collectImportedStylesheets = function(link, styleSheet, result) {
|
||||
var index, rule, rules, _len;
|
||||
try {
|
||||
rules = styleSheet != null ? styleSheet.cssRules : void 0;
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
if (rules && rules.length) {
|
||||
for (index = 0, _len = rules.length; index < _len; index++) {
|
||||
rule = rules[index];
|
||||
switch (rule.type) {
|
||||
case CSSRule.CHARSET_RULE:
|
||||
continue;
|
||||
case CSSRule.IMPORT_RULE:
|
||||
result.push({
|
||||
link: link,
|
||||
rule: rule,
|
||||
index: index,
|
||||
href: rule.href
|
||||
});
|
||||
this.collectImportedStylesheets(link, rule.styleSheet, result);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reloader.prototype.reattachStylesheetLink = function(link) {
|
||||
var clone, parent, timer;
|
||||
if (link.__LiveReload_pendingRemoval) return;
|
||||
link.__LiveReload_pendingRemoval = true;
|
||||
clone = link.cloneNode(false);
|
||||
clone.href = this.generateCacheBustUrl(link.href);
|
||||
parent = link.parentNode;
|
||||
if (parent.lastChild === link) {
|
||||
parent.appendChild(clone);
|
||||
} else {
|
||||
parent.insertBefore(clone, link.nextSibling);
|
||||
}
|
||||
timer = new this.Timer(function() {
|
||||
if (link.parentNode) return link.parentNode.removeChild(link);
|
||||
});
|
||||
return timer.start(this.stylesheetGracePeriod);
|
||||
};
|
||||
|
||||
Reloader.prototype.reattachImportedRule = function(_arg) {
|
||||
var href, index, link, media, newRule, parent, rule, tempLink;
|
||||
var _this = this;
|
||||
rule = _arg.rule, index = _arg.index, link = _arg.link;
|
||||
parent = rule.parentStyleSheet;
|
||||
href = this.generateCacheBustUrl(rule.href);
|
||||
media = rule.media.length ? [].join.call(rule.media, ', ') : '';
|
||||
newRule = "@import url(\"" + href + "\") " + media + ";";
|
||||
rule.__LiveReload_newHref = href;
|
||||
tempLink = this.document.createElement("link");
|
||||
tempLink.rel = 'stylesheet';
|
||||
tempLink.href = href;
|
||||
tempLink.__LiveReload_pendingRemoval = true;
|
||||
if (link.parentNode) link.parentNode.insertBefore(tempLink, link);
|
||||
return this.Timer.start(this.importCacheWaitPeriod, function() {
|
||||
if (tempLink.parentNode) tempLink.parentNode.removeChild(tempLink);
|
||||
if (rule.__LiveReload_newHref !== href) return;
|
||||
parent.insertRule(newRule, index);
|
||||
parent.deleteRule(index + 1);
|
||||
rule = parent.cssRules[index];
|
||||
rule.__LiveReload_newHref = href;
|
||||
return _this.Timer.start(_this.importCacheWaitPeriod, function() {
|
||||
if (rule.__LiveReload_newHref !== href) return;
|
||||
parent.insertRule(newRule, index);
|
||||
return parent.deleteRule(index + 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Reloader.prototype.generateUniqueString = function() {
|
||||
return 'livereload=' + Date.now();
|
||||
};
|
||||
|
||||
Reloader.prototype.generateCacheBustUrl = function(url, expando) {
|
||||
var hash, oldParams, params, _ref;
|
||||
if (expando == null) expando = this.generateUniqueString();
|
||||
_ref = splitUrl(url), url = _ref.url, hash = _ref.hash, oldParams = _ref.params;
|
||||
if (this.options.overrideURL) {
|
||||
if (url.indexOf(this.options.serverURL) < 0) {
|
||||
url = this.options.serverURL + this.options.overrideURL + "?url=" + encodeURIComponent(url);
|
||||
}
|
||||
}
|
||||
params = oldParams.replace(/(\?|&)livereload=(\d+)/, function(match, sep) {
|
||||
return "" + sep + expando;
|
||||
});
|
||||
if (params === oldParams) {
|
||||
if (oldParams.length === 0) {
|
||||
params = "?" + expando;
|
||||
} else {
|
||||
params = "" + oldParams + "&" + expando;
|
||||
}
|
||||
}
|
||||
return url + params + hash;
|
||||
};
|
||||
|
||||
return Reloader;
|
||||
|
||||
})();
|
||||
|
||||
// livereload
|
||||
var Connector, LiveReload, Options, Reloader, Timer;
|
||||
|
||||
Connector = __connector.Connector;
|
||||
|
||||
Timer = __timer.Timer;
|
||||
|
||||
Options = __options.Options;
|
||||
|
||||
Reloader = __reloader.Reloader;
|
||||
|
||||
__livereload.LiveReload = LiveReload = (function() {
|
||||
|
||||
function LiveReload(window) {
|
||||
var _this = this;
|
||||
this.window = window;
|
||||
this.listeners = {};
|
||||
this.plugins = [];
|
||||
this.pluginIdentifiers = {};
|
||||
this.console = this.window.location.href.match(/LR-verbose/) && this.window.console && this.window.console.log && this.window.console.error ? this.window.console : {
|
||||
log: function() {},
|
||||
error: function() {}
|
||||
};
|
||||
if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) {
|
||||
console.error("LiveReload disabled because the browser does not seem to support web sockets");
|
||||
return;
|
||||
}
|
||||
if (!(this.options = Options.extract(this.window.document))) {
|
||||
console.error("LiveReload disabled because it could not find its own <SCRIPT> tag");
|
||||
return;
|
||||
}
|
||||
this.reloader = new Reloader(this.window, this.console, Timer);
|
||||
this.connector = new Connector(this.options, this.WebSocket, Timer, {
|
||||
connecting: function() {},
|
||||
socketConnected: function() {},
|
||||
connected: function(protocol) {
|
||||
var _base;
|
||||
if (typeof (_base = _this.listeners).connect === "function") {
|
||||
_base.connect();
|
||||
}
|
||||
_this.log("LiveReload is connected to " + _this.options.host + ":" + _this.options.port + " (protocol v" + protocol + ").");
|
||||
return _this.analyze();
|
||||
},
|
||||
error: function(e) {
|
||||
if (e instanceof ProtocolError) {
|
||||
return console.log("" + e.message + ".");
|
||||
} else {
|
||||
return console.log("LiveReload internal error: " + e.message);
|
||||
}
|
||||
},
|
||||
disconnected: function(reason, nextDelay) {
|
||||
var _base;
|
||||
if (typeof (_base = _this.listeners).disconnect === "function") {
|
||||
_base.disconnect();
|
||||
}
|
||||
switch (reason) {
|
||||
case 'cannot-connect':
|
||||
return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + ", will retry in " + nextDelay + " sec.");
|
||||
case 'broken':
|
||||
return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + ", reconnecting in " + nextDelay + " sec.");
|
||||
case 'handshake-timeout':
|
||||
return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake timeout), will retry in " + nextDelay + " sec.");
|
||||
case 'handshake-failed':
|
||||
return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake failed), will retry in " + nextDelay + " sec.");
|
||||
case 'manual':
|
||||
break;
|
||||
case 'error':
|
||||
break;
|
||||
default:
|
||||
return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + " (" + reason + "), reconnecting in " + nextDelay + " sec.");
|
||||
}
|
||||
},
|
||||
message: function(message) {
|
||||
switch (message.command) {
|
||||
case 'reload':
|
||||
return _this.performReload(message);
|
||||
case 'alert':
|
||||
return _this.performAlert(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LiveReload.prototype.on = function(eventName, handler) {
|
||||
return this.listeners[eventName] = handler;
|
||||
};
|
||||
|
||||
LiveReload.prototype.log = function(message) {
|
||||
return this.console.log("" + message);
|
||||
};
|
||||
|
||||
LiveReload.prototype.performReload = function(message) {
|
||||
var _ref, _ref2;
|
||||
this.log("LiveReload received reload request for " + message.path + ".");
|
||||
return this.reloader.reload(message.path, {
|
||||
liveCSS: (_ref = message.liveCSS) != null ? _ref : true,
|
||||
liveImg: (_ref2 = message.liveImg) != null ? _ref2 : true,
|
||||
originalPath: message.originalPath || '',
|
||||
overrideURL: message.overrideURL || '',
|
||||
serverURL: "http://" + this.options.host + ":" + this.options.port
|
||||
});
|
||||
};
|
||||
|
||||
LiveReload.prototype.performAlert = function(message) {
|
||||
return alert(message.message);
|
||||
};
|
||||
|
||||
LiveReload.prototype.shutDown = function() {
|
||||
var _base;
|
||||
this.connector.disconnect();
|
||||
this.log("LiveReload disconnected.");
|
||||
return typeof (_base = this.listeners).shutdown === "function" ? _base.shutdown() : void 0;
|
||||
};
|
||||
|
||||
LiveReload.prototype.hasPlugin = function(identifier) {
|
||||
return !!this.pluginIdentifiers[identifier];
|
||||
};
|
||||
|
||||
LiveReload.prototype.addPlugin = function(pluginClass) {
|
||||
var plugin;
|
||||
var _this = this;
|
||||
if (this.hasPlugin(pluginClass.identifier)) return;
|
||||
this.pluginIdentifiers[pluginClass.identifier] = true;
|
||||
plugin = new pluginClass(this.window, {
|
||||
_livereload: this,
|
||||
_reloader: this.reloader,
|
||||
_connector: this.connector,
|
||||
console: this.console,
|
||||
Timer: Timer,
|
||||
generateCacheBustUrl: function(url) {
|
||||
return _this.reloader.generateCacheBustUrl(url);
|
||||
}
|
||||
});
|
||||
this.plugins.push(plugin);
|
||||
this.reloader.addPlugin(plugin);
|
||||
};
|
||||
|
||||
LiveReload.prototype.analyze = function() {
|
||||
var plugin, pluginData, pluginsData, _i, _len, _ref;
|
||||
if (!(this.connector.protocol >= 7)) return;
|
||||
pluginsData = {};
|
||||
_ref = this.plugins;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
plugin = _ref[_i];
|
||||
pluginsData[plugin.constructor.identifier] = pluginData = (typeof plugin.analyze === "function" ? plugin.analyze() : void 0) || {};
|
||||
pluginData.version = plugin.constructor.version;
|
||||
}
|
||||
this.connector.sendCommand({
|
||||
command: 'info',
|
||||
plugins: pluginsData,
|
||||
url: this.window.location.href
|
||||
});
|
||||
};
|
||||
|
||||
return LiveReload;
|
||||
|
||||
})();
|
||||
|
||||
// less
|
||||
var LessPlugin;
|
||||
__less = LessPlugin = (function() {
|
||||
LessPlugin.identifier = 'less';
|
||||
LessPlugin.version = '1.0';
|
||||
function LessPlugin(window, host) {
|
||||
this.window = window;
|
||||
this.host = host;
|
||||
}
|
||||
LessPlugin.prototype.reload = function(path, options) {
|
||||
if (this.window.less && this.window.less.refresh) {
|
||||
if (path.match(/\.less$/i)) {
|
||||
return this.reloadLess(path);
|
||||
}
|
||||
if (options.originalPath.match(/\.less$/i)) {
|
||||
return this.reloadLess(options.originalPath);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
LessPlugin.prototype.reloadLess = function(path) {
|
||||
var link, links, _i, _len;
|
||||
links = (function() {
|
||||
var _i, _len, _ref, _results;
|
||||
_ref = document.getElementsByTagName('link');
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
link = _ref[_i];
|
||||
if (link.href && link.rel === 'stylesheet/less' || (link.rel.match(/stylesheet/) && link.type.match(/^text\/(x-)?less$/))) {
|
||||
_results.push(link);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
})();
|
||||
if (links.length === 0) {
|
||||
return false;
|
||||
}
|
||||
for (_i = 0, _len = links.length; _i < _len; _i++) {
|
||||
link = links[_i];
|
||||
link.href = this.host.generateCacheBustUrl(link.href);
|
||||
}
|
||||
this.host.console.log("LiveReload is asking LESS to recompile all stylesheets");
|
||||
this.window.less.refresh(true);
|
||||
return true;
|
||||
};
|
||||
LessPlugin.prototype.analyze = function() {
|
||||
return {
|
||||
disable: !!(this.window.less && this.window.less.refresh)
|
||||
};
|
||||
};
|
||||
return LessPlugin;
|
||||
})();
|
||||
|
||||
// startup
|
||||
var CustomEvents, LiveReload, k;
|
||||
CustomEvents = __customevents;
|
||||
LiveReload = window.LiveReload = new (__livereload.LiveReload)(window);
|
||||
for (k in window) {
|
||||
if (k.match(/^LiveReloadPlugin/)) {
|
||||
LiveReload.addPlugin(window[k]);
|
||||
}
|
||||
}
|
||||
LiveReload.addPlugin(__less);
|
||||
LiveReload.on('shutdown', function() {
|
||||
return delete window.LiveReload;
|
||||
});
|
||||
LiveReload.on('connect', function() {
|
||||
return CustomEvents.fire(document, 'LiveReloadConnect');
|
||||
});
|
||||
LiveReload.on('disconnect', function() {
|
||||
return CustomEvents.fire(document, 'LiveReloadDisconnect');
|
||||
});
|
||||
CustomEvents.bind(document, 'LiveReloadShutDown', function() {
|
||||
return LiveReload.shutDown();
|
||||
});
|
||||
})();
|
@@ -0,0 +1,436 @@
|
||||
import sublime,sublime_plugin,threading,json,os,threading,subprocess,socket,shutil
|
||||
from base64 import b64encode, b64decode
|
||||
# python 2.6 differences
|
||||
try:
|
||||
from hashlib import md5, sha1
|
||||
except: from md5 import md5; from sha import sha as sha1
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
from struct import pack, unpack_from
|
||||
import array, struct, os
|
||||
import urllib2
|
||||
s2a = lambda s: [ord(c) for c in s]
|
||||
settings = sublime.load_settings('LiveReload.sublime-settings')
|
||||
port = settings.get('port')
|
||||
version = settings.get('version')
|
||||
##LOAD latest livereload.js from github (for v2 of protocol) or if this fails local version
|
||||
try:
|
||||
req = urllib2.urlopen(urllib2.Request("http://raw.github.com/livereload/livereload-js/master/dist/livereload.js"))
|
||||
source_livereload_js = req.read()
|
||||
if not "http://livereload.com/protocols/official-6" in source_livereload_js:
|
||||
raise Exception("Something wrong with download!")
|
||||
except Exception, u:
|
||||
print u
|
||||
try:
|
||||
path = os.path.join(sublime.packages_path(), "LiveReload")
|
||||
local = open(os.path.join(path, "livereload.js"), "rU")
|
||||
source_livereload_js = local.read()
|
||||
except IOError, e:
|
||||
print e
|
||||
sublime.error_message("livereload.js is missing from LiveReload package install")
|
||||
|
||||
|
||||
class LiveReload(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
global LivereloadFactory
|
||||
threading.Thread.__init__(self)
|
||||
LivereloadFactory = WebSocketServer(port,version)
|
||||
LivereloadFactory.start()
|
||||
|
||||
class LiveReloadChange(sublime_plugin.EventListener):
|
||||
def __init__ (self):
|
||||
LiveReload().start()
|
||||
|
||||
def __del__(self):
|
||||
global LivereloadFactory
|
||||
LivereloadFactory.stop()
|
||||
|
||||
def on_post_save(self, view):
|
||||
global LivereloadFactory
|
||||
settings = sublime.load_settings('LiveReload.sublime-settings')
|
||||
filename = view.file_name()
|
||||
if view.file_name().find('.scss') > 0 or view.file_name().find('.sass') > 0:
|
||||
compiler = CompassThread(filename,LivereloadFactory)
|
||||
compiler.start()
|
||||
else:
|
||||
filename = os.path.normcase(filename)
|
||||
filename = os.path.split(filename)[1]
|
||||
filename = filename.replace('.scss','.css').replace('.styl','.css').replace('.less','.css')
|
||||
filename = filename.replace('.coffee','.js')
|
||||
|
||||
data = json.dumps(["refresh", {
|
||||
"path": filename,
|
||||
"apply_js_live": settings.get('apply_js_live'),
|
||||
"apply_css_live": settings.get('apply_css_live'),
|
||||
"apply_images_live": settings.get('apply_images_live')
|
||||
}])
|
||||
sublime.set_timeout(lambda: LivereloadFactory.send_all(data), int(settings.get('delay_ms')))
|
||||
sublime.set_timeout(lambda: sublime.status_message("Sent LiveReload command for file: "+filename), int(settings.get('delay_ms')))
|
||||
|
||||
|
||||
class CompassThread(threading.Thread):
|
||||
|
||||
def __init__(self, filename,LivereloadFactory):
|
||||
self.dirname = os.path.dirname(filename)
|
||||
self.filename = filename.replace('.scss','.css').replace('.sass','.css')
|
||||
self.LivereloadFactory = LivereloadFactory
|
||||
self.stdout = None
|
||||
self.stderr = None
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
global LivereloadFactory
|
||||
print 'compass compile ' + self.dirname
|
||||
|
||||
# autocreate config.rb for compass
|
||||
if not os.path.exists(os.path.join(self.dirname, "config.rb")):
|
||||
print "Generating config.rb"
|
||||
shutil.copy(os.path.join(sublime.packages_path(), "LiveReload","assets","config.rb"), self.dirname)
|
||||
|
||||
# compass compile
|
||||
p = subprocess.Popen(['compass compile ' + self.dirname.replace('\\','/')],shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
|
||||
if p.stdout.read() :
|
||||
self.LivereloadFactory.send_all(json.dumps(["refresh", {
|
||||
"path": self.filename.replace('\\','/'),
|
||||
"apply_js_live": True,
|
||||
"apply_css_live": True,
|
||||
"apply_images_live": True
|
||||
}]))
|
||||
|
||||
|
||||
class WebSocketServer:
|
||||
"""
|
||||
Handle the Server, bind and accept new connections, open and close
|
||||
clients connections.
|
||||
"""
|
||||
|
||||
def __init__(self, port, version):
|
||||
self.clients = []
|
||||
self.port = port
|
||||
self.version = version
|
||||
self.s = None
|
||||
|
||||
def stop(self):
|
||||
[client.close() for client in self.clients]
|
||||
l = threading.Lock()
|
||||
l.acquire()
|
||||
l.release()
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start the server.
|
||||
"""
|
||||
try:
|
||||
self.s = socket.socket()
|
||||
self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.s.bind(('', self.port))
|
||||
self.s.listen(1)
|
||||
except Exception, e:
|
||||
self.stop()
|
||||
|
||||
try:
|
||||
while 1:
|
||||
conn, addr = self.s.accept()
|
||||
newClient = WebSocketClient(conn, addr, self)
|
||||
self.clients.append(newClient)
|
||||
newClient.start()
|
||||
except Exception, e:
|
||||
self.stop()
|
||||
|
||||
def send_all(self, data):
|
||||
"""
|
||||
Send a message to all the currenly connected clients.
|
||||
"""
|
||||
|
||||
[client.send(data) for client in self.clients]
|
||||
|
||||
def remove(self, client):
|
||||
"""
|
||||
Remove a client from the connected list.
|
||||
"""
|
||||
try:
|
||||
l = threading.Lock()
|
||||
l.acquire()
|
||||
self.clients.remove(client)
|
||||
l.release()
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
|
||||
class WebSocketClient(threading.Thread):
|
||||
"""
|
||||
A single connection (client) of the program
|
||||
"""
|
||||
|
||||
# Handshaking, create the WebSocket connection
|
||||
server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r
|
||||
Upgrade: websocket\r
|
||||
Connection: Upgrade\r
|
||||
Sec-WebSocket-Accept: %s\r
|
||||
"""
|
||||
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
def __init__(self, sock, addr, server):
|
||||
threading.Thread.__init__(self)
|
||||
self.s = sock
|
||||
self.addr = addr
|
||||
self.server = server
|
||||
|
||||
def run(self):
|
||||
|
||||
wsh = WSRequestHandler(self.s, self.addr, False)
|
||||
h = self.headers = wsh.headers
|
||||
ver = h.get('Sec-WebSocket-Version')
|
||||
if ver:
|
||||
# HyBi/IETF version of the protocol
|
||||
|
||||
# HyBi-07 report version 7
|
||||
# HyBi-08 - HyBi-12 report version 8
|
||||
# HyBi-13 reports version 13
|
||||
if ver in ['7', '8', '13']:
|
||||
self.version = "hybi-%02d" % int(ver)
|
||||
else:
|
||||
raise Exception('Unsupported protocol version %s' % ver)
|
||||
|
||||
key = h['Sec-WebSocket-Key']
|
||||
print key
|
||||
# Generate the hash value for the accept header
|
||||
accept = b64encode(sha1(key + self.GUID).digest())
|
||||
|
||||
response = self.server_handshake_hybi % accept
|
||||
response += "\r\n"
|
||||
print response
|
||||
self.s.send(response.encode())
|
||||
self.new_client()
|
||||
|
||||
# Receive and handle data
|
||||
while 1:
|
||||
try:
|
||||
data = self.s.recv(1024)
|
||||
except Exception, e:
|
||||
break
|
||||
if not data: break
|
||||
dec = WebSocketClient.decode_hybi(data)
|
||||
if dec["opcode"] == 8:
|
||||
self.close()
|
||||
else:
|
||||
self.onreceive(dec)
|
||||
|
||||
# Close the client connection
|
||||
self.close()
|
||||
|
||||
@staticmethod
|
||||
def unmask(buf, f):
|
||||
pstart = f['hlen'] + 4
|
||||
pend = pstart + f['length']
|
||||
# Slower fallback
|
||||
data = array.array('B')
|
||||
mask = s2a(f['mask'])
|
||||
data.fromstring(buf[pstart:pend])
|
||||
for i in range(len(data)):
|
||||
data[i] ^= mask[i % 4]
|
||||
return data.tostring()
|
||||
|
||||
@staticmethod
|
||||
def encode_hybi(buf, opcode, base64=False):
|
||||
""" Encode a HyBi style WebSocket frame.
|
||||
Optional opcode:
|
||||
0x0 - continuation
|
||||
0x1 - text frame (base64 encode buf)
|
||||
0x2 - binary frame (use raw buf)
|
||||
0x8 - connection close
|
||||
0x9 - ping
|
||||
0xA - pong
|
||||
"""
|
||||
if base64:
|
||||
buf = b64encode(buf)
|
||||
|
||||
b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
|
||||
payload_len = len(buf)
|
||||
if payload_len <= 125:
|
||||
header = pack('>BB', b1, payload_len)
|
||||
elif payload_len > 125 and payload_len < 65536:
|
||||
header = pack('>BBH', b1, 126, payload_len)
|
||||
elif payload_len >= 65536:
|
||||
header = pack('>BBQ', b1, 127, payload_len)
|
||||
|
||||
print("Encoded: %s" % repr(header + buf))
|
||||
|
||||
return header + buf, len(header), 0
|
||||
|
||||
@staticmethod
|
||||
def decode_hybi(buf, base64=False):
|
||||
""" Decode HyBi style WebSocket packets.
|
||||
Returns:
|
||||
{'fin' : 0_or_1,
|
||||
'opcode' : number,
|
||||
'mask' : 32_bit_number,
|
||||
'hlen' : header_bytes_number,
|
||||
'length' : payload_bytes_number,
|
||||
'payload' : decoded_buffer,
|
||||
'left' : bytes_left_number,
|
||||
'close_code' : number,
|
||||
'close_reason' : string}
|
||||
"""
|
||||
|
||||
f = {'fin' : 0,
|
||||
'opcode' : 0,
|
||||
'mask' : 0,
|
||||
'hlen' : 2,
|
||||
'length' : 0,
|
||||
'payload' : None,
|
||||
'left' : 0,
|
||||
'close_code' : None,
|
||||
'close_reason' : None}
|
||||
|
||||
blen = len(buf)
|
||||
f['left'] = blen
|
||||
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
|
||||
b1, b2 = unpack_from(">BB", buf)
|
||||
f['opcode'] = b1 & 0x0f
|
||||
f['fin'] = (b1 & 0x80) >> 7
|
||||
has_mask = (b2 & 0x80) >> 7
|
||||
|
||||
f['length'] = b2 & 0x7f
|
||||
|
||||
if f['length'] == 126:
|
||||
f['hlen'] = 4
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
(f['length'],) = unpack_from('>xxH', buf)
|
||||
elif f['length'] == 127:
|
||||
f['hlen'] = 10
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
(f['length'],) = unpack_from('>xxQ', buf)
|
||||
|
||||
full_len = f['hlen'] + has_mask * 4 + f['length']
|
||||
|
||||
if blen < full_len: # Incomplete frame
|
||||
return f # Incomplete frame header
|
||||
|
||||
# Number of bytes that are part of the next frame(s)
|
||||
f['left'] = blen - full_len
|
||||
|
||||
# Process 1 frame
|
||||
if has_mask:
|
||||
# unmask payload
|
||||
f['mask'] = buf[f['hlen']:f['hlen']+4]
|
||||
f['payload'] = WebSocketClient.unmask(buf, f)
|
||||
else:
|
||||
print("Unmasked frame: %s" % repr(buf))
|
||||
f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len]
|
||||
|
||||
if base64 and f['opcode'] in [1, 2]:
|
||||
try:
|
||||
f['payload'] = b64decode(f['payload'])
|
||||
except:
|
||||
print("Exception while b64decoding buffer: %s" %
|
||||
repr(buf))
|
||||
raise
|
||||
|
||||
if f['opcode'] == 0x08:
|
||||
if f['length'] >= 2:
|
||||
f['close_code'] = unpack_from(">H", f['payload'])
|
||||
if f['length'] > 3:
|
||||
f['close_reason'] = f['payload'][2:]
|
||||
|
||||
return f
|
||||
def close(self):
|
||||
"""
|
||||
Close this connection
|
||||
"""
|
||||
self.server.remove(self)
|
||||
self.s.close()
|
||||
|
||||
def send(self, msg):
|
||||
"""
|
||||
Send a message to this client
|
||||
"""
|
||||
msg = WebSocketClient.encode_hybi(msg, 0x1, False)
|
||||
self.s.send(msg[0])
|
||||
|
||||
def onreceive(self, data):
|
||||
"""
|
||||
Event called when a message is received from this client
|
||||
"""
|
||||
try:
|
||||
print data
|
||||
if "payload" in data:
|
||||
print "payload true"
|
||||
if "hello" in data.get("payload"):
|
||||
sublime.set_timeout(lambda: sublime.status_message("New LiveReload v2 client connected"), 100)
|
||||
self.send('{"command":"hello","protocols":["http://livereload.com/protocols/connection-check-1","http://livereload.com/protocols/official-6"]}')
|
||||
else:
|
||||
sublime.set_timeout(lambda: sublime.status_message("New LiveReload v1 client connected"), 100)
|
||||
self.send("!!ver:" + str(self.server.version))
|
||||
except Exception, e:
|
||||
print e
|
||||
|
||||
|
||||
def new_client(self):
|
||||
"""
|
||||
Event called when handshake is compleated
|
||||
"""
|
||||
#self.send("!!ver:"+str(self.server.version))
|
||||
|
||||
def _clean(self, msg):
|
||||
"""
|
||||
Remove special chars used for the transmission
|
||||
"""
|
||||
msg = msg.replace(b'\x00', b'', 1)
|
||||
msg = msg.replace(b'\xff', b'', 1)
|
||||
return msg
|
||||
|
||||
|
||||
# HTTP handler with WebSocket upgrade support
|
||||
class WSRequestHandler(SimpleHTTPRequestHandler):
|
||||
def __init__(self, req, addr, only_upgrade=True):
|
||||
self.only_upgrade = only_upgrade # only allow upgrades
|
||||
SimpleHTTPRequestHandler.__init__(self, req, addr, object())
|
||||
|
||||
def do_GET(self):
|
||||
if (self.headers.get('upgrade') and
|
||||
self.headers.get('upgrade').lower() == 'websocket'):
|
||||
|
||||
if (self.headers.get('sec-websocket-key1') or
|
||||
self.headers.get('websocket-key1')):
|
||||
# For Hixie-76 read out the key hash
|
||||
self.headers.__setitem__('key3', self.rfile.read(8))
|
||||
|
||||
# Just indicate that an WebSocket upgrade is needed
|
||||
self.last_code = 101
|
||||
self.last_message = "101 Switching Protocols"
|
||||
elif self.only_upgrade:
|
||||
# Normal web request responses are disabled
|
||||
self.last_code = 405
|
||||
self.last_message = "405 Method Not Allowed"
|
||||
else:
|
||||
#Self injecting plugin
|
||||
if "livereload.js" in self.path:
|
||||
self.send_response(200, 'OK')
|
||||
self.send_header('Content-type', 'text/javascript')
|
||||
self.send_header("Content-Length", len(source_livereload_js))
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes(source_livereload_js))
|
||||
return
|
||||
else:
|
||||
#Disable other requests
|
||||
self.send_response(405, 'Method Not Allowed')
|
||||
self.send_header('Content-type', 'text/plain')
|
||||
self.send_header("Content-Length", len("Method Not Allowed"))
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes("Method Not Allowed"))
|
||||
return
|
||||
|
||||
def send_response(self, code, message=None):
|
||||
# Save the status code
|
||||
self.last_code = code
|
||||
SimpleHTTPRequestHandler.send_response(self, code, message)
|
||||
|
||||
def log_message(self, f, *args):
|
||||
# Save instead of printing
|
||||
self.last_message = f % args
|
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"install": "messages/install_upgrade.txt",
|
||||
"upgrade": "messages/install_upgrade.txt"
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
Changelog:
|
||||
|
||||
Please be sure to restart Sublime Text 2 to start using this new version. For more information or how to use this plugin visit https://github.com/dz0ny/LiveReload-sublimetext2/blob/master/README.md
|
||||
|
||||
New Features
|
||||
- Support v2 of protocol
|
||||
|
||||
Bugs:
|
||||
- Potential security vulnerability with plugin https://github.com/dz0ny/LiveReload-sublimetext2/issues/7
|
@@ -0,0 +1 @@
|
||||
{"url": "https://github.com/dz0ny/LiveReload-sublimetext2", "version": "2013.03.07.03.08.21", "description": "LiveReload plugin for SublimeText"}
|
Reference in New Issue
Block a user