<commit_before>'use strict';

/**
 * @module main
 */

/* core modules */
var util = require('util');
var http = require('http');
var ReadStream = require('fs').ReadStream;

/* local modules */
var tools = require('./tools.js');
var Request = require('./request.js');

/* 3rd party module */
exports.FormData = require('form-data');

// private section

/**
 * Wrapper for all the responses having a body
 *
 * @private
 */
var requestWrapper = function(options, file, callback) {
	if (!callback) {
		callback = file;
		file = false;
	}

	var request = new Request(options);
	tools.checkCallback(callback, request.options.url);

	request.send(function(err, res) {
		if (err) {
			callback(err);
			return;
		}

		if (options.stream === true) {
			callback(null, request.stdResult());
			return;
		}

		switch (file) {
			case false:
				// buffer
				request.saveBuffer(function(err, stdResult) {
					if (err) {
						callback(err);
						return;
					}

					callback(null, stdResult);
				});
				break;

			case null:
				// simulate writing to /dev/null, carefully under node.js v0.10
				callback(null, request.stdResult());
				res.response.resume();
				break;

			default:
				// save to file
				if (typeof file !== 'string') {
					throw tools.formatError('Expecting a file path for saving the object from URL: %s.', options.url);
				}

				request.saveFile(file, function(err, stdResult) {
					if (err) {
						callback(err);
						return;
					}

					callback(null, stdResult);
				});
				break;
		}
	});
};

// public section

/**
 * Constructs a Request object for wrapping your own HTTP method / functionality
 *
 * @param {module:request~options} options The options Object taken by the {@link module:request~Request} constructor
 * @returns {module:request~Request} A new instance of the {@link module:request~Request} class
 */
exports.createHttpClient = function(options) {
	return new Request(options);
};

/**
 * HTTP GET method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'GET' and it can not be overridden
 * @param {String|null} file Optional; specify a filesystem path to save the response body as file instead of Buffer. Passing null turns of the data listeners
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// shorthand syntax, buffered response
http.get('http://localhost/get', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});

// save the response to file with a progress callback
http.get({
	url: 'http://localhost/get',
	progress: function (current, total) {
		console.log('downloaded %d bytes from %d', current, total);
	}
}, 'get.bin', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.file);
	}
});

// with explicit options, use a HTTP proxy, and limit the number of redirects
http.get({
	url: 'http://localhost/get',
	proxy: {
		host: 'localhost',
		port: 3128
	},
	maxRedirects: 2
}, function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});
 */
exports.get = function(options, file, callback) {
	options = tools.shortHand(options, 'get');
	requestWrapper(options, file, callback);
};

/**
 * HTTP HEAD method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'HEAD' and it can not be overridden
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// shorthand syntax, default options
http.head('http://localhost/head', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers);
	}
});
 */
exports.head = function(options, callback) {
	options = tools.shortHand(options, 'head');
	var request = new Request(options);
	tools.checkCallback(callback, request.options.url);

	request.send(function(err, res) {
		if (err) {
			callback(err);
			return;
		}

		callback(null, request.stdResult());
		res.response.resume();
	});
};

/**
 * HTTP DELETE method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'DELETE' and it can not be overridden
 * @param {String|null} file Optional; specify a filesystem path to save the response body as file instead of Buffer. Passing null turns of the data listeners
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// shorthand syntax, buffered response
http.delete('http://localhost/delete', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});

// with explicit options object, save to file the response body
http.delete({
	url: 'http://localhost/delete'
}, function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.file);
	}
});
 */
exports.delete = function(options, file, callback) {
	options = tools.shortHand(options, 'delete');
	requestWrapper(options, file, callback);
};

/**
 * HTTP POST method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'POST' and it can not be overridden
 * @param {String|null} file Optional; specify a filesystem path to save the response body as file instead of Buffer. Passing null turns of the data listeners
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// shorthand syntax, buffered response, content-type = application/x-www-form-urlencoded
// param1=value1&param2=value2 are stripped from the URL and sent via the request body

// do not pass the query values by using URL encoding
// the URL encoding is done automatically
http.post('http://localhost/post?param1=value1&param2=value2', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});

// explicit POST request, saves the response body to file

// for Buffer reqBody, the content-lenght header is reqBody.lenght
var reqBody = {
	param1: 'value1',
	param2: 'value2'
};

// this serialization also does URL encoding so you won't have to
reqBody = querystring.stringify(reqBody);

http.post({
	url: 'http://localhost/post',
	reqBody: new Buffer(reqBody),
	headers: {
		// specify how to handle the request, http-request makes no assumptions
		'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
	}
}, 'post.bin', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.file);
	}
});

// multipart/form-data request built with form-data (3rd party module)
// you have to use the http export of FormData
// the instanceof operator fails otherwise
// implementing a manual check is not very elegant
var form = new http.FormData();

form.append('param1', 'value1');
form.append('param2', 'value2');

http.post({
	url: 'http://localhost/post',
	reqBody: form
}, function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});
 */
exports.post = function(options, file, callback) {
	var reqBody;
	options = tools.shortHand(options, 'post');

	// shorthand POST request
	if (!options.reqBody && options.url.indexOf('?') !== -1) {
		reqBody = options.url.split('?');
		options.url = reqBody[0];

		reqBody = tools.urlEncode(reqBody[1]);
		options.reqBody = new Buffer(reqBody);
		options.headers['content-type'] = 'application/x-www-form-urlencoded;charset=utf-8';
	}

	if (options.reqBody instanceof exports.FormData) {
		options.reqBody.getLength(function(err, length) {
			if (err) {
				callback(err);
				return;
			}

			options.headers['content-length'] = length;
			options.headers = options.reqBody.getHeaders(options.headers);

			requestWrapper(options, file, callback);
		});
		return;
	}

	requestWrapper(options, file, callback);
};

/**
 * HTTP PUT method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'PUT' and it can not be overridden
 * @param {String|null} file Optional; specify a filesystem path to save the response body as file instead of Buffer. Passing null turns of the data listeners
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// put a buffer
http.put({
	url: 'http://localhost/put',
	reqBody: new Buffer('data to put'),
	headers: {
		// you need to define a type if required by the target application
		'content-type': 'text/plain'
	}
}, function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});

// put a readable stream with known size and type
http.put({
	url: 'http://localhost/put',
	reqBody: readableStream,
	headers: {
		'content-lenght': 1337,
		'content-type': 'text/plain'
	}
}, function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});
 */
exports.put = function (options, file, callback) {
	options = tools.shortHand(options, 'put');

	if (options.reqBody instanceof ReadStream) {
		options.reqBody.pause();
		fs.stat(options.reqBody.path, function (err, stats) {
			if (err) {
				callback(err);
				return;
			}

			options.headers['content-length'] = stats.size;

			/*jslint todo:true*/
			// TODO: get the content-type by using a proper mime lib
			// TODO: try mmmagic
			/*jslint todo:false*/

			requestWrapper(options, file, callback);
		});
		return;
	}

	requestWrapper(options, file, callback);
};

/**
 * Sets the http.Agent.defaultMaxSockets value
 *
 * @param {Number} value
 *
 * @example
// use up to 128 concurrent sockets
http.setMaxSockets(128);
 */
exports.setMaxSockets = function(value) {
	value = tools.absInt(value);
	if (!value) { // fallback to the default
		value = 5;
	}
	http.Agent.defaultMaxSockets = value;
};

// documentation section

/**
 * Describes the callback passed to each HTTP method wrapper
 *
 * @callback module:main~callback
 * @param {module:request~stdError} error The standard error or *null* on success
 * @param {module:request~stdResult} result The standard result object if error is null
 */
<commit_msg>Fixes the fs junk.<commit_after>'use strict';

/**
 * @module main
 */

/* core modules */
var util = require('util');
var http = require('http');
var fs = require('fs');
var ReadStream = fs.ReadStream;

/* local modules */
var tools = require('./tools.js');
var Request = require('./request.js');

/* 3rd party module */
exports.FormData = require('form-data');

// private section

/**
 * Wrapper for all the responses having a body
 *
 * @private
 */
var requestWrapper = function(options, file, callback) {
	if (!callback) {
		callback = file;
		file = false;
	}

	var request = new Request(options);
	tools.checkCallback(callback, request.options.url);

	request.send(function(err, res) {
		if (err) {
			callback(err);
			return;
		}

		if (options.stream === true) {
			callback(null, request.stdResult());
			return;
		}

		switch (file) {
			case false:
				// buffer
				request.saveBuffer(function(err, stdResult) {
					if (err) {
						callback(err);
						return;
					}

					callback(null, stdResult);
				});
				break;

			case null:
				// simulate writing to /dev/null, carefully under node.js v0.10
				callback(null, request.stdResult());
				res.response.resume();
				break;

			default:
				// save to file
				if (typeof file !== 'string') {
					throw tools.formatError('Expecting a file path for saving the object from URL: %s.', options.url);
				}

				request.saveFile(file, function(err, stdResult) {
					if (err) {
						callback(err);
						return;
					}

					callback(null, stdResult);
				});
				break;
		}
	});
};

// public section

/**
 * Constructs a Request object for wrapping your own HTTP method / functionality
 *
 * @param {module:request~options} options The options Object taken by the {@link module:request~Request} constructor
 * @returns {module:request~Request} A new instance of the {@link module:request~Request} class
 */
exports.createHttpClient = function(options) {
	return new Request(options);
};

/**
 * HTTP GET method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'GET' and it can not be overridden
 * @param {String|null} file Optional; specify a filesystem path to save the response body as file instead of Buffer. Passing null turns of the data listeners
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// shorthand syntax, buffered response
http.get('http://localhost/get', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});

// save the response to file with a progress callback
http.get({
	url: 'http://localhost/get',
	progress: function (current, total) {
		console.log('downloaded %d bytes from %d', current, total);
	}
}, 'get.bin', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.file);
	}
});

// with explicit options, use a HTTP proxy, and limit the number of redirects
http.get({
	url: 'http://localhost/get',
	proxy: {
		host: 'localhost',
		port: 3128
	},
	maxRedirects: 2
}, function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});
 */
exports.get = function(options, file, callback) {
	options = tools.shortHand(options, 'get');
	requestWrapper(options, file, callback);
};

/**
 * HTTP HEAD method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'HEAD' and it can not be overridden
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// shorthand syntax, default options
http.head('http://localhost/head', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers);
	}
});
 */
exports.head = function(options, callback) {
	options = tools.shortHand(options, 'head');
	var request = new Request(options);
	tools.checkCallback(callback, request.options.url);

	request.send(function(err, res) {
		if (err) {
			callback(err);
			return;
		}

		callback(null, request.stdResult());
		res.response.resume();
	});
};

/**
 * HTTP DELETE method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'DELETE' and it can not be overridden
 * @param {String|null} file Optional; specify a filesystem path to save the response body as file instead of Buffer. Passing null turns of the data listeners
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// shorthand syntax, buffered response
http.delete('http://localhost/delete', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});

// with explicit options object, save to file the response body
http.delete({
	url: 'http://localhost/delete'
}, function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.file);
	}
});
 */
exports.delete = function(options, file, callback) {
	options = tools.shortHand(options, 'delete');
	requestWrapper(options, file, callback);
};

/**
 * HTTP POST method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'POST' and it can not be overridden
 * @param {String|null} file Optional; specify a filesystem path to save the response body as file instead of Buffer. Passing null turns of the data listeners
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// shorthand syntax, buffered response, content-type = application/x-www-form-urlencoded
// param1=value1&param2=value2 are stripped from the URL and sent via the request body

// do not pass the query values by using URL encoding
// the URL encoding is done automatically
http.post('http://localhost/post?param1=value1&param2=value2', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});

// explicit POST request, saves the response body to file

// for Buffer reqBody, the content-lenght header is reqBody.lenght
var reqBody = {
	param1: 'value1',
	param2: 'value2'
};

// this serialization also does URL encoding so you won't have to
reqBody = querystring.stringify(reqBody);

http.post({
	url: 'http://localhost/post',
	reqBody: new Buffer(reqBody),
	headers: {
		// specify how to handle the request, http-request makes no assumptions
		'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
	}
}, 'post.bin', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.file);
	}
});

// multipart/form-data request built with form-data (3rd party module)
// you have to use the http export of FormData
// the instanceof operator fails otherwise
// implementing a manual check is not very elegant
var form = new http.FormData();

form.append('param1', 'value1');
form.append('param2', 'value2');

http.post({
	url: 'http://localhost/post',
	reqBody: form
}, function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});
 */
exports.post = function(options, file, callback) {
	var reqBody;
	options = tools.shortHand(options, 'post');

	// shorthand POST request
	if (!options.reqBody && options.url.indexOf('?') !== -1) {
		reqBody = options.url.split('?');
		options.url = reqBody[0];

		reqBody = tools.urlEncode(reqBody[1]);
		options.reqBody = new Buffer(reqBody);
		options.headers['content-type'] = 'application/x-www-form-urlencoded;charset=utf-8';
	}

	if (options.reqBody instanceof exports.FormData) {
		options.reqBody.getLength(function(err, length) {
			if (err) {
				callback(err);
				return;
			}

			options.headers['content-length'] = length;
			options.headers = options.reqBody.getHeaders(options.headers);

			requestWrapper(options, file, callback);
		});
		return;
	}

	requestWrapper(options, file, callback);
};

/**
 * HTTP PUT method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'PUT' and it can not be overridden
 * @param {String|null} file Optional; specify a filesystem path to save the response body as file instead of Buffer. Passing null turns of the data listeners
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// put a buffer
http.put({
	url: 'http://localhost/put',
	reqBody: new Buffer('data to put'),
	headers: {
		// you need to define a type if required by the target application
		'content-type': 'text/plain'
	}
}, function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});

// put a readable stream with known size and type
http.put({
	url: 'http://localhost/put',
	reqBody: readableStream,
	headers: {
		'content-lenght': 1337,
		'content-type': 'text/plain'
	}
}, function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});
 */
exports.put = function(options, file, callback) {
	options = tools.shortHand(options, 'put');

	if (options.reqBody instanceof ReadStream) {
		options.reqBody.pause();
		fs.stat(options.reqBody.path, function(err, stats) {
			if (err) {
				callback(err);
				return;
			}

			options.headers['content-length'] = stats.size;

			/*jslint todo:true*/
			// TODO: get the content-type by using a proper mime lib
			// TODO: try mmmagic
			/*jslint todo:false*/

			requestWrapper(options, file, callback);
		});
		return;
	}

	requestWrapper(options, file, callback);
};

/**
 * Sets the http.Agent.defaultMaxSockets value
 *
 * @param {Number} value
 *
 * @example
// use up to 128 concurrent sockets
http.setMaxSockets(128);
 */
exports.setMaxSockets = function(value) {
	value = tools.absInt(value);
	if (!value) { // fallback to the default
		value = 5;
	}
	http.Agent.defaultMaxSockets = value;
};

// documentation section

/**
 * Describes the callback passed to each HTTP method wrapper
 *
 * @callback module:main~callback
 * @param {module:request~stdError} error The standard error or *null* on success
 * @param {module:request~stdResult} result The standard result object if error is null
 */
<|endoftext|>


<commit_before>/*
 * This Source Code is subject to the terms of the Mozilla Public License
 * version 2.0 (the "License"). You can obtain a copy of the License at
 * http://mozilla.org/MPL/2.0/.
 */

let {WindowObserver} = require("windowObserver");
new WindowObserver({
  applyToWindow: function(window)
  {
    if (!("gBrowser" in window))
      return;

    window.gBrowser.addEventListener("mousedown", saveLinkTarget, true);
    window.gBrowser.addEventListener("mousedown", restoreLinkTarget, false);
    window.gBrowser.addEventListener("click", interceptEvent, true);
    window.gBrowser.addEventListener("keydown", interceptEvent, true);
  },

  removeFromWindow: function(window)
  {
    if (!("gBrowser" in window))
      return;

    window.gBrowser.removeEventListener("mousedown", saveLinkTarget, true);
    window.gBrowser.removeEventListener("mousedown", restoreLinkTarget, false);
    window.gBrowser.removeEventListener("click", interceptEvent, true);
    window.gBrowser.removeEventListener("keydown", interceptEvent, true);
  }
});

let currentLink = null;
let currentLinkHref = null;

let hostSet = new Set([
  "groups.google.com",
  "search.yahoo.com",
  "yandex.com",
  "yandex.com.tr",
  "yandex.by",
  "yandex.kz",
  "yandex.ru",
  "yandex.ua"
]);

function isSearchPage(window)
{
  try
  {
    let host = window.location.host;
    if (hostSet.has(host) || hostSet.has(host.replace(/^.*?\./, "")))
      return true;
  }
  catch (e) {}

  let sandbox = new Cu.Sandbox(window);
  sandbox.window = window;
  try
  {
    return Cu.evalInSandbox("(function(){var g = window.wrappedJSObject.google; return g && (g.sn || g.search) ? true : false;})()", sandbox);
  }
  catch (e)
  {
    return false;
  }
}

function saveLinkTarget(event)
{
  if (!isSearchPage(event.target.ownerDocument.defaultView))
    return;

  for (currentLink = event.target; currentLink; currentLink = currentLink.parentNode)
    if (currentLink.localName == "a")
      break;

  currentLinkHref = (currentLink ? currentLink.href : null);
}

function restoreLinkTarget(event)
{
  try
  {
    if (currentLink && currentLink.href != currentLinkHref)
      currentLink.href = currentLinkHref;
  }
  catch (e)
  {
    // Ignore, likely "can't access dead object" (currentLink has been garbage collected)
  }

  currentLink = currentLinkHref = null;
}

function interceptEvent(event)
{
  if (event == "keydown" && event.keyCode != Ci.nsIDOMKeyEvent.DOM_VK_RETURN)
    return;

  let link = null;
  for (link = event.target; link; link = link.parentNode)
    if (link.localName == "a")
      break;

  if (link && /^\s*https?:/i.test(link.getAttribute("href")) && isSearchPage(link.ownerDocument.defaultView))
    event.stopPropagation();
}
<commit_msg>Fixed #6 - Google application icon broken since last update<commit_after>/*
 * This Source Code is subject to the terms of the Mozilla Public License
 * version 2.0 (the "License"). You can obtain a copy of the License at
 * http://mozilla.org/MPL/2.0/.
 */

let {WindowObserver} = require("windowObserver");
new WindowObserver({
  applyToWindow: function(window)
  {
    if (!("gBrowser" in window))
      return;

    window.gBrowser.addEventListener("mousedown", saveLinkTarget, true);
    window.gBrowser.addEventListener("mousedown", restoreLinkTarget, false);
    window.gBrowser.addEventListener("click", interceptEvent, true);
    window.gBrowser.addEventListener("keydown", interceptEvent, true);
  },

  removeFromWindow: function(window)
  {
    if (!("gBrowser" in window))
      return;

    window.gBrowser.removeEventListener("mousedown", saveLinkTarget, true);
    window.gBrowser.removeEventListener("mousedown", restoreLinkTarget, false);
    window.gBrowser.removeEventListener("click", interceptEvent, true);
    window.gBrowser.removeEventListener("keydown", interceptEvent, true);
  }
});

let currentLink = null;
let currentLinkHref = null;

let hostSet = new Set([
  "groups.google.com",
  "search.yahoo.com",
  "yandex.com",
  "yandex.com.tr",
  "yandex.by",
  "yandex.kz",
  "yandex.ru",
  "yandex.ua"
]);

function isSearchPage(window)
{
  try
  {
    let host = window.location.host;
    if (hostSet.has(host) || hostSet.has(host.replace(/^.*?\./, "")))
      return true;
  }
  catch (e) {}

  let sandbox = new Cu.Sandbox(window);
  sandbox.window = window;
  try
  {
    return Cu.evalInSandbox("(function(){var g = window.wrappedJSObject.google; return g && (g.sn || g.search) ? true : false;})()", sandbox);
  }
  catch (e)
  {
    return false;
  }
}

function isSearchResult(link)
{
  for (let parent = link; parent; parent = parent.parentNode)
    if (parent.id == "search" || parent.id == "results")   // Google & Yahoo
      return true;
  return false;
}

function saveLinkTarget(event)
{
  if (!isSearchPage(event.target.ownerDocument.defaultView))
    return;

  for (currentLink = event.target; currentLink; currentLink = currentLink.parentNode)
    if (currentLink.localName == "a")
      break;

  currentLinkHref = (currentLink ? currentLink.href : null);
}

function restoreLinkTarget(event)
{
  try
  {
    if (currentLink && currentLink.href != currentLinkHref)
      currentLink.href = currentLinkHref;
  }
  catch (e)
  {
    // Ignore, likely "can't access dead object" (currentLink has been garbage collected)
  }

  currentLink = currentLinkHref = null;
}

function interceptEvent(event)
{
  if (event == "keydown" && event.keyCode != Ci.nsIDOMKeyEvent.DOM_VK_RETURN)
    return;

  let link = null;
  for (link = event.target; link; link = link.parentNode)
    if (link.localName == "a")
      break;

  if (link && isSearchResult(link) &&
      /^\s*https?:/i.test(link.getAttribute("href")) &&
      isSearchPage(link.ownerDocument.defaultView))
  {
    event.stopPropagation();
  }
}
<|endoftext|>


<commit_before>'use babel'

import { CompositeDisposable, Directory } from 'atom'
// import { isListItem, wrapText } from './functions'

CSON = require('season')
fs = require('fs')
GrammarCompiler = require('./GrammarCompiler')
path = require('path')

module.exports = {
  config: {
    addListItems: {
      title: 'Add new list-items',
      description: 'Automatically add a new list-item after the current (non-empty) one when pressing ENTER',
      type: 'boolean',
      default: true
    },

    disableLanguageGfm: {
      title: 'Disable language-gfm',
      description: 'Disable the default `language-gfm` package as this package is intended as its replacement',
      type: 'boolean',
      default: true
    },

    emphasisShortcuts: {
      title: 'Emphasis shortcuts',
      description: 'Enables keybindings `_` for emphasis, `*` for strong emphasis, and `~` for strike-through on selected text; emphasizing an already emphasized selection will de-emphasize it',
      type: 'boolean',
      default: true
    },

    indentListItems: {
      title: 'Indent list-items',
      description: 'Automatically in- and outdent list-items by pressing `TAB` and `SHIFT+TAB`',
      type: 'boolean',
      default: true
    },

    linkShortcuts: {
      title: 'Link shortcuts',
      description: 'Enables keybindings `@` for converting the selected text to a link and `!` for converting the selected text to an image',
      type: 'boolean',
      default: true
    },

    removeEmptyListItems: {
      title: 'Remove empty list-items',
      description: 'Remove the automatically created empty list-items when left empty, leaving an empty line',
      type: 'boolean',
      default: true
    }
  },

  subscriptions: null,

  activate (state) {
    this.subscriptions = new CompositeDisposable()
    this.addCommands()

    /*
    Unless you are an advanced user, there is no need to have both this package
    and the one it replaces (language-gfm) enabled.

    If you are an advanced user, you can easily re-enable language-gfm again.
    */
    if (atom.config.get('language-markdown.disableLanguageGfm')) {
      if (!atom.packages.isPackageDisabled('language-gfm')) {
        atom.packages.disablePackage('language-gfm')
      }
    }

    /*
    I forgot why this action is created inline in activate() and not as a
    separate method, but there was a good reason for it.
    */
    this.subscriptions.add(atom.workspace.observeTextEditors(editor => {
      editor.onDidInsertText(event => {
        const grammar = editor.getGrammar()

        if (grammar.name !== 'Markdown') return
        if (!atom.config.get('language-markdown.addListItems')) return
        if (event.text !== '\n') return

        /*
        At this point, it is rather tedious (as far as I know) to get to the
        tokenized version of {previousLine}. That is the reason why {tokens} a
        little further down is tokenized. But at this stage, we do need to know
        if {previousLine} was in fact Markdown, or from a different perspective,
        not a piece of embedded code. The reason for that is that the tokenized
        line below is tokenized without any context, so is Markdown by default.
        Therefore we determine if our current position is part of embedded code
        or not.
        */

        const previousRowNumber = event.range.start.row
        const previousRowRange = editor.buffer.rangeForRow(previousRowNumber)
        if (this.isEmbeddedCode(editor, previousRowRange)) return

        const previousLine = editor.getTextInRange(previousRowRange)
        let { tokens } = grammar.tokenizeLine(previousLine)
        tokens.reverse()
        for (const token of tokens) {
          let isPunctuation = false
          let isListItem = false
          let typeOfList

          const scopes = token.scopes.reverse()
          for (const scope of scopes) {
            const classes = scope.split('.')

            /*
            A list-item is valid when a punctuation class is immediately
            followed by a non-empty list-item class.
            */
            if (classes.includes('punctuation')) {
              isPunctuation = true
            } else if (isPunctuation && classes.includes('list')) {
              if (!classes.includes('empty')) {
                isListItem = true
                typeOfList = 'unordered'
                if (classes.includes('ordered')) {
                  typeOfList = 'ordered'
                }
                if (classes.includes('definition')) {
                  typeOfList = 'definition'
                }
                break
              } else {
                isListItem = false
                isPunctuation = false
                if (atom.config.get('language-markdown.removeEmptyListItems')) {
                  editor.setTextInBufferRange(previousRowRange, '')
                }
              }
            } else {
              isPunctuation = false
            }
          }

          if (isListItem && typeOfList !== 'definition') {
            let text = token.value
            if (typeOfList === 'ordered') {
              const length = text.length
              const punctuation = text.match(/[^\d]+/)
              const value = parseInt(text) + 1
              text = value + punctuation
              if (text.length < length) {
                for (let j = 0; j < text.length - length + 1; j++) {
                  text = '0' + text
                }
              }
            } else {
              text = text.replace('x', ' ')
            }
            editor.insertText(text + '')
            break
          }
        }
      })
    }))
  },

  addCommands () {
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:indent-list-item', (event) => this.indentListItem(event)))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:outdent-list-item', (event) => this.outdentListItem(event)))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:emphasis', (event) => this.emphasizeSelection(event, '_')))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:strong-emphasis', (event) => this.emphasizeSelection(event, '**')))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:strike-through', (event) => this.emphasizeSelection(event, '~~')))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:link', (event) => this.linkSelection(event)))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:image', (event) => this.linkSelection(event, true)))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:toggle-task', (event) => this.toggleTask(event)))

    if (atom.inDevMode()) {
      this.subscriptions.add(atom.commands.add('atom-workspace', 'markdown:compile-grammar-and-reload', () => this.compileGrammar()))
    }
  },

  indentListItem (event) {
    const { editor, position } = this._getEditorAndPosition(event)
    const indentListItems = atom.config.get('language-markdown.indentListItems')
    if (indentListItems && this.isListItem(editor, position)) {
      editor.indentSelectedRows(position.row)
      return
    }
    event.abortKeyBinding()
  },

  outdentListItem (event) {
    const { editor, position } = this._getEditorAndPosition(event)
    const indentListItems = atom.config.get('language-markdown.indentListItems')
    if (indentListItems && this.isListItem(editor, position)) {
      editor.outdentSelectedRows(position.row)
      return
    }
    event.abortKeyBinding()
  },

  emphasizeSelection (event, token) {
    if (atom.config.get('language-markdown.emphasisShortcuts')) {
      const editor = atom.workspace.getActiveTextEditor()
      if (!editor) return

      const ranges = this.getSelectedBufferRangesReversed(editor)
      for (const range of ranges) {
        const text = editor.getTextInBufferRange(range)
        /*
        Skip texts that contain a line-break, or are empty.
        Multi-line emphasis is not supported 'anyway'.
        */
        if (text.length === 0) continue
        if (text.indexOf('\n') !== -1) continue

        const wrappedText = this.wrapText(text, token)
        editor.setTextInBufferRange(range, wrappedText)
      }

      return
    }
    event.abortKeyBinding()
  },

  // TODO: Doesn't place the cursor at the right position afterwards
  linkSelection (event, isImage = false) {
    if (atom.config.get('language-markdown.linkShortcuts')) {
      const editor = atom.workspace.getActiveTextEditor()
      if (!editor) return

      const ranges = this.getSelectedBufferRangesReversed(editor)
      const cursorOffsets = []
      for (const range of ranges) {
        const text = editor.getTextInBufferRange(range)
        /*
        Skip texts that contain a line-break, or are empty.
        Multi-line emphasis is not supported 'anyway'.
        */
        if (text.length === 0) continue
        if (text.indexOf('\n') !== -1) continue

        const imageToken = isImage ? '!' : ''
        if (text.match(/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/)) {
          const newText = `${imageToken}[](${text})`
          editor.setTextInBufferRange(range, newText)
          cursorOffsets.push(text.length + 3)
        } else {
          const newText = `${imageToken}[${text}]()`
          editor.setTextInBufferRange(range, newText)
          cursorOffsets.push(1)
        }
      }

      /*
      Cursors aren't separate entities, but rather simple {Point}s, ie,
      positions in the buffer. There is no way of updating a cursor. Instead,
      we clear all cursors, and then re-create them from where our current
      selections are.

      After the image/link wrapping above, the cursor are positioned after the
      selections, and the desired relative locations for the new cursors are
      stored in {cursorOffsets}. We only need to loop through the current
      selections, and create a new cursor for every selection.

      A selection without a length is a simple cursor that can be re-created at
      that exact location.

      TODO: maybe one of those fancy generators can be used for our
      cursorOffsets?
      */
      const selections = editor.getSelectedBufferRanges()
      let count = 0
      let offsetCount = 0
      for (const selection of selections) {
        const { start, end } = selection
        if ((start.row === end.row) && (start.column === end.column)) {
          if (count) {
            editor.addCursorAtBufferPosition(start)
          } else {
            editor.setCursorBufferPosition(start)
          }
        } else {
          const position = {
            row: end.row,
            column: end.column - cursorOffsets[offsetCount]
          }
          if (count) {
            editor.addCursorAtBufferPosition(position)
          } else {
            editor.setCursorBufferPosition(position)
          }
          offsetCount++
        }
        count++;
      }

      return
    }
    event.abortKeyBinding()
  },

  _getEditorAndPosition (event) {
    const editor = atom.workspace.getActiveTextEditor()
    if (editor) {
      const positions = editor.getCursorBufferPositions()
      if (positions) {
        const position = positions[0]
        return { editor, position }
      }
    }
    event.abortKeyBinding()
  },

  toggleTask (event) {
    const { editor, position } = this._getEditorAndPosition(event)
    const listItem = this.isListItem(editor, position)
    if (listItem && listItem.includes('task')) {
      const currentLine = editor.lineTextForBufferRow(position.row)
      let newLine
      if (listItem.includes('completed')) {
        newLine = currentLine.replace(' [x] ', ' [ ] ')
      } else {
        newLine = currentLine.replace(' [ ] ', ' [x] ')
      }
      const range = [[position.row, 0], [position.row, newLine.length]]
      editor.setTextInBufferRange(range, newLine)
      return
    }
    event.abortKeyBinding()
  },

  isListItem (editor, position) {
    if (editor) {
      if (editor.getGrammar().name === 'Markdown') {
        const scopeDescriptor = editor.scopeDescriptorForBufferPosition(position)
        for (const scope of scopeDescriptor.scopes) {
          if (scope.includes('list')) {
            /*
            Return {scope}, which evaluates as {true} and can be used by other
            functions to determine the type of list-item
            */
            return scope;
          }
        }
      }
    }
    return false
  },

  wrapText (text, token) {
    const length = token.length
    if ((text.substr(0, length) === token) && (text.substr(-length) === token)) {
      return text.substr(length, text.length - length * 2)
    } else {
      return token + text + token
    }
  },

  isEmbeddedCode (editor, range) {
    const scopeDescriptor = editor.scopeDescriptorForBufferPosition(range.end)
    for (const scope of scopeDescriptor.scopes) {
      if (scope.includes('source')) return true
    }
    return false
  },

  /*
  Selection are returned in the reverse order that they were created by the
  user. We need them in the reverse order that they appear in the document,
  because we don't need a previous changes selection changing the buffer
  position of our selections.
  */
  getSelectedBufferRangesReversed (editor) {
    const ranges = editor.getSelectedBufferRanges()
    ranges.sort(function(a, b) {
      if (a.start.row > b.start.row) return -1
      if (b.start.row > a.start.row) return 1
      if (a.start.column > b.start.column) return -1
      return 1
    })
    return ranges
  },

  compileGrammar () {
    if (atom.inDevMode()) {
      const compiler = new GrammarCompiler()
      compiler.compile()
    }
  }
}
<commit_msg>Add multi cursor support for toggling tasks<commit_after>'use babel'

import { CompositeDisposable, Directory } from 'atom'
// import { isListItem, wrapText } from './functions'

CSON = require('season')
fs = require('fs')
GrammarCompiler = require('./GrammarCompiler')
path = require('path')

module.exports = {
  config: {
    addListItems: {
      title: 'Add new list-items',
      description: 'Automatically add a new list-item after the current (non-empty) one when pressing ENTER',
      type: 'boolean',
      default: true
    },

    disableLanguageGfm: {
      title: 'Disable language-gfm',
      description: 'Disable the default `language-gfm` package as this package is intended as its replacement',
      type: 'boolean',
      default: true
    },

    emphasisShortcuts: {
      title: 'Emphasis shortcuts',
      description: 'Enables keybindings `_` for emphasis, `*` for strong emphasis, and `~` for strike-through on selected text; emphasizing an already emphasized selection will de-emphasize it',
      type: 'boolean',
      default: true
    },

    indentListItems: {
      title: 'Indent list-items',
      description: 'Automatically in- and outdent list-items by pressing `TAB` and `SHIFT+TAB`',
      type: 'boolean',
      default: true
    },

    linkShortcuts: {
      title: 'Link shortcuts',
      description: 'Enables keybindings `@` for converting the selected text to a link and `!` for converting the selected text to an image',
      type: 'boolean',
      default: true
    },

    removeEmptyListItems: {
      title: 'Remove empty list-items',
      description: 'Remove the automatically created empty list-items when left empty, leaving an empty line',
      type: 'boolean',
      default: true
    }
  },

  subscriptions: null,

  activate (state) {
    this.subscriptions = new CompositeDisposable()
    this.addCommands()

    /*
    Unless you are an advanced user, there is no need to have both this package
    and the one it replaces (language-gfm) enabled.

    If you are an advanced user, you can easily re-enable language-gfm again.
    */
    if (atom.config.get('language-markdown.disableLanguageGfm')) {
      if (!atom.packages.isPackageDisabled('language-gfm')) {
        atom.packages.disablePackage('language-gfm')
      }
    }

    /*
    I forgot why this action is created inline in activate() and not as a
    separate method, but there was a good reason for it.
    */
    this.subscriptions.add(atom.workspace.observeTextEditors(editor => {
      editor.onDidInsertText(event => {
        const grammar = editor.getGrammar()

        if (grammar.name !== 'Markdown') return
        if (!atom.config.get('language-markdown.addListItems')) return
        if (event.text !== '\n') return

        /*
        At this point, it is rather tedious (as far as I know) to get to the
        tokenized version of {previousLine}. That is the reason why {tokens} a
        little further down is tokenized. But at this stage, we do need to know
        if {previousLine} was in fact Markdown, or from a different perspective,
        not a piece of embedded code. The reason for that is that the tokenized
        line below is tokenized without any context, so is Markdown by default.
        Therefore we determine if our current position is part of embedded code
        or not.
        */

        const previousRowNumber = event.range.start.row
        const previousRowRange = editor.buffer.rangeForRow(previousRowNumber)
        if (this.isEmbeddedCode(editor, previousRowRange)) return

        const previousLine = editor.getTextInRange(previousRowRange)
        let { tokens } = grammar.tokenizeLine(previousLine)
        tokens.reverse()
        for (const token of tokens) {
          let isPunctuation = false
          let isListItem = false
          let typeOfList

          const scopes = token.scopes.reverse()
          for (const scope of scopes) {
            const classes = scope.split('.')

            /*
            A list-item is valid when a punctuation class is immediately
            followed by a non-empty list-item class.
            */
            if (classes.includes('punctuation')) {
              isPunctuation = true
            } else if (isPunctuation && classes.includes('list')) {
              if (!classes.includes('empty')) {
                isListItem = true
                typeOfList = 'unordered'
                if (classes.includes('ordered')) {
                  typeOfList = 'ordered'
                }
                if (classes.includes('definition')) {
                  typeOfList = 'definition'
                }
                break
              } else {
                isListItem = false
                isPunctuation = false
                if (atom.config.get('language-markdown.removeEmptyListItems')) {
                  editor.setTextInBufferRange(previousRowRange, '')
                }
              }
            } else {
              isPunctuation = false
            }
          }

          if (isListItem && typeOfList !== 'definition') {
            let text = token.value
            if (typeOfList === 'ordered') {
              const length = text.length
              const punctuation = text.match(/[^\d]+/)
              const value = parseInt(text) + 1
              text = value + punctuation
              if (text.length < length) {
                for (let j = 0; j < text.length - length + 1; j++) {
                  text = '0' + text
                }
              }
            } else {
              text = text.replace('x', ' ')
            }
            editor.insertText(text + '')
            break
          }
        }
      })
    }))
  },

  addCommands () {
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:indent-list-item', (event) => this.indentListItem(event)))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:outdent-list-item', (event) => this.outdentListItem(event)))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:emphasis', (event) => this.emphasizeSelection(event, '_')))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:strong-emphasis', (event) => this.emphasizeSelection(event, '**')))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:strike-through', (event) => this.emphasizeSelection(event, '~~')))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:link', (event) => this.linkSelection(event)))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:image', (event) => this.linkSelection(event, true)))
    this.subscriptions.add(atom.commands.add('atom-text-editor', 'markdown:toggle-task', (event) => this.toggleTask(event)))

    if (atom.inDevMode()) {
      this.subscriptions.add(atom.commands.add('atom-workspace', 'markdown:compile-grammar-and-reload', () => this.compileGrammar()))
    }
  },

  indentListItem (event) {
    const { editor, position } = this._getEditorAndPosition(event)
    const indentListItems = atom.config.get('language-markdown.indentListItems')
    if (indentListItems && this.isListItem(editor, position)) {
      editor.indentSelectedRows(position.row)
      return
    }
    event.abortKeyBinding()
  },

  outdentListItem (event) {
    const { editor, position } = this._getEditorAndPosition(event)
    const indentListItems = atom.config.get('language-markdown.indentListItems')
    if (indentListItems && this.isListItem(editor, position)) {
      editor.outdentSelectedRows(position.row)
      return
    }
    event.abortKeyBinding()
  },

  emphasizeSelection (event, token) {
    if (atom.config.get('language-markdown.emphasisShortcuts')) {
      const editor = atom.workspace.getActiveTextEditor()
      if (!editor) return

      const ranges = this.getSelectedBufferRangesReversed(editor)
      for (const range of ranges) {
        const text = editor.getTextInBufferRange(range)
        /*
        Skip texts that contain a line-break, or are empty.
        Multi-line emphasis is not supported 'anyway'.
        */
        if (text.length === 0) continue
        if (text.indexOf('\n') !== -1) continue

        const wrappedText = this.wrapText(text, token)
        editor.setTextInBufferRange(range, wrappedText)
      }

      return
    }
    event.abortKeyBinding()
  },

  // TODO: Doesn't place the cursor at the right position afterwards
  linkSelection (event, isImage = false) {
    if (atom.config.get('language-markdown.linkShortcuts')) {
      const editor = atom.workspace.getActiveTextEditor()
      if (!editor) return

      const ranges = this.getSelectedBufferRangesReversed(editor)
      const cursorOffsets = []
      for (const range of ranges) {
        const text = editor.getTextInBufferRange(range)
        /*
        Skip texts that contain a line-break, or are empty.
        Multi-line emphasis is not supported 'anyway'.
        */
        if (text.length === 0) continue
        if (text.indexOf('\n') !== -1) continue

        const imageToken = isImage ? '!' : ''
        if (text.match(/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/)) {
          const newText = `${imageToken}[](${text})`
          editor.setTextInBufferRange(range, newText)
          cursorOffsets.push(text.length + 3)
        } else {
          const newText = `${imageToken}[${text}]()`
          editor.setTextInBufferRange(range, newText)
          cursorOffsets.push(1)
        }
      }

      /*
      Cursors aren't separate entities, but rather simple {Point}s, ie,
      positions in the buffer. There is no way of updating a cursor. Instead,
      we clear all cursors, and then re-create them from where our current
      selections are.

      After the image/link wrapping above, the cursor are positioned after the
      selections, and the desired relative locations for the new cursors are
      stored in {cursorOffsets}. We only need to loop through the current
      selections, and create a new cursor for every selection.

      A selection without a length is a simple cursor that can be re-created at
      that exact location.

      TODO: maybe one of those fancy generators can be used for our
      cursorOffsets?
      */
      const selections = editor.getSelectedBufferRanges()
      let count = 0
      let offsetCount = 0
      for (const selection of selections) {
        const { start, end } = selection
        if ((start.row === end.row) && (start.column === end.column)) {
          if (count) {
            editor.addCursorAtBufferPosition(start)
          } else {
            editor.setCursorBufferPosition(start)
          }
        } else {
          const position = {
            row: end.row,
            column: end.column - cursorOffsets[offsetCount]
          }
          if (count) {
            editor.addCursorAtBufferPosition(position)
          } else {
            editor.setCursorBufferPosition(position)
          }
          offsetCount++
        }
        count++;
      }

      return
    }
    event.abortKeyBinding()
  },

  _getEditorAndPosition (event) {
    const editor = atom.workspace.getActiveTextEditor()
    if (editor) {
      const positions = editor.getCursorBufferPositions()
      if (positions) {
        const position = positions[0]
        return { editor, position }
      }
    }
    event.abortKeyBinding()
  },

  toggleTask (event) {
    const editor = atom.workspace.getActiveTextEditor()
    if (!editor) {
      event.abortKeyBinding()
      return
    }

    const ranges = editor.getSelectedBufferRanges()
    for (const range of ranges) {
      const { start, end } = range
      for (let row = start.row; row <= end.row; row++) {
        const listItem = this.isListItem(editor, [row, 0])
        if (listItem && listItem.includes('task')) {
          const currentLine = editor.lineTextForBufferRow(row)
          let newLine
          if (listItem.includes('completed')) {
            newLine = currentLine.replace(/ \[(x|X)\] /, ' [ ] ')
          } else {
            newLine = currentLine.replace(' [ ] ', ' [x] ')
          }
          const newRange = [[row, 0], [row, newLine.length]]
          editor.setTextInBufferRange(newRange, newLine)
        }
      }
    }
    return
  },

  isListItem (editor, position) {
    if (editor) {
      if (editor.getGrammar().name === 'Markdown') {
        const scopeDescriptor = editor.scopeDescriptorForBufferPosition(position)
        for (const scope of scopeDescriptor.scopes) {
          if (scope.includes('list')) {
            /*
            Return {scope}, which evaluates as {true} and can be used by other
            functions to determine the type of list-item
            */
            return scope;
          }
        }
      }
    }
    return false
  },

  wrapText (text, token) {
    const length = token.length
    if ((text.substr(0, length) === token) && (text.substr(-length) === token)) {
      return text.substr(length, text.length - length * 2)
    } else {
      return token + text + token
    }
  },

  isEmbeddedCode (editor, range) {
    const scopeDescriptor = editor.scopeDescriptorForBufferPosition(range.end)
    for (const scope of scopeDescriptor.scopes) {
      if (scope.includes('source')) return true
    }
    return false
  },

  /*
  Selection are returned in the reverse order that they were created by the
  user. We need them in the reverse order that they appear in the document,
  because we don't need a previous changes selection changing the buffer
  position of our selections.
  */
  getSelectedBufferRangesReversed (editor) {
    const ranges = editor.getSelectedBufferRanges()
    ranges.sort(function(a, b) {
      if (a.start.row > b.start.row) return -1
      if (b.start.row > a.start.row) return 1
      if (a.start.column > b.start.column) return -1
      return 1
    })
    return ranges
  },

  compileGrammar () {
    if (atom.inDevMode()) {
      const compiler = new GrammarCompiler()
      compiler.compile()
    }
  }
}
<|endoftext|>


<commit_before>'use babel';

/* @flow */

import { Disposable, CompositeDisposable } from 'atom';
import type { Panel, TextEditor } from 'atom';

import 'rxjs';
import { createStore, applyMiddleware } from 'redux';
import { createEpicMiddleware } from 'redux-observable';
import reducers from './redux/reducers';
import epics from './redux/epics';
import * as actions from './redux/actions';

import Commands from './commands';
import EditorsRegistry from './editor-registry';
import TesterRegistry from './tester-registry';
// import { clearInlineMessages, decorateGutter, setInlineMessages } from './decorate-manager';
import ConsoleOutputView from './views/ConsoleOutputView';
import ResultView from './views/ResultView';
import StatusBarTile from './views/StatusBarTile';
import type { Tester as TesterProvider, Message, Store } from './types';

class Tester {
  commands: Commands;
  subscriptions: CompositeDisposable;
  modalPanel: Panel;
  registryTesters: TesterRegistry;
  registryEditors: EditorsRegistry;
  statusBarTile: Object;
  markers: Array<Object>;
  showInlineError: boolean;
  inlineErrorPosition: string;
  ansiToHtml: boolean;
  showOutputAfterTestRun: boolean;
  showNotifications: boolean;
  messageTimeout: number;
  runningTestersCount: number;
  scrollToBottom: boolean;
  panel: ?ConsoleOutputView;
  resultView: ?ResultView;
  messages:Array<Message>;
  output: string;
  testerStatusBar: Object;
  lastTestEditor: TextEditor;
  deserializeResultView: Function;
  deserializeConsoleOutput: Function;
  store: Store;
  getStore: Function;

  constructor(initialState) {
    this.commands = new Commands();
    this.registryEditors = new EditorsRegistry();
    this.registryTesters = new TesterRegistry();
    this.subscriptions = new CompositeDisposable();
    this.subscriptions.add(this.commands);
    this.subscriptions.add(this.registryEditors);
    this.subscriptions.add(this.registryTesters);
    this.markers = [];
    this.showInlineError = true;
    this.inlineErrorPosition = 'after';
    this.ansiToHtml = true;
    this.showOutputAfterTestRun = true;
    this.showNotifications = true;
    this.messageTimeout = 5;
    this.runningTestersCount = 0;
    this.scrollToBottom = true;
    this.messages = [];
    this.output = '';
    this.lastTestEditor = null;
    const self = this;

    this.getStore = () => {
      if (self.store == null) {
        self.store = createStore(
          reducers,
          initialState,
          applyMiddleware(createEpicMiddleware(epics)),
        );
        self.store.subscribe(async () => {
          console.log('store updated', self.store.getState());
          // update all views here
          if (self.statusBarTile) {
            await self.statusBarTile.update(self.getStore().getState());
          }
        });
      }
      return self.store;
    };
    this.subscriptions.add(atom.config.observe('tester.gutterEnabled', (gutterEnabled) => {
      this.registryEditors.gutterEnabled = gutterEnabled;
    }));
    this.subscriptions.add(atom.config.observe('tester.showInlineError', (showInlineError) => {
      this.showInlineError = showInlineError;
    }));
    this.subscriptions.add(atom.config.observe('tester.inlineErrorPosition', (inlineErrorPosition) => {
      this.inlineErrorPosition = inlineErrorPosition;
    }));
    this.subscriptions.add(atom.config.observe('tester.ansiToHtml', (ansiToHtml) => {
      this.ansiToHtml = ansiToHtml;
    }));
    this.subscriptions.add(atom.config.observe('tester.showOutputAfterTestRun', (showOutputAfterTestRun) => {
      this.showOutputAfterTestRun = showOutputAfterTestRun;
    }));
    this.subscriptions.add(atom.config.observe('tester.messageTimeout', (messageTimeout) => {
      this.messageTimeout = messageTimeout;
    }));
    this.subscriptions.add(atom.config.observe('tester.showNotifications', (showNotifications) => {
      this.showNotifications = showNotifications;
    }));
    this.subscriptions.add(atom.config.observe('tester.scrollToBottom', (scrollToBottom) => {
      this.scrollToBottom = scrollToBottom;
    }));
    this.subscriptions.add(atom.workspace.observeTextEditors(async (/* editor*/) => {
      // await self.updateStatusBar();
    }));
    this.subscriptions.add(atom.workspace.onDidChangeActivePaneItem(async (/* paneItem*/) => {
      // await self.updateStatusBar();
    }));

    this.subscriptions.add(new Disposable(() => {
      atom.workspace.getPaneItems().forEach((item) => {
        if (item instanceof ConsoleOutputView) {
          item.destroy();
        }
      });
    }));

    this.deserializeConsoleOutput = function deserializeConsoleOutput(serializedState :any) {
      console.log('serializedState', serializedState);
      self.panel = new ConsoleOutputView({
        state: self.getStore().getState(),
        ansiToHtml: self.ansiToHtml,
        scrollToBottom: self.scrollToBottom,
      });
      // self.panel = new ConsoleOutputView({
      //   output: self.output,
      //   ansiToHtml: self.ansiToHtml,
      //   scrollToBottom: self.scrollToBottom });
      return self.panel;
    };

    this.subscriptions.add(atom.workspace.addOpener((uri) => {
      if (uri === 'atom://tester-console-output') {
        return self.deserializeConsoleOutput();
      }
    }));

    this.subscriptions.add(new Disposable(() => {
      atom.workspace.getPaneItems().forEach((item) => {
        if (item instanceof ResultView) {
          item.destroy();
        }
      });
    }));

    this.deserializeResultView = function deserializeResultView(serializedState :any) {
      console.log('serializedState', serializedState);
      self.resultView = new ResultView({
        state: self.getStore().getState(),
        ansiToHtml: self.ansiToHtml,
        softWrap: atom.config.get('tester.softWrapDefault'),
      });
      // self.resultView = new ResultView({
      //   messages: self.messages,
      //   ansiToHtml: self.ansiToHtml,
      //   softWrap: atom.config.get('tester.softWrapDefault'),
      //   runningTestersCount: self.runningTestersCount,
      // });
      self.resultView.onTestButtonClick((additionalArgs) => {
        self.registryTesters.test(null, true, additionalArgs);
      });
      return self.resultView;
    };

    this.subscriptions.add(atom.workspace.addOpener((uri) => {
      if (uri === 'atom://tester-result-view') {
        return self.deserializeResultView();
      }
    }));

    const onShouldTestHandler = async () => {
      if (!self.lastTestEditor) {
        self.lastTestEditor = atom.workspace.getActiveTextEditor();
      }
      const activeItem = self.lastTestEditor || atom.workspace.getActivePaneItem();
      const editorTester = self.registryEditors.get(activeItem);

      if (editorTester) {
        // await self.updateStatusBar();
        editorTester.test();
      }
    };
    const onShouldTestProjectHandler = () => {
      self.registryTesters.test(null, true, self.resultView ? self.resultView.getAdditionalArgs() : '');
    };

    this.commands.onShouldTestLast(() => {
      if (self.lastTestEditor) {
        onShouldTestHandler();
      } else {
        onShouldTestProjectHandler();
      }
    });

    this.commands.onShouldTestProject(onShouldTestProjectHandler);

    this.commands.onShouldTest(onShouldTestHandler);

    this.commands.onShouldStop(() => {
      self.registryTesters.stop(atom.workspace.getActiveTextEditor());
    });

    this.commands.onShouldToggleTesterOutput(() => {
      this.togglePanel();
    });
    this.commands.onShouldToggleTesterResultView(() => {
      this.toggleResultView();
    });

    this.commands.onShouldGoToNextTest(() => {
      if (!this.resultView) { return; }
      this.resultView.goToNextTest();
    });
    this.commands.onShouldGoToPreviousTest(() => {
      if (!this.resultView) { return; }
      this.resultView.goToPreviousTest();
    });

    this.registryEditors.observe((editorTester) => {
      editorTester.onShouldTest(() => {
        self.registryTesters.test(editorTester, null, self.resultView ? self.resultView.getAdditionalArgs() : '');
      });
      editorTester.onShouldStop(() => {
        self.registryTesters.stop(editorTester);
      });
    });

    this.registryTesters.onDidUpdateMessages(async ({ editor, messages, output }) => {
      self.getStore().dispatch(actions.updateEditorAction(editor));
      self.getStore().dispatch(actions.updateMessagesAction(messages));
      self.getStore().dispatch(actions.updateOutputAction(output));

      // messages.forEach((message, index) => {
      //   if (!message || typeof message !== 'object') {
      //     messages.splice(index, 1);
      //     console.warn('Tester: Message is not valid. Remove it from globals.', message);
      //   }
      // });
      // this.messages = messages;
      // this.output = output;
      // await self.updateStatusBar();
      // if (this.panel) {
      //   await this.panel.update({ output });
      // }
      // if (this.resultView) {
      //   await this.resultView.update({ messages: this.messages });
      // }
      //
      // if (editor) {
      //   const gutter = self.registryEditors.handleGutter(editor);
      //   if (!gutter) {
      //     return;
      //   }
      //   clearInlineMessages(editor);
      //   decorateGutter(editor, gutter, this.messages);
      //   if (this.showInlineError) {
      //     setInlineMessages(editor, this.messages, this.ansiToHtml, this.inlineErrorPosition);
      //   }
      // }
    });

    this.registryTesters.onDidBeginTesting(async ({ editorTester }) => {
      self.getStore().dispatch(actions.updateEditorAction(editorTester ? editorTester.editor : null));
      self.getStore().dispatch(actions.increaseCounterAction());

      // self.lastTestEditor = editorTester ? editorTester.editor : null;
      // self.runningTestersCount += 1;
      // if (self.resultView) {
      //   await self.resultView.update({ runningTestersCount: self.runningTestersCount });
      // }
      // await self.updateStatusBar();
    });

    this.registryTesters.onDidFinishTesting(async () => {
      self.getStore().dispatch(actions.decreaseCounterAction());
      // self.runningTestersCount -= 1;
      // if (self.resultView) {
      //   await self.resultView.update({ runningTestersCount: self.runningTestersCount });
      // }
      // await self.updateStatusBar();
    });

    this.registryEditors.activate();

    setTimeout(() => {
      // NOTE: Atom triggers this on boot so wait a while
      if (!self.subscriptions.disposed) {
        self.subscriptions.add(atom.project.onDidChangePaths(() => {
          self.registryEditors.handleGutter();
          self.commands.test();
        }));
      }
    }, 100);
  }

  createStatusBar(statusBar : Object) {
    if (!atom.config.get('tester.showStatusBar')) {
      return;
    }
    const onclick = () => {
      const statusBarOnClick = atom.config.get('tester.statusBarOnClick');
      if (statusBarOnClick === 'console' || statusBarOnClick === 'both') {
        this.togglePanel();
      }
      if (statusBarOnClick === 'results' || statusBarOnClick === 'both') {
        this.toggleResultView();
      }
    };
    this.statusBarTile = new StatusBarTile({ state: this.getStore().getState(), onclick });
    // this.statusBarTile = new StatusBarTile({
    //   messages: this.messages,
    //   runningTestersCount: this.runningTestersCount,
    //   onclick,
    // });

    statusBar.addLeftTile({
      item: this.statusBarTile.element,
      priority: -50,
    });
  }
  //
  // async updateStatusBar() {
  //   if (this.statusBarTile) {
  //     await this.statusBarTile.update({
  //       messages: this.messages,
  //       runningTestersCount: this.runningTestersCount,
  //     });
  //   }
  // }

  togglePanel() {
    if (atom.getVersion() < '1.17') {
      return atom.notifications.addWarning('Please update atom to version >=1.17 to use tester dock views.');
    }
    atom.workspace.toggle('atom://tester-console-output');
  }

  toggleResultView() {
    if (atom.getVersion() < '1.17') {
      return atom.notifications.addWarning('Please update atom to version >=1.17 to use tester dock views.');
    }
    atom.workspace.toggle('atom://tester-result-view');
  }

  async dispose() {
    if (this.statusBarTile) {
      await this.statusBarTile.destroy();
    }
    if (this.panel) {
      await this.panel.destroy();
    }
    if (this.resultView) {
      await this.resultView.destroy();
    }
    this.subscriptions.dispose();
  }

  addTester(tester : TesterProvider) {
    this.registryTesters.addTester(tester);
  }

  deleteTester(tester : TesterProvider) {
    this.registryTesters.deleteTester(tester);
  }
}

export default {
  instance: null,
  consumeStatusBar(statusBar : any) {
    this.instance.createStatusBar(statusBar);
  },

  initialize(initialState: any) {
    console.log('initialState', initialState);
    this.instance = new Tester(initialState);
    this.deserializeResultView = this.instance.deserializeResultView;
    this.deserializeConsoleOutput = this.instance.deserializeConsoleOutput;
  },

  getInstance(): ?Tester {
    return this.instance;
  },

  consumeTester(tester : Tester) : Disposable {
    const testers = [].concat(tester);
    for (const entry of testers) {
      this.instance.addTester(entry);
    }
    return new Disposable(() => {
      for (const entry of testers) {
        this.instance.deleteTester(entry);
      }
    });
  },

  serialize() {
    console.log('serialize main');
    if (!this.instance) {
      return {};
    }
    return this.instance.getStore().getState();
  },

  deactivate() {
    this.instance.dispose();
  },
};
<commit_msg>fix init state<commit_after>'use babel';

/* @flow */

import { Disposable, CompositeDisposable } from 'atom';
import type { Panel, TextEditor } from 'atom';

import 'rxjs';
import { createStore, applyMiddleware } from 'redux';
import { createEpicMiddleware } from 'redux-observable';
import reducers from './redux/reducers';
import epics from './redux/epics';
import * as actions from './redux/actions';

import Commands from './commands';
import EditorsRegistry from './editor-registry';
import TesterRegistry from './tester-registry';
// import { clearInlineMessages, decorateGutter, setInlineMessages } from './decorate-manager';
import ConsoleOutputView from './views/ConsoleOutputView';
import ResultView from './views/ResultView';
import StatusBarTile from './views/StatusBarTile';
import type { Tester as TesterProvider, Message, Store } from './types';

class Tester {
  commands: Commands;
  subscriptions: CompositeDisposable;
  modalPanel: Panel;
  registryTesters: TesterRegistry;
  registryEditors: EditorsRegistry;
  statusBarTile: Object;
  markers: Array<Object>;
  showInlineError: boolean;
  inlineErrorPosition: string;
  ansiToHtml: boolean;
  showOutputAfterTestRun: boolean;
  showNotifications: boolean;
  messageTimeout: number;
  runningTestersCount: number;
  scrollToBottom: boolean;
  panel: ?ConsoleOutputView;
  resultView: ?ResultView;
  messages:Array<Message>;
  output: string;
  testerStatusBar: Object;
  lastTestEditor: TextEditor;
  deserializeResultView: Function;
  deserializeConsoleOutput: Function;
  store: Store;
  getStore: Function;

  constructor(initialState) {
    this.commands = new Commands();
    this.registryEditors = new EditorsRegistry();
    this.registryTesters = new TesterRegistry();
    this.subscriptions = new CompositeDisposable();
    this.subscriptions.add(this.commands);
    this.subscriptions.add(this.registryEditors);
    this.subscriptions.add(this.registryTesters);
    this.markers = [];
    this.showInlineError = true;
    this.inlineErrorPosition = 'after';
    this.ansiToHtml = true;
    this.showOutputAfterTestRun = true;
    this.showNotifications = true;
    this.messageTimeout = 5;
    this.runningTestersCount = 0;
    this.scrollToBottom = true;
    this.messages = [];
    this.output = '';
    this.lastTestEditor = null;
    const self = this;

    this.getStore = () => {
      if (self.store == null) {
        initialState = Object.assign({
          messages: [],
          output: '',
          counter: 0,
        }, initialState);
        self.store = createStore(
          reducers,
          initialState,
          applyMiddleware(createEpicMiddleware(epics)),
        );
        self.store.subscribe(async () => {
          console.log('store updated', self.store.getState());
          // update all views here
          if (self.statusBarTile) {
            await self.statusBarTile.update(self.getStore().getState());
          }
        });
      }
      return self.store;
    };
    this.subscriptions.add(atom.config.observe('tester.gutterEnabled', (gutterEnabled) => {
      this.registryEditors.gutterEnabled = gutterEnabled;
    }));
    this.subscriptions.add(atom.config.observe('tester.showInlineError', (showInlineError) => {
      this.showInlineError = showInlineError;
    }));
    this.subscriptions.add(atom.config.observe('tester.inlineErrorPosition', (inlineErrorPosition) => {
      this.inlineErrorPosition = inlineErrorPosition;
    }));
    this.subscriptions.add(atom.config.observe('tester.ansiToHtml', (ansiToHtml) => {
      this.ansiToHtml = ansiToHtml;
    }));
    this.subscriptions.add(atom.config.observe('tester.showOutputAfterTestRun', (showOutputAfterTestRun) => {
      this.showOutputAfterTestRun = showOutputAfterTestRun;
    }));
    this.subscriptions.add(atom.config.observe('tester.messageTimeout', (messageTimeout) => {
      this.messageTimeout = messageTimeout;
    }));
    this.subscriptions.add(atom.config.observe('tester.showNotifications', (showNotifications) => {
      this.showNotifications = showNotifications;
    }));
    this.subscriptions.add(atom.config.observe('tester.scrollToBottom', (scrollToBottom) => {
      this.scrollToBottom = scrollToBottom;
    }));
    this.subscriptions.add(atom.workspace.observeTextEditors(async (/* editor*/) => {
      // await self.updateStatusBar();
    }));
    this.subscriptions.add(atom.workspace.onDidChangeActivePaneItem(async (/* paneItem*/) => {
      // await self.updateStatusBar();
    }));

    this.subscriptions.add(new Disposable(() => {
      atom.workspace.getPaneItems().forEach((item) => {
        if (item instanceof ConsoleOutputView) {
          item.destroy();
        }
      });
    }));

    this.deserializeConsoleOutput = function deserializeConsoleOutput(serializedState :any) {
      console.log('serializedState', serializedState);
      self.panel = new ConsoleOutputView({
        state: self.getStore().getState(),
        ansiToHtml: self.ansiToHtml,
        scrollToBottom: self.scrollToBottom,
      });
      // self.panel = new ConsoleOutputView({
      //   output: self.output,
      //   ansiToHtml: self.ansiToHtml,
      //   scrollToBottom: self.scrollToBottom });
      return self.panel;
    };

    this.subscriptions.add(atom.workspace.addOpener((uri) => {
      if (uri === 'atom://tester-console-output') {
        return self.deserializeConsoleOutput();
      }
    }));

    this.subscriptions.add(new Disposable(() => {
      atom.workspace.getPaneItems().forEach((item) => {
        if (item instanceof ResultView) {
          item.destroy();
        }
      });
    }));

    this.deserializeResultView = function deserializeResultView(serializedState :any) {
      console.log('serializedState', serializedState);
      self.resultView = new ResultView({
        state: self.getStore().getState(),
        ansiToHtml: self.ansiToHtml,
        softWrap: atom.config.get('tester.softWrapDefault'),
      });
      // self.resultView = new ResultView({
      //   messages: self.messages,
      //   ansiToHtml: self.ansiToHtml,
      //   softWrap: atom.config.get('tester.softWrapDefault'),
      //   runningTestersCount: self.runningTestersCount,
      // });
      self.resultView.onTestButtonClick((additionalArgs) => {
        self.registryTesters.test(null, true, additionalArgs);
      });
      return self.resultView;
    };

    this.subscriptions.add(atom.workspace.addOpener((uri) => {
      if (uri === 'atom://tester-result-view') {
        return self.deserializeResultView();
      }
    }));

    const onShouldTestHandler = async () => {
      if (!self.lastTestEditor) {
        self.lastTestEditor = atom.workspace.getActiveTextEditor();
      }
      const activeItem = self.lastTestEditor || atom.workspace.getActivePaneItem();
      const editorTester = self.registryEditors.get(activeItem);

      if (editorTester) {
        // await self.updateStatusBar();
        editorTester.test();
      }
    };
    const onShouldTestProjectHandler = () => {
      self.registryTesters.test(null, true, self.resultView ? self.resultView.getAdditionalArgs() : '');
    };

    this.commands.onShouldTestLast(() => {
      if (self.lastTestEditor) {
        onShouldTestHandler();
      } else {
        onShouldTestProjectHandler();
      }
    });

    this.commands.onShouldTestProject(onShouldTestProjectHandler);

    this.commands.onShouldTest(onShouldTestHandler);

    this.commands.onShouldStop(() => {
      self.registryTesters.stop(atom.workspace.getActiveTextEditor());
    });

    this.commands.onShouldToggleTesterOutput(() => {
      this.togglePanel();
    });
    this.commands.onShouldToggleTesterResultView(() => {
      this.toggleResultView();
    });

    this.commands.onShouldGoToNextTest(() => {
      if (!this.resultView) { return; }
      this.resultView.goToNextTest();
    });
    this.commands.onShouldGoToPreviousTest(() => {
      if (!this.resultView) { return; }
      this.resultView.goToPreviousTest();
    });

    this.registryEditors.observe((editorTester) => {
      editorTester.onShouldTest(() => {
        self.registryTesters.test(editorTester, null, self.resultView ? self.resultView.getAdditionalArgs() : '');
      });
      editorTester.onShouldStop(() => {
        self.registryTesters.stop(editorTester);
      });
    });

    this.registryTesters.onDidUpdateMessages(async ({ editor, messages, output }) => {
      self.getStore().dispatch(actions.updateEditorAction(editor));
      self.getStore().dispatch(actions.updateMessagesAction(messages));
      self.getStore().dispatch(actions.updateOutputAction(output));

      // messages.forEach((message, index) => {
      //   if (!message || typeof message !== 'object') {
      //     messages.splice(index, 1);
      //     console.warn('Tester: Message is not valid. Remove it from globals.', message);
      //   }
      // });
      // this.messages = messages;
      // this.output = output;
      // await self.updateStatusBar();
      // if (this.panel) {
      //   await this.panel.update({ output });
      // }
      // if (this.resultView) {
      //   await this.resultView.update({ messages: this.messages });
      // }
      //
      // if (editor) {
      //   const gutter = self.registryEditors.handleGutter(editor);
      //   if (!gutter) {
      //     return;
      //   }
      //   clearInlineMessages(editor);
      //   decorateGutter(editor, gutter, this.messages);
      //   if (this.showInlineError) {
      //     setInlineMessages(editor, this.messages, this.ansiToHtml, this.inlineErrorPosition);
      //   }
      // }
    });

    this.registryTesters.onDidBeginTesting(async ({ editorTester }) => {
      self.getStore().dispatch(actions.updateEditorAction(editorTester ? editorTester.editor : null));
      self.getStore().dispatch(actions.increaseCounterAction());

      // self.lastTestEditor = editorTester ? editorTester.editor : null;
      // self.runningTestersCount += 1;
      // if (self.resultView) {
      //   await self.resultView.update({ runningTestersCount: self.runningTestersCount });
      // }
      // await self.updateStatusBar();
    });

    this.registryTesters.onDidFinishTesting(async () => {
      self.getStore().dispatch(actions.decreaseCounterAction());
      // self.runningTestersCount -= 1;
      // if (self.resultView) {
      //   await self.resultView.update({ runningTestersCount: self.runningTestersCount });
      // }
      // await self.updateStatusBar();
    });

    this.registryEditors.activate();

    setTimeout(() => {
      // NOTE: Atom triggers this on boot so wait a while
      if (!self.subscriptions.disposed) {
        self.subscriptions.add(atom.project.onDidChangePaths(() => {
          self.registryEditors.handleGutter();
          self.commands.test();
        }));
      }
    }, 100);
  }

  createStatusBar(statusBar : Object) {
    if (!atom.config.get('tester.showStatusBar')) {
      return;
    }
    const onclick = () => {
      const statusBarOnClick = atom.config.get('tester.statusBarOnClick');
      if (statusBarOnClick === 'console' || statusBarOnClick === 'both') {
        this.togglePanel();
      }
      if (statusBarOnClick === 'results' || statusBarOnClick === 'both') {
        this.toggleResultView();
      }
    };
    this.statusBarTile = new StatusBarTile({ state: this.getStore().getState(), onclick });
    // this.statusBarTile = new StatusBarTile({
    //   messages: this.messages,
    //   runningTestersCount: this.runningTestersCount,
    //   onclick,
    // });

    statusBar.addLeftTile({
      item: this.statusBarTile.element,
      priority: -50,
    });
  }
  //
  // async updateStatusBar() {
  //   if (this.statusBarTile) {
  //     await this.statusBarTile.update({
  //       messages: this.messages,
  //       runningTestersCount: this.runningTestersCount,
  //     });
  //   }
  // }

  togglePanel() {
    if (atom.getVersion() < '1.17') {
      return atom.notifications.addWarning('Please update atom to version >=1.17 to use tester dock views.');
    }
    atom.workspace.toggle('atom://tester-console-output');
  }

  toggleResultView() {
    if (atom.getVersion() < '1.17') {
      return atom.notifications.addWarning('Please update atom to version >=1.17 to use tester dock views.');
    }
    atom.workspace.toggle('atom://tester-result-view');
  }

  async dispose() {
    if (this.statusBarTile) {
      await this.statusBarTile.destroy();
    }
    if (this.panel) {
      await this.panel.destroy();
    }
    if (this.resultView) {
      await this.resultView.destroy();
    }
    this.subscriptions.dispose();
  }

  addTester(tester : TesterProvider) {
    this.registryTesters.addTester(tester);
  }

  deleteTester(tester : TesterProvider) {
    this.registryTesters.deleteTester(tester);
  }
}

export default {
  instance: null,
  consumeStatusBar(statusBar : any) {
    this.instance.createStatusBar(statusBar);
  },

  initialize(initialState: any) {
    console.log('initialState', initialState);
    this.instance = new Tester(initialState);
    this.deserializeResultView = this.instance.deserializeResultView;
    this.deserializeConsoleOutput = this.instance.deserializeConsoleOutput;
  },

  getInstance(): ?Tester {
    return this.instance;
  },

  consumeTester(tester : Tester) : Disposable {
    const testers = [].concat(tester);
    for (const entry of testers) {
      this.instance.addTester(entry);
    }
    return new Disposable(() => {
      for (const entry of testers) {
        this.instance.deleteTester(entry);
      }
    });
  },

  serialize() {
    console.log('serialize main');
    if (!this.instance) {
      return {};
    }
    return this.instance.getStore().getState();
  },

  deactivate() {
    this.instance.dispose();
  },
};
<|endoftext|>


<commit_before>/* globals Task, ClientID */

const {PlacesProvider} = require("lib/PlacesProvider");
const {SearchProvider} = require("lib/SearchProvider");
const {MetadataStore} = require("lib/MetadataStore");
const {ActivityStreams} = require("lib/ActivityStreams");
const {Cu} = require("chrome");

Cu.import("resource://gre/modules/ClientID.jsm");
Cu.import("resource://gre/modules/Task.jsm");

let app = null;
let metadataStore = null;

Object.assign(exports, {
  main(options) {

    // options.loadReason can be install/enable/startup/upgrade/downgrade
    PlacesProvider.links.init();
    SearchProvider.search.init();
    options.telemetry = false;

    Task.spawn(function*() {
      options.clientID = yield ClientID.getClientID();
      metadataStore = new MetadataStore();
      yield metadataStore.asyncConnect();
      app = new ActivityStreams(metadataStore, options);
    }.bind(this));
  },

  onUnload(reason) {
    if (app) {
      app.unload(reason);
      if (reason === "uninstall" || reason === "disable") {
        metadataStore.asyncTearDown();
      } else {
        metadataStore.asyncClose();
      }
      app = null;
    }
    PlacesProvider.links.uninit();
    SearchProvider.search.uninit();
  }
});
<commit_msg>fix(addon): #992 Always close metadataStore<commit_after>/* globals Task, ClientID */

const {PlacesProvider} = require("lib/PlacesProvider");
const {SearchProvider} = require("lib/SearchProvider");
const {MetadataStore} = require("lib/MetadataStore");
const {ActivityStreams} = require("lib/ActivityStreams");
const {Cu} = require("chrome");

Cu.import("resource://gre/modules/ClientID.jsm");
Cu.import("resource://gre/modules/Task.jsm");

let app = null;
let metadataStore = null;

Object.assign(exports, {
  main(options) {

    // options.loadReason can be install/enable/startup/upgrade/downgrade
    PlacesProvider.links.init();
    SearchProvider.search.init();
    options.telemetry = false;

    Task.spawn(function*() {
      options.clientID = yield ClientID.getClientID();
      metadataStore = new MetadataStore();
      yield metadataStore.asyncConnect();
      app = new ActivityStreams(metadataStore, options);
    }.bind(this));
  },

  onUnload(reason) {
    if (app) {
      app.unload(reason);
      if (reason === "uninstall" || reason === "disable") {
        metadataStore.asyncTearDown();
      }
      app = null;
    }
    metadataStore.asyncClose();
    PlacesProvider.links.uninit();
    SearchProvider.search.uninit();
  }
});
<|endoftext|>


<commit_before>var canvas = document.getElementById('g');
var controls = new Controls();
var camera = new Camera(canvas, 0, 0, 3.0);
var backgroundLayer = [];
var entities = [];
var player = new Player(0, 0);
var enemyTimer = 0;
var healthTimer = 0;
var enemyCountdown = 10;

entities.push(player);
placeRandomEnemy();

var loop = new GameLoop();

loop.start(function frame(deltaT) {
    enemyTimer += deltaT;
    healthTimer += deltaT;

    if(Math.round(enemyTimer) >= enemyCountdown) {
        placeRandomEnemy();
        enemyTimer -= enemyCountdown;

        if(enemyCountdown > 1)
            enemyCountdown--;
    }

    if(healthTimer >= 1 && player.health > 0) {
        healthTimer--;
        player.health = player.health >= 100 ? 100 : player.health + 1;
    }

    backgroundLayer.forEach(function(entity) {
        if(entity.update)
            entity.update(controls.states, deltaT);
    });

    entities.forEach(function(entity) {
        entity.update(controls.states, deltaT);
    });

    camera.render(backgroundLayer, entities);
});

function placeRandomEnemy() {
    if(player.health > 0) {
        var x = player.x + Math.sign(Math.random() - 0.5) * 100;
        var y = player.y + Math.sign(Math.random() - 0.5) * 100;

        entities.push(new Enemy(x, y));
    }
}<commit_msg>When the game is over you can now reset the game by pressing space.<commit_after>var canvas = document.getElementById('g');
var controls = new Controls();
var camera = new Camera(canvas, 0, 0, 3.0);
var backgroundLayer = [];
var entities = [];
var player = new Player(0, 0);
var enemyTimer = 0;
var healthTimer = 0;
var enemyCountdown = 10;

entities.push(player);
placeRandomEnemy();

var loop = new GameLoop();

loop.start(function frame(deltaT) {
    enemyTimer += deltaT;
    healthTimer += deltaT;

    if(Math.round(enemyTimer) >= enemyCountdown) {
        placeRandomEnemy();
        enemyTimer -= enemyCountdown;

        if(enemyCountdown > 1)
            enemyCountdown--;
    }

    if(healthTimer >= 1 && player.health > 0) {
        healthTimer--;
        player.health = player.health >= 100 ? 100 : player.health + 1;
    }

    if(player.health <= 0 && controls.states.shoot) {
        resetGame();
    }

    backgroundLayer.forEach(function(entity) {
        if(entity.update)
            entity.update(controls.states, deltaT);
    });

    entities.forEach(function(entity) {
        entity.update(controls.states, deltaT);
    });

    camera.render(backgroundLayer, entities);
});

function placeRandomEnemy() {
    if(player.health > 0) {
        var x = player.x + Math.sign(Math.random() - 0.5) * 100;
        var y = player.y + Math.sign(Math.random() - 0.5) * 100;

        entities.push(new Enemy(x, y));
    }
}

function resetGame() {
    backgroundLayer = [];
    entities = [];
    player = new Player(0, 0);
    enemyTimer = 0;
    healthTimer = 0;
    enemyCountdown = 10;

    entities.push(player);
    placeRandomEnemy();
}<|endoftext|>


<commit_before>var Program = require('commander');
var Commands = require('./commands');

// Define the program commands.
Program.version(require('../package.json').version);

Program
  .command('join [name]')
  .alias('j')
  .description('Join a room with the specified name.')
  .action(Commands.joinRoom);

Program
  .command('authorize')
  .alias('set-auth')
  .description('Set the access-key token for client authentication.')
  .action(Commands.authorize);

Program
  .command('whoami')
  .alias('me')
  .description('Display your user information based on the existing token.')
  .action(Commands.whoAmI);

Program
  .command('rooms')
  .alias('status')
  .description('Display a list of rooms that you are part of.')
  .action(Commands.rooms);

Program
  .option('--token [token]', 'Set the access-key token for client authentication. This won\'t be persisted.');

// Parse the application arguments.
Program.parse(process.argv);

// Check if there are no arguments.
if (!process.argv.slice(2).length) {
  // Display the application help.
  Program.outputHelp();
}

// Check if there is a token on the arguments.
if (Program.token) {
  Command.setToken(Program.token);
}
<commit_msg>Fix a typo in main.js<commit_after>var Program = require('commander');
var Commands = require('./commands');

// Define the program commands.
Program.version(require('../package.json').version);

Program
  .command('join [name]')
  .alias('j')
  .description('Join a room with the specified name.')
  .action(Commands.joinRoom);

Program
  .command('authorize')
  .alias('set-auth')
  .description('Set the access-key token for client authentication.')
  .action(Commands.authorize);

Program
  .command('whoami')
  .alias('me')
  .description('Display your user information based on the existing token.')
  .action(Commands.whoAmI);

Program
  .command('rooms')
  .alias('status')
  .description('Display a list of rooms that you are part of.')
  .action(Commands.rooms);

Program
  .option('--token [token]', 'Set the access-key token for client authentication. This won\'t be persisted.');

// Parse the application arguments.
Program.parse(process.argv);

// Check if there are no arguments.
if (!process.argv.slice(2).length) {
  // Display the application help.
  Program.outputHelp();
}

// Check if there is a token on the arguments.
if (Program.token) {
  Commands.setToken(Program.token);
}
<|endoftext|>