(function (root, factory) {
	if (typeof define === 'function' && define.amd) {
		define([], factory);
	} else {
		root.orion = root.orion || {};
		root.orion.codeEdit = factory();
	}
}(this, function () {
/**
 * almond 0.2.4 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved.
 * Available via the MIT or new BSD license.
 * see: http://github.com/jrburke/almond for details
 */
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*jslint sloppy: true */
/*global setTimeout: false */

var requirejs, require, define;
(function (undef) {
    var main, req, makeMap, handlers,
        defined = {},
        waiting = {},
        config = {},
        defining = {},
        hasOwn = Object.prototype.hasOwnProperty,
        aps = [].slice;

    function hasProp(obj, prop) {
        return hasOwn.call(obj, prop);
    }

    /**
     * Given a relative module name, like ./something, normalize it to
     * a real name that can be mapped to a path.
     * @param {String} name the relative name
     * @param {String} baseName a real name that the name arg is relative
     * to.
     * @returns {String} normalized name
     */
    function normalize(name, baseName) {
        var nameParts, nameSegment, mapValue, foundMap,
            foundI, foundStarMap, starI, i, j, part,
            baseParts = baseName && baseName.split("/"),
            map = config.map,
            starMap = (map && map['*']) || {};

        //Adjust any relative paths.
        if (name && name.charAt(0) === ".") {
            //If have a base name, try to normalize against it,
            //otherwise, assume it is a top-level require that will
            //be relative to baseUrl in the end.
            if (baseName) {
                //Convert baseName to array, and lop off the last part,
                //so that . matches that "directory" and not name of the baseName's
                //module. For instance, baseName of "one/two/three", maps to
                //"one/two/three.js", but we want the directory, "one/two" for
                //this normalization.
                baseParts = baseParts.slice(0, baseParts.length - 1);

                name = baseParts.concat(name.split("/"));

                //start trimDots
                for (i = 0; i < name.length; i += 1) {
                    part = name[i];
                    if (part === ".") {
                        name.splice(i, 1);
                        i -= 1;
                    } else if (part === "..") {
                        if (i === 1 && (name[2] === '..' || name[0] === '..')) {
                            //End of the line. Keep at least one non-dot
                            //path segment at the front so it can be mapped
                            //correctly to disk. Otherwise, there is likely
                            //no path mapping for a path starting with '..'.
                            //This can still fail, but catches the most reasonable
                            //uses of ..
                            break;
                        } else if (i > 0) {
                            name.splice(i - 1, 2);
                            i -= 2;
                        }
                    }
                }
                //end trimDots

                name = name.join("/");
            } else if (name.indexOf('./') === 0) {
                // No baseName, so this is ID is resolved relative
                // to baseUrl, pull off the leading dot.
                name = name.substring(2);
            }
        }

        //Apply map config if available.
        if ((baseParts || starMap) && map) {
            nameParts = name.split('/');

            for (i = nameParts.length; i > 0; i -= 1) {
                nameSegment = nameParts.slice(0, i).join("/");

                if (baseParts) {
                    //Find the longest baseName segment match in the config.
                    //So, do joins on the biggest to smallest lengths of baseParts.
                    for (j = baseParts.length; j > 0; j -= 1) {
                        mapValue = map[baseParts.slice(0, j).join('/')];

                        //baseName segment has  config, find if it has one for
                        //this name.
                        if (mapValue) {
                            mapValue = mapValue[nameSegment];
                            if (mapValue) {
                                //Match, update name to the new value.
                                foundMap = mapValue;
                                foundI = i;
                                break;
                            }
                        }
                    }
                }

                if (foundMap) {
                    break;
                }

                //Check for a star map match, but just hold on to it,
                //if there is a shorter segment match later in a matching
                //config, then favor over this star map.
                if (!foundStarMap && starMap && starMap[nameSegment]) {
                    foundStarMap = starMap[nameSegment];
                    starI = i;
                }
            }

            if (!foundMap && foundStarMap) {
                foundMap = foundStarMap;
                foundI = starI;
            }

            if (foundMap) {
                nameParts.splice(0, foundI, foundMap);
                name = nameParts.join('/');
            }
        }

        return name;
    }

    function makeRequire(relName, forceSync) {
        return function () {
            //A version of a require function that passes a moduleName
            //value for items that may need to
            //look up paths relative to the moduleName
            return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync]));
        };
    }

    function makeNormalize(relName) {
        return function (name) {
            return normalize(name, relName);
        };
    }

    function makeLoad(depName) {
        return function (value) {
            defined[depName] = value;
        };
    }

    function callDep(name) {
        if (hasProp(waiting, name)) {
            var args = waiting[name];
            delete waiting[name];
            defining[name] = true;
            main.apply(undef, args);
        }

        if (!hasProp(defined, name) && !hasProp(defining, name)) {
            throw new Error('No ' + name);
        }
        return defined[name];
    }

    //Turns a plugin!resource to [plugin, resource]
    //with the plugin being undefined if the name
    //did not have a plugin prefix.
    function splitPrefix(name) {
        var prefix,
            index = name ? name.indexOf('!') : -1;
        if (index > -1) {
            prefix = name.substring(0, index);
            name = name.substring(index + 1, name.length);
        }
        return [prefix, name];
    }

    /**
     * Makes a name map, normalizing the name, and using a plugin
     * for normalization if necessary. Grabs a ref to plugin
     * too, as an optimization.
     */
    makeMap = function (name, relName) {
        var plugin,
            parts = splitPrefix(name),
            prefix = parts[0];

        name = parts[1];

        if (prefix) {
            prefix = normalize(prefix, relName);
            plugin = callDep(prefix);
        }

        //Normalize according
        if (prefix) {
            if (plugin && plugin.normalize) {
                name = plugin.normalize(name, makeNormalize(relName));
            } else {
                name = normalize(name, relName);
            }
        } else {
            name = normalize(name, relName);
            parts = splitPrefix(name);
            prefix = parts[0];
            name = parts[1];
            if (prefix) {
                plugin = callDep(prefix);
            }
        }

        //Using ridiculous property names for space reasons
        return {
            f: prefix ? prefix + '!' + name : name, //fullName
            n: name,
            pr: prefix,
            p: plugin
        };
    };

    function makeConfig(name) {
        return function () {
            return (config && config.config && config.config[name]) || {};
        };
    }

    handlers = {
        require: function (name) {
            return makeRequire(name);
        },
        exports: function (name) {
            var e = defined[name];
            if (typeof e !== 'undefined') {
                return e;
            } else {
                return (defined[name] = {});
            }
        },
        module: function (name) {
            return {
                id: name,
                uri: '',
                exports: defined[name],
                config: makeConfig(name)
            };
        }
    };

    main = function (name, deps, callback, relName) {
        var cjsModule, depName, ret, map, i,
            args = [],
            usingExports;

        //Use name if no relName
        relName = relName || name;

        //Call the callback to define the module, if necessary.
        if (typeof callback === 'function') {

            //Pull out the defined dependencies and pass the ordered
            //values to the callback.
            //Default to [require, exports, module] if no deps
            deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
            for (i = 0; i < deps.length; i += 1) {
                map = makeMap(deps[i], relName);
                depName = map.f;

                //Fast path CommonJS standard dependencies.
                if (depName === "require") {
                    args[i] = handlers.require(name);
                } else if (depName === "exports") {
                    //CommonJS module spec 1.1
                    args[i] = handlers.exports(name);
                    usingExports = true;
                } else if (depName === "module") {
                    //CommonJS module spec 1.1
                    cjsModule = args[i] = handlers.module(name);
                } else if (hasProp(defined, depName) ||
                           hasProp(waiting, depName) ||
                           hasProp(defining, depName)) {
                    args[i] = callDep(depName);
                } else if (map.p) {
                    map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
                    args[i] = defined[depName];
                } else {
                    throw new Error(name + ' missing ' + depName);
                }
            }

            ret = callback.apply(defined[name], args);

            if (name) {
                //If setting exports via "module" is in play,
                //favor that over return value and exports. After that,
                //favor a non-undefined return value over exports use.
                if (cjsModule && cjsModule.exports !== undef &&
                        cjsModule.exports !== defined[name]) {
                    defined[name] = cjsModule.exports;
                } else if (ret !== undef || !usingExports) {
                    //Use the return value from the function.
                    defined[name] = ret;
                }
            }
        } else if (name) {
            //May just be an object definition for the module. Only
            //worry about defining if have a module name.
            defined[name] = callback;
        }
    };

    requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
        if (typeof deps === "string") {
            if (handlers[deps]) {
                //callback in this case is really relName
                return handlers[deps](callback);
            }
            //Just return the module wanted. In this scenario, the
            //deps arg is the module name, and second arg (if passed)
            //is just the relName.
            //Normalize module name, if it contains . or ..
            return callDep(makeMap(deps, callback).f);
        } else if (!deps.splice) {
            //deps is a config object, not an array.
            config = deps;
            if (callback.splice) {
                //callback is an array, which means it is a dependency list.
                //Adjust args if there are dependencies
                deps = callback;
                callback = relName;
                relName = null;
            } else {
                deps = undef;
            }
        }

        //Support require(['a'])
        callback = callback || function () {};

        //If relName is a function, it is an errback handler,
        //so remove it.
        if (typeof relName === 'function') {
            relName = forceSync;
            forceSync = alt;
        }

        //Simulate async callback;
        if (forceSync) {
            main(undef, deps, callback, relName);
        } else {
            //Using a non-zero value because of concern for what old browsers
            //do, and latest browsers "upgrade" to 4 if lower value is used:
            //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
            //If want a value immediately, use require('id') instead -- something
            //that works in almond on the global level, but not guaranteed and
            //unlikely to work in other AMD implementations.
            setTimeout(function () {
                main(undef, deps, callback, relName);
            }, 4);
        }

        return req;
    };

    /**
     * Just drops the config on the floor, but returns req in case
     * the config return value is used.
     */
    req.config = function (cfg) {
        config = cfg;
        return req;
    };

    define = function (name, deps, callback) {

        //This module may not have dependencies
        if (!deps.splice) {
            //deps is not an array, so probably means
            //an object literal or factory function for
            //the value. Adjust args.
            callback = deps;
            deps = [];
        }

        if (!hasProp(defined, name) && !hasProp(waiting, name)) {
            waiting[name] = [name, deps, callback];
        }
    };

    define.amd = {
        jQuery: true
    };
}());

define("almond", function(){});

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd, node*/
(function(root, factory) { // UMD
    if (typeof define === "function" && define.amd) { //$NON-NLS-0$
        define('orion/Deferred',factory);
    } else if (typeof exports === "object") { //$NON-NLS-0$
        module.exports = factory();
    } else {
        root.orion = root.orion || {};
        root.orion.Deferred = factory();
    }
}(this, function() {
    var queue = [],
        running = false;

    function run() {
        var fn;
        while ((fn = queue.shift())) {
            fn();
        }
        running = false;
    }

	var runAsync = (function() {
		if (typeof process !== "undefined" && typeof process.nextTick === "function") {
			var nextTick = process.nextTick;
    		return function() {
    			nextTick(run);
    		};
		} else if (typeof MutationObserver === "function") {
			var div = document.createElement("div");
			var observer = new MutationObserver(run);
			observer.observe(div, {
            	attributes: true
        	});
        	return function() {
        		div.setAttribute("class", "_tick");
        	};
		}
		return function() {
			setTimeout(run, 0);
		};
	})();

    function enqueue(fn) {
        queue.push(fn);
        if (!running) {
            running = true;
            runAsync();
        }
    }

    function noReturn(fn) {
        return function(result) {
            fn(result);
        };
    }
    
    function settleDeferred(fn, result, deferred) {
    	try {
    		var listenerResult = fn(result);
    		var listenerThen = listenerResult && (typeof listenerResult === "object" || typeof listenerResult === "function") && listenerResult.then;
    		if (typeof listenerThen === "function") {
    			if (listenerResult === deferred.promise) {
    				deferred.reject(new TypeError());
    			} else {
    				var listenerResultCancel = listenerResult.cancel;
    				if (typeof listenerResultCancel === "function") {
    					deferred._parentCancel = listenerResultCancel.bind(listenerResult);
    				} else {
    					delete deferred._parentCancel;
    				}
    				listenerThen.call(listenerResult, noReturn(deferred.resolve), noReturn(deferred.reject), noReturn(deferred.progress));
    			}
    		} else {
    			deferred.resolve(listenerResult);
    		}
    	} catch (e) {
    		deferred.reject(e);
    	}
    }


    /**
     * @name orion.Promise
     * @class Interface representing an eventual value.
     * @description Promise is an interface that represents an eventual value returned from the single completion of an operation.
     *
     * <p>For a concrete class that implements Promise and provides additional API, see {@link orion.Deferred}.</p>
     * @see orion.Deferred
     * @see orion.Deferred#promise
     */
    /**
     * @name then
     * @function
     * @memberOf orion.Promise.prototype
     * @description Adds handlers to be called on fulfillment or progress of this promise.
     * @param {Function} [onResolve] Called when this promise is resolved.
     * @param {Function} [onReject] Called when this promise is rejected.
     * @param {Function} [onProgress] May be called to report progress events on this promise.
     * @returns {orion.Promise} A new promise that is fulfilled when the given <code>onResolve</code> or <code>onReject</code>
     * callback is finished. The callback's return value gives the fulfillment value of the returned promise.
     */
    /**
     * Cancels this promise.
     * @name cancel
     * @function
     * @memberOf orion.Promise.prototype
     * @param {Object} reason The reason for canceling this promise.
     * @param {Boolean} [strict]
     */

    /**
     * @name orion.Deferred
     * @borrows orion.Promise#then as #then
     * @borrows orion.Promise#cancel as #cancel
     * @class Provides abstraction over asynchronous operations.
     * @description Deferred provides abstraction over asynchronous operations.
     *
     * <p>Because Deferred implements the {@link orion.Promise} interface, a Deferred may be used anywhere a Promise is called for.
     * However, in most such cases it is recommended to use the Deferred's {@link #promise} field instead, which exposes a 
     * simplified, minimally <a href="https://github.com/promises-aplus/promises-spec">Promises/A+</a>-compliant interface to callers.</p>
     */
    function Deferred() {
        var result, state, listeners = [],
            _this = this;

        function notify() {
            var listener;
            while ((listener = listeners.shift())) {
                var deferred = listener.deferred;
                var methodName = state === "fulfilled" ? "resolve" : "reject"; //$NON-NLS-0$ //$NON-NLS-1$ //$NON-NLS-2$
                var fn = listener[methodName];
                if (typeof fn === "function") { //$NON-NLS-0$
                	settleDeferred(fn, result, deferred);
                } else {
                    deferred[methodName](result);
                }
            }
        }

        function _reject(error) {
            delete _this._parentCancel;
            state = "rejected";
            result = error;
            if (listeners.length) {
                enqueue(notify);
            }
        }

        function _resolve(value) {
            function once(fn) {
                return function(result) {
                    if (!state || state === "assumed") {
                          fn(result);
                    }
                };
            }
            delete _this._parentCancel;
            try {
                var valueThen = value && (typeof value === "object" || typeof value === "function") && value.then;
                if (typeof valueThen === "function") {
                    if (value === _this) {
                        _reject(new TypeError());
                    } else {
                        state = "assumed";
                        var valueCancel = value && value.cancel;
                        if (typeof valueCancel !== "function") {
                            var deferred = new Deferred();
                            value = deferred.promise;
                            try {
                                valueThen(deferred.resolve, deferred.reject, deferred.progress);
                            } catch (thenError) {
                                deferred.reject(thenError);
                            }
                            valueCancel = value.cancel;
                            valueThen = value.then;
                        }
                        result = value;
                        valueThen.call(value, once(_resolve), once(_reject));
                        _this._parentCancel = valueCancel.bind(value);
                    }
                } else {
                    state = "fulfilled";
                    result = value;
                    if (listeners.length) {
                        enqueue(notify);
                    }
                }
            } catch (error) {
                once(_reject)(error);
            }
        }

        function cancel() {
            var parentCancel = _this._parentCancel;
            if (parentCancel) {
                delete _this._parentCancel;
                parentCancel();
            } else if (!state) {
                var cancelError = new Error("Cancel");
                cancelError.name = "Cancel";
                _reject(cancelError);
            }
        }


        /**
         * Resolves this Deferred.
         * @name resolve
         * @function
         * @memberOf orion.Deferred.prototype
         * @param {Object} value
         * @returns {orion.Promise}
         */
        this.resolve = function(value) {
            if (!state) {
                _resolve(value);
            }
            return _this;
        };

        /**
         * Rejects this Deferred.
         * @name reject
         * @function
         * @memberOf orion.Deferred.prototype
         * @param {Object} error
         * @param {Boolean} [strict]
         * @returns {orion.Promise}
         */
        this.reject = function(error) {
            if (!state) {
                _reject(error);
            }
            return _this;
        };

        /**
         * Notifies listeners of progress on this Deferred.
         * @name progress
         * @function
         * @memberOf orion.Deferred.prototype
         * @param {Object} update The progress update.
         * @returns {orion.Promise}
         */
        this.progress = function(update) {
            if (!state) {
                listeners.forEach(function(listener) {
                    if (listener.progress) {
                        try {
                            listener.progress(update);
                        } catch (ignore) {
                            // ignore
                        }
                    }
                });
            }
            return _this.promise;
        };

        this.cancel = function() {
            if (_this._parentCancel) {
                setTimeout(cancel, 0);
            } else {
                cancel();
            }
            return _this;
        };

        // Note: "then" ALWAYS returns before having onResolve or onReject called as per http://promises-aplus.github.com/promises-spec/
        this.then = function(onFulfill, onReject, onProgress) {
        	var deferred = new Deferred();
            deferred._parentCancel = _this.promise.cancel;
            listeners.push({
                resolve: onFulfill,
                reject: onReject,
                progress: onProgress,
                deferred: deferred
            });
            if (state === "fulfilled" || state === "rejected") {
                enqueue(notify);
            }
            return deferred.promise;
        };

        /**
         * The promise exposed by this Deferred.
         * @name promise
         * @field
         * @memberOf orion.Deferred.prototype
         * @type orion.Promise
         */
        this.promise = {
            then: _this.then,
            cancel: _this.cancel
        };
    }

    /**
     * Returns a promise that represents the outcome of all the input promises.
     * <p>When <code>all</code> is called with a single parameter, the returned promise has <dfn>eager</dfn> semantics,
     * meaning that if any input promise rejects, the returned promise immediately rejects, without waiting for the rest of the
     * input promises to fulfill.</p>
     *
     * To obtain <dfn>lazy</dfn> semantics (meaning the returned promise waits for every input promise to fulfill), pass the
     * optional parameter <code>optOnError</code>.
     * @name all
     * @function
     * @memberOf orion.Deferred
     * @static
     * @param {orion.Promise[]} promises The input promises.
     * @param {Function} [optOnError] Handles a rejected input promise. <code>optOnError</code> is invoked for every rejected
     * input promise, and is passed the reason the input promise was rejected. <p><code>optOnError</code> can return a value, which
     * allows it to act as a transformer: the return value serves as the final fulfillment value of the rejected promise in the 
     * results array generated by <code>all</code>.
     * @returns {orion.Promise} A new promise. The returned promise is generally fulfilled to an <code>Array</code> whose elements
     * give the fulfillment values of the input promises. <p>However, if an input promise rejects and eager semantics is used, the 
     * returned promise will instead be fulfilled to a single error value.</p>
     */
    Deferred.all = function(promises, optOnError) {
        var count = promises.length,
            result = [],
            rejected = false,
            deferred = new Deferred();

        deferred.then(undefined, function() {
            rejected = true;
            promises.forEach(function(promise) {
                if (promise.cancel) {
                    promise.cancel();
                }
            });
        });

        function onResolve(i, value) {
            if (!rejected) {
                result[i] = value;
                if (--count === 0) {
                    deferred.resolve(result);
                }
            }
        }

        function onReject(i, error) {
            if (!rejected) {
                if (optOnError) {
                    try {
                        onResolve(i, optOnError(error));
                        return;
                    } catch (e) {
                        error = e;
                    }
                }
                deferred.reject(error);
            }
        }

        if (count === 0) {
            deferred.resolve(result);
        } else {
            promises.forEach(function(promise, i) {
                promise.then(onResolve.bind(undefined, i), onReject.bind(undefined, i));
            });
        }
        return deferred.promise;
    };

    /**
     * Applies callbacks to a promise or to a regular object.
     * @name when
     * @function
     * @memberOf orion.Deferred
     * @static
     * @param {Object|orion.Promise} value Either a {@link orion.Promise}, or a normal value.
     * @param {Function} onResolve Called when the <code>value</code> promise is resolved. If <code>value</code> is not a promise,
     * this function is called immediately.
     * @param {Function} onReject Called when the <code>value</code> promise is rejected. If <code>value</code> is not a promise, 
     * this function is never called.
     * @param {Function} onProgress Called when the <code>value</code> promise provides a progress update. If <code>value</code> is
     * not a promise, this function is never called.
     * @returns {orion.Promise} A new promise.
     */
    Deferred.when = function(value, onResolve, onReject, onProgress) {
        var promise, deferred;
        if (value && typeof value.then === "function") { //$NON-NLS-0$
            promise = value;
        } else {
            deferred = new Deferred();
            deferred.resolve(value);
            promise = deferred.promise;
        }
        return promise.then(onResolve, onReject, onProgress);
    };

    return Deferred;
}));
/*******************************************************************************
 * @license
 * Copyright (c) 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
// Encoding Shim -- see http://encoding.spec.whatwg.org/
(function(global) {
	function EncodingError() {
		Error.apply(this, arguments);
	}
	EncodingError.prototype = new Error();
	EncodingError.prototype.constructor = EncodingError;
	EncodingError.prototype.name = "EncodingError";

	function between(value, min, max) {
		return value >= min && value <= max;
	}

	function TextDecoder(label, options) {
		var encoding = label || "utf-8";
		if (encoding !== "utf-8" && encoding !== "utf8" && encoding !== "unicode-1-1-utf-8") {
			throw new TypeError("only utf-8 supported");
		}
		Object.defineProperties(this, {
			encoding: {
				value: encoding,
				enumerable: true
			},
			_fatal: {
				value: options && options.fatal
			},
			_saved: {
				value: [],
				writable: true
			},
			_checkBOM: {
				value: true,
				writable: true
			}
		});
	}
	TextDecoder.prototype.decode = function(input, options) {
		input = (input instanceof Uint8Array) ? input : new Uint8Array(input);
		var first, second, third, fourth, point;
		var stream = options && options.stream;
		var savedlen = this._saved.length;
		var inputlen = input.length;
		var offset = 0;
		var used = 0;
		var charCodes = new Uint16Array(inputlen + savedlen);
		var written = 0;

		function onError() {
			if (this._fatal) {
				this._saved.length = savedlen = 0;
				used = offset;
				this._checkBOM = this._checkBOM || !stream;
				throw new EncodingError();
			}
			charCodes[written++] = 0xFFFD;
		}
		
		if (this._checkBOM && inputlen) {
			if ((savedlen + inputlen) > 2) {
				for (var i = savedlen; i < 3; i++) {
					this._saved.push(input[offset++]);
				}
				if (this._saved[0] !== 0xEF || this._saved[1] !== 0xBB || this._saved[2] !== 0xBF) {
					offset = 0;
					this._saved.length = savedlen;
				} else {
					savedlen = this._saved.length -= 3;
				}
				this._checkBOM = false;
			} else if (stream) {
				while (offset < inputlen) {
					this._saved.push(input[offset++]);
				}
			}
		}
		while (offset < inputlen) {
			first = savedlen > 0 ? this._saved[0] : input[offset++];
			if (first < 0x80) {
				charCodes[written++] = first;
			} else if (between(first, 0xC2, 0xDF)) {
				if (offset === inputlen) break;
				second = savedlen > 1 ? this._saved[1] : input[offset++];
				if (!between(second, 0x80, 0xBF)) {
					onError();
					continue;
				}
				charCodes[written++] = ((first & 0x1F) << 6) | (second & 0x3F);
			} else if (between(first, 0xE0, 0xEF)) {
				if (offset === inputlen) break;
				second = savedlen > 1 ? this._saved[1] : input[offset++];
				if ((first === 0xE0 && !between(second, 0xA0, 0xBF)) || (first === 0xED && !between(second, 0x80, 0x9F)) || !between(second, 0x80, 0xBF)) {
					onError();
					continue;
				}
				if (offset === inputlen) break;
				third = savedlen > 2 ? this._saved[2] : input[offset++];
				if (!between(third, 0x80, 0xBF)) {
					onError();
					continue;
				}
				charCodes[written++] = ((first & 0x0F) << 12) | ((second & 0x3F) << 6) | (third & 0x3F);
			} else if (between(first, 0xF0, 0xF4)) {
				if (offset === inputlen) break;
				second = savedlen > 1 ? this._saved[1] : input[offset++];
				if ((first === 0xF0 && !between(second, 0x90, 0xBF)) || (first === 0xF4 && !between(second, 0x80, 0x8F)) || !between(second, 0x80, 0xBF)) {
					onError();
					continue;
				}
				if (offset === inputlen) break;
				third = savedlen > 2 ? this._saved[2] : input[offset++];
				if (!between(third, 0x80, 0xBF)) {
					onError();
					continue;
				}
				if (offset === inputlen) break;
				fourth = input[offset++];
				if (!between(fourth, 0x80, 0xBF)) {
					onError();
					continue;
				}
				point = (((first & 0x07) << 18) | ((second & 0x3F) << 12) | ((third & 0x3F) << 6) | (fourth & 0x3F)) & 0xFFFF;
				charCodes[written++] = (point >> 10) | 0xD800;
				charCodes[written++] = (point & 0x3FF) | 0xDC00;
			} else onError();
			used = offset;
			if (savedlen) {
				this._saved.length = savedlen = 0;
			}
		}
		while (used !== offset) {
			this._saved.push(input[used++]);
		}
		this._checkBOM = this._checkBOM || !stream;
		if (!stream && this._saved.length !== 0) {
			throw new EncodingError();
		}
		var result = [];
		for (var begin = 0; begin < written; begin += 0x10000) {
			result.push(String.fromCharCode.apply(null, charCodes.subarray(begin, Math.min(written, begin + 0x10000))));
		}
		return result.join("");
	};

	function TextEncoder(utfLabel) {
		var encoding = utfLabel || "utf-8";
		if (encoding !== "utf-8" && encoding !== "utf8" && encoding !== "unicode-1-1-utf-8") {
			throw new TypeError("only utf-8 supported");
		}
		Object.defineProperties(this, {
			encoding: {
				value: encoding,
				enumerable: true
			},
			_saved: {
				value: null,
				writable: true
			}
		});
	}
	TextEncoder.prototype.encode = function(input, options) {
		input = String(input !== undefined ? input : "");
		var first, second, point;
		var stream = options && options.stream;
		var inputlen = input.length;
		var offset = 0;
		var utf8 = new Uint8Array(3 * (inputlen + (this._saved === null ? 0 : 1)));
		var written = 0;

		while (offset < inputlen) {
			if (this._saved === null) {
				first = input.charCodeAt(offset++);
			} else {
				first = this._saved;
				this._saved = null;
			}
			if (first < 0x80) {
				utf8[written++] = first;
			} else if (first < 0x800) {
				utf8[written++] = 0xC0 | (first >> 6);
				utf8[written++] = 0x80 | (first & 0x3F);
			} else if (first < 0xD800 || first > 0xDBFF) {
				utf8[written++] = 0xE0 | (first >> 12);
				utf8[written++] = 0x80 | ((first >> 6) & 0x3F);
				utf8[written++] = 0x80 | (first & 0x3F);
			} else {
				if (offset < inputlen) {
					second = input.charCodeAt(offset++);
				} else if (stream) {
					this._saved = first;
					break;
				} else throw new EncodingError();
				if (!between(second, 0xDC00, 0xDFFF)) {
					throw new EncodingError();
				}
				point = 0x10000 | ((first & 0x03FF) << 10) | (second & 0x03FF);
				utf8[written++] = 0xF0 | (point >> 18);
				utf8[written++] = 0x80 | ((point >> 12) & 0x3F);
				utf8[written++] = 0x80 | ((point >> 6) & 0x3F);
				utf8[written++] = 0x80 | (point & 0x3F);
			}
		}
		if (!stream && this._saved !== null) {
			throw new EncodingError();
		}
		return utf8.buffer.slice ? new Uint8Array(utf8.buffer.slice(0, written)) : utf8.subarray(0, written);
	};

	global.TextDecoder = global.TextDecoder || TextDecoder;
	global.TextEncoder = global.TextEncoder || TextEncoder;
}((typeof global === "undefined") ? this || self : global));
define("orion/encoding-shim", function(){});

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
/*global StopIteration*/
// URL Shim -- see http://url.spec.whatwg.org/ and http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html

(function() {
    try {
        var testURL;
        if (typeof self.URL === "function" && self.URL.length !== 0 &&
                (testURL = new self.URL("http://www.w3.org?q")).protocol === "http:" && testURL.query) {
            return;
        }
    } catch (e) {}

    //[1]scheme, [2]authority, [3]path, [4]query, [5]fragment
    var _URI_RE = /^(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?$/;
    //[ userinfo "@" ] host [ ":" port ]
    var _AUTHORITY_RE = /^(?:(.*)@)?(\[[^\]]*\]|[^:]*)(?::(.*))?$/;

    var _NO_WS_RE = /^\S*$/;
    var _SCHEME_RE = /^([a-zA-Z](?:[a-zA-Z0-9+-.])*)$/;
    var _PORT_RE = /^\d*$/;
    var _HOST_RE = /^(\[[^\]\/?#\s]*\]|[^:\/?#\s]*)$/;
    var _HOSTPORT_RE = /^(\[[^\]\/?#\s]*\]|[^:\/?#\s]*)(?::(\d*))?$/;
    var _PATH_RE = /^([^?#\s]*)$/;
    var _QUERY_RE = /^([^\s]*)$/;
    var _FRAGMENT_RE = _NO_WS_RE;
    var _USERNAME_PASSWORD_RE = /([^:]*):?(.*)/;

    var STOP_ITERATION = typeof StopIteration !== "undefined" ? StopIteration : new Error("Stop Iteration");
    var DEFAULT_PORTS = {
        "ftp:": "21",
            "gopher:": "70",
            "http:": "80",
            "https:": "443",
            "ws:": "80",
            "wss:": "443"
    };

    function _checkString(txt) {
        if (typeof txt !== "string") {
            throw new TypeError();
        }
    }

    function _parseQuery(query) {
        return query ? query.split("&") : [];
    }

    function _stringifyQuery(pairs) {
        if (pairs.length === 0) {
            return "";
        }
        return pairs.join("&");
    }

    function _parsePair(pair) {
        var parsed = /([^=]*)(?:=?)(.*)/.exec(pair);
        var key = parsed[1] ? decodeURIComponent(parsed[1]) : "";
        var value = parsed[2] ? decodeURIComponent(parsed[2]) : "";
        return [key, value];
    }

    function _stringifyPair(entry) {
        var pair = encodeURIComponent(entry[0]);
        if (entry[1]) {
            pair += "=" + encodeURIComponent(entry[1]);
        }
        return pair;
    }

    function _createMapIterator(url, kind) {
        var query = "";
        var pairs = [];
        var index = 0;
        return {
            next: function() {
                if (query !== url.query) {
                    query = url.query;
                    pairs = _parseQuery(query);
                }
                if (index < pairs.length) {
                    var entry = _parsePair(pairs[index++]);
                    switch (kind) {
                        case "keys":
                            return entry[0];
                        case "values":
                            return entry[1];
                        case "keys+values":
                            return [entry[0], entry[1]];
                        default:
                            throw new TypeError();
                    }
                }
                throw STOP_ITERATION;
            }
        };
    }

    // See http://url.spec.whatwg.org/#interface-urlquery
    function URLQuery(url) {
        Object.defineProperty(this, "_url", {
            get: function() {
                return url._url;
            }
        });
    }

    Object.defineProperties(URLQuery.prototype, {
        get: {
            value: function(key) {
                _checkString(key);
                var result;
                var pairs = _parseQuery(this._url.query);
                pairs.some(function(pair) {
                    var entry = _parsePair(pair);
                    if (entry[0] === key) {
                        result = entry[1];
                        return true;
                    }
                });
                return result;
            },
            enumerable: true
        },
        set: {
            value: function(key, value) {
                _checkString(key);
                _checkString(value);
                var pairs = _parseQuery(this._url.query);
                var found = pairs.some(function(pair, i) {
                    var entry = _parsePair(pair);
                    if (entry[0] === key) {
                        entry[1] = value;
                        pairs[i] = _stringifyPair(entry);
                        return true;
                    }
                });
                if (!found) {
                    pairs.push(_stringifyPair([key, value]));
                }
                this._url.query = _stringifyQuery(pairs);
            },
            enumerable: true
        },
        has: {
            value: function(key) {
                _checkString(key);
                var pairs = _parseQuery(this._url.query);
                return pairs.some(function(pair) {
                    var entry = _parsePair(pair);
                    if (entry[0] === key) {
                        return true;
                    }
                });
            },
            enumerable: true
        },
            "delete": {
            value: function(key) {
                _checkString(key);
                var pairs = _parseQuery(this._url.query);
                var filtered = pairs.filter(function(pair) {
                    var entry = _parsePair(pair);
                    return entry[0] !== key;
                });
                if (filtered.length !== pairs.length) {
                    this._url.query = _stringifyQuery(filtered);
                    return true;
                }
                return false;
            },
            enumerable: true
        },
        clear: {
            value: function() {
                this._url.query = "";
            },
            enumerable: true
        },
        forEach: {
            value: function(callback, thisArg) {
                if (typeof callback !== "function") {
                    throw new TypeError();
                }
                var iterator = _createMapIterator(this._url, "keys+values");
                try {
                    while (true) {
                        var entry = iterator.next();
                        callback.call(thisArg, entry[1], entry[0], this);
                    }
                } catch (e) {
                    if (e !== STOP_ITERATION) {
                        throw e;
                    }
                }
            },
            enumerable: true
        },
        keys: {
            value: function() {
                return _createMapIterator(this._url, "keys");
            },
            enumerable: true
        },
        values: {
            value: function() {
                return _createMapIterator(this._url, "values");
            },
            enumerable: true
        },
        items: {
            value: function() {
                return _createMapIterator(this._url, "keys+values");
            }
        },
        size: {
            get: function() {
                return _parseQuery(this._url.query).length;
            },
            enumerable: true
        },
        getAll: {
            value: function(key) {
                _checkString(key);
                var result = [];
                var pairs = _parseQuery(this._url.query);
                pairs.forEach(function(pair) {
                    var entry = _parsePair(pair);
                    if (entry[0] === key) {
                        result.push(entry[1]);
                    }
                });
                return result;
            },
            enumerable: true
        },
        append: {
            value: function(key, value) {
                _checkString(key);
                _checkString(value);
                var pairs = _parseQuery(this._url.query);
                pairs.push(_stringifyPair([key, value]));
                this._url.query = _stringifyQuery(pairs);
            },
            enumerable: true
        }
    });

    function _makeAbsoluteURL(url, base) {
        if (!url.scheme && base) {
            url.scheme = base.scheme;
            if (!url.host && base.host) {
                url.userinfo = base.userinfo;
                url.host = base.host;
                url.port = base.port;
                url.pathRelative = true;
            }
        }
        if (url.pathRelative) {
            if (!url.path) {
                url.path = base.path;
            } else if (url.path[0] !== "/") {
                var basePath = /^(.*\/)[^\/]*$/.exec(base.path)[1] || "/";
                url.path = basePath + url.path;
            }
        }
    }

    function _normalizeScheme(scheme) {
        return scheme.toLowerCase();
    }

    function _normalizePort(port) {
        return port ? (/[1-9]\d*$/).exec(port)[0] : "";
    }

    function _normalizePath(path) {
        var result = [];
        path.split("/").forEach(function(segment) {
            if (segment === "..") {
            	if (result.length > 1) {
                	result.pop();
            	}
            } else if (segment !== ".") {
                result.push(segment);
            }
        });
        return result.join("/");
    }


    function _normalizeURL(url) {
        if (url.scheme) {
            url.scheme = _normalizeScheme(url.scheme);
        }
        if (url.port) {
            url.port = _normalizePort(url.port);
        }
        if (url.host && url.path) {
            url.path = _normalizePath(url.path);
        }
    }

    function _encodeWhitespace(text) {
        return text.replace(/\s/g, function(c) {
            return "%" + c.charCodeAt(0).toString(16);
        });
    }

    function _parseURL(input, base) {
        if (typeof input !== "string") {
            throw new TypeError();
        }

        input = _encodeWhitespace(input);

        var parsedURI = _URI_RE.exec(input);
        if (!parsedURI) {
            return null;
        }
        var url = {};
        url.scheme = parsedURI[1] || "";
        if (url.scheme && !_SCHEME_RE.test(url.scheme)) {
            return null;
        }
        var authority = parsedURI[2];
        if (authority) {
            var parsedAuthority = _AUTHORITY_RE.exec(authority);
            url.userinfo = parsedAuthority[1];
            url.host = parsedAuthority[2];
            url.port = parsedAuthority[3];
            if (url.port && !_PORT_RE.test(url.port)) {
                return null;
            }
        }
        url.path = parsedURI[3];
        url.query = parsedURI[4];
        url.fragment = parsedURI[5];

        _makeAbsoluteURL(url, base);
        _normalizeURL(url);
        return url;
    }

    function _serialize(url) {
        var result = (url.scheme ? url.scheme + ":" : "");
        if (url.host) {
            result += "//";
            if (url.userinfo) {
                result += url.userinfo + "@";
            }
            result += url.host;
            if (url.port) {
                result += ":" + url.port;
            }
        }
        result += url.path;
        if (url.query) {
            result += "?" + url.query;
        }
        if (url.fragment) {
            result += "#" + url.fragment;
        }
        return result;
    }

    // See http://url.spec.whatwg.org/#api
    function URL(input, base) {
        var baseURL;
        if (base) {
            base = base.href || base;
            baseURL = _parseURL(base);
            if (!baseURL || !baseURL.scheme) {
                throw new SyntaxError();
            }
            Object.defineProperty(this, "_baseURL", {
                value: baseURL
            });
        }

        var url = _parseURL(input, baseURL);
        if (!url) {
            throw new SyntaxError();
        }

        Object.defineProperty(this, "_input", {
            value: input,
            writable: true
        });

        Object.defineProperty(this, "_url", {
            value: url,
            writable: true
        });

        var query = new URLQuery(this);
        Object.defineProperty(this, "query", {
            get: function() {
                return this._url ? query : null;
            },
            enumerable: true
        });
    }

    Object.defineProperties(URL.prototype, {
    	toString: {
    		value: function() {
    			return this.href;
    		}		
    	},
        href: {
            get: function() {
                return this._url ? _serialize(this._url) : this._input;
            },
            set: function(value) {
                _checkString(value);
                this._input = value;
                this._url = _parseURL(this._input, this._baseURL);
            },
            enumerable: true
        },
        origin: {
            get: function() {
                return (this._url && this._url.host ? this.protocol + "//" + this.host : "");
            },
            enumerable: true
        },
        protocol: {
            get: function() {
                return this._url ? this._url.scheme + ":" : ":";
            },
            set: function(value) {
                _checkString(value);
                if (!this._url) {
                    return;
                }
                var scheme = (value.slice(-1) === ":") ? value.substring(0, value.length - 1) : value;
                if (scheme === "" || _SCHEME_RE.test(scheme)) {
                    this._url.scheme = _normalizeScheme(scheme);
                }

            },
            enumerable: true
        },
        _userinfo: { // note: not part of spec so not enumerable
            get: function() {
                return this._url ? this._url.userinfo : "";
            },
            set: function(value) {
                _checkString(value);
                if (!this._url) {
                    return;
                }
                this._url.userinfo = value;
            }
        },
        username: {
            get: function() {
                if (!this._url) {
                    return "";
                }
                var parsed = _USERNAME_PASSWORD_RE.exec(this._userinfo);
                var username = decodeURIComponent(parsed[1] || "");
                return username;
            },
            set: function(value) {
                _checkString(value);
                if (!this._url) {
                    return;
                }
                var parsed = _USERNAME_PASSWORD_RE.exec(this._userinfo);
                var userpass = [encodeURIComponent(value || "")];
                if (parsed[2]) {
                    userpass.push(parsed[2]);
                }
                this._userinfo = userpass.join(":");
            },
            enumerable: true
        },
        password: {
            get: function() {
                if (!this._url) {
                    return "";
                }
                var parsed = _USERNAME_PASSWORD_RE.exec(this._userinfo);
                var password = decodeURIComponent(parsed[2] || "");
                return password;
            },
            set: function(value) {
                _checkString(value);
                if (!this._url) {
                    return;
                }
                var parsed = _USERNAME_PASSWORD_RE.exec(this._userinfo);
                var userpass = [parsed[1] || ""];
                if (value) {
                    userpass.push(encodeURIComponent(value));
                }
                this._userinfo = userpass.join(":");
            },
            enumerable: true
        },
        host: {
            get: function() {
                var result = "";
                if (this._url && this._url.host) {
                    result += this._url.host;
                    if (this._url.port) {
                        result += ":" + this._url.port;
                    }
                }
                return result;
            },
            set: function(value) {
                _checkString(value);
                if (!this._url) {
                    return;
                }
                var result = _HOSTPORT_RE.exec(value);
                if (result) {
                    this._url.host = result[1];
                    this._url.port = _normalizePort(result[2]);
                }
            },
            enumerable: true
        },
        hostname: {
            get: function() {
                return this._url ? this._url.host : "";
            },
            set: function(value) {
                _checkString(value);
                if (!this._url) {
                    return;
                }
                var result = _HOST_RE.exec(value);
                if (result) {
                    this._url.host = value;
                }
            },
            enumerable: true
        },
        port: {
            get: function() {
                var port = this._url ? this._url.port || "" : "";
                if (port && port === DEFAULT_PORTS[this.protocol]) {
                    port = "";
                }
                return port;
            },
            set: function(value) {
                _checkString(value);
                if (!this._url) {
                    return;
                }
                var result = _PORT_RE.exec(value);
                if (result) {
                    this._url.port = _normalizePort(value);
                }
            },
            enumerable: true
        },
        pathname: {
            get: function() {
                return this._url ? this._url.path : "";
            },
            set: function(value) {
                _checkString(value);
                if (!this._url) {
                    return;
                }
                var result = _PATH_RE.exec(value);
                if (result) {
                    if (this._url.host && value && value[0] !== "/") {
                        value = "/" + value;
                    }
                    this._url.path = value ? _normalizePath(value) : "";
                }
            },
            enumerable: true
        },
        search: {
            get: function() {
                return (this._url && this._url.query ? "?" + this._url.query : "");
            },
            set: function(value) {
                _checkString(value);
                if (!this._url) {
                    return;
                }
                if (value && value[0] === "?") {
                    value = value.substring(1);
                }
                var result = _QUERY_RE.exec(value);
                if (result) {
                    this._url.query = value;
                }
            },
            enumerable: true
        },
        hash: {
            get: function() {
                return (this._url && this._url.fragment ? "#" + this._url.fragment : "");
            },
            set: function(value) {
                _checkString(value);
                if (!this._url) {
                    return;
                }
                if (value && value[0] === "#") {
                    value = value.substring(1);
                }
                var result = _FRAGMENT_RE.exec(value);
                if (result) {
                    this._url.fragment = value;
                }
            },
            enumerable: true
        }
    });

	var _URL = self.URL || self.webkitURL;
    if (_URL && _URL.createObjectURL) {
        Object.defineProperty(URL, "createObjectURL", {
            value: _URL.createObjectURL.bind(_URL),
            enumerable: false
        });

        Object.defineProperty(URL, "revokeObjectURL", {
            value: _URL.revokeObjectURL.bind(_URL),
            enumerable: false
        });
    }
    self.URL = URL;
}());
define("orion/URL-shim", function(){});

/*******************************************************************************
 * @license
 * Copyright (c) 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/

/*eslint-env browser, amd*/
/*global URL*/
define('embeddedEditor/helper/embeddedFileImpl',["orion/Deferred", "orion/encoding-shim", "orion/URL-shim"], function(Deferred) {
	
	function EmbeddedFileImpl(fileBase) {
		this.fileBase = fileBase;
		this.fileRoot = {};
	}
	
	EmbeddedFileImpl.prototype = {
		fetchChildren: function(/*fLocation*/) {
			return new Deferred().resolve([]);
		},
		loadWorkspaces: function() {
			return new Deferred().resolve([]);
		},
		loadWorkspace: function(/*fLocation*/) {
			return new Deferred().resolve([]);
		},
		createProject: function(/*url, projectName, serverPath, create*/) {
			throw new Error("Not supported"); //$NON-NLS-0$ 
		},
		createFolder: function(/*parentLocation, folderName*/) {
			throw new Error("Not supported"); //$NON-NLS-0$ 
		},
		createFile: function(/*parentLocation, fileName*/) {
			throw new Error("Not supported"); //$NON-NLS-0$ 
		},
		moveFile: function(/*sourceLocation, targetLocation, name*/) {
			throw new Error("Not supported"); //$NON-NLS-0$ 
		},
		copyFile: function(/*sourceLocation, targetLocation, name*/) {
			throw new Error("Not supported"); //$NON-NLS-0$ 
		},
		remoteImport: function(/*targetLocation, options*/) {
			throw new Error("Not supported"); //$NON-NLS-0$ 
		},
		remoteExport: function(/*sourceLocation, options) {
			throw new Error("Not supported"); //$NON-NLS-0$ 
		},
		readBlob: function(/*fLocation*/) {
			throw new Error("Not supported"); //$NON-NLS-0$ 
		},
		writeBlob: function(/*fLocation, contents, args*/) {
			throw new Error("Not supported"); //$NON-NLS-0$ 
		},
		_getFile: function(fLocation, create) {
			var locationURL = new URL(fLocation);
			var filePath = locationURL.pathname;
			if (!this.fileRoot[filePath] && create) {
				this.fileRoot[filePath] = {
					Name: locationURL.pathname.split("/").pop(),
					Location: filePath,
					LocalTimeStamp: Date.now(),
					ETag: 0
				};
			}
			return this.fileRoot[filePath];
		},
		/**
		 * Returns the contents or metadata of the file at the given location.
		 *
		 * @param {String} location The location of the file to get contents for
		 * @param {Boolean} [isMetadata] If defined and true, returns the file metadata, 
		 *   otherwise file contents are returned
		 * @return A deferred that will be provided with the contents or metadata when available
		 */
		read: function(fLocation, isMetadata) {
			var file = this._getFile(fLocation);
			if (!file) return new Deferred().reject();
			if(isMetadata){
				var meta = {
					Length: file.length,
					Directory: !!file.Directory,
					LocalTimeStamp: file.LocalTimeStamp,
					ETag: file.ETag,
					Location: file.Location,
					Name: file.Name,
					Parents: []
				};
				return new Deferred().resolve(meta);
			}
			return new Deferred().resolve(file.contents);
		},
		/**
		 * Writes the contents or metadata of the file at the given location.
		 *
		 * @param {String} location The location of the file to set contents for
		 * @param {String|Object} contents The content string, object describing the location of content, or a metadata object to write
		 * @param {String|Object} args Additional arguments used during write operation (i.e. ETag) 
		 * @return A deferred for chaining events after the write completes with new metadata object
		 */		
		write: function(fLocation, contents, args) {
			var file = this._getFile(fLocation, true);
			if (typeof contents === "string") {
				file.ETag++;
				file.LocalTimeStamp = Date.now();
				file.contents = contents;
			}
			return new Deferred().resolve(contents);
		},
		/**
		 * Deletes a file, directory, or project.
		 * @param {String} location The location of the file or directory to delete.
		 */
		deleteFile: function(fLocation) {
			var locationURL = new URL(fLocation);
			var filePath = locationURL.pathname;
			delete this.fileRoot[filePath];
			return new Deferred().resolve();
		}
	};
	EmbeddedFileImpl.prototype.constructor = EmbeddedFileImpl;

	return EmbeddedFileImpl;
});
/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define('orion/EventTarget',[],function() {
	/**
	 * Creates an Event Target
	 *
	 * @name orion.EventTarget
	 * @class Base for creating an Orion event target
	 */
	function EventTarget() {
		this._namedListeners = {};
	}

	EventTarget.prototype = /** @lends orion.EventTarget.prototype */
	{
		/**
		 * Dispatches a named event along with an arbitrary set of arguments. Any arguments after <code>eventName</code>
		 * will be passed to the event listener(s).
		 * @param {Object} event The event to dispatch. The event object MUST have a type field
		 * @returns {boolean} false if the event has been canceled and any associated default action should not be performed
		 * listeners (if any) have resolved.
		 */
		dispatchEvent: function(event) {
			if (!event.type) {
				throw new Error("unspecified type");
			}
			var listeners = this._namedListeners[event.type];
			if (listeners) {
				listeners.forEach(function(listener) {
					try {
						if (typeof listener === "function") {
							listener(event);
						} else {
							listener.handleEvent(event);
						}
					} catch (e) {
						if (typeof console !== 'undefined') {
							console.log(e); // for now, probably should dispatch an ("error", e)
						}
					}			
				});
			}
			return !event.defaultPrevented;
		},

		/**
		 * Adds an event listener for a named event
		 * @param {String} eventName The event name
		 * @param {Function} listener The function called when an event occurs
		 */
		addEventListener: function(eventName, listener) {
			if (typeof listener === "function" || listener.handleEvent) {
				this._namedListeners[eventName] = this._namedListeners[eventName] || [];
				this._namedListeners[eventName].push(listener);
			}
		},

		/**
		 * Removes an event listener for a named event
		 * @param {String} eventName The event name
		 * @param {Function} listener The function called when an event occurs
		 */
		removeEventListener: function(eventName, listener) {
			var listeners = this._namedListeners[eventName];
			if (listeners) {
				for (var i = 0; i < listeners.length; i++) {
					if (listeners[i] === listener) {
						if (listeners.length === 1) {
							delete this._namedListeners[eventName];
						} else {
							listeners.splice(i, 1);
						}
						break;
					}
				}
			}
		}
	};
	EventTarget.prototype.constructor = EventTarget;
	
	EventTarget.attach = function(obj) {
		var eventTarget = new EventTarget();
		obj.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget);
		obj.addEventListener = eventTarget.addEventListener.bind(eventTarget);
		obj.removeEventListener = eventTarget.removeEventListener.bind(eventTarget);
	};
	
	return EventTarget;
});
/*******************************************************************************
 * @license
 * Copyright (c) 2011, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define('orion/serviceregistry',["orion/Deferred", "orion/EventTarget"], function(Deferred, EventTarget) {

	/**
	 * @name orion.serviceregistry.ServiceReference
	 * @description Creates a new service reference.
	 * @class A reference to a service in the Orion service registry
	 * @param {String} serviceId The symbolic id of this service instance
	 * @param {String} name The service name
	 * @param {Object} properties A JSON object containing the service's declarative properties
	 */
	function ServiceReference(serviceId, objectClass, properties) {
		this._properties = properties || {};
		this._properties["service.id"] = serviceId;
		this._properties.objectClass = objectClass;
		this._properties["service.names"] = objectClass;
	}

	ServiceReference.prototype = /** @lends orion.serviceregistry.ServiceReference.prototype */
	{
		/**
		 * @name getPropertyKeys
		 * @description Returns the names of the declarative properties of this service.
		 * @function
		 * @public
		 * @memberof orion.serviceregistry.ServiceReference.prototype
		 * @returns the names of the declarative properties of this service
		 */
		getPropertyKeys: function() {
			var result = [];
			var name;
			for (name in this._properties) {
				if (this._properties.hasOwnProperty(name)) {
					result.push(name);
				}
			}
			return result;
		},
		/**
		 * @name getProperty
		 * @description Returns the declarative service property with the given name, or <code>undefined</code>
		 * if this service does not have such a property.
		 * @function
		 * @public
		 * @memberof orion.serviceregistry.ServiceReference.prototype
		 * @param {String} propertyName The name of the service property to return
		 * @returns The {String} property with the given name or <code>undefined</code>
		 */
		getProperty: function(propertyName) {
			return this._properties[propertyName];
		}
	};
	ServiceReference.prototype.constructor = ServiceReference;

	/**
	 * @name orion.serviceregistry.ServiceRegistration
	 * @description Creates a new service registration. This constructor is private and should only be called by the service registry.
	 * @class A reference to a registered service in the Orion service registry
	 * @param {String} serviceId The symbolic id of this service
	 * @param {String} serviceReference A reference to the service
	 * @param {Object} internalRegistry A JSON object containing the service's declarative properties
	 */
	function ServiceRegistration(serviceId, serviceReference, internalRegistry) {
		this._serviceId = serviceId;
		this._serviceReference = serviceReference;
		this._internalRegistry = internalRegistry;
	}

	ServiceRegistration.prototype = /** @lends orion.serviceregistry.ServiceRegistration.prototype */
	{
		/**
		 * @name unregister
		 * @description Unregister this service registration. Clients registered for <code>unregistering</code> service events
		 * will be notified of this change.
		 * @function
		 * @private
		 * @memberof orion.serviceregistry.ServiceRegistration.prototype
		 */
		unregister: function() {
			this._internalRegistry.unregisterService(this._serviceId);
		},

		/**
		 * @name getReference
		 * @description Returns the {@link orion.serviceregistry.ServiceReference} in this registration
		 * @function
		 * @private
		 * @memberof orion.serviceregistry.ServiceRegistration.prototype
		 * @param properties
		 * @returns the {@link orion.serviceregistry.ServiceReference} in this registration
		 * @throws An error is the service has been unregistered
		 */
		getReference: function() {
			if (!this._internalRegistry.isRegistered(this._serviceId)) {
				throw new Error("Service has already been unregistered: "+this._serviceId);
			}
			return this._serviceReference;
		},
		/**
		 * @name setProperties
		 * @description Sets the properties of this registration to the new given properties. Clients registered for <code>modified</code> service events
		 * will be notified of the change.
		 * @function
		 * @private
		 * @memberof orion.serviceregistry.ServiceRegistration.prototype
		 * @param {Object} properties
		 */
		setProperties: function(properties) {
			var oldProperties = this._serviceReference._properties;
			this._serviceReference._properties = properties || {};
			properties["service.id"] = this._serviceId;
			properties.objectClass = oldProperties.objectClass;
			properties["service.names"] = oldProperties["service.names"];
			this._internalRegistry.modifyService(this._serviceId);
		}
	};
	ServiceRegistration.prototype.constructor = ServiceRegistration;

	/**
	 * @name orion.serviceregistry.DeferredService
	 * @description Creates a new service promise to resolve the service at a later time.
	 * @class A service that is resolved later
	 * @private
	 * @param {orion.serviceregistry.ServiceReference} implementation The implementation of the service
	 * @param {Function} isRegistered A function to call to know if the service is already registered
	 */
	function DeferredService(implementation, isRegistered) {

		function _createServiceCall(methodName) {
			return function() {
					var d;
					try {
						if (!isRegistered()) {
							throw new Error("Service was unregistered");
						}
						var result = implementation[methodName].apply(implementation, Array.prototype.slice.call(arguments));
						if (result && typeof result.then === "function") {
							return result;
						} else {
							d = new Deferred();
							d.resolve(result);
						}
					} catch (e) {
							d = new Deferred();
							d.reject(e);
					}
					return d.promise;
			};
		}

		var method;
		for (method in implementation) {
			if (typeof implementation[method] === 'function') {
				this[method] = _createServiceCall(method);
			}
		}
	}

	/**
	 * @name orion.serviceregistry.ServiceEvent
	 * @description An event that is fired from the service registry. Clients must register to listen to events using 
	 * the {@link orion.serviceregistry.ServiceRegistry#addEventListener} function.
	 * <br> 
	 * There are three types of events that this registry will send:
	 * <ul>
	 * <li>modified - the service has been modified</li> 
	 * <li>registered - the service has been registered</li> 
	 * <li>unregistering - the service is unregistering</li>
	 * </ul> 
	 * @class A service event
	 * @param {String} type The type of the event, one of <code>modified</code>, <code>registered</code> or <code>unregistered</code>
	 * @param {orion.serviceregistry.ServiceReference} serviceReference the service reference the event is for
	 */
	function ServiceEvent(type, serviceReference) {
		this.type = type;
		this.serviceReference = serviceReference;
	}

	/**
	 * @name orion.serviceregistry.ServiceRegistry
	 * @description Creates a new service registry
	 * @class The Orion service registry
	 */
	function ServiceRegistry() {
		this._entries = [];
		this._namedReferences = {};
		this._serviceEventTarget = new EventTarget();
		var _this = this;
		this._internalRegistry = {
			/**
			 * @name isRegistered
			 * @description Returns if the service with the given identifier is registered or not.
			 * @function
			 * @private
			 * @memberof orion.serviceregistry.ServiceRegistry
			 * @param {String} serviceId the identifier of the service
			 * @returns <code>true</code> if the service with the given identifier is registered, <code>false</code> otherwise
			 */
			isRegistered: function(serviceId) {
				return _this._entries[serviceId] ? true : false;
			},
			
			/**
			 * @name unregisterService
			 * @description Unregisters a service with the given identifier. This function will notify
			 * clients registered for <code>unregistering</code> service events.
			 * @function
			 * @private
			 * @memberof orion.serviceregistry.ServiceRegistry
			 * @param {String} serviceId the identifier of the service
			 * @throws An error if the service has already been unregistered
			 * @see orion.serviceregistry.ServiceEvent
			 */
			unregisterService: function(serviceId) {
				var entry = _this._entries[serviceId];
				if (!entry) {
					throw new Error("Service has already been unregistered: "+serviceId);
				}
				var reference = entry.reference;
				_this._serviceEventTarget.dispatchEvent(new ServiceEvent("unregistering", reference));
				_this._entries[serviceId] = null;
				var objectClass = reference.getProperty("objectClass");
				objectClass.forEach(function(type) {
					var namedReferences = _this._namedReferences[type];
					for (var i = 0; i < namedReferences.length; i++) {
						if (namedReferences[i] === reference) {
							if (namedReferences.length === 1) {
								delete _this._namedReferences[type];
							} else {
								namedReferences.splice(i, 1);
							}
							break;
						}
					}
				});
			},
			/**
			 * @name modifyService
			 * @description Notifies that the service with the given identifier has been modified. This function will notify clients
			 * registered for <code>modified</code> service events.
			 * @function
			 * @private
			 * @memberof orion.serviceregistry.ServiceRegistry
			 * @param {String} serviceId the identifier of the service
			 * @throws An error if the service for the given identifier does not exist
			 * @see orion.serviceregistry.ServiceEvent
			 */
			modifyService: function(serviceId) {
				var entry = _this._entries[serviceId];
				if (!entry) {
					throw new Error("Service not found while trying to send modified event: "+serviceId);
				}
				var reference = entry.reference;
				_this._serviceEventTarget.dispatchEvent(new ServiceEvent("modified", reference));
			}
		};
	}

	ServiceRegistry.prototype = /** @lends orion.serviceregistry.ServiceRegistry.prototype */
	{
		/**
		 * @name getService
		 * @description Returns the service with the given name or reference.
		 * @function
		 * @public
		 * @memberof orion.serviceregistry.ServiceRegistry.prototype
		 * @param {String|orion.serviceregistry.ServiceReference} nameOrServiceReference The service name or a service reference
		 * @returns {orion.serviceregistry.ServiceReference|null} The service implementation, or <code>null</code> if no such service was found.
		 */
		getService: function(typeOrServiceReference) {
			var service;
			if (typeof typeOrServiceReference === "string") {
				var references = this._namedReferences[typeOrServiceReference];
				if (references) {
					references.some(function(reference) {
						service = this._entries[reference.getProperty("service.id")].service;
						return !!service;
					}, this);
				}
			} else {
				var entry = this._entries[typeOrServiceReference.getProperty("service.id")];
				if (entry) {
					service = entry.service;
				}
			}
			return service || null;
		},

		/**
		 * @name getServiceReferences
		 * @description Returns all references to the service with the given name.
		 * @function
		 * @public
		 * @memberof orion.serviceregistry.ServiceRegistry.prototype
		 * @param {String} name The name of the service to return
		 * @returns {orion.serviceregistry.ServiceReference[]} An array of service references
		 */
		getServiceReferences: function(name) {
			if (name) {
				return this._namedReferences[name] ? this._namedReferences[name] : [];
			}
			var result = [];
			this._entries.forEach(function(entry) {
				if (entry) {
					result.push(entry.reference);
				}
			});
			return result;
		},
		
		/**
		 * @name registerService
		 * @description Registers a service with this registry. This function will notify clients registered
		 * for <code>registered</code> service events.
		 * @function
		 * @public
		 * @memberof orion.serviceregistry.ServiceRegistry.prototype
		 * @param {String|String[]} names the name or names of the service being registered
		 * @param {Object} service The service implementation
		 * @param {Object} properties A JSON collection of declarative service properties
		 * @returns {orion.serviceregistry.ServiceRegistration} A service registration object for the service.
		 * @see orion.serviceregistry.ServiceEvent
		 */
		registerService: function(names, service, properties) {
			if (typeof service === "undefined" ||  service === null) {
				throw new Error("invalid service");
			}

			if (typeof names === "string") {
				names = [names];
			} else if (!Array.isArray(names)) {
				names = [];
			}

			var serviceId = this._entries.length;
			var reference = new ServiceReference(serviceId, names, properties);
			var namedReferences = this._namedReferences;
			names.forEach(function(name) {
				namedReferences[name] = namedReferences[name] || [];
				namedReferences[name].push(reference);
			});
			var deferredService = new DeferredService(service, this._internalRegistry.isRegistered.bind(null, serviceId));
			this._entries.push({
				reference: reference,
				service: deferredService
			});
			var internalRegistry = this._internalRegistry;
			this._serviceEventTarget.dispatchEvent(new ServiceEvent("registered", reference));
			return new ServiceRegistration(serviceId, reference, internalRegistry);
		},

		/**
		 * @name addEventListener
		 * @description Adds a listener for events on this registry.
		 * <br> 
		 * The events that this registry notifies about:
		 * <ul>
		 * <li>modified - the service has been modified</li> 
		 * <li>registered - the service has been registered</li> 
		 * <li>unregistering - the service is unregistering</li> 
		 * </ul> 
		 * @function
		 * @public
		 * @memberof orion.serviceregistry.ServiceRegistry.prototype
		 * @param {String} eventName The name of the event to be notified about.
		 * @param {Function} listener The listener to add
		 * @see orion.serviceregistry.ServiceEvent
		 */
		addEventListener: function(eventName, listener) {
			this._serviceEventTarget.addEventListener(eventName, listener);
		},

		/**
		 * @name removeEventListener
		 * @description Removes a listener for service events in this registry.
		 * @function
		 * @public
		 * @memberof orion.serviceregistry.ServiceRegistry.prototype
		 * @param {String} eventName The name of the event to stop listening for
		 * @param {Function} listener The listener to remove
		 * @see orion.serviceregistry.ServiceEvent
		 */
		removeEventListener: function(eventName, listener) {
			this._serviceEventTarget.removeEventListener(eventName, listener);
		}
	};
	ServiceRegistry.prototype.constructor = ServiceRegistry;

	//return the module exports
	return {
		ServiceRegistry: ServiceRegistry
	};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2015 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/* eslint-disable missing-nls */
/*eslint-env browser, amd*/
/*global URL*/
define('orion/pluginregistry',["orion/Deferred", "orion/EventTarget", "orion/URL-shim"], function(Deferred, EventTarget) {
    
    function _equal(obj1, obj2) {
        var keys1 = Object.keys(obj1);
        var keys2 = Object.keys(obj2);
        if (keys1.length !== keys2.length) {
            return false;
        }
        keys1.sort();
        keys2.sort();
        for (var i = 0, len = keys1.length; i < len; i++) {
            var key = keys1[i];
            if (key !== keys2[i]) {
                return false;
            }
            var value1 = obj1[key],
                value2 = obj2[key];
            if (value1 === value2) {
                continue;
            }
            if (JSON.stringify(value1) !== JSON.stringify(value2)) {
                return false;
            }
        }
        return true;
    }
    
    function _mixin(target/*, source..*/) {
		var hasOwnProperty = Object.prototype.hasOwnProperty;
		for (var j = 1, len = arguments.length; j < len; j++) {
			var source = arguments[j];
			for (var key in source) {
				if (hasOwnProperty.call(source, key)) {
					target[key] = source[key];
				}
			}
		}
		return target;
	}

    var httpOrHttps = new RegExp("^http[s]?", "i");

    function _normalizeURL(url) {
        if (url.indexOf("://") === -1) { //$NON-NLS-0$
            try {
                return new URL(url, location.href).href;
            } catch (e) {
                // URL SyntaxError, etc.
            }
        }
        return url;
    }

    function _asStorage(obj) {
        var _keys = null;

        function _getKeys() {
            return (_keys = _keys || Object.keys(obj));
        }

        var result = {
            key: function(index) {
                return _getKeys()[index];
            },
            getItem: function(key) {
                return obj[key];
            },
            setItem: function(key, value) {
                obj[key] = value;
                _keys = null;
            },
            removeItem: function(key) {
                delete obj[key];
                _keys = null;
            },
            clear: function() {
                _getKeys().forEach(function(key) {
                    delete obj[key];
                }.bind(this));
                _keys = null;
            }
        };
        Object.defineProperty(result, "length", {
            get: function() {
                return _getKeys().length;
            }
        });
        return result;
    }

    function _jsonXMLHttpRequestReplacer(name, value) {
        if (value && value instanceof XMLHttpRequest) {
            var status, statusText;
            try {
                status = value.status;
                statusText = value.statusText;
            } catch (e) {
                // https://bugs.webkit.org/show_bug.cgi?id=45994
                status = 0;
                statusText = ""; //$NON-NLS-0
            }
            return {
                status: status || 0,
                statusText: statusText
            };
        }
        return value;
    }

    function _serializeError(error) {
        var result = error ? JSON.parse(JSON.stringify(error, _jsonXMLHttpRequestReplacer)) : error; // sanitizing Error object
        if (error instanceof Error) {
            result.__isError = true;
            result.message = result.message || error.message;
            result.name = result.name || error.name;
        }
        return result;
    }

    function PluginEvent(type, plugin) {
        this.type = type;
        this.plugin = plugin;
    }

    function ObjectReference(objectId, methods) {
        this.__objectId = objectId;
        this.__methods = methods;
    }

    /**
     * Creates a new plugin. This constructor is private and should only be called by the plugin registry.
     * @class Represents a single plugin in the plugin registry.
     * @description
     * <p>At any given time, a plugin is in exactly one of the following six states:</p>
     *
     * <dl>
     *
     * <dt><code>"uninstalled"</code></dt>
     * <dd>The plugin has been uninstalled and may not be used.
     * <p>The <code>uninstalled</code> state is only visible after a plugin has been uninstalled; the plugin is unusable, but
     * references to its <code>Plugin</code> object may still be available and used for introspection.
     * </dd>
     *
     * <dt><code>"installed"</code></dt>
     * <dd>The plugin is installed, but not yet resolved.
     * <p></p>
     * </dd>
     *
     * <dt><code>"resolved"</code></dt>
     * <dd>The plugin is resolved and is able to be started.
     * <p>Note that the plugin is not active yet. A plugin must be in the <code>resolved</code> state before it can be started.</p>
     * <p>The <code>resolved</code> state is reserved for future use. Future versions of the framework may require successful
     * dependency resolution before moving a plugin to the <code>resolved</code> state.</p>
     * </dd>
     *
     * <dt><code>"starting"</code></dt>
     * <dd>The plugin is in the process of starting.
     * <p>A plugin is in the <code>starting</code> state when its {@link #start} method has been called but has not yet resolved.
     * Once the start call resolves, the plugin has successfully started and moves to the <code>active</code> state.</p>
     * <p>If the plugin has a lazy activation policy, it may remain in the <code>starting</code> state for some time until the
     * activation is triggered.</p>
     * </dd>
     *
     * <dt><code>"stopping"</code></dt>
     * <dd>The plugin is in the process of stopping. 
     * <p>A plugin is in the <code>stopping</code> state when its {@link #stop} method has been called but not yet resolved.
     * Once the stop call resolves, the plugin moves to the <code>resolved</code> state.</dd>
     *
     * <dt><code>"active"</code></dt>
     * <dd>The plugin is running. It has been successfully started and activated.
     * <p>In the <code>active</code> state, any services the plugin provides are available for use.</p></dd>
     *
     * </dl>
     *
     * @name orion.pluginregistry.Plugin
     */
    function Plugin(_url, _manifest, _internalRegistry) {
        var _this = this;
        _manifest = _manifest || {};
        var _created = _manifest.created || new Date().getTime();
        var _headers = _manifest.headers || {};
        var _services = _manifest.services || [];
        var _autostart = _manifest.autostart;
        var _lastModified = _manifest.lastModified || 0;

        var _state = "installed";

        var _deferredStateChange;
        var _deferredLoad;

        var _channel;
        var _parent;
        var _remoteServices = {};

        var _currentMessageId = 0;
        var _currentObjectId = 0;
        //var _currentServiceId = 0; not supported yet...

        var _requestReferences = {};
        var _responseReferences = {};
        var _objectReferences = {};
        var _serviceReferences = {};


        function _notify(message) {
            if (!_channel) {
                return;
            }
            _internalRegistry.postMessage(message, _channel);
        }

        function _request(message) {
            if (!_channel) {
                return new Deferred().reject(new Error("plugin not connected"));
            }

            message.id = String(_currentMessageId++);
            var d = new Deferred();
            _responseReferences[message.id] = d;
            d.then(null, function(error) {
                if (_state === "active" && error instanceof Error && error.name === "Cancel") {
                    _notify({
                        requestId: message.id,
                        method: "cancel",
                        params: error.message ? [error.message] : []
                    });
                }
            });

            var toString = Object.prototype.toString;
            message.params.forEach(function(param, i) {
                if (toString.call(param) === "[object Object]" && !(param instanceof ObjectReference)) {
                    var candidate, methods;
                    for (candidate in param) {
                        if (toString.call(param[candidate]) === "[object Function]") {
                            methods = methods || [];
                            methods.push(candidate);
                        }
                    }
                    if (methods) {
                        var objectId = _currentObjectId++;
                        _objectReferences[objectId] = param;
                        var removeReference = function() {
                            delete _objectReferences[objectId];
                        };
                        d.then(removeReference, removeReference);
                        message.params[i] = new ObjectReference(objectId, methods);
                    }
                }
            });
            _internalRegistry.postMessage(message, _channel);
            return d.promise;
        }

        function _throwError(messageId, error) {
            if (messageId || messageId === 0) {
                _notify({
                    id: messageId,
                    result: null,
                    error: error
                });
            } else {
                console.log(error);
            }

        }

        function _callMethod(messageId, implementation, method, params) {
            params.forEach(function(param, i) {
                if (param && typeof param.__objectId !== "undefined") {
                    var obj = {};
                    param.__methods.forEach(function(method) {
                        obj[method] = function() {
                            return _request({
                                objectId: param.__objectId,
                                method: method,
                                params: Array.prototype.slice.call(arguments)
                            });
                        };
                    });
                    params[i] = obj;
                }
            });

            var response = typeof messageId === "undefined" ? null : {
                id: messageId,
                result: null,
                error: null
            };
            try {
                var promiseOrResult = method.apply(implementation, params);
                if (!response) {
                    return;
                }

                if (promiseOrResult && typeof promiseOrResult.then === "function") { //$NON-NLS-0$
                    _requestReferences[messageId] = promiseOrResult;
                    promiseOrResult.then(function(result) {
                        delete _requestReferences[messageId];
                        response.result = result;
                        _notify(response);
                    }, function(error) {
                        if (_requestReferences[messageId]) {
                            delete _requestReferences[messageId];
                            response.error = _serializeError(error);
                            _notify(response);
                        }
                    }, function() {
                        _notify({
                            responseId: messageId,
                            method: "progress",
                            params: Array.prototype.slice.call(arguments)
                        }); //$NON-NLS-0$
                    });
                } else {
                    response.result = promiseOrResult;
                    _notify(response);
                }
            } catch (error) {
                if (response) {
                    response.error = _serializeError(error);
                    _notify(response);
                }
            }
        }


        var _update; // this is a forward reference to a function declared above this.update

        function _messageHandler(message) {
            try {
                if (message.method) { // request
                    var method = message.method,
                        params = message.params || [];
                    if ("serviceId" in message) {
                        var service = _serviceReferences[message.serviceId];
                        if (!service) {
                            _throwError(message.id, "service not found");
                        } else if (method in service) {
                            _callMethod(message.id, service, service[method], params);
                        } else {
                            _throwError(message.id, "method not found");
                        }
                    } else if ("objectId" in message) {
                        var object = _objectReferences[message.objectId];
                        if (!object) {
                            _throwError(message.id, "object not found");
                        } else if (method in object) {
                            _callMethod(message.id, object, object[method], params);
                        } else {
                            _throwError(message.id, "method not found");
                        }
                    } else if ("requestId" in message) {
                        var request = _requestReferences[message.requestId];
                        if (request && method === "cancel" && request.cancel) {
                            request.cancel.apply(request, params);
                        }
                    } else if ("responseId" in message) {
                        var response = _responseReferences[message.responseId];
                        if (response && method === "progress" && response.progress) {
                            response.progress.apply(response, params);
                        }
                    } else if ("loading" === message.method) {
                        _channel.loading();
                    } else {
                        if ("plugin" === message.method) { //$NON-NLS-0$
                            _channel.connected();
                            var manifest = message.params[0];
                            _update({
                                headers: manifest.headers,
                                services: manifest.services
                            }).then(function() {
                                if (_deferredLoad) {
                                    _deferredLoad.resolve(_this);
                                }
                            });
                        } else if ("timeout" === message.method || "error" === message.method) {
                            if (_deferredLoad) {
                                _deferredLoad.reject(message.error);
                            }
                        } else {
                            throw new Error("Bad method: " + message.method);
                        }
                    }
                } else if (message.id) {
                    var deferred = _responseReferences[String(message.id)];
                    if (deferred) {
	                    delete _responseReferences[String(message.id)];
	                    if (message.error) {
	                        var error = _internalRegistry.handleServiceError(_this, message.error);
	                        deferred.reject(error);
	                    } else {
	                        deferred.resolve(message.result);
	                    }
                    }
                }
            } catch (e) {
                console.log("Plugin._messageHandler " + e);
            }
        }

        function _createServiceProxy(service) {
            var serviceProxy = {};
            if (service.methods) {
                service.methods.forEach(function(method) {
                    serviceProxy[method] = function() {
                        var message = {
                            serviceId: service.serviceId,
                            method: method,
                            params: Array.prototype.slice.call(arguments)
                        };
                        if (_state === "active") {
                            return _request(message);
                        } else {
                            return _this.start({
                                "transient": true
                            }).then(function() {
                                return _request(message);
                            });
                        }
                    };
                });

                if (serviceProxy.addEventListener && serviceProxy.removeEventListener) {
                    var eventTarget = new EventTarget();
                    var objectId = _currentObjectId++;
                    _objectReferences[objectId] = {
                        handleEvent: eventTarget.dispatchEvent.bind(eventTarget)
                    };
                    var listenerReference = new ObjectReference(objectId, ["handleEvent"]);

                    var _addEventListener = serviceProxy.addEventListener;
                    serviceProxy.addEventListener = function(type, listener) {
                        if (!eventTarget._namedListeners[type]) {
                            _addEventListener(type, listenerReference);
                        }
                        eventTarget.addEventListener(type, listener);
                    };
                    var _removeEventListener = serviceProxy.removeEventListener;
                    serviceProxy.removeEventListener = function(type, listener) {
                        eventTarget.removeEventListener(type, listener);
                        if (!eventTarget._namedListeners[type]) {
                            _removeEventListener(type, listenerReference);
                        }
                    };
                }
            }
            return serviceProxy;
        }

        function _createServiceProperties(service) {
            var properties = JSON.parse(JSON.stringify(service.properties));
            properties.__plugin__ = _url; //TODO: eliminate
            var objectClass = service.names || service.type || [];
            if (!Array.isArray(objectClass)) {
                objectClass = [objectClass];
            }
            properties.objectClass = objectClass;
            return properties;
        }

        function _registerService(service) {
            var serviceProxy = _createServiceProxy(service);
            var properties = _createServiceProperties(service);
            var registration = _internalRegistry.registerService(service.names || service.type, serviceProxy, properties);
            _remoteServices[service.serviceId] = {
                registration: registration,
                proxy: serviceProxy
            };
        }

        function _persist() {
            _internalRegistry.persist(_url, {
                created: _created,
                headers: _headers,
                services: _services,
                autostart: _autostart,
                lastModified: _lastModified
            });
        }
        
        this._default = false; // used to determine if a plugin is part of the configuration

        this._persist = _persist;

        this._resolve = function() {
            // check manifest dependencies when we support them
            _state = "resolved";
            _internalRegistry.dispatchEvent(new PluginEvent("resolved", _this));
        };

        this._getAutostart = function() {
            return _autostart;
        };

        this._getCreated = function() {
            return _created;
        };

        /**
         * Returns the URL location of this plugin.
         * @name orion.pluginregistry.Plugin#getLocation
         * @return {String} The URL of this plugin.
         * @function
         */
        this.getLocation = function() {
            return _url;
        };

        /**
         * Returns the headers of this plugin.
         * @name orion.pluginregistry.Plugin#getHeaders
         * @return {Object} The plugin headers.
         * @function
         */
        this.getHeaders = function() {
            return JSON.parse(JSON.stringify(_headers));
        };

        this.getName = function() {
            var headers = this.getHeaders();
            if (headers) {
                return headers.name || "";
            }
            return null;
        };

        this.getVersion = function() {
            var headers = this.getHeaders();
            if (headers) {
                return headers.version || "0.0.0";
            }
            return null;
        };

        this.getLastModified = function() {
            return _lastModified;
        };

        /**
         * Returns the service references provided by this plugin.
         * @name orion.pluginregistry.Plugin#getServiceReferences
         * @return {orion.serviceregistry.ServiceReference[]} The service references provided by this plugin.
         * @function 
         */
        this.getServiceReferences = function() {
            var result = [];
            Object.keys(_remoteServices).forEach(function(serviceId) {
                result.push(_remoteServices[serviceId].registration.getReference());
            });
            return result;
        };

        /**
         * Sets the parent of this plugin.
         * @name orion.pluginregistry.Plugin#setParent
         * @param {DOMElement} [parent=null] the plugin parent. <code>null</code> puts the plugin in the default parent of the plugin registry
         * @return {orion.Promise} A promise that will resolve when the plugin parent has been set.
         * @function 
         */
        this.setParent = function(parent) {
            if (_parent !== parent) {
                _parent = parent;
                return _this.stop({
                    "transient": true
                }).then(function() {
                    if ("started" === _autostart) {
                        return _this.start({
                            "transient": true
                        });
                    } else if ("lazy" === _autostart) {
                        return _this.start({    
                            "lazy": true,
                            "transient": true
                        });
                    }
                });    
            }
            return new Deferred().resolve();
        };

        /**
         * Returns this plugin's current state.
         * @name orion.pluginregistry.Plugin#getState
         * @returns {String} This plugin's state.
         * @function
         */
        this.getState = function() {
            return _state;
        };
        
         /**
         * @name orion.pluginregistry.Plugin#getProblemLoading
         * @description Returns true if there was a problem loading this plug-in, false otherwise. This function is not API and may change in future releases.
         * @private
         * @function
         * @returns {String} Return an true if there was a problem loading this plug-in.
         */
        this.getProblemLoading = function() {
            if (_this._problemLoading){
                return true;
            }
            return false;
        };

        this.start = function(optOptions) {
            if (_state === "uninstalled") {
                return new Deferred().reject(new Error("Plugin is uninstalled"));
            }

            if (_deferredStateChange) {
                return _deferredStateChange.promise.then(this.start.bind(this, optOptions));
            }

            if (_state === "active") {
                return new Deferred().resolve();
            }

            if (!optOptions || !optOptions.transient) {
                var autostart = optOptions && optOptions.lazy ? "lazy" : "started";
                if (autostart !== _autostart) {
                    _autostart = autostart;
                    _persist();
                }
            }

            var frameworkState = _internalRegistry.getState();
            if (frameworkState !== "starting" && frameworkState !== "active") {
                if (optOptions.transient) {
                    return new Deferred().reject(new Error("start transient error"));
                }
                return new Deferred().resolve();
            }

            if (_state === "installed") {
                try {
                    this._resolve();
                } catch (e) {
                    return new Deferred().reject(e);
                }
            }

            if (_state === "resolved") {
                _services.forEach(function(service) {
                    _registerService(service);
                });
            }

            if (optOptions && optOptions.lazy) {
                if (_state !== "starting") {
                    _state = "starting";
                    _internalRegistry.dispatchEvent(new PluginEvent("lazy activation", _this));
                }
                var now = new Date().getTime();
		        if (!this.getLastModified() || now > this.getLastModified() + 86400000) { // 24 hours
	                 return this.update();
                }
                return new Deferred().resolve();
            }
            
            var deferredStateChange = new Deferred();
            _deferredStateChange = deferredStateChange;
            _state = "starting";
            _this._problemLoading = null;
            _internalRegistry.dispatchEvent(new PluginEvent("starting", _this));
            _deferredLoad = new Deferred();
            _channel = _internalRegistry.connect(_url, _messageHandler, _parent);
            _deferredLoad.then(function() {
                _deferredLoad = null;
                _state = "active";
                _internalRegistry.dispatchEvent(new PluginEvent("started", _this));
                _deferredStateChange = null;
                deferredStateChange.resolve();
            }, function(error) {
                _deferredLoad = null;
                _state = "stopping";
                _internalRegistry.dispatchEvent(new PluginEvent("stopping", _this));
                Object.keys(_remoteServices).forEach(function(serviceId) {
                    _remoteServices[serviceId].registration.unregister();
                    delete _remoteServices[serviceId];
                });
                _internalRegistry.disconnect(_channel);
                _channel = null;
                _state = "resolved";
                _deferredStateChange = null;
                _internalRegistry.dispatchEvent(new PluginEvent("stopped", _this));
                _this._problemLoading = true;
                deferredStateChange.reject(new Error("Failed to load plugin: " + _url + (error && error.message ? "\n    Reason: " + error.message : "")));
                if (_this._default) {
                    _lastModified = 0;
                    _persist();
                }
            });
            return deferredStateChange.promise;
        };

        this.stop = function(optOptions) {
            if (_state === "uninstalled") {
                return new Deferred().reject(new Error("Plugin is uninstalled"));
            }

            if (_deferredStateChange) {
                return _deferredStateChange.promise.then(this.stop.bind(this, optOptions));
            }

            if (!optOptions || !optOptions.transient) {
                if ("stopped" !== _autostart) {
                    _autostart = "stopped";
                    _persist();
                }
            }

            if (_state !== "active" && _state !== "starting") {
                return new Deferred().resolve();
            }

            var deferredStateChange = new Deferred();
            _deferredStateChange = deferredStateChange;

            _state = "stopping";
            _internalRegistry.dispatchEvent(new PluginEvent("stopping", _this));
            Object.keys(_remoteServices).forEach(function(serviceId) {
                _remoteServices[serviceId].registration.unregister();
                delete _remoteServices[serviceId];
            });
            if (_channel) {
                _internalRegistry.disconnect(_channel);
                _channel = null;
            }
            _state = "resolved";
            _deferredStateChange = null;
            _internalRegistry.dispatchEvent(new PluginEvent("stopped", _this));
            deferredStateChange.resolve();

            return deferredStateChange.promise;
        };

        _update = function(input) {
            _this.problemLoading = null;
            
            if (_state === "uninstalled") {
                return new Deferred().reject(new Error("Plugin is uninstalled"));
            }

            if (!input) {
                if (_lastModified === 0) {
                    _lastModified = new Date().getTime();
                    _persist();
                }
                return _internalRegistry.loadManifest(_url).then(_update, function() {
                    _this._problemLoading = true;
                    if (_this._default) {
                        _lastModified = 0;
                        _persist();
                    }
                    console.log("Failed to load plugin: " + _url);
                });
            }

            var oldHeaders = _headers;
            var oldServices = _services;
            var oldAutostart = _autostart;
            _headers = input.headers || {};
            _services = input.services || [];
            _autostart = input.autostart || _autostart;

            if (input.lastModified) {
                _lastModified = input.lastModified;
            } else {
                _lastModified = new Date().getTime();
                _persist();
            }

            if (_equal(_headers, oldHeaders) && _equal(_services, oldServices) && _autostart === oldAutostart) {
                return new Deferred().resolve();
            }

            if (_state === "active" || _state === "starting") {
                var serviceIds = [];
                Object.keys(_services).forEach(function(serviceId) {
                    var service = _services[serviceId];
                    serviceIds.push(serviceId);
                    var remoteService = _remoteServices[serviceId];
                    if (remoteService) {
                        if (_equal(service.methods, Object.keys(remoteService.proxy))) {
                            var properties = _createServiceProperties(service);
                            var reference = remoteService.registration.getReference();
                            var currentProperties = {};
                            reference.getPropertyKeys().forEach(function(name) {
                                currentProperties[name] = reference.getProperty(name);
                            });
                            if (!_equal(properties, currentProperties)) {
                                remoteService.registration.setProperties(properties);
                            }
                            return;
                        }
                        remoteService.registration.unregister();
                        delete _remoteServices[serviceId];
                    }
                    _registerService(service);
                });
                Object.keys(_remoteServices).forEach(function(serviceId) {
                    if (serviceIds.indexOf(serviceId) === -1) {
                        _remoteServices[serviceId].registration.unregister();
                        delete _remoteServices[serviceId];
                    }
                });
            }

            if (_state === "active") {
                _internalRegistry.disconnect(_channel);
                _deferredLoad = new Deferred();
                _channel = _internalRegistry.connect(_url, _messageHandler, _parent);
                _deferredLoad.then(function() {
                    _deferredLoad = null;
                }, function() {
                    _deferredLoad = null;
                    _state = "stopping";
                    _internalRegistry.dispatchEvent(new PluginEvent("stopping"), _this);
                    Object.keys(_remoteServices).forEach(function(serviceId) {
                        _remoteServices[serviceId].registration.unregister();
                        delete _remoteServices[serviceId];
                    });
                    _internalRegistry.disconnect(_channel);
                    _channel = null;
                    _state = "resolved";
                    _internalRegistry.dispatchEvent(new PluginEvent("stopped", _this));
                });
            }
            return new Deferred().resolve();
        };

        this.update = function(input) {
            return _update(input).then(function() {
                _internalRegistry.dispatchEvent(new PluginEvent("updated", _this));
            });
        };
        
        /**
         * Uninstalls this plugin.
         * @name orion.pluginregistry.Plugin#uninstall
         * @return {orion.Promise} A promise that will resolve when the plugin has been uninstalled.
         * @function
         */
        this.uninstall = function() {
            if (_state === "uninstalled") {
                return new Deferred().reject(new Error("Plugin is uninstalled"));
            }

            if (_state === "active" || _state === "starting" || _state === "stopping") {
                return this.stop().then(this.uninstall.bind(this), this.uninstall.bind(this));
            }

            _internalRegistry.removePlugin(this);
            _state = "uninstalled";
            _internalRegistry.dispatchEvent(new PluginEvent("uninstalled", _this));
            return new Deferred().resolve();
        };
    }

    var workerRegex = /Worker\.js$/;
    var sharedWorkerRegex = /SharedWorker\.js$/;
    var pluginHtml = "Plugin.html"; //$NON-NLS-0$
    var workerJS = "Worker.js"; //$NON-NLS-0$

    /**
     * Dispatched when a plugin has been installed. The type of this event is <code>"installed"</code>.
     * @name orion.pluginregistry.PluginRegistry#installed
     * @event
     */
    /**
     * Dispatched when a plugin has been resolved. The type of this event is <code>"resolved"</code>.
     * @name orion.pluginregistry.PluginRegistry#resolved
     * @event
     */
    /**
     * Dispatched when a plugin is starting due to a lazy activation. The type of this event is <code>"lazy activation"</code>.
     * @name orion.pluginregistry.PluginRegistry#lazy_activation
     * @event
     */
    /**
     * Dispatched when a plugin is starting. The type of this event is <code>"starting"</code>.
     * @name orion.pluginregistry.PluginRegistry#starting
     * @event
     */
    /**
     * Dispatched when a plugin is started. The type of this event is <code>"started"</code>.
     * @name orion.pluginregistry.PluginRegistry#started
     * @event
     */
    /**
     * Dispatched when a plugin is stopping. The type of this event is <code>"stopping"</code>.
     * @name orion.pluginregistry.PluginRegistry#stopping
     * @event
     */
    /**
     * Dispatched when a plugin is stopped. The type of this event is <code>"stopped"</code>.
     * @name orion.pluginregistry.PluginRegistry#stopped
     * @event
     */
    /**
     * Dispatched when a plugin has been updated. The type of this event is <code>"updated"</code>.
     * @name orion.pluginregistry.PluginRegistry#updated
     * @event
     */
    /**
     * Dispatched when a plugin has been uninstalled. The type of this event is <code>"uninstalled"</code>.
     * @name orion.pluginregistry.PluginRegistry#uninstalled
     * @event
     */

    /**
     * Creates a new plugin registry.
     * @class The Orion plugin registry
     * @name orion.pluginregistry.PluginRegistry
     * @description The plugin registry maintains a list of {@link orion.pluginregistry.Plugin}s, which can provide services
     * to the given <code>serviceRegistry</code>.
     *
     * <p>The plugin registry dispatches plugin events when one of its plugins changes state. Each such event contains a
     * <code>plugin</code> field giving the affected {@link orion.pluginregistry.Plugin}.
     * </p>
     *
     * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry The service registry to register plugin-provided services with.
     * @param {Object} [opt_storage=localStorage] Target object to read and write plugin metadata from.
     * @param {Boolean} [opt_visible=false] Whether a loaded plugin's iframe will be displayed. By default it is not displayed.
     * @borrows orion.serviceregistry.EventTarget#addEventListener as #addEventListener
     * @borrows orion.serviceregistry.EventTarget#removeEventListener as #removeEventListener
     */
    function PluginRegistry(serviceRegistry, configuration) {
        configuration = configuration || {};
        var _storage = configuration.storage || localStorage;
        if (!_storage.getItem) {
            _storage = _asStorage(_storage);
        }
        var _defaultTimeout = parseInt(_storage.getItem("pluginregistry.default.timeout"), 10) || undefined;
        var _state = "installed";
        var _parent;
        var _plugins = [];
        var _channels = [];
        var _pluginEventTarget = new EventTarget();
        var _installing = {};

        var internalRegistry = {
            registerService: serviceRegistry.registerService.bind(serviceRegistry),
            connect: function(url, handler, parent, timeout) {
                var channel = {
                    handler: handler,
                    url: url
                };

                function log(state) {
                    if (localStorage.pluginLogging) console.log(state + "(" + (new Date().getTime() - channel._startTime) + "ms)=" + url); //$NON-NLS-1$ //$NON-NLS-0$
                }

                function sendTimeout(message) {
                    log("timeout"); //$NON-NLS-0$
                    var error = new Error(message);
                    error.name = "timeout";
                    handler({
                        method: "timeout",
                        error: error
                    });
                }
                
                timeout = timeout || _defaultTimeout;
                
                channel._updateTimeout = function() {
                    var message, newTimeout;
                    if (!this._connected && !this._closed) {
                        if (this._handshake) {
                            // For each plugin being loaded add 1000 ms extra time to the handshake timeout
                            var extraTimeout = 0;
                            _channels.forEach(function(c) {
                                if (!c._connected && !c._closed) {
                                    extraTimeout += 1000;
                                }
                            });
                            message = "Plugin handshake timeout for: " + url;
                           newTimeout = this._loading ? 5000 : (timeout || 60000) + extraTimeout;
                        } else {
                            message = "Plugin load timeout for: " + url;
                            newTimeout = timeout || 15000;
                        }
                    }
                    if (this._loadTimeout) clearTimeout(this._loadTimeout);
                    this._loadTimeout = 0;
                    if (newTimeout) this._loadTimeout = setTimeout(sendTimeout.bind(null, message), newTimeout);
                };

                var isWorker = !!(url.match(workerRegex) && typeof(Worker) !== "undefined");
                var isSharedWorker = !!(url.match(sharedWorkerRegex) && typeof(SharedWorker) !== "undefined");
                if ((!localStorage.useSharedWorkers || !isSharedWorker) && url.match(sharedWorkerRegex)) {
                    url = url.replace(sharedWorkerRegex, workerJS);
                    isSharedWorker = false;
                }
                if ((!localStorage.useWorkers || !isWorker) && url.match(workerRegex)) {
                    url = url.replace(workerRegex, pluginHtml);
                    isWorker = isSharedWorker = false;
                }               

                channel.url = url;
                channel._updateTimeout();
                channel._startTime = new Date().getTime();
                if (isWorker) {
                    var worker;
                    if (isSharedWorker) {
                        worker = new SharedWorker(url);
                        channel.target = worker.port;
                        worker.port.start();
                        channel._close = function() {
                            worker.port.close();
                        };
                    } else {
                        worker = new Worker(url);
                        channel.target = worker;
                        channel._close = function() {
                            worker.terminate();
                        };
                    }
                    channel.postMessage = function(message) {
                        this.target.postMessage((this.useStructuredClone ? message : JSON.stringify(message)), []);
                    };
                    channel.target.addEventListener("message", function(evt) {
                    	_channelHandler(channel, evt);
                    });
                } else {
                   	var iframe = document.createElement("iframe"); //$NON-NLS-0$
                    iframe.name = url + "_" + channel._startTime;
                    iframe.src = url;
                    iframe.onload = function() {
                        log("handshake"); //$NON-NLS-0$
                        channel._handshake = true;
                        channel._updateTimeout();
                    };
                    iframe.sandbox = "allow-scripts allow-same-origin allow-forms allow-popups"; //$NON-NLS-0$
                    iframe.style.width = iframe.style.height = "100%"; //$NON-NLS-0$
                    iframe.frameBorder = 0;
                    (parent || _parent).appendChild(iframe);
                    channel.target = iframe.contentWindow;
                    channel.postMessage = function(message) {
                        this.target.postMessage((this.useStructuredClone ? message : JSON.stringify(message)), this.url);
                    };
                    channel._close = function() {
                        if (iframe) {
                            var frameParent = iframe.parentNode;
                            if (frameParent) {
                                frameParent.removeChild(iframe);
                            }
                            iframe = null;
                        }
                    }; 
                }
                channel.connected = function() {
                    log("connected"); //$NON-NLS-0$
                    this._connected = true;
                    this._updateTimeout();
                    
                };
                channel.loading = function() {
                    log("loading"); //$NON-NLS-0$
                    this._loading = true;
                    this._updateTimeout();
                };
                channel.close = function() {
                    log("closed"); //$NON-NLS-0$
                    this._closed = true;
                    this._updateTimeout();
                    this._close();
                };
                _channels.push(channel);
                return channel;
            },
            disconnect: function(channel) {
                for (var i = 0; i < _channels.length; i++) {
                    if (channel === _channels[i]) {
                        _channels.splice(i, 1);
                        try {
                            channel.close();
                        } catch (e) {
                            // best effort
                        }
                        break;
                    }
                }
            },
            removePlugin: function(plugin) {
                for (var i = 0; i < _plugins.length; i++) {
                    if (plugin === _plugins[i]) {
                        _plugins.splice(i, 1);
                        break;
                    }
                }
                _storage.removeItem("plugin." + plugin.getLocation());
            },
            getPersisted: function(url) {
            	var key = "plugin." + url;
            	var manifest = _storage.getItem(key);
            	if (manifest) {
            		manifest = JSON.parse(manifest);
            		if (manifest.created) {
            			return manifest;
            		}
            	}
            	return null;
            },
            persist: function(url, manifest) {
                _storage.setItem("plugin." + url, JSON.stringify(manifest)); //$NON-NLS-0$
            },
            postMessage: function(message, channel) {
                channel.postMessage(message);
            },
            dispatchEvent: function(event) {
                try {
                    _pluginEventTarget.dispatchEvent(event);
                } catch (e) {
                    if (console) {
                        console.log("PluginRegistry.dispatchEvent " + e);
                    }
                }
            },
            loadManifest: function(url) {
                var d = new Deferred();
                var channel = internalRegistry.connect(url, function(message) {
                    if (!channel || !message.method) {
                        return;
                    }
                    if ("manifest" === message.method || "plugin" === message.method) { //$NON-NLS-0$
                        var manifest = message.params[0];
                        internalRegistry.disconnect(channel);
                        channel = null;
                        d.resolve(manifest);
                    } else if ("timeout" === message.method || "error" === message.method) {
                        internalRegistry.disconnect(channel);
                        channel = null;
                        d.reject(message.error);
                    } else if ("loading" === message.method) {
                        channel.loading();
                    }
                });
                return d.promise;
            },
            getState: function() {
                return _state;
            },
            handleServiceError: function(plugin, error) {
                if (error && error.status === 401) {
                    var headers = plugin.getHeaders();
                    var name = plugin.getName() || plugin.getLocation();
                    var span = document.createElement("span");
                    span.appendChild(document.createTextNode("Authentication required for: " + name + "."));
                    if (headers.login) {
                        span.appendChild(document.createTextNode(" "));
                        var anchor = document.createElement("a");
                        anchor.target = "_blank";
                        anchor.textContent = "Login";
                        anchor.href = headers.login;
                        if (!httpOrHttps.test(anchor.href)) {
                            console.log("Illegal Login URL: " + headers.login);
                        } else {
                            span.appendChild(anchor);
                            span.appendChild(document.createTextNode(" and re-try the request."));
                        }
                    }
                    var serializer = new XMLSerializer();
                    return {
                        Severity: "Error",
                        HTML: true,
                        Message: serializer.serializeToString(span)
                    };
                }
                if (error.__isError) {
                    var original = error;
                    error = new Error(original.message);
                    Object.keys(original).forEach(function(key) {
                        error[key] = original[key];
                    });
                    delete error.__isError;
                }
                return error;
            }
        };

        this.getLocation = function() {
            return "System";
        };

        this.getHeaders = function() {
            return {};
        };

        this.getName = function() {
            return "System";
        };

        this.getVersion = function() {
            return "0.0.0";
        };

        this.getLastModified = function() {
            return 0;
        };

        this.getState = internalRegistry.getState;

		function _channelHandler(channel, event) {
			try {
                var message;
                if (typeof channel.useStructuredClone === "undefined") {
                    var useStructuredClone = typeof event.data !== "string"; //$NON-NLS-0$
                    message = useStructuredClone ? event.data : JSON.parse(event.data);
                    channel.useStructuredClone = useStructuredClone;
                } else {
                    message = channel.useStructuredClone ? event.data : JSON.parse(event.data);
                }
                channel.handler(message);
            } catch (e) {
                // not a valid message -- ignore it
            }
		}

        function _messageHandler(event) {
            var source = event.source;
            _channels.some(function(channel) {
                if (source === channel.target) {
                    _channelHandler(channel, event);
                    return true; // e.g. break
                }
            });
        }


        this.init = function() {
            if (_state === "starting" || _state === "active" || _state === "stopping") {
                return;
            }
            addEventListener("message", _messageHandler, false);
            
            if (configuration.parent) {
                _parent = configuration.parent;
            } else {
                _parent = document.createElement("div"); //$NON-NLS-0$
                if (!configuration.visible) {
                    _parent.style.display = "none"; //$NON-NLS-0$
                    _parent.style.visibility = "hidden"; //$NON-NLS-0$
                }
                document.body.appendChild(_parent);
            }

            if (configuration.plugins) {
                Object.keys(configuration.plugins).forEach(function(url) {
                    url = _normalizeURL(url);
                    //                    if (!httpOrHttps.test(url)) {
                    //                        console.log("Illegal Plugin URL: " + url);
                    //                        return;
                    //                    }
                    var plugin = this.getPlugin(url);
                    if (!plugin) {
                        var manifest = configuration.plugins[url];
                        if (typeof manifest !== "object") {
                        	manifest = internalRegistry.getPersisted(url) || {};
                        }
                        manifest.autostart = manifest.autostart || configuration.defaultAutostart || "lazy";
                        plugin = new Plugin(url, manifest, internalRegistry);
                        plugin._default = true;
                        _plugins.push(plugin);
                    } else {
                        plugin._default = true;
                    }
                }.bind(this));
            }
            _plugins.sort(function(a, b) {
                return a._getCreated() < b._getCreated() ? -1 : 1;
            });
            _state = "starting";
        };

        /**
         * Starts the plugin registry.
         * @name orion.pluginregistry.PluginRegistry#start
         * @return {orion.Promise} A promise that will resolve when the registry has been fully started.
         * @function 
         */
        this.start = function() {
            if (_state !== "starting") {
                this.init();
            }
            if (_state !== "starting") {
                return new Deferred().reject("Cannot start framework. Framework is already " + _state + ".");
            }
            var deferreds = [];
            _plugins.forEach(function(plugin) {
                var autostart = plugin._getAutostart();
                if ("started" === autostart) {
                    deferreds.push(plugin.start({
                        "transient": true
                    }));
                } else if ("lazy" === autostart) {
                    deferreds.push(plugin.start({
                        "lazy": true,
                            "transient": true
                    }));
                } else {
                    plugin._resolve();
                }
            });
            return Deferred.all(deferreds, function(e) {
                console.log("PluginRegistry.stop " + e);
            }).then(function() {
                _state = "active";
            });
        };

        /**
         * Shuts down the plugin registry.
         * @name orion.pluginregistry.PluginRegistry#stop
         * @function 
         * @returns {orion.Promise} A promise that will resolve when the registry has been stopped.
         */
        this.stop = function() {
            if (_state !== "starting" && _state !== "active") {
                return new Deferred().reject("Cannot stop registry. Registry is already " + _state + ".");
            }
            _state = "stopping";
            var deferreds = [];
            _plugins.forEach(function(plugin) {
                deferreds.push(plugin.stop({
                    "transient": true
                }));
            });
            return Deferred.all(deferreds, function(e) {
                console.log("PluginRegistry.stop " + e);
            }).then(function() {
                if (!configuration.parent) {
                    var parentNode = _parent.parentNode;
                    if (parentNode) {
                        parentNode.removeChild(_parent);
                    }
                }
                _parent = null;
                removeEventListener("message", _messageHandler);
                _state = "resolved";
            });
        };

        this.update = function() {
            this.stop().then(this.start.bind(this));
        };

        this.uninstall = function() {
            return new Deferred().reject("Cannot uninstall registry");
        };


        /**
         * Installs the plugin at the given location into the plugin registry.
         * @name orion.pluginregistry.PluginRegistry#installPlugin
         * @param {String} url The location of the plugin.
         * @param {Object} [optManifest] The plugin metadata.
         * @returns {orion.Promise} A promise that will resolve when the plugin has been installed.
         * @function 
         */
        this.installPlugin = function(url, optManifest) {
            url = _normalizeURL(url);
            //            if (!httpOrHttps.test(url)) {
            //                return new Deferred().reject("Illegal Plugin URL: " + url);
            //            }
            var plugin = this.getPlugin(url);
            if (plugin) {
                return new Deferred().resolve(plugin);
            }

            if (_installing[url]) {
                return _installing[url];
            }
            
            if (optManifest) {
            	if (optManifest.autostart === "lazy") {
	                optManifest = _mixin({}, internalRegistry.getPersisted(url), optManifest);
            	}
                plugin = new Plugin(url, optManifest, internalRegistry);
                _plugins.push(plugin);
                plugin._persist();
                internalRegistry.dispatchEvent(new PluginEvent("installed", plugin));
                return new Deferred().resolve(plugin);
            }

            var promise = internalRegistry.loadManifest(url).then(function(manifest) {
                plugin = new Plugin(url, manifest, internalRegistry);
                _plugins.push(plugin);
                plugin._persist();
                delete _installing[url];
                internalRegistry.dispatchEvent(new PluginEvent("installed", plugin));
                return plugin;
            }, function(error) {
                delete _installing[url];
                throw error;
            });
            _installing[url] = promise;
            return promise;
        };

        /**
         * Returns all installed plugins.
         * @name orion.pluginregistry.PluginRegistry#getPlugins
         * @return {orion.pluginregistry.Plugin[]} An array of all installed plugins.
         * @function 
         */
        this.getPlugins = function() {
            return _plugins.slice();
        };

        /**
         * Returns the installed plugin with the given URL.
         * @name orion.pluginregistry.PluginRegistry#getPlugin
         * @return {orion.pluginregistry.Plugin} The installed plugin matching the given URL, or <code>null</code>
         * if no such plugin is installed.
         * @function 
         */
        this.getPlugin = function(url) {
            var result = null;
            url = _normalizeURL(url);
            _plugins.some(function(plugin) {
                if (url === plugin.getLocation()) {
                    result = plugin;
                    return true;
                }
            });
            return result;
        };

        this.addEventListener = _pluginEventTarget.addEventListener.bind(_pluginEventTarget);

        this.removeEventListener = _pluginEventTarget.removeEventListener.bind(_pluginEventTarget);

        this.resolvePlugins = function() {
            var allResolved = true;
            _plugins.forEach(function(plugin) {
                allResolved = allResolved && plugin._resolve();
            });
            return allResolved;
        };
    }
    return {
        Plugin: Plugin,
        PluginRegistry: PluginRegistry
    };
});

/*******************************************************************************
 * @license
 * Copyright (c) 2011, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
/*global URL*/
var _code_edit_script_source = null; //We need to know where the editor script lives
var _all_script = document.getElementsByTagName('script');
if (_all_script && _all_script.length && _all_script.length > 0) {
	for (var j = 0; j < 2; j++) { // try twice in all the script tags
		for (var i = 0; i < _all_script.length; i++) {
			if (j === 0) { //First try: if the script id is ""orion.browse.browser""
				if (_all_script[i].id === "orion.codeEdit") {
					_code_edit_script_source = _all_script[i].src;
					break;
				}
			} else {
				var regex = /.*built-codeEdit.*.js/;
				if (_all_script[i].src && regex.exec(_all_script[i].src)) {
					_code_edit_script_source = _all_script[i].src;
					break;
				}
			}
		}
		if (_code_edit_script_source) {
			break;
		}
	}
	if (!_code_edit_script_source) {
		_code_edit_script_source = _all_script[_all_script.length - 1].src;
	}
}
define('embeddedEditor/helper/bootstrap',[
	'embeddedEditor/helper/embeddedFileImpl',
	'orion/serviceregistry',
	'orion/pluginregistry',
	'orion/Deferred',
	'orion/URL-shim'
], function(
	EmbeddedFileImpl,
	mServiceRegistry, 
	mPluginRegistry,
	Deferred
) {

	var once; // Deferred
	var fPattern = "/__embed/";
	var defaultPluginURLs = [
		"../javascript/plugins/javascriptPlugin.html",
		"../webtools/plugins/webToolsPlugin.html",
		"../plugins/embeddedToolingPlugin.html"
	];

	function startup(options) {
		if (once) {
			return once;
		}
		//TODO: We should create this hidden div somewhere else
		//The hidden DIV that allows some commands for editorCommnads to be rendered. We only want to use keybinding of them though.
		var orionHiddenDiv = document.createElement("div");
		orionHiddenDiv.id = "_orion_hidden_actions";
		document.body.appendChild(orionHiddenDiv);
		orionHiddenDiv.style.display = "none";
		//options._defaultPlugins is for internal use to load plugins in dev mode
		var pluginsToLoad = (options && options._defaultPlugins) ? options._defaultPlugins : defaultPluginURLs;
		
		once = new Deferred();
		var serviceRegistry = new mServiceRegistry.ServiceRegistry();
		var fService = new EmbeddedFileImpl(fPattern);
		serviceRegistry.registerService("orion.core.file", fService, {
			Name: 'Embedded File System',
			top: fPattern,
			pattern: fPattern
		});
		var plugins = {};
		pluginsToLoad.forEach(function(pluginURLString){
			var pluginURL = new URL(pluginURLString, _code_edit_script_source);
			plugins[pluginURL.href] = {autostart: "lazy"};
		});
		
		pluginsToLoad = (options && options.userPlugins) ? options.userPlugins : [];
		pluginsToLoad.forEach(function(pluginURLString){
			plugins[pluginURLString] = {autostart: "lazy"};
		});
		
		var pluginRegistry = new mPluginRegistry.PluginRegistry(serviceRegistry, {
			storage: {},
			plugins: plugins
		});
		return pluginRegistry.start().then(function() {
			var result = {
				serviceRegistry: serviceRegistry,
				pluginRegistry: pluginRegistry
			};
			once.resolve(result);
			return result;
		});
	}
	return {startup: startup};
});

/*******************************************************************************
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: 
 *		Felipe Heidrich (IBM Corporation) - initial API and implementation
 *		Silenio Quarti (IBM Corporation) - initial API and implementation
 ******************************************************************************/
 
/*eslint-env browser, amd*/
define("orion/editor/eventTarget", [], function() { //$NON-NLS-0$
	/** 
	 * Constructs a new EventTarget object.
	 * 
	 * @class 
	 * @name orion.editor.EventTarget
	 */
	function EventTarget() {
	}
	/**
	 * Adds in the event target interface into the specified object.
	 *
	 * @param {Object} object The object to add in the event target interface.
	 */
	EventTarget.addMixin = function(object) {
		var proto = EventTarget.prototype;
		for (var p in proto) {
			if (proto.hasOwnProperty(p)) {
				object[p] = proto[p];
			}
		}
	};
	EventTarget.prototype = /** @lends orion.editor.EventTarget.prototype */ {
		/**
		 * Adds an event listener to this event target.
		 * 
		 * @param {String} type The event type.
		 * @param {Function|EventListener} listener The function or the EventListener that will be executed when the event happens. 
		 * @param {Boolean} [useCapture=false] <code>true</code> if the listener should be trigged in the capture phase.
		 * 
		 * @see orion.editor.EventTarget#removeEventListener
		 */
		addEventListener: function(type, listener, useCapture) {
			if (!this._eventTypes) { this._eventTypes = {}; }
			var state = this._eventTypes[type];
			if (!state) {
				state = this._eventTypes[type] = {level: 0, listeners: []};
			}
			var listeners = state.listeners;
			listeners.push({listener: listener, useCapture: useCapture});
		},
		/**
		 * Dispatches the given event to the listeners added to this event target.
		 * @param {Event} evt The event to dispatch.
		 */
		dispatchEvent: function(evt) {
			var type = evt.type;
			this._dispatchEvent("pre" + type, evt); //$NON-NLS-0$
			this._dispatchEvent(type, evt);
			this._dispatchEvent("post" + type, evt); //$NON-NLS-0$
		},
		_dispatchEvent: function(type, evt) {
			var state = this._eventTypes ? this._eventTypes[type] : null;
			if (state) {
				var listeners = state.listeners;
				try {
					state.level++;
					if (listeners) {
						for (var i=0, len=listeners.length; i < len; i++) {
							if (listeners[i]) {
								var l = listeners[i].listener;
								if (typeof l === "function") { //$NON-NLS-0$
									l.call(this, evt);
								} else if (l.handleEvent && typeof l.handleEvent === "function") { //$NON-NLS-0$
									l.handleEvent(evt);
								}
							}
						}
					}
				} finally {
					state.level--;
					if (state.compact && state.level === 0) {
						for (var j=listeners.length - 1; j >= 0; j--) {
							if (!listeners[j]) {
								listeners.splice(j, 1);
							}
						}
						if (listeners.length === 0) {
							delete this._eventTypes[type];
						}
						state.compact = false;
					}
				}
			}
		},
		/**
		 * Returns whether there is a listener for the specified event type.
		 * 
		 * @param {String} type The event type
		 * 
		 * @see orion.editor.EventTarget#addEventListener
		 * @see orion.editor.EventTarget#removeEventListener
		 */
		isListening: function(type) {
			if (!this._eventTypes) { return false; }
			return this._eventTypes[type] !== undefined;
		},		
		/**
		 * Removes an event listener from the event target.
		 * <p>
		 * All the parameters must be the same ones used to add the listener.
		 * </p>
		 * 
		 * @param {String} type The event type
		 * @param {Function|EventListener} listener The function or the EventListener that will be executed when the event happens. 
		 * @param {Boolean} [useCapture=false] <code>true</code> if the listener should be trigged in the capture phase.
		 * 
		 * @see orion.editor.EventTarget#addEventListener
		 */
		removeEventListener: function(type, listener, useCapture){
			if (!this._eventTypes) { return; }
			var state = this._eventTypes[type];
			if (state) {
				var listeners = state.listeners;
				for (var i=0, len=listeners.length; i < len; i++) {
					var l = listeners[i];
					if (l && l.listener === listener && l.useCapture === useCapture) {
						if (state.level !== 0) {
							listeners[i] = null;
							state.compact = true;
						} else {
							listeners.splice(i, 1);
						}
						break;
					}
				}
				if (listeners.length === 0) {
					delete this._eventTypes[type];
				}
			}
		}
	};
	return {EventTarget: EventTarget};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2011, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
/**
 * @name orion.regex
 * @class Utilities for dealing with regular expressions.
 * @description Utilities for dealing with regular expressions.
 */
define("orion/regex", [], function() { //$NON-NLS-0$
	/**
	 * @memberOf orion.regex
	 * @function
	 * @static
	 * @description Escapes regex special characters in the input string.
	 * @param {String} str The string to escape.
	 * @returns {String} A copy of <code>str</code> with regex special characters escaped.
	 */
	function escape(str) {
		return str.replace(/([\\$\^*\/+?\.\(\)|{}\[\]])/g, "\\$&"); //$NON-NLS-0$
	}

	/**
	 * @memberOf orion.regex
	 * @function
	 * @static
	 * @description Parses a pattern and flags out of a regex literal string.
	 * @param {String} str The string to parse. Should look something like <code>"/ab+c/"</code> or <code>"/ab+c/i"</code>.
	 * @returns {Object} If <code>str</code> looks like a regex literal, returns an object with properties
	 * <code><dl>
	 * <dt>pattern</dt><dd>{String}</dd>
	 * <dt>flags</dt><dd>{String}</dd>
	 * </dl></code> otherwise returns <code>null</code>.
	 */
	function parse(str) {
		var regexp = /^\s*\/(.+)\/([gim]{0,3})\s*$/.exec(str);
		if (regexp) {
			return {
				pattern : regexp[1],
				flags : regexp[2]
			};
		}
		return null;
	}

	return {
		escape: escape,
		parse: parse
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors: IBM Corporation - initial API and implementation
 *******************************************************************************/

/*eslint-env browser, amd*/
define('orion/util',[],function() {

	var userAgent = navigator.userAgent;
	var isIE = (userAgent.indexOf("MSIE") !== -1 || userAgent.indexOf("Trident") !== -1) ? document.documentMode : undefined; //$NON-NLS-1$ //$NON-NLS-0$
	var isFirefox = parseFloat(userAgent.split("Firefox/")[1] || userAgent.split("Minefield/")[1]) || undefined; //$NON-NLS-1$ //$NON-NLS-0$
	var isOpera = userAgent.indexOf("Opera") !== -1 ? parseFloat(userAgent.split("Version/")[1]) : undefined; //$NON-NLS-0$
	var isChrome = parseFloat(userAgent.split("Chrome/")[1]) || undefined; //$NON-NLS-0$
	var isSafari = userAgent.indexOf("Safari") !== -1 && !isChrome; //$NON-NLS-0$
	var isWebkit = parseFloat(userAgent.split("WebKit/")[1]) || undefined; //$NON-NLS-0$
	var isAndroid = userAgent.indexOf("Android") !== -1; //$NON-NLS-0$
	var isIPad = userAgent.indexOf("iPad") !== -1; //$NON-NLS-0$
	var isIPhone = userAgent.indexOf("iPhone") !== -1; //$NON-NLS-0$
	var isIOS = isIPad || isIPhone;
	var isMac = navigator.platform.indexOf("Mac") !== -1; //$NON-NLS-0$
	var isWindows = navigator.platform.indexOf("Win") !== -1; //$NON-NLS-0$
	var isLinux = navigator.platform.indexOf("Linux") !== -1; //$NON-NLS-0$
	var isTouch = typeof document !== "undefined" && "ontouchstart" in document.createElement("input"); //$NON-NLS-1$ //$NON-NLS-0$
	
	var platformDelimiter = isWindows ? "\r\n" : "\n"; //$NON-NLS-1$ //$NON-NLS-0$

	function formatMessage(msg) {
		var args = arguments;
		return msg.replace(/\$\{([^\}]+)\}/g, function(str, index) { return args[(index << 0) + 1]; });
	}
	
	var XHTML = "http://www.w3.org/1999/xhtml"; //$NON-NLS-0$
	function createElement(document, tagName) {
		if (document.createElementNS) {
			return document.createElementNS(XHTML, tagName);
		}
		return document.createElement(tagName);
	}

	return {
		formatMessage: formatMessage,
		
		createElement: createElement,
		
		/** Browsers */
		isIE: isIE,
		isFirefox: isFirefox,
		isOpera: isOpera,
		isChrome: isChrome,
		isSafari: isSafari,
		isWebkit: isWebkit,
		isAndroid: isAndroid,
		isIPad: isIPad,
		isIPhone: isIPhone,
		isIOS: isIOS,
		
		/** OSs */
		isMac: isMac,
		isWindows: isWindows,
		isLinux: isLinux,

		/** Capabilities */
		isTouch: isTouch,

		platformDelimiter: platformDelimiter
	};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: 
 *		Felipe Heidrich (IBM Corporation) - initial API and implementation
 *		Silenio Quarti (IBM Corporation) - initial API and implementation
 ******************************************************************************/
 
/*eslint-env browser, amd*/
define("orion/editor/textModel", ['orion/editor/eventTarget', 'orion/regex', 'orion/util'], function(mEventTarget, mRegex, util) { //$NON-NLS-2$  //$NON-NLS-1$ //$NON-NLS-0$

	/**
	 * Constructs a new TextModel with the given text and default line delimiter.
	 *
	 * @param {String} [text=""] the text that the model will store
	 * @param {String} [lineDelimiter=platform delimiter] the line delimiter used when inserting new lines to the model.
	 *
	 * @name orion.editor.TextModel
	 * @class The TextModel is an interface that provides text for the view. Applications may
	 * implement the TextModel interface to provide a custom store for the view content. The
	 * view interacts with its text model in order to access and update the text that is being
	 * displayed and edited in the view. This is the default implementation.
	 * <p>
	 * <b>See:</b><br/>
	 * {@link orion.editor.TextView}<br/>
	 * {@link orion.editor.TextView#setModel}
	 * </p>
	 * @borrows orion.editor.EventTarget#addEventListener as #addEventListener
	 * @borrows orion.editor.EventTarget#removeEventListener as #removeEventListener
	 * @borrows orion.editor.EventTarget#dispatchEvent as #dispatchEvent
	 */
	function TextModel(text, lineDelimiter) {
		this._lastLineIndex = -1;
		this._text = [""];
		this._lineOffsets = [0];
		this.setText(text);
		this.setLineDelimiter(lineDelimiter);
	}

	TextModel.prototype = /** @lends orion.editor.TextModel.prototype */ {
		/**
		 * Destroys this text model.
		 */
		destroy: function() {
		},
		/**
		 * @class This object describes the options to use while finding occurrences of a string in a text model.
		 * @name orion.editor.FindOptions
		 *
		 * @property {String} string the search string to be found.
		 * @property {Boolean} [regex=false] whether or not the search string is a regular expression.
		 * @property {Boolean} [wrap=false] whether or not to wrap search.
		 * @property {Boolean} [wholeWord=false] whether or not to search only whole words.
		 * @property {Boolean} [caseInsensitive=false] whether or not search is case insensitive.
		 * @property {Boolean} [reverse=false] whether or not to search backwards.
		 * @property {Number} [start=0] The start offset to start searching
		 * @property {Number} [end=charCount] The end offset of the search. Used to search in a given range.
		 */
		/**
		 * @class This object represents a find occurrences iterator.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextModel#find}<br/>
		 * </p>		 
		 * @name orion.editor.FindIterator
		 * 
		 * @property {Function} hasNext Determines whether there are more occurrences in the iterator.
		 * @property {Function} next Returns the next matched range {start,end} in the iterator.
		 */	
		/**
		 * Finds occurrences of a string in the text model.
		 *
		 * @param {orion.editor.FindOptions} options the search options
		 * @return {orion.editor.FindIterator} the find occurrences iterator.
		 */
		find: function(options) {
			if (this._text.length > 1) {
				this._text = [this._text.join("")];
			}
			var string = options.string;
			var regex = options.regex;
			var pattern = string;
			var flags = "";
			var caseInsensitive = options.caseInsensitive;
			if (pattern) {
				if (regex) {
					var parsed = mRegex.parse(pattern);
					if (parsed) {
						pattern = parsed.pattern;
						flags = parsed.flags;
					}
				} else {
					pattern = string.replace(/([\\$\^*\/+?\.\(\)|{}\[\]])/g, "\\$&"); //$NON-NLS-0$
					/*
					* Bug in JS RegEx. In a Turkish locale, dotless i (u0131) capitalizes to I (u0049) and i (u0069) 
					* capitalizes to dot I (u0130). The JS RegEx does not match correctly the Turkish i's in case
					* insensitive mode. The fix is to detect the presence of Turkish i's in the search pattern and 
					* to modify the pattern to search for both upper and lower case.
					*/
					if (caseInsensitive) {  //$NON-NLS-1$ //$NON-NLS-0$
						pattern = pattern.replace(/[iI\u0130\u0131]/g, "[Ii\u0130\u0131]"); //$NON-NLS-0$
					}
				}
			}
			var current = null, skip;
			if (pattern) {
				var reverse = options.reverse;
				var wrap = options.wrap;
				var wholeWord = options.wholeWord;
				var start = options.start || 0;
				var end = options.end;
				var isRange = (end !== null && end !== undefined);
				if (flags.indexOf("g") === -1) { flags += "g"; } //$NON-NLS-1$ //$NON-NLS-0$
				if (flags.indexOf("m") === -1) { flags += "m"; } //$NON-NLS-1$ //$NON-NLS-0$
				if (caseInsensitive) {
					if (flags.indexOf("i") === -1) { flags += "i"; } //$NON-NLS-1$ //$NON-NLS-0$
				}
				if (wholeWord) {
					pattern = "\\b" + pattern + "\\b"; //$NON-NLS-1$ //$NON-NLS-0$
				}
				var text = this._text[0], result, lastIndex, offset = 0;
				if (isRange) {
					var s = start < end ? start : end;
					var e = start < end ? end : start;
					text = text.substring(s, e);
					offset = s;
				}
				var re = new RegExp(pattern, flags);
				if (reverse) {
					skip = function() {
						var match = null;
						re.lastIndex = 0;
						while (true) {
							lastIndex = re.lastIndex;
							result = re.exec(text);
							if (lastIndex === re.lastIndex) {
								return null;
							}
							if (result) {
								if (result.index + offset < start) {
									match = {start: result.index + offset, end: re.lastIndex + offset};
								} else {
									if (!wrap || match) {
										break;
									}
									start = text.length + offset;
									match = {start: result.index + offset, end: re.lastIndex + offset};
								}
							} else {
								break;
							}
						}
						if (match) { start = match.start; }
						return match;
					};
				} else {
					if (!isRange) {
						re.lastIndex = start;
					}
					skip = function() {
						while (true) {
							lastIndex = re.lastIndex;
							result = re.exec(text);
							if (lastIndex === re.lastIndex) {
								return null;
							}
							if (result) {
								return {start: result.index + offset, end: re.lastIndex + offset};
							}
							if (lastIndex !== 0) {
								if (wrap) {
									continue;
								}
							}
							break;
						}
						return null;
					};
				}
				current = skip();
			}
			return {
				next: function() {
					var result = current;
					if (result) { current = skip(); }
					return result;					
				},
				hasNext: function() {
					return current !== null;
				}
			};
		},
		/**
		 * Returns the number of characters in the model.
		 *
		 * @returns {Number} the number of characters in the model.
		 */
		getCharCount: function() {
			var count = 0;
			for (var i = 0; i<this._text.length; i++) {
				count += this._text[i].length;
			}
			return count;
		},
		/**
		 * Returns the text of the line at the given index.
		 * <p>
		 * The valid indices are 0 to line count exclusive.  Returns <code>null</code> 
		 * if the index is out of range. 
		 * </p>
		 *
		 * @param {Number} lineIndex the zero based index of the line.
		 * @param {Boolean} [includeDelimiter=false] whether or not to include the line delimiter. 
		 * @returns {String} the line text or <code>null</code> if out of range.
		 *
		 * @see orion.editor.TextModel#getLineAtOffset
		 */
		getLine: function(lineIndex, includeDelimiter) {
			var lineCount = this.getLineCount();
			if (!(0 <= lineIndex && lineIndex < lineCount)) {
				return null;
			}
			var start = this._lineOffsets[lineIndex];
			if (lineIndex + 1 < lineCount) {
				var text = this.getText(start, this._lineOffsets[lineIndex + 1]);
				if (includeDelimiter) {
					return text;
				}
				var end = text.length, c;
				while (((c = text.charCodeAt(end - 1)) === 10) || (c === 13)) {
					end--;
				}
				return text.substring(0, end);
			} else {
				return this.getText(start); 
			}
		},
		/**
		 * Returns the line index at the given character offset.
		 * <p>
		 * The valid offsets are 0 to char count inclusive. The line index for
		 * char count is <code>line count - 1</code>. Returns <code>-1</code> if
		 * the offset is out of range.
		 * </p>
		 *
		 * @param {Number} offset a character offset.
		 * @returns {Number} the zero based line index or <code>-1</code> if out of range.
		 */
		getLineAtOffset: function(offset) {
			var charCount = this.getCharCount();
			if (!(0 <= offset && offset <= charCount)) {
				return -1;
			}
			var lineCount = this.getLineCount();
			if (offset === charCount) {
				return lineCount - 1; 
			}
			var lineStart, lineEnd;
			var index = this._lastLineIndex;
			if (0 <= index && index < lineCount) {
				lineStart = this._lineOffsets[index];
				lineEnd = index + 1 < lineCount ? this._lineOffsets[index + 1] : charCount;
				if (lineStart <= offset && offset < lineEnd) {
					return index;
				}
			}
			var high = lineCount;
			var low = -1;
			while (high - low > 1) {
				index = Math.floor((high + low) / 2);
				lineStart = this._lineOffsets[index];
				lineEnd = index + 1 < lineCount ? this._lineOffsets[index + 1] : charCount;
				if (offset <= lineStart) {
					high = index;
				} else if (offset < lineEnd) {
					high = index;
					break;
				} else {
					low = index;
				}
			}
			this._lastLineIndex = high;
			return high;
		},
		/**
		 * Returns the number of lines in the model.
		 * <p>
		 * The model always has at least one line.
		 * </p>
		 *
		 * @returns {Number} the number of lines.
		 */
		getLineCount: function() {
			return this._lineOffsets.length;
		},
		/**
		 * Returns the line delimiter that is used by the view
		 * when inserting new lines. New lines entered using key strokes 
		 * and paste operations use this line delimiter.
		 *
		 * @return {String} the line delimiter that is used by the view when inserting new lines.
		 */
		getLineDelimiter: function() {
			return this._lineDelimiter;
		},
		/**
		 * Returns the end character offset for the given line. 
		 * <p>
		 * The end offset is not inclusive. This means that when the line delimiter is included, the 
		 * offset is either the start offset of the next line or char count. When the line delimiter is
		 * not included, the offset is the offset of the line delimiter.
		 * </p>
		 * <p>
		 * The valid indices are 0 to line count exclusive.  Returns <code>-1</code> 
		 * if the index is out of range. 
		 * </p>
		 *
		 * @param {Number} lineIndex the zero based index of the line.
		 * @param {Boolean} [includeDelimiter=false] whether or not to include the line delimiter. 
		 * @return {Number} the line end offset or <code>-1</code> if out of range.
		 *
		 * @see orion.editor.TextModel#getLineStart
		 */
		getLineEnd: function(lineIndex, includeDelimiter) {
			var lineCount = this.getLineCount();
			if (!(0 <= lineIndex && lineIndex < lineCount)) {
				return -1;
			}
			if (lineIndex + 1 < lineCount) {
				var end = this._lineOffsets[lineIndex + 1];
				if (includeDelimiter) {
					return end;
				}
				var text = this.getText(Math.max(this._lineOffsets[lineIndex], end - 2), end);
				var i = text.length, c;
				while (((c = text.charCodeAt(i - 1)) === 10) || (c === 13)) {
					i--;
				}
				return end - (text.length - i);
			} else {
				return this.getCharCount();
			}
		},
		/**
		 * Returns the start character offset for the given line.
		 * <p>
		 * The valid indices are 0 to line count exclusive.  Returns <code>-1</code> 
		 * if the index is out of range. 
		 * </p>
		 *
		 * @param {Number} lineIndex the zero based index of the line.
		 * @return {Number} the line start offset or <code>-1</code> if out of range.
		 *
		 * @see orion.editor.TextModel#getLineEnd
		 */
		getLineStart: function(lineIndex) {
			if (!(0 <= lineIndex && lineIndex < this.getLineCount())) {
				return -1;
			}
			return this._lineOffsets[lineIndex];
		},
		/**
		 * Returns the text for the given range.
		 * <p>
		 * The end offset is not inclusive. This means that character at the end offset
		 * is not included in the returned text.
		 * </p>
		 *
		 * @param {Number} [start=0] the zero based start offset of text range.
		 * @param {Number} [end=char count] the zero based end offset of text range.
		 *
		 * @see orion.editor.TextModel#setText
		 */
		getText: function(start, end) {
			if (start === undefined) { start = 0; }
			if (end === undefined) { end = this.getCharCount(); }
			if (start === end) { return ""; }
			var offset = 0, chunk = 0, length;
			while (chunk<this._text.length) {
				length = this._text[chunk].length; 
				if (start <= offset + length) { break; }
				offset += length;
				chunk++;
			}
			var firstOffset = offset;
			var firstChunk = chunk;
			while (chunk<this._text.length) {
				length = this._text[chunk].length; 
				if (end <= offset + length) { break; }
				offset += length;
				chunk++;
			}
			var lastOffset = offset;
			var lastChunk = chunk;
			if (firstChunk === lastChunk) {
				return this._text[firstChunk].substring(start - firstOffset, end - lastOffset);
			}
			var beforeText = this._text[firstChunk].substring(start - firstOffset);
			var afterText = this._text[lastChunk].substring(0, end - lastOffset);
			return beforeText + this._text.slice(firstChunk+1, lastChunk).join("") + afterText; 
		},
		/**
		 * Notifies all listeners that the text is about to change.
		 * <p>
		 * This notification is intended to be used only by the view. Application clients should
		 * use {@link orion.editor.TextView#event:onModelChanging}.
		 * </p>
		 * <p>
		 * NOTE: This method is not meant to called directly by application code. It is called internally by the TextModel
		 * as part of the implementation of {@link #setText}. This method is included in the public API for documentation
		 * purposes and to allow integration with other toolkit frameworks.
		 * </p>
		 *
		 * @param {orion.editor.ModelChangingEvent} modelChangingEvent the changing event
		 */
		onChanging: function(modelChangingEvent) {
			return this.dispatchEvent(modelChangingEvent);
		},
		/**
		 * Notifies all listeners that the text has changed.
		 * <p>
		 * This notification is intended to be used only by the view. Application clients should
		 * use {@link orion.editor.TextView#event:onModelChanged}.
		 * </p>
		 * <p>
		 * NOTE: This method is not meant to called directly by application code. It is called internally by the TextModel
		 * as part of the implementation of {@link #setText}. This method is included in the public API for documentation
		 * purposes and to allow integration with other toolkit frameworks.
		 * </p>
		 *
		 * @param {orion.editor.ModelChangedEvent} modelChangedEvent the changed event
		 */
		onChanged: function(modelChangedEvent) {
			return this.dispatchEvent(modelChangedEvent);
		},
		/**
		 * Sets the line delimiter that is used by the view
		 * when new lines are inserted in the model due to key
		 * strokes and paste operations. The line delimiter of
		 * existing lines are unchanged unless the to <code>all</code>
		 * argument is <code>true</code>.
		 * <p>
		 * If lineDelimiter is "auto", the delimiter is computed to be
		 * the first delimiter found in the current text. If lineDelimiter
		 * is undefined or if there are no delimiters in the current text, the
		 * platform delimiter is used.
		 * </p>
		 *
		 * @param {String} lineDelimiter the line delimiter that is used by the view when inserting new lines.
		 * @param {Boolean} [all=false] whether or not the delimiter of existing lines are changed.
		 */
		setLineDelimiter: function(lineDelimiter, all) {
			if (lineDelimiter === "auto") { //$NON-NLS-0$
				lineDelimiter = undefined;
				if (this.getLineCount() > 1) {
					lineDelimiter = this.getText(this.getLineEnd(0), this.getLineEnd(0, true));
				}
			}
			this._lineDelimiter = lineDelimiter ? lineDelimiter : util.platformDelimiter;
			if (all) {
				var lineCount = this.getLineCount();
				if (lineCount > 1) {
					var lines = new Array(lineCount);
					for (var i=0; i<lineCount; i++) {
						lines[i] = this.getLine(i);
					}
					this.setText(lines.join(this._lineDelimiter));
				}
			}
		},
		/**
		 * Replaces the text in the given range with the given text.
		 * <p>
		 * The end offset is not inclusive. This means that the character at the 
		 * end offset is not replaced.
		 * </p>
		 * <p>
		 * The text model must notify the listeners before and after the
		 * the text is changed by calling {@link #onChanging} and {@link #onChanged}
		 * respectively. 
		 * </p>
		 *
		 * @param {String} [text=""] the new text.
		 * @param {Number} [start=0] the zero based start offset of text range.
		 * @param {Number} [end=char count] the zero based end offset of text range.
		 *
		 * @see orion.editor.TextModel#getText
		 */
		setText: function(text, start, end) {
			if (text === undefined) { text = ""; }
			if (start === undefined) { start = 0; }
			if (end === undefined) { end = this.getCharCount(); }
			if (start === end && text === "") { return; }
			var startLine = this.getLineAtOffset(start);
			var endLine = this.getLineAtOffset(end);
			var eventStart = start;
			var removedCharCount = end - start;
			var removedLineCount = endLine - startLine;
			var addedCharCount = text.length;
			var addedLineCount = 0;
			var lineCount = this.getLineCount();
			
			var cr = 0, lf = 0, index = 0;
			var newLineOffsets = [];
			while (true) {
				if (cr !== -1 && cr <= index) { cr = text.indexOf("\r", index); } //$NON-NLS-0$
				if (lf !== -1 && lf <= index) { lf = text.indexOf("\n", index); } //$NON-NLS-0$
				if (lf === -1 && cr === -1) { break; }
				if (cr !== -1 && lf !== -1) {
					if (cr + 1 === lf) {
						index = lf + 1;
					} else {
						index = (cr < lf ? cr : lf) + 1;
					}
				} else if (cr !== -1) {
					index = cr + 1;
				} else {
					index = lf + 1;
				}
				newLineOffsets.push(start + index);
				addedLineCount++;
			}
		
			var modelChangingEvent = {
				type: "Changing", //$NON-NLS-0$
				text: text,
				start: eventStart,
				removedCharCount: removedCharCount,
				addedCharCount: addedCharCount,
				removedLineCount: removedLineCount,
				addedLineCount: addedLineCount
			};
			this.onChanging(modelChangingEvent);
			
			//TODO this should be done the loops below to avoid getText()
			if (newLineOffsets.length === 0) {
				var startLineOffset = this.getLineStart(startLine), endLineOffset;
				if (endLine + 1 < lineCount) {
					endLineOffset = this.getLineStart(endLine + 1);
				} else {
					endLineOffset = this.getCharCount();
				}
				if (start !== startLineOffset) {
					text = this.getText(startLineOffset, start) + text;
					start = startLineOffset;
				}
				if (end !== endLineOffset) {
					text = text + this.getText(end, endLineOffset);
					end = endLineOffset;
				}
			}
			
			var changeCount = addedCharCount - removedCharCount;
			for (var j = startLine + removedLineCount + 1; j < lineCount; j++) {
				this._lineOffsets[j] += changeCount;
			}
			
			/*
			* Feature in Chrome.  Chrome exceeds the maximum call stack when calling splice
			* around 62k arguments. The limit seems to be higher on IE (250K) and Firefox (450k).
			* The fix is to break the splice in junks of 50k.
			*/
			var SPLICE_LIMIT = 50000;
			var limit = SPLICE_LIMIT, args;
			if (newLineOffsets.length < limit) {
				args = [startLine + 1, removedLineCount].concat(newLineOffsets);
				Array.prototype.splice.apply(this._lineOffsets, args);
			} else {
				index = startLine + 1;
				this._lineOffsets.splice(index, removedLineCount);
				for (var k = 0; k < newLineOffsets.length; k += limit) {
					args = [index, 0].concat(newLineOffsets.slice(k, Math.min(newLineOffsets.length, k + limit)));
					Array.prototype.splice.apply(this._lineOffsets, args);
					index += limit;
				}
			}
			
			var offset = 0, chunk = 0, length;
			while (chunk<this._text.length) {
				length = this._text[chunk].length; 
				if (start <= offset + length) { break; }
				offset += length;
				chunk++;
			}
			var firstOffset = offset;
			var firstChunk = chunk;
			while (chunk<this._text.length) {
				length = this._text[chunk].length; 
				if (end <= offset + length) { break; }
				offset += length;
				chunk++;
			}
			var lastOffset = offset;
			var lastChunk = chunk;
			var firstText = this._text[firstChunk];
			var lastText = this._text[lastChunk];
			var beforeText = firstText.substring(0, start - firstOffset);
			var afterText = lastText.substring(end - lastOffset);
			var params = [firstChunk, lastChunk - firstChunk + 1];
			if (beforeText) { params.push(beforeText); }
			if (text) { params.push(text); }
			if (afterText) { params.push(afterText); }
			Array.prototype.splice.apply(this._text, params);
			if (this._text.length === 0) { this._text = [""]; }
			
			var modelChangedEvent = {
				type: "Changed", //$NON-NLS-0$
				start: eventStart,
				removedCharCount: removedCharCount,
				addedCharCount: addedCharCount,
				removedLineCount: removedLineCount,
				addedLineCount: addedLineCount
			};
			this.onChanged(modelChangedEvent);
		}
	};
	mEventTarget.EventTarget.addMixin(TextModel.prototype);
	
	return {TextModel: TextModel};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/

/*eslint-env browser, amd*/
define("orion/editor/undoStack", [], function() { //$NON-NLS-0$

	/** 
	 * Constructs a new Change object.
	 * 
	 * @class 
	 * @name orion.editor.Change
	 * @private
	 */
	function Change(model, offset, text, previousText, type) {
		this.model = model;
		this.offset = offset;
		this.text = text;
		this.previousText = previousText;
		this.type = type;
	}
	Change.prototype = {
		/** @ignore */
		getRedoChanges: function() {
			return [{start: this.offset, end: this.offset + this.previousText.length, text: this.text}];
		},
		/** @ignore */
		getUndoChanges: function() {
			return [{start: this.offset, end: this.offset + this.text.length, text: this.previousText}];
		},
		/** @ignore */
		undo: function (view, select) {
			this._doUndoRedo(this.offset, this.previousText, this.text, view, select);
			return true;
		},
		/** @ignore */
		redo: function (view, select) {
			this._doUndoRedo(this.offset, this.text, this.previousText, view, select);
			return true;
		},
		merge: function(start, text, previousText, type, end) {
			if (type === this.type) {
				if (type === 1 && start === this.offset + this.text.length) {
					this.text += text;
					return true;
				} else if (type === -1 && end === this.offset) {
					this.offset = start;
					this.previousText = previousText + this.previousText;
					return true;
				} else if (type === -1 && start === this.offset) {
					this.previousText = this.previousText + previousText;
					return true;
				}
			}
			return false;
		},
		_doUndoRedo: function(offset, text, previousText, view, select) {
			this.model.setText(text, offset, offset + previousText.length);
			if (select && view) {
				var model = view.getModel();
				if (model !== this.model) {
					offset = model.mapOffset(offset, true);
				}
				view.setSelection(offset, offset + text.length);
			}
		}
	};

	/** 
	 * Constructs a new CompoundChange object.
	 * 
	 * @param owner the owner of the compound change
	 *
	 * @class 
	 * @name orion.editor.CompoundChange
	 * @private
	 */
	function CompoundChange (owner) {
		this.owner = owner;
		this.changes = [];
	}
	CompoundChange.prototype = {
		/** @ignore */
		getRedoChanges: function() {
			var changes = [];
			for (var i=0; i<this.changes.length; i++) {
				changes = changes.concat(this.changes[i].getRedoChanges());
			}
			return changes;
		},
		/** @ignore */
		getUndoChanges: function() {
			var changes = [];
			for (var i=this.changes.length - 1; i >= 0; i--) {
				changes = changes.concat(this.changes[i].getUndoChanges());
			}
			return changes;
		},
		/** @ignore */
		add: function (change) {
			this.changes.push(change);
		},
		/** @ignore */
		end: function (view) {
			if (view) {
				this.endSelection = view.getSelections();
			}
			var owner = this.owner;
			if (owner && owner.end) {
				owner.end();
			}
		},
		/** @ignore */
		undo: function (view, select) {
			if (this.changes.length > 1 && view) {
				view.setRedraw(false);
			}
			for (var i=this.changes.length - 1; i >= 0; i--) {
				this.changes[i].undo(view, false);
			}
			if (select && view) {
				view.setSelections(this.startSelection);
			}
			if (this.changes.length > 1 && view) {
				view.setRedraw(true);
			}
			var owner = this.owner;
			if (owner && owner.undo) {
				owner.undo();
			}
			return this.changes.length > 0;
		},
		/** @ignore */
		redo: function (view, select) {
			if (this.changes.length > 1 && view) {
				view.setRedraw(false);
			}
			for (var i = 0; i < this.changes.length; i++) {
				this.changes[i].redo(view, false);
			}
			if (select && view) {
				view.setSelections(this.endSelection);
			}
			if (this.changes.length > 1, view) {
				view.setRedraw(true);
			}
			var owner = this.owner;
			if (owner && owner.redo) {
				owner.redo();
			}
			return this.changes.length > 0;
		},
		merge: function(start, text, previousText, type, end, compoundChange) {
			var length = this.changes.length;
			if (length > 0 && this === compoundChange) {
				return this.changes[length - 1].merge(start, text, previousText, type, end);
			}
			return false;
		},
		/** @ignore */
		start: function (view) {
			if (view) {
				this.startSelection = view.getSelections();
			}
			var owner = this.owner;
			if (owner && owner.start) {
				owner.start();
			}
		}
	};

	/**
	 * Constructs a new UndoStack on a text view.
	 *
	 * @param {orion.editor.TextView} view the text view for the undo stack.
	 * @param {Number} [size=100] the size for the undo stack.
	 *
	 * @name orion.editor.UndoStack
	 * @class The UndoStack is used to record the history of a text model associated to an view. Every
	 * change to the model is added to stack, allowing the application to undo and redo these changes.
	 *
	 * <p>
	 * <b>See:</b><br/>
	 * {@link orion.editor.TextView}<br/>
	 * </p>
	 */
	function UndoStack (view, size) {
		this.size = size !== undefined ? size : 100;
		this.reset();
		var self = this;
		this._listener = {
			onChanging: function(e) {
				self._onChanging(e);
			},
			onDestroy: function(e) {
				self._onDestroy(e);
			}
		};
		if (view.getModel) {
			var model = view.getModel();
			if (model.getBaseModel) {
				model = model.getBaseModel();
			}
			this.model = model;
			this.setView(view);
		} else {
			this.shared = true;
			this.model = view;
		}
		this.model.addEventListener("Changing", this._listener.onChanging); //$NON-NLS-0$
	}
	UndoStack.prototype = /** @lends orion.editor.UndoStack.prototype */ {
		/**
		 * Destroy the undo stack.
		 */
		destroy: function() {
			this._onDestroy();
		},
		/**
		 * Adds a change to the stack.
		 * 
		 * @param change the change to add.
		 */
		add: function (change) {
			if (this.compoundChange) {
				this.compoundChange.add(change);
			} else {
				var length = this.stack.length;
				this.stack.splice(this.index, length-this.index, change);
				this.index++;
				if (this.stack.length > this.size) {
					this.stack.shift();
					this.index--;
				}
			}
		},
		/** 
		 * Marks the current state of the stack as clean.
		 *
		 * <p>
		 * This function is typically called when the content of view associated with the stack is saved.
		 * </p>
		 *
		 * @see orion.editor.UndoStack#isClean
		 */
		markClean: function() {
			this._commitUndo();
			this.cleanChange = this.stack[this.index - 1];
			if (this.cleanChange) {
				this.cleanChange.type = 2;
			}
		},
		/**
		 * Returns true if current state of stack is the same
		 * as the state when markClean() was called.
		 *
		 * <p>
		 * For example, the application calls markClean(), then calls undo() four times and redo() four times.
		 * At this point isClean() returns true.  
		 * </p>
		 * <p>
		 * This function is typically called to determine if the content of the view associated with the stack
		 * has changed since the last time it was saved.
		 * </p>
		 *
		 * @return {Boolean} returns if the state is the same as the state when markClean() was called.
		 *
		 * @see orion.editor.UndoStack#markClean
		 */
		isClean: function() {
			return this.cleanChange === this.stack[this.index - 1];
		},
		/**
		 * Returns true if there is at least one change to undo.
		 *
		 * @return {Boolean} returns true if there is at least one change to undo.
		 *
		 * @see orion.editor.UndoStack#canRedo
		 * @see orion.editor.UndoStack#undo
		 */
		canUndo: function() {
			return this.index > 0;
		},
		/**
		 * Returns true if there is at least one change to redo.
		 *
		 * @return {Boolean} returns true if there is at least one change to redo.
		 *
		 * @see orion.editor.UndoStack#canUndo
		 * @see orion.editor.UndoStack#redo
		 */
		canRedo: function() {
			return (this.stack.length - this.index) > 0;
		},
		/**
		 * Finishes a compound change.
		 *
		 * @see orion.editor.UndoStack#startCompoundChange
		 */
		endCompoundChange: function() {
			if (this.compoundChange) {
				this.compoundChange.end(this.view);
			}
			this.compoundChange = undefined;
		},
		/**
		 * Returns the sizes of the stack.
		 *
		 * @return {object} a object where object.undo is the number of changes that can be un-done, 
		 *  and object.redo is the number of changes that can be re-done.
		 *
		 * @see orion.editor.UndoStack#canUndo
		 * @see orion.editor.UndoStack#canRedo
		 */
		getSize: function() {
			return {
				undo: this.index,
				redo: this.stack.length - this.index
			};
		},
		/**
		 * @class This object represents a text change.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.UndoStack}<br/>
		 * {@link orion.editor.UndoStack#getUndoChanges}<br/>
		 * {@link orion.editor.UndoStack#getRedoChanges}<br/>
		 * </p>
		 * @name orion.editor.TextChange
		 * 
		 * @property {Number} start The start offset in the model of the range to be replaced.
		 * @property {Number} end The end offset in the model of the range to be replaced
		 * @property {String} text the text to be inserted
		 */
		/**
		 * Returns the redo changes.
		 *
		 * @return {orion.editor.TextChange[]} an array of TextChanges that are returned in the order
		 * that they occurred (most recent change last).
		 *
		 * @see orion.editor.UndoStack#getUndoChanges
		 */
		getRedoChanges: function() {
			this._commitUndo();
			var changes = [];
			for (var i=this.index; i<this.stack.length; i++) {
				changes = changes.concat(this.stack[i].getRedoChanges());
			}
			return changes;
		},
		/**
		 * Returns the undo changes.
		 *
		 * @return {orion.editor.TextChange[]} an array of TextChanges that are returned in the reverse order
		 * that they occurred (most recent change first).
		 *
		 * @see orion.editor.UndoStack#getRedoChanges
		 */
		getUndoChanges: function() {
			this._commitUndo();
			var changes = [];
			for (var i=this.index; i >= 0; i--) {
				changes = changes.concat(this.stack[i].getUndoChanges());
			}
			return changes;
		},
		/**
		 * Undo the last change in the stack.
		 *
		 * @return {Boolean} returns true if a change was un-done.
		 *
		 * @see orion.editor.UndoStack#redo
		 * @see orion.editor.UndoStack#canUndo
		 */
		undo: function() {
			this._commitUndo();
			var change, result = false;
			this._ignoreUndo = true;
			do {
				if (this.index <= 0) {
					break;
				}
				change = this.stack[--this.index];
			} while (!(result = change.undo(this.view, true)));
			this._ignoreUndo = false;
			return result;
		},
		/**
		 * Redo the last change in the stack.
		 *
		 * @return {Boolean} returns true if a change was re-done.
		 *
		 * @see orion.editor.UndoStack#undo
		 * @see orion.editor.UndoStack#canRedo
		 */
		redo: function() {
			this._commitUndo();
			var change, result = false;
			this._ignoreUndo = true;
			do {
				if (this.index >= this.stack.length) {
					break;
				}
				change = this.stack[this.index++];
			} while (!(result = change.redo(this.view, true)));
			this._ignoreUndo = false;
			return result;
		},
		/**
		 * Reset the stack to its original state. All changes in the stack are thrown away.
		 */
		reset: function() {
			this.index = 0;
			this.cleanChange = undefined;
			this.stack = [];
			this._ignoreUndo = false;
			this._compoundChange = undefined;
		},
		setView: function(view) {
			if (this.view === view) { return; }
			if (this.view) {
				view.removeEventListener("Destroy", this._listener.onDestroy); //$NON-NLS-0$
			}
			this.view = view;
			if (this.view) {
				view.addEventListener("Destroy", this._listener.onDestroy); //$NON-NLS-0$
			}
		},
		/**
		 * Starts a compound change. 
		 * <p>
		 * All changes added to stack from the time startCompoundChange() is called
		 * to the time that endCompoundChange() is called are compound on one change that can be un-done or re-done
		 * with one single call to undo() or redo().
		 * </p>
		 *
		 * @param owner the owner of the compound change which is called for start, end, undo and redo.
		 *		 
		 * @return the compound change
		 *
		 * @see orion.editor.UndoStack#endCompoundChange
		 */
		startCompoundChange: function(owner) {
			this._commitUndo();
			var change = new CompoundChange(owner);
			this.add(change);
			this.compoundChange = change;
			this.compoundChange.start(this.view);
			return this.compoundChange;
		},
		_commitUndo: function () {
			this.endCompoundChange();
		},
		_onDestroy: function(evt) {
			if (!evt /* undo stack destroyed */ || !this.shared) {
				this.model.removeEventListener("Changing", this._listener.onChanging); //$NON-NLS-0$
			}
			if (this.view) {
				this.view.removeEventListener("Destroy", this._listener.onDestroy); //$NON-NLS-0$
				this.view = null;
			}
		},
		_trackUnsavedChanges: function(e) {
			if (!this._unsavedChanges) return;
			var length = this._unsavedChanges.length;
			var addedCharCount = e.addedCharCount;
			var removedCharCount = e.removedCharCount;
			var start = e.start;
			var end = e.start + removedCharCount;
			var type = 0;
			if (addedCharCount === 0) {
				type = -1;
			} else if (removedCharCount === 0) {
				type = 1;
			}
			if (length > 0) {
				if (type === this._previousChangeType) {
					var previousChange = this._unsavedChanges[length-1];
					if (removedCharCount === 0 && start === previousChange.end + previousChange.text.length) {
						previousChange.text += e.text;
						return;
					}
					if (e.addedCharCount === 0 && end === previousChange.start) {
						previousChange.start = start;
						return;
					}
				}
			}
			this._previousChangeType = type;
			this._unsavedChanges.push({start:start, end:end, text:e.text});
		},
		_onChanging: function(e) {
			this._trackUnsavedChanges(e);
			if (this._ignoreUndo) {
				return;
			}
			var text = e.text;
			var start = e.start;
			var addedCharCount = e.addedCharCount;
			var removedCharCount = e.removedCharCount;
			var end = start + removedCharCount;
			var type = 0;
			if (addedCharCount === 0 && removedCharCount === 1) {
				type = -1;
			} else if (addedCharCount === 1 && removedCharCount === 0) {
				type = 1;
			}
			var length = this.stack.length;
			var previousText = this.model.getText(start, end);
			if (length > 0 && this.index === length) {
				var change = this.stack[length - 1];
				if (change.merge(start, text, previousText, type, end, this.compoundChange)) {
					return;
				}
			}
			this.add(new Change(this.model, start, text, previousText, type));
		}
	};
	
	return {
		UndoStack: UndoStack
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/webui/littlelib',["orion/util"], function(util) {
	/**
	 * @name orion.webui.littlelib
	 * @class A small library of DOM and UI helpers.
	 */

	/**
	 * Alias for <code>node.querySelector()</code>.
	 * @name orion.webui.littlelib.$
	 * @function
	 * @static
	 * @param {String} selectors Selectors to match on.
	 * @param {Node} [node=document] Node to query under.
	 * @returns {Element}
	 */
	function $(selector, node) {
		if (!node) {
			node = document;
		}
		return node.querySelector(selector);
	}

	/**
	 * Alias for <code>node.querySelectorAll()</code>.
	 * @name orion.webui.littlelib.$$
	 * @function
	 * @static
	 * @param {String} selectors Selectors to match on.
	 * @param {Node} [node=document] Node to query under.
	 * @returns {NodeList}
	 */
	function $$(selector, node) {
		if (!node) {
			node = document;
		}
		return node.querySelectorAll(selector);
	}

	/**
	 * Identical to {@link orion.webui.littlelib.$$}, but returns an Array instead of a NodeList.
	 * @name orion.webui.littlelib.$$array
	 * @function
	 * @static
	 * @param {String} selectors Selectors to match on.
	 * @param {Node} [node=document] Node to query under.
	 * @returns {Element[]}
	 */
	function $$array(selector, node) {
		return Array.prototype.slice.call($$(selector,node));
	}

	/**
	 * Alias for <code>document.getElementById</code>, but returns the input unmodified when passed a Node (or other non-string).
	 * @function
	 * @param {String|Element} elementOrId
	 * @returns {Element}
	 */
	function node(either) {
		var theNode = either;
		if (typeof(either) === "string") { //$NON-NLS-0$
			theNode = document.getElementById(either);
		}	
		return theNode;
	}

	/**
	 * Returns whether <code>child</code> is a descendant of <code>parent</code> in the DOM order.
	 * @function
	 * @param {Node} parent
	 * @param {Node} child
	 * @returns {Boolean}
	 */
	function contains(parent, child) {
		if (!parent || !child) { return false; }
		if (parent === child) { return true; }
		var compare = parent.compareDocumentPosition(child);  // useful to break out for debugging
		return Boolean(compare & 16);
	}

	/**
	 * Returns the bounds of a node. The returned coordinates are absolute (not relative to the viewport).
	 * @function
	 * @param {Node} node
	 * @returns {Object}
	 */
	function bounds(node) {
		var clientRect = node.getBoundingClientRect();
		var scrollLeft = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft);
		var scrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
		return { 
			left: clientRect.left + scrollLeft,
			top: clientRect.top + scrollTop,
			width: clientRect.width,
			height: clientRect.height
		};
	}

	/**
	 * Removes all children of the given node.
	 * @name orion.webui.littlelib.empty
	 * @function
	 * @static
	 * @param {Node} node
	 */
	function empty(node) {
		while (node.hasChildNodes()) {
			var child = node.firstChild;
			node.removeChild(child);
		}
	}

	function _getTabIndex(node) {
		var result = node.tabIndex;
		if (result === 0 && util.isIE) {
			/*
			 * The default value of tabIndex is 0 on IE, even for elements that are not focusable
			 * by default (http://msdn.microsoft.com/en-us/library/ie/ms534654(v=vs.85).aspx).
			 * Handle this browser difference by treating this value as -1 if the node is a type
			 * that is not focusable by default according to the MS doc and has not had this
			 * attribute value explicitly set on it.
			 */
			var focusableElements = {
				a: true,
				body: true,
				button: true,
				frame: true,
				iframe: true,
				img: true,
				input: true,
				isindex: true,
				object: true,
				select: true,
				textarea: true
			};
			if (!focusableElements[node.nodeName.toLowerCase()] && !node.attributes.tabIndex) {
				result = -1;
			}
		}
		return result;
	}

	/* 
	 * Inspired by http://brianwhitmer.blogspot.com/2009/05/jquery-ui-tabbable-what.html
	 */
	function firstTabbable(node) {
		if (_getTabIndex(node) >= 0) {
			return node;
		}
		if (node.hasChildNodes()) {
			for (var i=0; i<node.childNodes.length; i++) {
				var result = firstTabbable(node.childNodes[i]);
				if (result) {
					return result;
				}
			}
		}
		return null;
	}
	
	function lastTabbable(node) {
		if (_getTabIndex(node) >= 0) {
			return node;
		}
		if (node.hasChildNodes()) {
			for (var i=node.childNodes.length - 1; i>=0; i--) {
				var result = lastTabbable(node.childNodes[i]);
				if (result) {
					return result;
				}
			}
		}
		return null;
	}

	var variableRegEx = /\$\{([^\}]+)\}/;
	// Internal helper
	function processNodes(node, replace) {
		if (node.nodeType === 3) { // TEXT_NODE
			var matches = variableRegEx.exec(node.nodeValue);
			if (matches && matches.length > 1) {
				replace(node, matches);
			}
		}
		if (node.hasChildNodes()) {
			for (var i=0; i<node.childNodes.length; i++) {
				processNodes(node.childNodes[i], replace);
			}
		}
	}

	/**
	 * @name pixelValueOf
	 * @description Returns the pixel value of the given CSS attribute
	 * @param node The element whose style is to be checked
	 * @param attrName The name of the attribute to check. Use the literal CSS name
	 * (i.e. 'padding-left' rather than 'paddingLeft').
	 * @returns returns The value (in pixels) of the attribute. If the value cannot be parsed to an int the zero is returned
	 */
	function pixelValueOf(node, attrName) {
		if (!node || !attrName) {
			return 0;
		}
		
		var doc = node.ownerDocument;
		var win =  doc.defaultView || doc.parentWindow;
		if (win.getComputedStyle) {
			var style = win.getComputedStyle(node, null);
			var value = style.getPropertyValue(attrName);
			// Ensure that value ends in "px"
			if (value.length > 2 && value.indexOf("px", value.length - 2) !== -1) {
				value = value.slice(0,-2);
				var intVal = parseInt(value,10);
				if (intVal !== intVal) {
					return 0;  // value was NaN
				} else {
					return intVal;
				}
			}
		}
		return 0;
	}
	
	/**
	 * Performs substitution of strings into textContent within the given node and its descendants. An occurrence of <code>${n}</code>
	 * in text content will be replaced with the string <code>messages[n]</code>.
	 * <p>This function is recommended for binding placeholder text in template-created DOM elements to actual display strings.</p>
	 * @name orion.webui.littlelib.processTextNodes
	 * @function
	 * @param {Node} node The node to perform replacement under.
	 * @param {String[]} messages The replacement strings.
	 */
	function processTextNodes(node, messages) {
		processNodes(node, function(targetNode, matches) {
			var replaceText = messages[matches[1]] || matches[1];
			targetNode.parentNode.replaceChild(document.createTextNode(replaceText), targetNode);
		});
	}

	/**
	 * Performs substitution of DOM nodes into textContent within the given node and its descendants. An occurrence of <code>${n}</code>
	 * in text content will be replaced by the DOM node <code>replaceNodes[n]</code>.
	 * <p>This function is recommended for performing rich-text replacement within a localized string. The use of actual DOM nodes
	 * avoids the need for embedded HTML in strings.</p>
	 * @name orion.webui.littlelib.processDOMNodes
	 * @function
	 * @param {Node} node The node to perform replacement under.
	 * @param {Node[]} replaceNodes The replacement nodes.
	 */
	function processDOMNodes(node, replaceNodes) {
		processNodes(node, function(targetNode, matches) {
			var replaceNode = replaceNodes[matches[1]];
			if (replaceNode) {
				var range = document.createRange();
				var start = matches.index;
				range.setStart(targetNode, start);
				range.setEnd(targetNode, start + matches[0].length);
				range.deleteContents();
				range.insertNode(replaceNode);
			}
		});
	}

	/**
	 * Adds auto-dismiss functionality to the document. When a click event occurs whose <code>target</code> is not a descendant of
	 * one of the <code>excludeNodes</code>, the <code>dismissFunction</code> is invoked.
	 * @name orion.webui.littlelib.addAutoDismiss
	 * @function
	 * @static
	 * @param {Node[]} excludeNodes Clicks targeting any descendant of these nodes will not trigger the dismissFunction.
	 * @param {Function} dismissFunction The dismiss handler.
	 */
	
	var autoDismissNodes = null;

	function addAutoDismiss(excludeNodes, dismissFunction) {
		// auto dismissal.  Click anywhere else means close.
		function onclick(event) {
			autoDismissNodes.forEach(function(autoDismissNode) {
				var excludeNodeInDocument = false;
				var excluded = autoDismissNode.excludeNodes.some(function(excludeNode) {
					if(document.body.contains(excludeNode)) {
						excludeNodeInDocument = true;
						return excludeNode.contains(event.target);
					}
					return false;
				});
				if (excludeNodeInDocument && !excluded) {
					try {
						autoDismissNode.dismiss(event);
					} catch (e) {
						if (typeof console !== "undefined" && console) { //$NON-NLS-0$
							console.error(e && e.message);
						}
					}
				}
			});
			autoDismissNodes = autoDismissNodes.filter(function(autoDismissNode) {
				// true if at least one excludeNode is in document.body
				return autoDismissNode.excludeNodes.some(function(excludeNode) {
					return document.body.contains(excludeNode);
				});
			});
		}

		// Hook listener only once
		if (autoDismissNodes === null) {
			autoDismissNodes = [];
			document.addEventListener("click", onclick, true); //$NON-NLS-0$
			if (util.isIOS) {
				document.addEventListener("touchend", function(event){
					function unhook(){
						event.target.removeEventListener("click", unhook);
					}
					if (event.touches.length === 0) {
						// we need a click eventlistener on the target to have ios really trigger a click
						event.target.addEventListener("click", unhook);
					}	
				}, false);
			}
		}
		
		autoDismissNodes.push({excludeNodes: excludeNodes, dismiss: dismissFunction});
	}
	
	/**
	 * Removes all auto-dismiss nodes which trigger the specified dismiss function.
	 * 
	 * @name orion.webui.littlelib.removeAutoDismiss
	 * @function
	 * @static
	 * @param {Function} dismissFunction The dismiss function to look for.
	 */
	function removeAutoDismiss(dismissFunction) {
		autoDismissNodes = autoDismissNodes.filter(function(autoDismissNode) {
			return dismissFunction !== autoDismissNode.dismiss;
		});
	}
	
	/**
	 * Returns the parent of the node that has the vertical scroll bar.
	 * 
	 * @name orion.webui.littlelib.getOffsetParent
	 * @function
	 * @static
	 * @param {Element} node The node to lookup the offset parent
	 */
	function getOffsetParent(node) {
		var offsetParent = node.parentNode, documentElement = document.documentElement;
		while (offsetParent && offsetParent !== documentElement) {
			var style = window.getComputedStyle(offsetParent, null);
			if (!style) { break; }
			var overflow = style.getPropertyValue("overflow-y"); //$NON-NLS-0$
			if (overflow === "auto" || overflow === "scroll") { break; } //$NON-NLS-1$ //$NON-NLS-0$
			offsetParent = offsetParent.parentNode;
		}
		return offsetParent;
	}
	
	/**
	 * Cancels the default behavior of an event and stops its propagation.
	 * @name orion.webui.littlelib.stop
	 * @function
	 * @static
	 * @param {Event} event
	 */
	function stop(event) {
		if (window.document.all) { 
			event.keyCode = 0;
		}
		if (event.preventDefault) {
			event.preventDefault();
			event.stopPropagation();
		}
	}
	
	function setFramesEnabled(enable) {
		var frames = document.getElementsByTagName("iframe"); //$NON-NLS-0$
		for (var i = 0; i<frames.length; i++) {
			frames[i].parentNode.style.pointerEvents = enable ? "" : "none"; //$NON-NLS-0$
		}
	}

	/**
	 * Holds useful <code>keyCode</code> values.
	 * @name orion.webui.littlelib.KEY
	 * @static
	 */
	var KEY = {
		BKSPC: 8,
		TAB: 9,
		ENTER: 13,
		SHIFT: 16,
		CONTROL: 17,
		ALT: 18,
		ESCAPE: 27,
		SPACE: 32,
		PAGEUP: 33,
		PAGEDOWN: 34,
		END: 35,
		HOME: 36,
		LEFT: 37,
		UP: 38,
		RIGHT: 39,
		DOWN: 40,
		INSERT: 45,
		DEL: 46,
		COMMAND: 991
	};
	/**
	 * Maps a <code>keyCode</code> to <tt>KEY</tt> name. This is the inverse of {@link orion.webui.littlelib.KEY}.
	 * @private
	 */
	var KEY_CODE = Object.create(null);
	Object.keys(KEY).forEach(function(name) {
		KEY_CODE[KEY[name]] = name;
	});

	/**
	 * @param {Number} keyCode
	 * @returns The name of the <code>lib.KEY</code> entry for keyCode, or null.
	 */
	function keyName(keyCode) {
		return KEY_CODE[keyCode] || null;
	}

	/**
	 * Creates DOM nodes from the specified template string.
	 * 
	 * @param {String} templateString 	A string containing the HTML template to use
	 * @param {Node} parentNode 		Optional. The parent node to insert the new nodes into. 
	 * 									The parent's contents will be completely replaced.
	 * @returns If the template string contains a single node or a wrapper node which
	 * 			wraps all the other nodes that single DOM node will be returned. 
	 * 			Otherwise if the template string contains multiple top-level nodes an
	 * 			{HTMLCollection} object containing all the top-level nodes will be returned.
	 */
	function createNodes(templateString, parentNode) {
		var parent = parentNode;
		var newNodes = null;
		
		if (undefined === parent) {
			parent = document.createElement("div"); //$NON-NLS-0$
		}

		parent.innerHTML = templateString;	
		if (parent.children.length > 1) {
			newNodes = parent.children;
		} else {
			newNodes = parent.firstChild;
		}
		
		return newNodes;
	}

	//return module exports
	return {
		$: $,
		$$: $$,
		$$array: $$array,
		node: node,
		contains: contains,
		bounds: bounds,
		empty: empty,
		firstTabbable: firstTabbable,
		lastTabbable: lastTabbable,
		pixelValueOf: pixelValueOf,
		stop: stop,
		processTextNodes: processTextNodes,
		processDOMNodes: processDOMNodes,
		addAutoDismiss: addAutoDismiss,
		setFramesEnabled: setFramesEnabled,
		getOffsetParent: getOffsetParent,
		removeAutoDismiss: removeAutoDismiss,
		keyName: keyName,
		KEY: KEY,
		createNodes: createNodes
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2010,2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
 
define('orion/commandsProxy',[
	'orion/util',
	'orion/webui/littlelib'
], function(util, lib) {
	
	function handleKeyEvent(evt, processKeyFunc) {
		function isContentKey(e) {
			// adapted from handleKey in http://git.eclipse.org/c/platform/eclipse.platform.swt.git/plain/bundles/org.eclipse.swt/Eclipse%20SWT%20Custom%20Widgets/common/org/eclipse/swt/custom/StyledText.java
			if (util.isMac) {
				// COMMAND+ALT combinations produce characters on the mac, but COMMAND or COMMAND+SHIFT do not.
				if (e.metaKey && !e.altKey) {  //command without alt
					// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=390341
					// special case for select all, cut, copy, paste, and undo.  A slippery slope...
					if (!e.shiftKey && !e.ctrlKey && (e.keyCode === 65 || e.keyCode === 67 || e.keyCode === 86 || e.keyCode === 88 || e.keyCode === 90)) {
						return true;
					}
					return false;
				}
				if (e.ctrlKey) {
					return false;
				}
			} else {
				// CTRL or ALT combinations are not characters, however both of them together (CTRL+ALT)
				// are the Alt Gr key on some keyboards.  See Eclipse bug 20953. If together, they might
				// be a character. However there aren't usually any commands associated with Alt Gr keys.
				if (e.ctrlKey && !e.altKey) {
					// special case for select all, cut, copy, paste, and undo.  
					if (!e.shiftKey && (e.keyCode === 65 || e.keyCode === 67 || e.keyCode === 86 || e.keyCode === 88 || e.keyCode === 90)) {
						return true;
					}
					return false;
				}
				if (e.altKey && !e.ctrlKey) {
					return false;
				}
				if (e.ctrlKey && e.altKey){
					return false;
				}
			}
			if (e['char']) { //$NON-NLS-0$
				return e['char'].length > 0;  // empty string for non characters //$NON-NLS-0$
			} else if (e.charCode || e.keyCode) {
				var keyCode= e.charCode || e.keyCode;
				// anything below SPACE is not a character except for line delimiter keys, tab, and delete.
				switch (keyCode) {
					case 8:  // backspace
					case 9:  // tab
					case 13: // enter
					case 46: // delete
						return true;
					default:
						return (keyCode >= 32 && keyCode < 112) || // space key and above until function keys
							keyCode > 123; // above function keys  
				}
			}
			// If we can't identify as a character, assume it's not
			return false;
		}
		
		evt = evt || window.event;
		if (isContentKey(evt)) {
			// bindings that are text content keys are ignored if we are in a text field or editor
			// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=375058
			if (evt.target.contentEditable === "true") { //$NON-NLS-0$
				return;
			}
			var tagType = evt.target.nodeName.toLowerCase();
			if (tagType === 'input') { //$NON-NLS-0$
				var inputType = evt.target.type.toLowerCase();
				// Any HTML5 input type that involves typing text should be ignored
				switch (inputType) {
					case "text": //$NON-NLS-0$
					case "password": //$NON-NLS-0$
					case "search": //$NON-NLS-0$
					case "color": //$NON-NLS-0$
					case "date": //$NON-NLS-0$
					case "datetime": //$NON-NLS-0$
					case "datetime-local": //$NON-NLS-0$
					case "email": //$NON-NLS-0$
					case "month": //$NON-NLS-0$
					case "number": //$NON-NLS-0$
					case "range": //$NON-NLS-0$
					case "tel": //$NON-NLS-0$
					case "time": //$NON-NLS-0$
					case "url": //$NON-NLS-0$
					case "week": //$NON-NLS-0$
						return;
				}
			} else if (tagType === 'textarea') { //$NON-NLS-0$
				return;
			}
		}
		processKeyFunc(evt);
	}
		
	function CommandsProxy() {
		this._init();
	}
	CommandsProxy.prototype = {
		destroy: function() {
			if (this._listener) {
				document.removeEventListener("keydown", this._listener); //$NON-NLS-0$
				this._listener = null;
			}
		},
		setProxy: function(proxy) {
			this.proxy = proxy;
		},
		setKeyBindings: function(bindings) {
			this.bindings = bindings;
		},
		_init: function() {
			var self = this;
			document.addEventListener("keydown", this._listener = function(evt) { //$NON-NLS-0$
				return handleKeyEvent(evt, function(evt) {
					var proxy = self.proxy;
					var bindings = self.bindings;
					if (!bindings || !proxy) {
						return;
					}
					for (var i=0; i<bindings.length; i++) {
						if (bindings[i].match(evt)) {
							proxy.processKey({
								type: evt.type,
								keyCode: evt.keyCode,
								altKey: evt.altKey,
								ctrlKey: evt.ctrlKey,
								metaKey: evt.metaKey,
								shiftKey: evt.shiftKey
							});
							lib.stop(evt);
						}
					}
				});
			});
		}
	};
	
	//return the module exports
	return {
		handleKeyEvent: handleKeyEvent,
		CommandsProxy: CommandsProxy,
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/webui/dropdown',['orion/webui/littlelib', 'orion/EventTarget'], function(lib, EventTarget) {

	/**
	 * Attaches dropdown behavior to a given node.  Assumes the triggering node and dropdown node
	 * have the same parent.  Trigger should have "dropdownTrigger" class, and the dropdown node should 
	 * have "dropdownMenu" class.  Dropdown items should be <li> elements, so typically the dropdown node
	 * supplied is a <ul>.
	 *
	 * "dropdowntriggerbutton.html" contains an appropriate HTML fragment for a triggering button and associated
	 * dropdown.  Clients can add this fragment to the DOM and then attach Dropdown behavior to it.
	 * 
	 * Nested ("sub") menu behavior is accomplished by adding the class "dropdownSubMenu" to one of the <li> items.
	 * This item can then parent another trigger and <ul>.
	 *
	 * "submenutriggerbutton.html" contains an appropriate HTML fragment for a menu item that triggers a sub menu.
	 * Clients can add this fragment to a dropdown menu and then attach Dropdown behavior to the sub menu item.
	 *
	 * The items inside each <li> item in a dropdown can be almost any type of node.  The class "dropdownMenuItem" is
	 * used on the node inside the li to find items and style them appropriately.  There are HTML fragments for some
	 * common menu types.  For example, "checkedmenuitem.html" is a fragment appropriate for checked menu items.
	 *
	 * @param {Object} options The options object, which must minimally specify the dropdown dom node
	 * @param options.dropdown The node for the dropdown presentation.  Required.
	 * @param options.populate A function that should be called to populate the dropdown before it
	 * opens each time.  Optional.
	 * @param options.triggerNode The node which will listen for events that trigger the 
	 * opening of this drop down. If it is not specified the parent of the dropdown node will be searched
	 * for a node containing the dropdownTrigger class. Optional.
	 * @param options.parentDropdown The Dropdown that is the parent of this one if this is a sub-dropdown. Optional.
	 * @param options.positioningNode The Node that the dropdown uses so that it always renders under the positioningNode's left bottom corner. Optional.
	 * @param options.skipTriggerEventListeners A boolean indicating whether or not to skip adding event
	 * listeners to the triggerNode. Optional.
	 * 
	 * @name orion.webui.dropdown.Dropdown
	 *
	 */
	function Dropdown(options) {
		EventTarget.attach(this);
		this._init(options);		
	}
	Dropdown.prototype = /** @lends orion.webui.dropdown.Dropdown.prototype */ {
			
		_init: function(options) {
			this._dropdownNode = lib.node(options.dropdown);
			if (!this._dropdownNode) { throw "no dom node for dropdown found"; } //$NON-NLS-0$
			this._populate = options.populate;
			this._selectionClass = options.selectionClass;
			this._parentDropdown = options.parentDropdown;
			this._positioningNode = options.positioningNode;
			
			if (!this._parentDropdown) {
				//if a parentDropdown isn't specified move up in dom tree looking for one
				var parentNode = this._dropdownNode.parentNode;
				while(parentNode && (document !== parentNode)) {
					if (parentNode.classList && parentNode.classList.contains("dropdownMenu")) { //$NON-NLS-0$
						this._parentDropdown = parentNode.dropdown;
						break;
					}
					parentNode = parentNode.parentNode;
				}
			}
			
			this._dropdownNode.tabIndex = 0;

			if (options.triggerNode) {
				this._triggerNode = options.triggerNode;
			} else {
				this._triggerNode = lib.$(".dropdownTrigger", this._dropdownNode.parentNode); //$NON-NLS-0$	
			}
			if (!this._triggerNode) { throw "no dom node for dropdown trigger found"; } //$NON-NLS-0$
			
			var triggerClickHandler = function(event) {
				var actionTaken = false;
				
				if (this._triggerNode.classList.contains("dropdownMenuItem")) { //$NON-NLS-0$
					// if the trigger is a dropdownMenuItem we only want it to open the submenu
					actionTaken = this.open(event);
				} else {
					actionTaken = this.toggle(event);
				}
				
				if (actionTaken) {
					lib.stop(event);
				}
			}.bind(this);
			
			if (!options.skipTriggerEventListeners) {
				// click on trigger opens or toggles.
				this._triggerNode.addEventListener("click", triggerClickHandler, false); //$NON-NLS-0$

				// if trigger node is not key enabled...
				if (this._triggerNode.tagName.toLowerCase() === "span") { //$NON-NLS-0$
					this._triggerNode.addEventListener("keydown", function(event) { //$NON-NLS-0$
						if (event.keyCode === lib.KEY.ENTER || event.keyCode === lib.KEY.SPACE) {
							triggerClickHandler(event);
						}
					}.bind(this), false);
				}
			}
						
			// keys
			this._dropdownNode.addEventListener("keydown", this._dropdownKeyDown.bind(this), false); //$NON-NLS-0$
		},
		
		addTriggerNode: function(node){
			var self = this;
			node.addEventListener("click", function(event) { //$NON-NLS-0$
				if (self.toggle(event))  {
					lib.stop(event);
				}
			}, false);			
		},
		
		/**
		 * Toggle the open/closed state of the dropdown.  Return a boolean that indicates whether action was taken.
		 */			
		toggle: function(mouseEvent /* optional */) {
			if (this.isVisible()) {
				return this.close();
			} else {
				return this.open(mouseEvent);
			}
		},
		
		/**
		 * Answers whether the dropdown is visible.
		 */			
		isVisible: function() {
			return this._isVisible;
		},
		
		/**
		 * Open the dropdown.
		 */			
		open: function(mouseEvent /* optional */) {
			var actionTaken = false;
			if (!this.isVisible()) {
				this.dispatchEvent({type: "triggered", dropdown: this, event: mouseEvent}); //$NON-NLS-0$
				lib.setFramesEnabled(false);
				if (this._populate) {
					this.empty();
					this._populate(this._dropdownNode);
				}
				var items = this.getItems();
				if (items.length > 0) {
					if (this._boundAutoDismiss) {
						lib.removeAutoDismiss(this._boundAutoDismiss);
					} 
					this._boundAutoDismiss = this._autoDismiss.bind(this);

					// add auto dismiss.  Clicking anywhere but trigger or a submenu item means close.
					var submenuNodes = lib.$$array(".dropdownSubMenu", this._dropdownNode); //$NON-NLS-0$
					lib.addAutoDismiss([this._triggerNode].concat(submenuNodes), this._boundAutoDismiss);

					this._triggerNode.classList.add("dropdownTriggerOpen"); //$NON-NLS-0$
					if (this._selectionClass) {
						this._triggerNode.classList.add(this._selectionClass);
					}
					this._dropdownNode.classList.add("dropdownMenuOpen"); //$NON-NLS-0$
					this._isVisible = true;
					
					this._positionDropdown(mouseEvent);
					
					this._focusDropdownNode();
					actionTaken = true;
					
					if (this._parentDropdown) {
						this._parentDropdown.submenuOpen(this);
					}
				}
			}
			return actionTaken;
		},
		
		_focusDropdownNode :function() {//Sub classes can override this to set focus on different items.
			this._dropdownNode.focus();
		},
		
		_autoDismiss: function(event) {
			if (this.close(false)) {
				// only trigger dismissal of parent menus if
				// this dropdown's node contains the event.target
				if (this._dropdownNode.contains(event.target)) {
					// Dismiss parent menus
					var temp = this._parentDropdown;
					while (temp) {
						temp.close(false);
						temp = temp._parentDropdown;
					}
				}
			}
		},
		
		/**
		 * This method positions the dropdown menu.
		 * The specified mouseEvent is ignored. However, subclasses 
		 * can override this method if they wish to take the mouse 
		 * position contained in the mouse event into account.
		 * 
		 * @param {MouseEvent} mouseEvent
		 */
		_positionDropdown: function(mouseEvent) {//Sub classes can override this to position the drop down differently.
			this._dropdownNode.style.left = "";
			this._dropdownNode.style.top = "";
			
			if(this._positioningNode) {
				this._dropdownNode.style.left = this._positioningNode.offsetLeft + "px";
				return;
			}
			
			var bounds = lib.bounds(this._dropdownNode);
			var bodyBounds = lib.bounds(document.body);
			if (bounds.left + bounds.width > (bodyBounds.left + bodyBounds.width)) {
				if (this._triggerNode.classList.contains("dropdownMenuItem")) { //$NON-NLS-0$
					this._dropdownNode.style.left = -bounds.width + "px"; //$NON-NLS-0$
				} else {
					var totalBounds = lib.bounds(this._boundingNode(this._triggerNode));
					var triggerBounds = lib.bounds(this._triggerNode);
					this._dropdownNode.style.left = (triggerBounds.left  - totalBounds.left - bounds.width + triggerBounds.width) + "px"; //$NON-NLS-0$
				}
			}
			
			//ensure menu fits on page vertically
			var overflowY = (bounds.top + bounds.height) - (bodyBounds.top + bodyBounds.height);
			if (0 < overflowY) {
				this._dropdownNode.style.top = Math.floor(this._dropdownNode.style.top - overflowY) + "px"; //$NON-NLS-0$
			}
		},
		
		_boundingNode: function(node) {
			var style = window.getComputedStyle(node, null);
			if (style === null) {
				return node;
			}
			var position = style.getPropertyValue("position"); //$NON-NLS-0$
			if (position === "absolute" || !node.parentNode || node === document.body) { //$NON-NLS-0$
				return node;
			}
			return this._boundingNode(node.parentNode);
		},
		
		
		/**
		 * Close the dropdown.
		 */			
		close: function(restoreFocus) {
			var actionTaken = false;
			if (this.isVisible()) {
				this._triggerNode.classList.remove("dropdownTriggerOpen"); //$NON-NLS-0$
				if (this._selectionClass) {
					this._triggerNode.classList.remove(this._selectionClass);
				}
				this._dropdownNode.classList.remove("dropdownMenuOpen"); //$NON-NLS-0$
				lib.setFramesEnabled(true);
				if (restoreFocus) {
					this._triggerNode.focus();
				}
				
				this._isVisible = false;
				if (this._selectedItem) {
					this._selectedItem.classList.remove("dropdownMenuItemSelected"); //$NON-NLS-0$		
					this._selectedItem = null;	
				}
				
				if (this._boundAutoDismiss) {
					lib.removeAutoDismiss(this._boundAutoDismiss);
					this._boundAutoDismiss = null;
				} 
				actionTaken = true;
			}
			return actionTaken;
		},
		
		/**
		 *
		 */
		getItems: function() {
			var items = lib.$$array("li:not(.dropdownSeparator) > .dropdownMenuItem", this._dropdownNode, true); //$NON-NLS-0$
			// We only want the direct li children, not any descendants.  But we can't preface a query with ">"
			// So we do some reachy filtering here.
			var filtered = [];
			var self = this;
			items.forEach(function(item) {
				if (item.parentNode.parentNode === self._dropdownNode) {
					filtered.push(item);
				}
			});
			
			//add handler to close open submenu when other items in the parent menu are hovered
			filtered.forEach(function(item){
				if (!item._hasDropdownMouseover) {
					item.addEventListener("mouseover", function(e){ //$NON-NLS-0$
						if (item.dropdown) {
							item.dropdown.open(e);
						} else {
							self._closeSelectedSubmenu();
							lib.stop(e);
						}
						self._selectItem(item); // select the item on mouseover
					});
					item._hasDropdownMouseover = true;
				}
			});
			return filtered;
		},
		
		/**
		 *
		 */
		empty: function() {
			var items = lib.$$array("li", this._dropdownNode); //$NON-NLS-0$
			var self = this;
			// We only want the direct li children, not any descendants. 
			items.forEach(function(item) {
				if (item.parentNode === self._dropdownNode) {
					item.parentNode.removeChild(item);
				}
			});
		},
		
		 
		/**
		 * A key is down in the dropdown node
		 */
		 _dropdownKeyDown: function(event) {
			if (event.keyCode === lib.KEY.UP || event.keyCode === lib.KEY.DOWN || event.keyCode === lib.KEY.RIGHT || event.keyCode === lib.KEY.ENTER || event.keyCode === lib.KEY.LEFT) {
				var items = this.getItems();	
				if (items.length && items.length > 0) {
					if (this._selectedItem) {
						var index = items.indexOf(this._selectedItem);
						// for inputs nested in labels, we should check the parent node since the label is the item
						if (index < 0) {
							index = items.indexOf(this._selectedItem.parentNode);
						}
						if (index >= 0) {
							if (event.keyCode === lib.KEY.UP && index > 0) {
								index--;
								this._selectItem(items[index]);
							} else if (event.keyCode === lib.KEY.DOWN && index < items.length - 1) {
								index++;
								this._selectItem(items[index]);
							} else if (event.keyCode === lib.KEY.ENTER || event.keyCode === lib.KEY.RIGHT) {
								if (this._selectedItem.classList.contains("dropdownTrigger") && this._selectedItem.dropdown) { //$NON-NLS-0$
									this._selectedItem.dropdown.open();
									this._selectedItem.dropdown._selectItem(); // select first item in submenu
								} else if (event.keyCode === lib.KEY.ENTER) {
									this._selectedItem.click();
								}
							} else if (event.keyCode === lib.KEY.LEFT && this._selectedItem.parentNode.parentNode.classList.contains("dropdownMenuOpen")) { //$NON-NLS-0$
								this.close(true);
								if (this._parentDropdown) {
									this._parentDropdown._dropdownNode.focus();
								}
							}
						}
					} else {
						this._selectItem(items[0]);	
					}
					lib.stop(event);
				}
			} else if (event.keyCode === lib.KEY.ESCAPE) {
				this.close(true);
				lib.stop(event);
			}
		 },
		 
		 /**
		  * Selects the specified dropdown menu item or the first
		  * dropdown menu item if none is specified.
		  * @param {Object} item The dropdown menu item that should be selected. See @ref getItems() for details. Optional.
		  */
		 _selectItem: function(item){
		 	var itemToSelect = item || this.getItems()[0];
		 	if (itemToSelect) {
		 		if (this._selectedItem) {
		 			this._selectedItem.classList.remove("dropdownMenuItemSelected"); //$NON-NLS-0$
			 	}
			 	this._selectedItem = itemToSelect;
			 	this._selectedItem.classList.add("dropdownMenuItemSelected"); //$NON-NLS-0$	
		 	}
		 	if (document.activeElement !== this._dropdownNode) {
		 		// ensure that the dropdown node has the focus in 
		 		// order for keydown events to be handled properly
		 		this._dropdownNode.focus();
		 	}
		 },
		 
		 /**
		  * Closes this._selectedSubmenu, and its children, if it is open.
		  * Sets the this._selectedSubmenu to the one that's passed in.
		  * @param submenu The submenu that was opened and should be set as the next this._selectedSubmenu
		  */
		submenuOpen: function(submenu) {
			if (submenu !== this._selectedSubmenu) {
				//close the current menu and all its children
				this._closeSelectedSubmenu();
				this._selectedSubmenu = submenu;
			}
		 },
		 
		_closeSelectedSubmenu: function() {
			var currentSubmenu = this._selectedSubmenu;
			while(currentSubmenu) {
				currentSubmenu.close();
				currentSubmenu = currentSubmenu._selectedSubmenu;
			}
		 },
		 
		destroy: function() {
			this.empty();
			if (this._boundAutoDismiss) {
				lib.removeAutoDismiss(this._boundAutoDismiss);
				this._boundAutoDismiss = null;
			}
		},
		
		/**
		 * Creates a new menu item and appends it to the bottom of this dropdown.
		 * @param {String} text The text to display inside the new menu item. Optional.
		 * @param {String} innerNodeType The type of the inner node to create. The default is "span". Optional.
		 * @returns {Object} The top-most new element that was created
		 */
		appendMenuItem: function(text, innerNodeType) {
			var li = createMenuItem(text, innerNodeType);
			this._dropdownNode.appendChild(li);
			return li;
		},
		
		/**
		 * Creates a new separator and appends it to the bottom of this dropdown.
		 */
		appendSeparator: function() {
			// Add a separator
			var li = createSeparator();
			this._dropdownNode.appendChild(li);
			return li;
		}
	};
	
	/**
	 * Creates a new menu item and returns it to the caller.
	 * @param {String} text The text to display inside the new menu item. Optional.
	 * @param {String} innerNodeType The type of the inner node to create. The default is "span". Optional.
	 * @returns {Object} The top-most new element that was created
	 */
	function createMenuItem(text, innerNodeType) {
		innerNodeType = innerNodeType === undefined ? "span" : innerNodeType; //$NON-NLS-0$
	 	
	 	var element = document.createElement(innerNodeType); //$NON-NLS-0$
		element.tabIndex = 0;
		element.className = "dropdownMenuItem"; //$NON-NLS-0$
		element.role = "menuitem";  //$NON-NLS-0$
		
		if (text) {
			var span = document.createElement("span");  //$NON-NLS-0$
			span.appendChild(document.createTextNode(text));
			span.classList.add("dropdownCommandName"); //$NON-NLS-0$
			element.appendChild(span);
		}
	 	
	 	var li = document.createElement("li"); //$NON-NLS-0$
	 	li.appendChild(element); //$NON-NLS-0$
		
		return li;
	}
	
	/**
	 * Creates a new separator menu item and returns it to the caller.
	 * @returns {Object} The new separator element that was created
	 */
	function createSeparator() {
		var li = document.createElement("li"); //$NON-NLS-0$
		li.classList.add("dropdownSeparator"); //$NON-NLS-0$
		return li;
	}
	
	/**
	 * Appends the specified keyBindingString to the specified menu item.
	 * @param {Object} element The menu item to append the keybinding string to. Required.
	 * @param {String} keyBindingString The keybinding string to append. Required.
	 */
	function appendKeyBindingString(element, keyBindingString) {
		var span = document.createElement("span"); //$NON-NLS-0$
		span.classList.add("dropdownKeyBinding"); //$NON-NLS-0$
		span.appendChild(document.createTextNode(keyBindingString));
		element.appendChild(span);
	}
		
	Dropdown.prototype.constructor = Dropdown;
	//return the module exports
	return {Dropdown: Dropdown,
			appendKeyBindingString: appendKeyBindingString,
			createMenuItem: createMenuItem,
			createSeparator: createSeparator};
});

/**
 * @license RequireJS text 2.0.12 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
 * Available via the MIT or new BSD license.
 * see: http://github.com/requirejs/text for details
 */
/*jslint regexp: true */
/*global require, XMLHttpRequest, ActiveXObject,
  define, window, process, Packages,
  java, location, Components, FileUtils */

define('text',['module'], function (module) {
    'use strict';

    var text, fs, Cc, Ci, xpcIsWindows,
        progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
        xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,
        bodyRegExp = /<body[^>]*>\s*([\s\S]+)\s*<\/body>/im,
        hasLocation = typeof location !== 'undefined' && location.href,
        defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''),
        defaultHostName = hasLocation && location.hostname,
        defaultPort = hasLocation && (location.port || undefined),
        buildMap = {},
        masterConfig = (module.config && module.config()) || {};

    text = {
        version: '2.0.12',

        strip: function (content) {
            //Strips <?xml ...?> declarations so that external SVG and XML
            //documents can be added to a document without worry. Also, if the string
            //is an HTML document, only the part inside the body tag is returned.
            if (content) {
                content = content.replace(xmlRegExp, "");
                var matches = content.match(bodyRegExp);
                if (matches) {
                    content = matches[1];
                }
            } else {
                content = "";
            }
            return content;
        },

        jsEscape: function (content) {
            return content.replace(/(['\\])/g, '\\$1')
                .replace(/[\f]/g, "\\f")
                .replace(/[\b]/g, "\\b")
                .replace(/[\n]/g, "\\n")
                .replace(/[\t]/g, "\\t")
                .replace(/[\r]/g, "\\r")
                .replace(/[\u2028]/g, "\\u2028")
                .replace(/[\u2029]/g, "\\u2029");
        },

        createXhr: masterConfig.createXhr || function () {
            //Would love to dump the ActiveX crap in here. Need IE 6 to die first.
            var xhr, i, progId;
            if (typeof XMLHttpRequest !== "undefined") {
                return new XMLHttpRequest();
            } else if (typeof ActiveXObject !== "undefined") {
                for (i = 0; i < 3; i += 1) {
                    progId = progIds[i];
                    try {
                        xhr = new ActiveXObject(progId);
                    } catch (e) {}

                    if (xhr) {
                        progIds = [progId];  // so faster next time
                        break;
                    }
                }
            }

            return xhr;
        },

        /**
         * Parses a resource name into its component parts. Resource names
         * look like: module/name.ext!strip, where the !strip part is
         * optional.
         * @param {String} name the resource name
         * @returns {Object} with properties "moduleName", "ext" and "strip"
         * where strip is a boolean.
         */
        parseName: function (name) {
            var modName, ext, temp,
                strip = false,
                index = name.indexOf("."),
                isRelative = name.indexOf('./') === 0 ||
                             name.indexOf('../') === 0;

            if (index !== -1 && (!isRelative || index > 1)) {
                modName = name.substring(0, index);
                ext = name.substring(index + 1, name.length);
            } else {
                modName = name;
            }

            temp = ext || modName;
            index = temp.indexOf("!");
            if (index !== -1) {
                //Pull off the strip arg.
                strip = temp.substring(index + 1) === "strip";
                temp = temp.substring(0, index);
                if (ext) {
                    ext = temp;
                } else {
                    modName = temp;
                }
            }

            return {
                moduleName: modName,
                ext: ext,
                strip: strip
            };
        },

        xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/,

        /**
         * Is an URL on another domain. Only works for browser use, returns
         * false in non-browser environments. Only used to know if an
         * optimized .js version of a text resource should be loaded
         * instead.
         * @param {String} url
         * @returns Boolean
         */
        useXhr: function (url, protocol, hostname, port) {
            var uProtocol, uHostName, uPort,
                match = text.xdRegExp.exec(url);
            if (!match) {
                return true;
            }
            uProtocol = match[2];
            uHostName = match[3];

            uHostName = uHostName.split(':');
            uPort = uHostName[1];
            uHostName = uHostName[0];

            return (!uProtocol || uProtocol === protocol) &&
                   (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&
                   ((!uPort && !uHostName) || uPort === port);
        },

        finishLoad: function (name, strip, content, onLoad) {
            content = strip ? text.strip(content) : content;
            if (masterConfig.isBuild) {
                buildMap[name] = content;
            }
            onLoad(content);
        },

        load: function (name, req, onLoad, config) {
            //Name has format: some.module.filext!strip
            //The strip part is optional.
            //if strip is present, then that means only get the string contents
            //inside a body tag in an HTML string. For XML/SVG content it means
            //removing the <?xml ...?> declarations so the content can be inserted
            //into the current doc without problems.

            // Do not bother with the work if a build and text will
            // not be inlined.
            if (config && config.isBuild && !config.inlineText) {
                onLoad();
                return;
            }

            masterConfig.isBuild = config && config.isBuild;

            var parsed = text.parseName(name),
                nonStripName = parsed.moduleName +
                    (parsed.ext ? '.' + parsed.ext : ''),
                url = req.toUrl(nonStripName),
                useXhr = (masterConfig.useXhr) ||
                         text.useXhr;

            // Do not load if it is an empty: url
            if (url.indexOf('empty:') === 0) {
                onLoad();
                return;
            }

            //Load the text. Use XHR if possible and in a browser.
            if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) {
                text.get(url, function (content) {
                    text.finishLoad(name, parsed.strip, content, onLoad);
                }, function (err) {
                    if (onLoad.error) {
                        onLoad.error(err);
                    }
                });
            } else {
                //Need to fetch the resource across domains. Assume
                //the resource has been optimized into a JS module. Fetch
                //by the module name + extension, but do not include the
                //!strip part to avoid file system issues.
                req([nonStripName], function (content) {
                    text.finishLoad(parsed.moduleName + '.' + parsed.ext,
                                    parsed.strip, content, onLoad);
                });
            }
        },

        write: function (pluginName, moduleName, write, config) {
            if (buildMap.hasOwnProperty(moduleName)) {
                var content = text.jsEscape(buildMap[moduleName]);
                write.asModule(pluginName + "!" + moduleName,
                               "define(function () { return '" +
                                   content +
                               "';});\n");
            }
        },

        writeFile: function (pluginName, moduleName, req, write, config) {
            var parsed = text.parseName(moduleName),
                extPart = parsed.ext ? '.' + parsed.ext : '',
                nonStripName = parsed.moduleName + extPart,
                //Use a '.js' file name so that it indicates it is a
                //script that can be loaded across domains.
                fileName = req.toUrl(parsed.moduleName + extPart) + '.js';

            //Leverage own load() method to load plugin value, but only
            //write out values that do not have the strip argument,
            //to avoid any potential issues with ! in file names.
            text.load(nonStripName, req, function (value) {
                //Use own write() method to construct full module value.
                //But need to create shell that translates writeFile's
                //write() to the right interface.
                var textWrite = function (contents) {
                    return write(fileName, contents);
                };
                textWrite.asModule = function (moduleName, contents) {
                    return write.asModule(moduleName, fileName, contents);
                };

                text.write(pluginName, nonStripName, textWrite, config);
            }, config);
        }
    };

    if (masterConfig.env === 'node' || (!masterConfig.env &&
            typeof process !== "undefined" &&
            process.versions &&
            !!process.versions.node &&
            !process.versions['node-webkit'])) {
        //Using special require.nodeRequire, something added by r.js.
        fs = require.nodeRequire('fs');

        text.get = function (url, callback, errback) {
            try {
                var file = fs.readFileSync(url, 'utf8');
                //Remove BOM (Byte Mark Order) from utf8 files if it is there.
                if (file.indexOf('\uFEFF') === 0) {
                    file = file.substring(1);
                }
                callback(file);
            } catch (e) {
                if (errback) {
                    errback(e);
                }
            }
        };
    } else if (masterConfig.env === 'xhr' || (!masterConfig.env &&
            text.createXhr())) {
        text.get = function (url, callback, errback, headers) {
            var xhr = text.createXhr(), header;
            xhr.open('GET', url, true);

            //Allow plugins direct access to xhr headers
            if (headers) {
                for (header in headers) {
                    if (headers.hasOwnProperty(header)) {
                        xhr.setRequestHeader(header.toLowerCase(), headers[header]);
                    }
                }
            }

            //Allow overrides specified in config
            if (masterConfig.onXhr) {
                masterConfig.onXhr(xhr, url);
            }

            xhr.onreadystatechange = function (evt) {
                var status, err;
                //Do not explicitly handle errors, those should be
                //visible via console output in the browser.
                if (xhr.readyState === 4) {
                    status = xhr.status || 0;
                    if (status > 399 && status < 600) {
                        //An http 4xx or 5xx error. Signal an error.
                        err = new Error(url + ' HTTP status: ' + status);
                        err.xhr = xhr;
                        if (errback) {
                            errback(err);
                        }
                    } else {
                        callback(xhr.responseText);
                    }

                    if (masterConfig.onXhrComplete) {
                        masterConfig.onXhrComplete(xhr, url);
                    }
                }
            };
            xhr.send(null);
        };
    } else if (masterConfig.env === 'rhino' || (!masterConfig.env &&
            typeof Packages !== 'undefined' && typeof java !== 'undefined')) {
        //Why Java, why is this so awkward?
        text.get = function (url, callback) {
            var stringBuffer, line,
                encoding = "utf-8",
                file = new java.io.File(url),
                lineSeparator = java.lang.System.getProperty("line.separator"),
                input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)),
                content = '';
            try {
                stringBuffer = new java.lang.StringBuffer();
                line = input.readLine();

                // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324
                // http://www.unicode.org/faq/utf_bom.html

                // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK:
                // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058
                if (line && line.length() && line.charAt(0) === 0xfeff) {
                    // Eat the BOM, since we've already found the encoding on this file,
                    // and we plan to concatenating this buffer with others; the BOM should
                    // only appear at the top of a file.
                    line = line.substring(1);
                }

                if (line !== null) {
                    stringBuffer.append(line);
                }

                while ((line = input.readLine()) !== null) {
                    stringBuffer.append(lineSeparator);
                    stringBuffer.append(line);
                }
                //Make sure we return a JavaScript string and not a Java string.
                content = String(stringBuffer.toString()); //String
            } finally {
                input.close();
            }
            callback(content);
        };
    } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env &&
            typeof Components !== 'undefined' && Components.classes &&
            Components.interfaces)) {
        //Avert your gaze!
        Cc = Components.classes;
        Ci = Components.interfaces;
        Components.utils['import']('resource://gre/modules/FileUtils.jsm');
        xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc);

        text.get = function (url, callback) {
            var inStream, convertStream, fileObj,
                readData = {};

            if (xpcIsWindows) {
                url = url.replace(/\//g, '\\');
            }

            fileObj = new FileUtils.File(url);

            //XPCOM, you so crazy
            try {
                inStream = Cc['@mozilla.org/network/file-input-stream;1']
                           .createInstance(Ci.nsIFileInputStream);
                inStream.init(fileObj, 1, 0, false);

                convertStream = Cc['@mozilla.org/intl/converter-input-stream;1']
                                .createInstance(Ci.nsIConverterInputStream);
                convertStream.init(inStream, "utf-8", inStream.available(),
                Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);

                convertStream.readString(inStream.available(), readData);
                convertStream.close();
                inStream.close();
                callback(readData.value);
            } catch (e) {
                throw new Error((fileObj && fileObj.path || '') + ': ' + e);
            }
        };
    }
    return text;
});


define('text!orion/webui/dropdowntriggerbutton.html',[],function () { return '<button class="dropdownTrigger">${ButtonText}<!--span class="dropdownArrowDown core-sprite-openarrow"></span--></button><ul class="dropdownMenu"></ul>';});


define('text!orion/webui/dropdowntriggerbuttonwitharrow.html',[],function () { return '<button class="dropdownTrigger dropdownDefaultButton">${ButtonText}<span class="dropdownArrowDown core-sprite-openarrow"></span></button><ul class="dropdownMenu"></ul>';});


define('text!orion/webui/checkedmenuitem.html',[],function () { return '<li><label class="dropdownMenuItem"><input class="checkedMenuItem" role="menuitem" type="checkbox" />${ItemText}</label></li>';});

/*******************************************************************************
 * @license
 * Copyright (c) 2012, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/webui/tooltip',['orion/webui/littlelib'], function(lib) {

	/**
	 * Attaches tooltip behavior to a given node.  The tooltip will be assigned class "tooltip" which can be
	 * used to control appearance.  Uses the "CSS Triangle Trick" 
	 * http://css-tricks.com/snippets/css/css-triangle/
	 * for the tooltip shape and CSS transitions for fade in and fade out.
	 *
	 * Clients should destroy the tooltip if removing the node from the document.
	 *
	 * @param {Object} options The options object, which must minimally specify the tooltip dom node
	 * @param options.node The node showing the tooltip.  Required.
	 * @param options.text The text in the tooltip.  Optional.  If not specified, the client is expected to add content
	 * to the tooltip prior to triggering it.
	 * @param options.trigger The event that triggers the tooltip.  Optional.  Defaults to "mouseover".  Can be one of "mouseover",
	 * "click", "focus", or "none".  If "none" then the creator will be responsible for showing, hiding, and destroying the tooltip.
	 * If "mouseover" then the aria attributes for tooltips will be set up.
	 * @param options.position An array specifying the preferred positions to try positioning the tooltip.  Positions can be "left", "right", 
	 * "above", or "below".  If no position will fit on the screen, the first position specified is used.  Optional.  Defaults to 
	 * ["right", "above", "below", "left"].
	 * @param options.showDelay Specifies the number of millisecond delay before the tooltip begins to appear.
	 * Optional.  Valid only for "mouseover" trigger.  Defaults to 1000.
	 * @param options.hideDelay Specifies the number of millisecond delay before the tooltip begins to disappear.
	 * Optional.  Defaults to 200.  Valid only for "mouseover" trigger.
	 * @param options.tailSize Specifies the number of pixels to allocate for the tail.  Optional.  Defaults to 10.
	 * @name orion.webui.tooltip.Tooltip
	 *
	 */
	function Tooltip(options) {
		this._init(options);
	}
	Tooltip.prototype = /** @lends orion.webui.tooltip.Tooltip.prototype */ {
			
		_init: function(options) {
			this._node = lib.node(options.node);
			if (!this._node) { throw "no dom node for tooltip found"; } //$NON-NLS-0$
			this._position = options.position || ["right", "above", "below", "left"]; //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			this._text = options.text;
			this._hideDelay = options.hideDelay === undefined ? 200 : options.hideDelay;
			this._tailSize = options.tailSize || 10;
			this._trigger = options.trigger || "mouseover"; //$NON-NLS-0$
			this._afterShowing = options.afterShowing;
			this._afterHiding = options.afterHiding;
			
			var self = this;
			// set up events
			if (this._trigger === "click") { //$NON-NLS-0$
				this._showDelay = 0;
				this._node.addEventListener("click", this._clickHandler = function(event) { //$NON-NLS-0$
					if (event.target === self._node) {
						self.show();
						lib.stop(event);
					}
				}, false);
			} else if (this._trigger === "mouseover") { //$NON-NLS-0$
				this._showDelay = options.showDelay === undefined ? 500 : options.showDelay;
				var leave = ["mouseout", "click"];  //$NON-NLS-1$ //$NON-NLS-0$
				this._node.addEventListener("mouseover", this._mouseoverHandler = function(event) { //$NON-NLS-0$
					if (lib.contains(self._node, event.target)) {
						self.show();
						lib.stop(event);
					}
				}, false);
				
				this._leaveHandler = function(event) { //$NON-NLS-0$
					if (lib.contains(self._node, event.target)) {
						self.hide();
					}
				};

				for (var i=0; i<leave.length; i++) {
					this._node.addEventListener(leave[i], this._leaveHandler, false);
				}
			} else if (this._trigger === "focus") { //$NON-NLS-0$
				this._showDelay = options.showDelay === undefined ? 0 : options.showDelay;
				this._hideDelay = options.hideDelay === undefined ? 0 : options.hideDelay;
				this._node.addEventListener("focus", this._focusHandler = function(event) { //$NON-NLS-0$
					if (lib.contains(self._node, event.target)) {
						self.show();
					}
				}, false);
				
				this._blurHandler = function(event) { //$NON-NLS-0$
					if (lib.contains(self._node, event.target)) {
						self.hide();
					}
				};
				
				this._node.addEventListener("blur", this._blurHandler, false); //$NON-NLS-0$
			}						
		},
		
		_makeTipNode: function() {
			if (!this._tip) {
				this._tip = document.createElement("span"); //$NON-NLS-0$
				this._tip.classList.add("tooltipContainer"); //$NON-NLS-0$
				this._tipInner = document.createElement("div");  //$NON-NLS-0$
				this._tipInner.classList.add("tooltip");  //$NON-NLS-0$
				if (this._text) {
					this._tipTextContent = document.createElement("div");  //$NON-NLS-0$
					this._tipTextContent.classList.add("textContent");  //$NON-NLS-0$
					this._tipInner.appendChild(this._tipTextContent);
					var textNode = document.createTextNode(this._text);
					this._tipTextContent.appendChild(textNode);
				}
				this._tip.appendChild(this._tipInner);
				document.body.appendChild(this._tip);
				var self = this;
				lib.addAutoDismiss([this._tip, this._node], function() {self.hide();});
				if (this._trigger === "mouseover") { //$NON-NLS-0$
					 this._tipInner.role = "tooltip"; //$NON-NLS-0$
					 this._tipInner.id = "tooltip" + new Date().getTime().toString(); //$NON-NLS-0$
					 this._node.setAttribute("aria-describedby", this._tipInner.id); //$NON-NLS-0$
				
					// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=398960
					// mousing over the tip itself will cancel any pending timeout to close it, but then we must
					// also close it when we leave the tip.
					this._tip.addEventListener("mouseover", function(event) { //$NON-NLS-0$
						if (self._timeout) {
							window.clearTimeout(self._timeout);
							self._timeout = null;
						}
						self._tip.addEventListener("mouseout", function(event) { //$NON-NLS-0$
							if (lib.contains(self._tip, event.target)) {
								self.hide();
								lib.stop(event);
							}
						}, false);
					}, false);
				}
			}
			return this._tip;
		},
		
		_positionTip: function(position, force) {
			this._makeTipNode();  // lazy initialize
			
			this._tip.classList.add("tooltipShowing"); //$NON-NLS-0$
			
			// special case for left tooltip to ensure inner span is adjacent to tail.
			if (position === "left") { //$NON-NLS-0$
				this._tipInner.classList.add("left"); //$NON-NLS-0$
			} else {
				this._tipInner.classList.remove("left"); //$NON-NLS-0$
			}

			// Sometimes _node is not visible (eg. if _node is a dropdown menu item in a closed menu), so find
			// the nearest ancestor with a reasonable bound
			var posNode = this._node;
			var rect;
			for (rect = lib.bounds(posNode); posNode && !rect.width && !rect.height; posNode = posNode.parentNode) {
				rect = lib.bounds(posNode);
			}
			var tipRect = lib.bounds(this._tipInner);
			var top, left;
			
			switch (position) {
				case "above": //$NON-NLS-0$
					top = rect.top - tipRect.height - this._tailSize - 1;
					left = rect.left - this._tailSize;
					break;
				case "below": //$NON-NLS-0$
					top = rect.top + rect.height + this._tailSize + 1;
					left = rect.left - this._tailSize;
					break;
				case "left": //$NON-NLS-0$
					top = rect.top - this._tailSize / 2;
					left = rect.left - tipRect.width - this._tailSize - 1;
					break;
				default:  // right
					top = rect.top - this._tailSize / 2;
					left = rect.left + rect.width + this._tailSize + 1;
					break;
			}
			//Checking if the tooltip will fit inside the viewport of the browser
			var body = document.body, html = document.documentElement;
			var vPortLeft = Math.max(html.scrollLeft, body.scrollLeft);
			var vPortTop = Math.max(html.scrollTop, body.scrollTop);
			var vPortRight = vPortLeft + html.clientWidth;
			var vPortBottom = vPortTop + html.clientHeight;			
			
			if (top + tipRect.height > vPortBottom) {
				if (force) {
					top = vPortBottom - tipRect.height - 1;
				} else {
					return false;
				}
			}
			if (left + tipRect.width > vPortRight) {
				if (force) {
					left = vPortRight - tipRect.width - 1;
				} else {
					return false;
				}
			}
			if (left < vPortLeft) {
				if (force) {
					left = vPortLeft + 4;
				} else {
					return false;
				}
			}
			if (top < vPortTop) {
				if (force) {
					top = vPortTop + 4;
				} else {
					return false;
				}
			}
			
			if (this._tail && (this._tail.previousPosition !== position)) {
				//position has changed, tail needs to be modified
				this._tip.removeChild(this._tail);
				this._tail = null;
			}
			
			if (!this._tail) {
				this._tail = document.createElement("span"); //$NON-NLS-0$
				this._tail.classList.add("tooltipTailFrom"+position); //$NON-NLS-0$
				if (position === "above" || position === "left") { //$NON-NLS-1$//$NON-NLS-0$
					// tip goes after content
					this._tip.appendChild(this._tail);
				} else {
					this._tip.insertBefore(this._tail, this._tipInner);
				}
				this._tail.previousPosition = position;
			}
			this._tip.style.top = top + "px"; //$NON-NLS-0$
			this._tip.style.left = left + "px"; //$NON-NLS-0$ 
			return true;
		},
		
		contentContainer: function() {
			this._makeTipNode();
			return this._tipInner;
		},
		
		/**
		 * @return True if this tooltip is visible, false otherwise
		 */
		isShowing: function() {
			return this._tip && this._tip.classList.contains("tooltipShowing"); //$NON-NLS-0$
		},
		
		/**
		 * Show the tooltip.
		 */			
		show: function() {
			if (this.isShowing()) { //$NON-NLS-0$
				return;
			}
			if (this._timeout) {
				window.clearTimeout(this._timeout);
				this._timeout = null;
			}
			if (this._showDelay) {
				this._timeout = window.setTimeout(this._showImmediately.bind(this), this._showDelay);	
			} else {
				this._showImmediately();
			}
		},
		
		_showImmediately: function() {
			var positioned = false;
			var index = 0;
			while (!positioned && index < this._position.length) {
				positioned = this._positionTip(this._position[index]);
				index++;
			}
			if (!positioned) {
				this._positionTip(this._position[0], true);  // force it in, it doesn't fit anywhere
			}
			if (this._afterShowing) {
				this._afterShowing();
			}
		},
		
		/**
		 * Hide the tooltip.
		 */			
		hide: function(hideDelay) {
			if (this._timeout) {
				window.clearTimeout(this._timeout);
				this._timeout = null;
			}
			if (!this.isShowing()) { //$NON-NLS-0$
				return;
			}
			if (hideDelay === undefined) {
				hideDelay = this._hideDelay;
			}
			var self = this;
			this._timeout = window.setTimeout(function() {
				self._tip.classList.remove("tooltipShowing"); //$NON-NLS-0$
				self._tip.removeAttribute("style"); //$NON-NLS-0$
				if (self._afterHiding) {
					self._afterHiding();
				}
			}, hideDelay);
		},
		
		destroy: function() {
			if (this._timeout) {
				window.clearTimeout(this._timeout);
				this._timeout = null;
			}
			if (this._tip) {
				document.body.removeChild(this._tip);
				this._tip = null;
				this._tipInner = null;
				this._tipTextContent = null;
				this._tail = null;
			}
			if (this._node) {
				this._node.removeEventListener("click", this._clickHandler, false); //$NON-NLS-0$
				this._node.removeEventListener("mouseover", this._mouseoverHandler, false); //$NON-NLS-0$
				this._node.removeEventListener("focus", this._focusHandler, false); //$NON-NLS-0$
				this._node.removeEventListener("blur", this._blurHandler, false); //$NON-NLS-0$
				var leave = ["mouseout", "click"];  //$NON-NLS-1$ //$NON-NLS-0$
				for (var i=0; i<leave.length; i++) {
					this._node.removeEventListener(leave[i], this._leaveHandler, false);
				}
			}
		}
	};
	Tooltip.prototype.constructor = Tooltip;
	//return the module exports
	return {Tooltip: Tooltip};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/

/*eslint-env browser, amd*/
define('orion/metrics',[], function() {

	var _services = [];

	function init(services, args) {
		/* the following definitions are from https://developers.google.com/analytics/devguides/collection/analyticsjs/pages */
		var href = window.location.protocol + '//' + window.location.hostname + window.location.pathname + window.location.search; //$NON-NLS-0$
		var page = window.location.pathname + window.location.search;
		var title = document.title;

		_services = services;
		_services.forEach(function(current) {
			current.pageLoad(href, page, title, args);
		});
	};

	function initFromRegistry(serviceRegistry, args) {
		var refs = serviceRegistry.getServiceReferences("orion.metrics"); //$NON-NLS-0$
		var services = [];
		refs.forEach(function(current) {
			services.push(serviceRegistry.getService(current));
		});
		init(services, args);
	};

	function logEvent(category, action, label, value) {
		_services.forEach(function(current) {
			current.logEvent(category, action, label || "", value);
		});
	}

	var timingVars = {};
	function logPageLoadTiming(timingVar, timingLabel) {
		/* 
		 * The level of window.performance implementation varies across the browsers,
		 * so check for the existence of all utilized functions up-front.
		 */
		if (window.performance) {
			 /* ensure that no more timings of this type are logged for this page */
			if (window.performance.getEntriesByName && window.performance.mark) {
				if (window.performance.getEntriesByName(timingVar).length) {
					return;
				}
				window.performance.mark(timingVar);
			} else {
				if (timingVars[timingVar]) {
					return;
				}
				timingVars[timingVar] = new Date();				
			}
			logTiming("page", timingVar, window.performance.now(), timingLabel); //$NON-NLS-0$
		}
	}

	function logTiming(timingCategory, timingVar, timingValue, timingLabel) {
		_services.forEach(function(current) {
			current.logTiming(timingCategory, timingVar, timingValue, timingLabel);
		});
	}

	return {
		init: init,
		initFromRegistry: initFromRegistry,
		logEvent: logEvent,
		logPageLoadTiming: logPageLoadTiming,
		logTiming: logTiming
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2010,2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
 
define('orion/commands',[
	'orion/webui/littlelib',
	'orion/commandsProxy',
	'orion/webui/dropdown',
	'text!orion/webui/dropdowntriggerbutton.html',
	'text!orion/webui/dropdowntriggerbuttonwitharrow.html',
	'text!orion/webui/checkedmenuitem.html',
	'orion/webui/tooltip',
	'orion/metrics'
], function(lib, mCommandsProxy, Dropdown, DropdownButtonFragment, DropdownButtonWithArrowFragment, CheckedMenuItemFragment, Tooltip, mMetrics) {
		/**
		 * @name orion.commands.NO_IMAGE
		 * @description Image data for 16x16 transparent png.
		 * @property
		 */
		var NO_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAAtJREFUCNdjIBEAAAAwAAFletZ8AAAAAElFTkSuQmCC"; //$NON-NLS-0$

		/* a function that can be set for retrieving bindings stored elsewhere, such as a command registry */
		var getBindings = null;
		
		/* key bindings registered locally
		 *
		 * object keyed by command id, value is { keyBinding: keyBinding, command: command, invocation: commandInvocation }
		 *
		 */
		var localKeyBindings = {};
		
		/*
		 * Set a function that will provide key bindings when key events are processed.  This is used when an external party
		 * (such as a command registry) wants its bindings to be honored by the command key listener.
		 */
		function setKeyBindingProvider(getBindingsFunction) {
			getBindings = getBindingsFunction;
		}

		/**
		 * Executes a binding if possible.
		 * @name orion.commands.executeBinding
		 * @function
		 * @static
		 * @param {Object} binding
		 * @returns {Boolean} <code>true</code> if the binding was executed, <code>false</code> otherwise.
		 */
		function executeBinding(binding) {
			var invocation = binding.invocation;
			if (invocation) {
				var command = binding.command;
				if (typeof(command.hrefCallback) === 'function') {
					var href = command.hrefCallback.call(invocation.handler || window, invocation);
					if (href.then){
						href.then(function(l){
							window.open(l);
						});
					} else {
						// We assume window open since there's no link gesture to tell us what to do.
						window.open(href);
					}
					return true;
				} else if (invocation.commandRegistry) {
					// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=411282
					invocation.commandRegistry._invoke(invocation);
					return true;
				} else if (command.onClick || command.callback) {
					// TODO: what is this timeout for?
					window.setTimeout(function() {
						(command.onClick || command.callback).call(invocation.handler || window, invocation);
					}, 0);
					return true;
				}
			}
			return false;
		}

		/*
		 * Process a key event against the provided bindings.
		 */
		function _processKey(event, bindings) {
			for (var id in bindings) {
				if (bindings[id] && bindings[id].keyBinding && bindings[id].command) {
					if (bindings[id].keyBinding.match(event)) {
						var activeBinding = bindings[id];
						var keyBinding = activeBinding.keyBinding;
						// Check for keys that are scoped to a particular part of the DOM
						if (!keyBinding.domScope || lib.contains(lib.node(keyBinding.domScope), event.target)) {
							if (executeBinding(activeBinding)) {
								lib.stop(event);
								return;
							}
						}
					}
				}
			}
		}
		
		function getKeyBindings() {
			var allBindings = {};
			
			if (getBindings) {
				var i, keys, objectKey;
				keys = Object.keys(localKeyBindings);
				for (i=0; i<keys.length; i++) {
					objectKey = keys[i];
					allBindings[objectKey] = localKeyBindings[objectKey];
				}
				var otherBindings = getBindings();
				keys = Object.keys(otherBindings);
				for (i=0; i<keys.length; i++) {
					objectKey = keys[i];
					allBindings[objectKey] = otherBindings[objectKey];
				}
			} else {
				allBindings = localKeyBindings;
			}
			return allBindings;
		}
		
		function processKey(evt) {
			_processKey(evt, getKeyBindings());
		}
		
		window.document.addEventListener("keydown", function(evt) { //$NON-NLS-0$
			return mCommandsProxy.handleKeyEvent(evt, processKey);
		}, false);

	function _addImageToElement(command, element, name) {
		element.classList.add("commandImage"); //$NON-NLS-0$
		var node;
		if (command.imageClass) {
			if (command.addImageClassToElement) {
				element.classList.add(command.imageClass);
			} else {
				node = document.createElement("span"); //$NON-NLS-0$
				element.appendChild(node);
				node.classList.add(command.spriteClass);
				node.classList.add(command.imageClass);
			}
		} else {
			node = new Image();
			node.alt = command.name;
			node.name = name;
			node.id = name;
			node.src = command.image;
			element.appendChild(node);
		}
		return node;
	}

	function createDropdownMenu(parent, name, populateFunction, buttonClass, buttonIconClass, showName, selectionClass, positioningNode, displayDropdownArrow, extraClasses) {
		
		parent = lib.node(parent);
		if (!parent) {
			throw "no parent node was specified"; //$NON-NLS-0$
		}
		var range = document.createRange();
		range.selectNode(parent);
		var buttonFragment = displayDropdownArrow ? range.createContextualFragment(DropdownButtonWithArrowFragment) : range.createContextualFragment(DropdownButtonFragment);
		// bind name to fragment variable
		lib.processTextNodes(buttonFragment, {ButtonText: name});
		parent.appendChild(buttonFragment);
		var newMenu = parent.lastChild;
		var menuButton;
		var dropdownArrow;
		if (displayDropdownArrow) {
			menuButton = newMenu.previousSibling;
			dropdownArrow = menuButton.lastChild;
		} else {
			menuButton = newMenu.previousSibling;
		}
		if (buttonClass) {
			menuButton.classList.add(buttonClass); //$NON-NLS-0$
		} else {
			menuButton.classList.add("orionButton"); //$NON-NLS-0$
			menuButton.classList.add("commandButton"); //$NON-NLS-0$
		}
		if (extraClasses) {
			extraClasses.split(" ").forEach(menuButton.classList.add.bind(menuButton.classList));
		}
		
		if (buttonIconClass) {
			if(!showName) {
				menuButton.textContent = ""; //$NON-NLS-0$
				menuButton.setAttribute("aria-label", name); //$NON-NLS-0$
			}
			_addImageToElement({ spriteClass: "commandSprite", imageClass: buttonIconClass }, menuButton, name); //$NON-NLS-0$
			menuButton.classList.add("orionButton"); //$NON-NLS-0$
		}
		menuButton.dropdown = new Dropdown.Dropdown({
			dropdown: newMenu, 
			populate: populateFunction,
			selectionClass: selectionClass,
			skipTriggerEventListeners: !!dropdownArrow,
			positioningNode: positioningNode
		});
		newMenu.dropdown = menuButton.dropdown;
		return {menuButton: menuButton, menu: newMenu, dropdown: menuButton.dropdown, dropdownArrow: dropdownArrow};
	}
	
	function createCheckedMenuItem(parent, name, checked, onChange) {
		parent = lib.node(parent);
		if (!parent) {
			throw "no parent node was specified"; //$NON-NLS-0$
		}
		var range = document.createRange();
		range.selectNode(parent);
		var buttonFragment = range.createContextualFragment(CheckedMenuItemFragment);
		// bind name to fragment variable
		lib.processTextNodes(buttonFragment, {ItemText: name});
		parent.appendChild(buttonFragment);
		var itemParent = parent.lastChild;
		var checkbox = lib.$(".checkedMenuItem", itemParent); //$NON-NLS-0$
		checkbox.checked = checked;
		checkbox.addEventListener("change", onChange, false); //$NON-NLS-0$
		return checkbox;
	}

	function createCommandItem(parent, command, commandInvocation, id, keyBinding, useImage, callback) {
		var element;
		var clickTarget;
		useImage = useImage || (!command.name && command.hasImage());
		
		var renderButton = function() {
				if (useImage) {
					if (command.hasImage()) {
						_addImageToElement(command, element, id);
						// ensure there is accessible text describing this image
						if (command.name) {
							element.setAttribute("aria-label", command.name); //$NON-NLS-0$
						}
					} else {
						element.classList.add("commandButton"); //$NON-NLS-0$
						element.classList.add("commandMissingImageButton"); //$NON-NLS-0$
						element.appendChild(document.createTextNode(command.name));
					}
				} else {
					element.classList.add("commandButton"); //$NON-NLS-0$
					var text = document.createTextNode(command.name);
					element.appendChild(text);
				}
		};
		
		if (typeof(command.hrefCallback) === 'function') {
			element = clickTarget = document.createElement("a"); //$NON-NLS-0$
			element.id = id;
			if (useImage && command.hasImage()) {
				_addImageToElement(command, element, id);
			} else {
				element.className = "commandLink"; //$NON-NLS-0$
				element.appendChild(document.createTextNode(command.name));
			}
			var href = command.hrefCallback.call(commandInvocation.handler, commandInvocation);
			if (href.then){
				href.then(function(l){
					element.href = l;
				});
			} else if (href) {
				element.href = href; 
			} else {  // no href
				element.href = "#"; //$NON-NLS-0$
			}
		} else {
			if (command.type === "switch") { //$NON-NLS-0$
				element = document.createElement("div"); //$NON-NLS-0$
				element.tabIndex = 0;
				element.className = "orionSwitch"; //$NON-NLS-0$
				var input = clickTarget = document.createElement("input"); //$NON-NLS-0$
				input.type = "checkbox"; //$NON-NLS-0$
				input.className = "orionSwitchCheck"; //$NON-NLS-0$
				input.id = "orionSwitchCheck" + command.id; //$NON-NLS-0$
				if(parent.id) {
					input.id = input.id + parent.id;
				}
				element.appendChild(input);
				var label = document.createElement("label"); //$NON-NLS-0$
				label.className = "orionSwitchLabel"; //$NON-NLS-0$
				label.setAttribute("for", input.id); //$NON-NLS-0$  
				var span1 = document.createElement("span"); //$NON-NLS-0$
				span1.className = "orionSwitchInner"; //$NON-NLS-0$
				var span2 = document.createElement("span"); //$NON-NLS-0$
				span2.className = "orionSwitchSwitch"; //$NON-NLS-0$
				label.appendChild(span1);
				label.appendChild(span2);
				element.appendChild(label);
				element.addEventListener("keydown", function(e) { //$NON-NLS-0$
					if (e.keyCode === lib.KEY.ENTER || e.keyCode === lib.KEY.SPACE) {
						input.click();
					}
				}, false);

				input.checked = command.checked;
				span1.classList.add(command.imageClass);
			} else if (command.type === "toggle") {  //$NON-NLS-0$
				element = clickTarget = document.createElement("button"); //$NON-NLS-0$
				element.className = "orionButton"; //$NON-NLS-0$
				element.classList.add(command.checked ? "orionToggleOn" : "orionToggleOff");  //$NON-NLS-1$ //$NON-NLS-0$
				element.id = "orionToggle" + command.id; //$NON-NLS-0$
				if(parent.id) {
					element.id = element.id + parent.id;
				}
				renderButton();
			} else {
				element = clickTarget = document.createElement("button"); //$NON-NLS-0$
				element.className = "orionButton"; //$NON-NLS-0$
				if (command.extraClass) {
					element.classList.add(command.extraClass);
				}
				renderButton();
			}
			var onClick = callback || command.callback;
			if (onClick) {
				var done = function() {onClick.call(commandInvocation.handler, commandInvocation);};
				command.onClick = onClick;
				clickTarget.addEventListener("click", function(e) { //$NON-NLS-0$
					var onClickThen;
					if (command.type === "switch" || command.type === "toggle") { //$NON-NLS-1$ //$NON-NLS-0$
						onClickThen = function (doIt) {
							if (command.type === "toggle") { //$NON-NLS-0$
								if(doIt) {
									command.checked = !command.checked;
								}
								if (command.checked) {
									element.classList.remove("orionToggleOff"); //$NON-NLS-0$
									element.classList.add("orionToggleOn"); //$NON-NLS-0$
									element.classList.add("orionToggleAnimate"); //$NON-NLS-0$
								} else {
									element.classList.remove("orionToggleOn"); //$NON-NLS-0$
									element.classList.add("orionToggleOff"); //$NON-NLS-0$
									element.classList.add("orionToggleAnimate"); //$NON-NLS-0$
								}
							}else {
								if(doIt) {
									command.checked = input.checked;
								} else {
									input.checked = !input.checked;
								}
							}
							if(doIt) {
								window.setTimeout(done, 250);
							}
						};
					} else {
						onClickThen = function (doIt) { if(doIt) {
								done();
							}
						};
					}
					if(command.preCallback) {
						command.preCallback(commandInvocation).then( function(doIt) {
							onClickThen(doIt);
						});
					} else {
						onClickThen(true);
					}
					e.stopPropagation();
				}, false);
			}
		}
		if (parent.nodeName.toLowerCase() === "ul") { //$NON-NLS-0$
			var li = document.createElement("li"); //$NON-NLS-0$
			parent.appendChild(li);
			parent = li;
		} else {
			element.classList.add("commandMargins"); //$NON-NLS-0$
		}
		parent.appendChild(element);
		if (command.tooltip) {
			element.commandTooltip = new Tooltip.Tooltip({
				node: element,
				text: command.tooltip,
				position: ["above", "below", "right", "left"] //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			});
		}
		if (keyBinding) {
			localKeyBindings[command.id] = { keyBinding: keyBinding, command: command, invocation: commandInvocation };
		}
		return element;
	}
	
	function createCommandMenuItem(parent, command, commandInvocation, keyBinding, callback, keyBindingString) {
		var element, li;
		var dropdown = parent.dropdown;
		if (typeof(command.hrefCallback) === 'function') {
			li = Dropdown.createMenuItem(command.name, "a"); //$NON-NLS-0$
			element = li.firstElementChild;
			var href = command.hrefCallback.call(commandInvocation.handler, commandInvocation);
			if (href.then){
				href.then(function(l){
					element.href = l;
				});
			} else if (href) {
				element.href = href; 
			} else {  // no href
				element.href = "#"; //$NON-NLS-0$
			}
			element.addEventListener("keydown", function(e) { //$NON-NLS-0$
				if (e.keyCode === lib.KEY.ENTER || e.keyCode === lib.KEY.SPACE) {
					element.click();
				}
			}, false);
		} else {
			li = Dropdown.createMenuItem(command.name); //$NON-NLS-0$
			element = li.firstElementChild;
			var onClick = callback || command.callback;
			if (onClick) {
				command.onClick = onClick;
				element.addEventListener("click", function(e) { //$NON-NLS-0$
					if (dropdown){
						dropdown.close(true);
					}
					onClick.call(commandInvocation.handler, commandInvocation);
				}, false);
				element.addEventListener("keydown", function(e) { //$NON-NLS-0$
					if (e.keyCode === lib.KEY.ENTER || e.keyCode === lib.KEY.SPACE) {
						e.preventDefault(); /* prevents dropdown from receiving event and re-opening */
						element.click();
					}
				}, false);
			}
		}

		if (command.tooltip) {
			/* nested menu items may represent commands, hence require tooltips */
			element.commandTooltip = new Tooltip.Tooltip({
				node: element,
				text: command.tooltip,
				position: ["right", "above", "below", "left"] //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			});
		}
		
		if (keyBindingString) {
			Dropdown.appendKeyBindingString(element, keyBindingString);
		}
		
		parent.appendChild(li);
		
		if (keyBinding) {
			localKeyBindings[command.id] = { keyBinding: keyBinding, command: command, invocation: commandInvocation };
		}

		return element;
	}
	

	/**
	 * CommandInvocation is a data structure that carries all relevant information about a command invocation.
	 * It represents a unique invocation of a command by the user.  Each time a user invokes a command (by click, keystroke, URL),
	 * a new invocation is passed to the client.
	 * <p>Note:  When retrieving parameters from a command invocation, clients should always use {@link #parameters}
	 * rather than obtaining the parameter object originally specified for the command (using {@link #command}.parameters).
	 * This ensures that the parameter values for a unique invocation are used vs. any default parameters that may have been
	 * specified originally.  Similarly, if a client wishes to store data that will preserved across multiple invocations of a command,
	 * that data can be stored in the original parameters description and a reference maintained by the client.
	 * </p>
	 * 
	 * @name orion.commands.CommandInvocation
	 * @class Carries information about a command invocation.
	 * @param {Object} handler
	 * @param {Array} items
	 * @param {Object} [userData]
	 * @param {orion.commands.Command} command
	 * @param {orion.commandregistry.CommandRegistry} [commandRegistry]
	 */
	/**
	 * @name orion.commands.CommandInvocation#commandRegistry
	 * @type orion.commandregistry.CommandRegistry
	 */
	/**
	 * @name orion.commands.CommandInvocation#handler
	 * @type Object
	 */
	/**
	 * @name orion.commands.CommandInvocation#command
	 * @type orion.commands.Command
	 */
	/**
	 * @name orion.commands.CommandInvocation#items
	 * @type Array
	 */
	/**
	 * @name orion.commands.CommandInvocation#parameters
	 * @type orion.commands.ParametersDescription
	 */
	/**
	 * @name orion.commands.CommandInvocation#userData
	 * @type Object
	 */
	/**
	 * @name orion.commands.CommandInvocation#userData
	 * @type Object
	 */
	function CommandInvocation (handler, items, /* optional */userData, command, /* optional */ commandRegistry) {
		this.commandRegistry = commandRegistry;
		this.handler = handler;
		this.items = items;
		this.userData = userData;
		this.command = command;
		if (command.parameters) {
			this.parameters = command.parameters.makeCopy(); // so that we aren't retaining old values from previous invocations
		}
		this.id = command.id;
	}
	CommandInvocation.prototype = /** @lends orion.commands.CommandInvocation.prototype */ {
		/**
		 * Returns whether this command invocation can collect parameters.
		 * 
		 * @returns {Boolean} whether parameters can be collected
		 */
		collectsParameters: function() {
			return this.commandRegistry && this.commandRegistry.collectsParameters();
		},
	
		/**
		 * Makes and returns a (shallow) copy of this command invocation.
		 * @param {orion.commands.ParametersDescription} parameters A description of parameters to be used in the copy.  Optional.
		 * If not specified, then the existing parameters should be copied.
		 */
		makeCopy: function(parameters) {
			var copy =  new CommandInvocation(this.handler, this.items, this.userData, this.command, this.commandRegistry);
			copy.domNode = this.domNode;
			copy.domParent = this.domParent;
			if (parameters) {
				copy.parameters = parameters.makeCopy();
			} else if (this.parameters) {
				copy.parameters = this.parameters.makeCopy();
			}
			return copy;
		}

	};
	CommandInvocation.prototype.constructor = CommandInvocation;



	/**
	 * Constructs a new command with the given options.
	 * @param {Object} [options] The command options object.
	 * @param {String} [options.id] the unique id to be used when referring to the command in the command service.
	 * @param {String} [options.name] the name to be used when showing the command as text.
	 * @param {String} [options.tooltip] the tooltip description to use when explaining the purpose of the command.
	 * @param {Function} [options.callback] the callback to call when the command is activated.  The callback should either 
	 *  perform the command or return a deferred that represents the asynchronous performance of the command.  Optional.
	 * @param {Function} [options.hrefCallback] if specified, this callback is used to retrieve
	 *  a URL that can be used as the location for a command represented as a hyperlink.  The callback should return 
	 *  the URL.  In this release, the callback may also return a deferred that will eventually return the URL, but this 
	 *  functionality may not be supported in the future.  See https://bugs.eclipse.org/bugs/show_bug.cgi?id=341540.
	 *  Optional.
	 * @param {Function} [options.choiceCallback] a callback which retrieves choices that should be shown in a secondary
	 *  menu from the command itself.  Returns a list of choices that supply the name and image to show, and the callback
	 *  to call when the choice is made.  Optional.
	 * @param {String} [options.imageClass] a CSS class name suitable for showing a background image.  Optional.
	 * @param {Boolean} [options.addImageClassToElement] If true, the image class will be added to the element's
	 *  class list. Otherwise, a span element with the image class is created and appended to the element.  Optional.
	 * @param {String} [options.selectionClass] a CSS class name to be appended when the command button is selected. Optional.
	 * @param {String} [options.spriteClass] an additional CSS class name that can be used to specify a sprite background image.  This
	 *  useful with some sprite generation tools, where imageClass specifies the location in a sprite, and spriteClass describes the
	 *  sprite itself.  Optional.
	 * @param {Function} [options.visibleWhen] A callback that returns a boolean to indicate whether the command should be visible
	 *  given a particular set of items that are selected.  Optional, defaults to always visible.
	 * @param {orion.commands.ParametersDescription} [options.parameters] A description of parameters that should be collected before invoking
	 *  the command.
	 * @param {Image} [options.image] the image that may be used to represent the callback.  A text link will be shown in lieu
	 *  of an image if no image is supplied.  Optional.
	 * @class A command is an object that describes an action a user can perform, as well as when and
	 *  what it should look like when presented in various contexts.  Commands are identified by a
	 *  unique id.
	 * @name orion.commands.Command
	 */
	function Command (options) {
		this._init(options);
	}
	Command.prototype = /** @lends orion.commands.Command.prototype */ {
		_init: function(options) {
			this.id = options.id;  // unique id
			this.name = options.name;
			this.tooltip = options.tooltip;
			this.callback = options.callback; // optional callback that should be called when command is activated (clicked)
			this.preCallback = options.preCallback; // optional callback that should be called when command is activated (clicked)
			this.hrefCallback = options.hrefCallback; // optional callback that returns an href for a command link
			this.choiceCallback = options.choiceCallback; // optional callback indicating that the command will supply secondary choices.  
														// A choice is an object with a name, callback, and optional image
			this.positioningNode = options.positioningNode; // optional positioning node choice command.
			this.image = options.image || NO_IMAGE;
			this.imageClass = options.imageClass;   // points to the location in a sprite
			this.addImageClassToElement = options.addImageClassToElement; // optional boolean if true will add the image class to the 
																		// element's class list
			this.extraClass = options.extraClass;
			this.selectionClass = options.selectionClass;
			this.spriteClass = options.spriteClass || "commandSprite"; // defines the background image containing sprites //$NON-NLS-0$
			this.visibleWhen = options.visibleWhen;
			this.parameters = options.parameters;  // only used when a command is used in the command registry. 
			this.isEditor = options.isEditor;
			this.type = options.type;
			this.checked = options.checked;
			this.track = options.track;
		},
		
		/**
		 * Populate the specified menu with choices using the choiceCallback.
		 * Used internally by the command service.  Not intended to be overridden or called
		 * externally.
		 */
		 populateChoicesMenu: function(parent, items, handler, userData, commandService) {
			var choices = this.getChoices(items, handler, userData);
			var addCheck = choices.some(function(choice) {
				return choice.checked;
			});
			choices.forEach(function(choice) {
				if (choice.name) {
					var itemNode = document.createElement("li"); //$NON-NLS-0$
					parent.appendChild(itemNode);
					var node = document.createElement("span"); //$NON-NLS-0$
					node.tabIndex = 0; 
					node.role = "menuitem"; //$NON-NLS-0$
					node.classList.add("dropdownMenuItem"); //$NON-NLS-0$
					if (addCheck) {
						var check = document.createElement("span"); //$NON-NLS-0$
						check.classList.add("check"); //$NON-NLS-0$
						check.appendChild(document.createTextNode(choice.checked ? "\u25CF" : "")); //$NON-NLS-1$ //$NON-NLS-0$
						node.appendChild(check);
					}
					if (choice.imageClass) {
						var image = document.createElement("span"); //$NON-NLS-0$
						image.classList.add(choice.imageClass);
						node.appendChild(image);
					}
					var span = document.createElement("span"); //$NON-NLS-0$
					var text = document.createTextNode(choice.name);
					span.appendChild(text);
					node.appendChild(span);
					itemNode.appendChild(node);
					node.choice = choice;
					node.addEventListener("click", function(event) { //$NON-NLS-0$
						mMetrics.logEvent("command", "invoke", this.id + ">" + choice.name); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
						choice.callback.call(choice, items);
					}.bind(this), false); 
					node.addEventListener("keydown", function(event) { //$NON-NLS-0$
						if (event.keyCode === lib.KEY.ENTER || event.keyCode === lib.KEY.SPACE) {
							mMetrics.logEvent("command", "invoke", this.id + ">" + choice.name); //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-0$
							choice.callback.call(choice, items);
						}
					}.bind(this), false);
				} else {  // anything not named is a separator
					commandService._generateMenuSeparator(parent);
				}
			}.bind(this));
		},
		
		/**
		 * Get the appropriate choices using the choiceCallback.
		 * Used internally by the command service.  Not intended to be overridden or called
		 * externally.
		 */
		getChoices: function(items, handler, userData) {
			if (this.choiceCallback) {
				return this.choiceCallback.call(handler, items, userData);
			}
			return null;
		},
		
		/**
		 * Make a choice callback appropriate for the given choice and items.
		 * Used internally by the command service.  Not intended to be overridden or called
		 * externally.
		 */
		makeChoiceCallback: function(choice, items) {
			return function(event) {
				if (choice.callback) {
					choice.callback.call(choice, items, event);
				}
			};
		},
		
		/**
		 * Return a boolean indicating whether this command has a specific image associated
		 * with it. Used internally by the command service.  Not intended to be overridden or called
		 * externally.
		 */
		hasImage: function() {
			return this.imageClass || this.image !== NO_IMAGE; //$NON-NLS-0$
		}
	};  // end Command prototype
	Command.prototype.constructor = Command;
	
	//return the module exports
	return {
		Command: Command,
		CommandInvocation: CommandInvocation,
		createDropdownMenu: createDropdownMenu,
		createCheckedMenuItem: createCheckedMenuItem,
		createCommandItem: createCommandItem,
		createCommandMenuItem: createCommandMenuItem,
		executeBinding: executeBinding,
		setKeyBindingProvider: setKeyBindingProvider,
		localKeyBindings: localKeyBindings,
		getKeyBindings: getKeyBindings,
		processKey: processKey,
		NO_IMAGE: NO_IMAGE,
		_testMethodProcessKey: _processKey  // only exported for test cases
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: 
 *		Felipe Heidrich (IBM Corporation) - initial API and implementation
 *		Silenio Quarti (IBM Corporation) - initial API and implementation
 ******************************************************************************/

/*eslint-env browser, amd*/
define("orion/keyBinding", ['orion/util'], function(util) { //$NON-NLS-1$ //$NON-NLS-0$

    /**
	 * @class A KeyBinding is an interface used to define keyboard shortcuts.
	 * @name orion.KeyBinding
	 * 
	 * @property {Function} match The function to match events.
	 * @property {Function} equals The funtion to compare to key bindings.
	 *
	 * @see orion.KeyStroke
	 * @see orion.KeySequence
	 */

	/**
	 * Constructs a new key stroke with the given key code, modifiers and event type.
	 * 
	 * @param {String|Number} keyCode the key code.
	 * @param {Boolean} mod1 the primary modifier (usually Command on Mac and Control on other platforms).
	 * @param {Boolean} mod2 the secondary modifier (usually Shift).
	 * @param {Boolean} mod3 the third modifier (usually Alt).
	 * @param {Boolean} mod4 the fourth modifier (usually Control on the Mac).
	 * @param {String} type the type of event that the keybinding matches; either "keydown" or "keypress".
	 * 
	 * @class A KeyStroke represents of a key code and modifier state that can be triggered by the user using the keyboard.
	 * @name orion.KeyStroke
	 * 
	 * @property {String|Number} keyCode The key code.
	 * @property {Boolean} mod1 The primary modifier (usually Command on Mac and Control on other platforms).
	 * @property {Boolean} mod2 The secondary modifier (usually Shift).
	 * @property {Boolean} mod3 The third modifier (usually Alt).
	 * @property {Boolean} mod4 The fourth modifier (usually Control on the Mac).
	 * @property {String} [type=keydown] The type of event that the keybinding matches; either "keydown" or "keypress"
	 *
	 * @see orion.editor.TextView#setKeyBinding
	 */
	function KeyStroke (keyCode, mod1, mod2, mod3, mod4, type) {
		this.type = type || "keydown"; //$NON-NLS-0$
		if (typeof(keyCode) === "string" && this.type === "keydown") { //$NON-NLS-1$ //$NON-NLS-0$
			this.keyCode = keyCode.toUpperCase().charCodeAt(0);
		} else {
			this.keyCode = keyCode;
		}
		this.mod1 = mod1 !== undefined && mod1 !== null ? mod1 : false;
		this.mod2 = mod2 !== undefined && mod2 !== null ? mod2 : false;
		this.mod3 = mod3 !== undefined && mod3 !== null ? mod3 : false;
		this.mod4 = mod4 !== undefined && mod4 !== null ? mod4 : false;
	}
	KeyStroke.prototype = /** @lends orion.KeyStroke.prototype */ {
		getKeys: function() {
			return [this];
		},
		/**
		 * Determines either this key stroke matches the specifed event.  It can match either a
		 * a whole sequence of key events or a single key event at a specified index.
		 * <p>
		 * <code>KeyStroke</code> only matches single key events. <code>KeySequence</code> handles
		 * matching a sequence of events.
		 * </p>
		 * TODO explain this better
		 * 
		 * @param {DOMEvent|DOMEvent[]} e the key event or list of events to match.
		 * @param index the key event to match.
		 * @returns {Boolean} <code>true</code> whether the key binding matches the key event.
		 *
		 * @see orion.KeySequence#match
		 */
		match: function (e, index) {
			if (index !== undefined) {
				if (index !== 0) {
					return false;
				}
			} else {
				if (e instanceof Array) {
					if (e.length > 1) {
						return false;
					}
					e = e[0];
				}
			}
			if (e.type !== this.type) {
				return false;
			}
			if (this.keyCode === e.keyCode || this.keyCode === String.fromCharCode(util.isOpera ? e.which : (e.charCode !== undefined ? e.charCode : e.keyCode))) {
				var mod1 = util.isMac ? e.metaKey : e.ctrlKey;
				if (this.mod1 !== mod1) { return false; }
				if (this.type === "keydown") { //$NON-NLS-0$
					if (this.mod2 !== e.shiftKey) { return false; }
				}
				if (this.mod3 !== e.altKey) { return false; }
				if (util.isMac && this.mod4 !== e.ctrlKey) { return false; }
				return true;
			}
			return false;
		},
		/**
		 * Returns whether this key stroke is the same as the given parameter.
		 * 
		 * @param {orion.KeyBinding} kb the key binding to compare with.
		 * @returns {Boolean} whether or not the parameter and the receiver describe the same key binding.
		 */
		equals: function(kb) {
			if (!kb) { return false; }
			if (this.keyCode !== kb.keyCode) { return false; }
			if (this.mod1 !== kb.mod1) { return false; }
			if (this.mod2 !== kb.mod2) { return false; }
			if (this.mod3 !== kb.mod3) { return false; }
			if (this.mod4 !== kb.mod4) { return false; }
			if (this.type !== kb.type) { return false; }
			return true;
		} 
	};
	
	/**
	 * Constructs a new key sequence with the given key strokes.
	 * 
	 * @param {orion.KeyStroke[]} keys the key strokes for this sequence.
	 * 
	 * @class A KeySequence represents of a list of key codes and a modifiers state that can be triggered by the user using the keyboard.
	 * @name orion.KeySequence
	 * 
	 * @property {orion.KeyStroke[]} keys the list of key strokes.
	 *
	 * @see orion.editor.TextView#setKeyBinding
	 */
	function KeySequence (keys) {
		this.keys = keys;
	}
	KeySequence.prototype = /** @lends orion.KeySequence.prototype */ {
		getKeys: function() {
			return this.keys.slice(0);
		},
		match: function (e, index) {
			var keys = this.keys;
			if (index !== undefined) {
				if (index > keys.length) {
					return false;
				}
				if (keys[index].match(e)) {
					if (index === keys.length - 1) {
						return true;
					}
					return index + 1;
				}
				return false;
			} else {
				if (!(e instanceof Array)) {
					e = [e];
				}
				if (e.length > keys.length) {
					return false;
				}
				var i;
				for (i = 0; i < e.length; i++) {
					if (!keys[i].match(e[i])) {
						return false;
					}
				}
				if (i === keys.length) {
					return true;
				}
				return i;
			}
		},
		/**
		 * Returns whether this key sequence is the same as the given parameter.
		 * 
		 * @param {orion.KeyBinding|orion.KeySequence} kb the key binding to compare with.
		 * @returns {Boolean} whether or not the parameter and the receiver describe the same key binding.
		 */
		equals: function(kb) {
			if (!kb.keys) { return false; }
			if (kb.keys.length !== this.keys.length) { return false; }
			for (var i=0; i<kb.keys.length; i++) {
				if (!kb.keys[i].equals(this.keys[i])) { return false; }
			}
			return true;
		}	
	};
	
	return {
		KeyBinding: KeyStroke, // for backwards compatibility
		KeyStroke: KeyStroke,
		KeySequence: KeySequence
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2009, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors: IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define('orion/explorers/navigationUtils',[], function() {
	var userAgent = navigator.userAgent;
	var isPad = userAgent.indexOf("iPad") !== -1; //$NON-NLS-0$
	
	/**
	 * Generate a grid navigation item into a given array. A grid navigation item is presented by a wrapper object wrapping the domNode. 
	 *
	 * @param {Array} domNodeWrapperList the array that holds the grid navigation item. Normally the .gridChildren property from a row model.
	 * @param {Element} element the html dom element representing a grid. Normally left or right arrow keys on the current row highlight the dom element.
	 *        When a grid is rendered, the caller has to decide what dom element can be passed. 
	 */
	 
	 
	function generateNavGrid(domNodeWrapperList, domNode, widget, onClick) {
		if(isPad){
			return;
		}
		if(!domNodeWrapperList){
			return;
		}
		domNodeWrapperList.push({domNode: domNode});
		domNode.style.outline = "none"; //$NON-NLS-0$
	}
                
	/**
	 * Add a grid navigation item to the navigation dictionary. A row navigation model normally comes from any node in a {treeModelIterator}.
	 * The .gridChildren property will be lazily created on the row model as an array where all the grid navigation items live.
	 *
	 * @param {ExplorerNavDict} navDict the dictionary that holds the info of all navigation info from model id.
	 * @param {object} rowModel the row model from the {treeModelIterator}.
	 * @param {Element} element the html dom element representing a grid. Normally left or right arrow keys on the current row highlight the dom element.
	 *        When a grid is rendered, the caller has to decide what dom element can be passed. 
	 */
	function addNavGrid(navDict, rowModel, domNode) {
		if(!navDict){
			return;
		}
		var navHolder = navDict.getGridNavHolder(rowModel, true);
		if(navHolder){
			generateNavGrid(navHolder, domNode);
		}
	}
	
	/**
	 * Remove a grid navigation item from a given array. A grid navigation item is presented by a wrapper object wrapping the domNode, widget and onClick properties. 
	 *
	 * @param {Array} domNodeWrapperList the array that holds the grid navigation item. Normally the .gridChildren property from a row model.
	 * @param {DomNode} domNode the html dom node representing a grid. Normally left or right arrow keys on the current row highlight the dom node.
	 *        When a grid is rendered, the caller has to decide what dom node can be passed. 
	 */
	function removeNavGrid(domNodeWrapperList, domNode) {
		if(!domNodeWrapperList){
			return;
		}
		
		for(var i = 0; i < domNodeWrapperList.length ; i++){
			if(domNodeWrapperList[i].domNode === domNode){
				domNodeWrapperList.splice(i, 1);
				return;
			}
		}
	}
	//return module exports
	return {
		addNavGrid: addNavGrid,
		generateNavGrid: generateNavGrid,
		removeNavGrid: removeNavGrid
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define('orion/PageUtil',[],function(){
	function hash() {
		/* See https://bugzilla.mozilla.org/show_bug.cgi?id=483304 */
		var result = window.location.href.split("#")[1]; //$NON-NLS-0$
		result = result ? "#" + result : ""; //$NON-NLS-0$
		return result;
	}
	
	function matchResourceParameters(optURIText) {
		optURIText = optURIText || window.location.toString();
		var result = {resource:""};
		var hashIndex = optURIText.indexOf("#"); //$NON-NLS-0$
		if (hashIndex !== -1) {
			var text = optURIText.substring(hashIndex + 1);
			if (text.length !== 0) {
				var params = text.split(","); //$NON-NLS-0$
				result.resource = decodeURIComponent(params[0]);
				for (var i = 1; i < params.length; i++) {
					//We can not use params[i].split("=") here because a param's value may contain "=", which is not encoded.
					var pair = params[i];
					var parsed = /([^=]*)(=?)(.*)/.exec(pair);
					var name = decodeURIComponent(parsed[1] || ""); //$NON-NLS-0$
					var value = decodeURIComponent(parsed[3] || ""); //$NON-NLS-0$
					if(name !== "" && name !== "resource"){ //$NON-NLS-0$ //$NON-NLS-0$
						result[name] = value;
					}
				}
			}			
		}
		return result;
	}
	
	var httpOrHttps = new RegExp("^http[s]?","i");

	function validateURLScheme(url, optAllowedSchemes) {
		var absoluteURL = url;
		if (url.indexOf("://") === -1) { //$NON-NLS-0$
			var temp = document.createElement('a'); //$NON-NLS-0$
			temp.href = url;
	        absoluteURL = temp.href;
		}
		var match = false;
		if (optAllowedSchemes) {
			match = optAllowedSchemes.some(function(scheme){
				return new RegExp("^" + scheme + ":", "i").test(absoluteURL);
			});
		} else {
			match = httpOrHttps.test(absoluteURL);
		}
		if (match) {
			return url;
		} else {
			console.log("Illegal URL Scheme: '" + url + "'");
			return "";
		}
	}
	return {
		hash: hash,
		matchResourceParameters: matchResourceParameters,
		validateURLScheme: validateURLScheme	
	};
});
/**
 * @license RequireJS i18n 2.0.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
 * Available via the MIT or new BSD license.
 * see: http://github.com/requirejs/i18n for details
 */
/*jslint regexp: true */
/*global require: false, navigator: false, define: false */

/**
 * This plugin handles i18n! prefixed modules. It does the following:
 *
 * 1) A regular module can have a dependency on an i18n bundle, but the regular
 * module does not want to specify what locale to load. So it just specifies
 * the top-level bundle, like "i18n!nls/colors".
 *
 * This plugin will load the i18n bundle at nls/colors, see that it is a root/master
 * bundle since it does not have a locale in its name. It will then try to find
 * the best match locale available in that master bundle, then request all the
 * locale pieces for that best match locale. For instance, if the locale is "en-us",
 * then the plugin will ask for the "en-us", "en" and "root" bundles to be loaded
 * (but only if they are specified on the master bundle).
 *
 * Once all the bundles for the locale pieces load, then it mixes in all those
 * locale pieces into each other, then finally sets the context.defined value
 * for the nls/colors bundle to be that mixed in locale.
 *
 * 2) A regular module specifies a specific locale to load. For instance,
 * i18n!nls/fr-fr/colors. In this case, the plugin needs to load the master bundle
 * first, at nls/colors, then figure out what the best match locale is for fr-fr,
 * since maybe only fr or just root is defined for that locale. Once that best
 * fit is found, all of its locale pieces need to have their bundles loaded.
 *
 * Once all the bundles for the locale pieces load, then it mixes in all those
 * locale pieces into each other, then finally sets the context.defined value
 * for the nls/fr-fr/colors bundle to be that mixed in locale.
 */
(function () {
    'use strict';

    //regexp for reconstructing the master bundle name from parts of the regexp match
    //nlsRegExp.exec("foo/bar/baz/nls/en-ca/foo") gives:
    //["foo/bar/baz/nls/en-ca/foo", "foo/bar/baz/nls/", "/", "/", "en-ca", "foo"]
    //nlsRegExp.exec("foo/bar/baz/nls/foo") gives:
    //["foo/bar/baz/nls/foo", "foo/bar/baz/nls/", "/", "/", "foo", ""]
    //so, if match[5] is blank, it means this is the top bundle definition.
    var nlsRegExp = /(^.*(^|\/)nls(\/|$))([^\/]*)\/?([^\/]*)/;

    //Helper function to avoid repeating code. Lots of arguments in the
    //desire to stay functional and support RequireJS contexts without having
    //to know about the RequireJS contexts.
    function addPart(locale, master, needed, toLoad, prefix, suffix) {
        if (master[locale]) {
            needed.push(locale);
            if (master[locale] === true || master[locale] === 1) {
                toLoad.push(prefix + locale + '/' + suffix);
            }
        }
    }

    function addIfExists(req, locale, toLoad, prefix, suffix) {
        var fullName = prefix + locale + '/' + suffix;
        if (require._fileExists(req.toUrl(fullName + '.js'))) {
            toLoad.push(fullName);
        }
    }

    /**
     * Simple function to mix in properties from source into target,
     * but only if target does not already have a property of the same name.
     * This is not robust in IE for transferring methods that match
     * Object.prototype names, but the uses of mixin here seem unlikely to
     * trigger a problem related to that.
     */
    function mixin(target, source, force) {
        var prop;
        for (prop in source) {
            if (source.hasOwnProperty(prop) && (!target.hasOwnProperty(prop) || force)) {
                target[prop] = source[prop];
            } else if (typeof source[prop] === 'object') {
                if (!target[prop] && source[prop]) {
                    target[prop] = {};
                }
                mixin(target[prop], source[prop], force);
            }
        }
    }

    define('i18n',['module'], function (module) {
        var masterConfig = module.config ? module.config() : {};

        return {
            version: '2.0.4',
            /**
             * Called when a dependency needs to be loaded.
             */
            load: function (name, req, onLoad, config) {
                config = config || {};

                if (config.locale) {
                    masterConfig.locale = config.locale;
                }

                var masterName,
                    match = nlsRegExp.exec(name),
                    prefix = match[1],
                    locale = match[4],
                    suffix = match[5],
                    parts = locale.split("-"),
                    toLoad = [],
                    value = {},
                    i, part, current = "";

                //If match[5] is blank, it means this is the top bundle definition,
                //so it does not have to be handled. Locale-specific requests
                //will have a match[4] value but no match[5]
                if (match[5]) {
                    //locale-specific bundle
                    prefix = match[1];
                    masterName = prefix + suffix;
                } else {
                    //Top-level bundle.
                    masterName = name;
                    suffix = match[4];
                    locale = masterConfig.locale;
                    if (!locale) {
                        locale = masterConfig.locale =
                            typeof navigator === "undefined" ? "root" :
                            (navigator.language ||
                             navigator.userLanguage || "root").toLowerCase();
                    }
                    parts = locale.split("-");
                }

                if (config.isBuild) {
                    //Check for existence of all locale possible files and
                    //require them if exist.
                    toLoad.push(masterName);
                    addIfExists(req, "root", toLoad, prefix, suffix);
                    for (i = 0; i < parts.length; i++) {
                        part = parts[i];
                        current += (current ? "-" : "") + part;
                        addIfExists(req, current, toLoad, prefix, suffix);
                    }
                                        
                    if(config.locales) {
                    	var j, k; 
                    	for (j = 0; j < config.locales.length; j++) {
                    		locale = config.locales[j];
                    		parts = locale.split("-");
                    		current = "";
	                    	for (k = 0; k < parts.length; k++) {
		                        part = parts[k];
		                        current += (current ? "-" : "") + part;
		                        addIfExists(req, current, toLoad, prefix, suffix);
	                    	}
                    	}
                    }

                    req(toLoad, function () {
                        onLoad();
                    });
                } else {
                    //First, fetch the master bundle, it knows what locales are available.
                    req([masterName], function (master) {
                        //Figure out the best fit
                        var needed = [],
                            part;

                        //Always allow for root, then do the rest of the locale parts.
                        addPart("root", master, needed, toLoad, prefix, suffix);
                        for (i = 0; i < parts.length; i++) {
                            part = parts[i];
                            current += (current ? "-" : "") + part;
                            addPart(current, master, needed, toLoad, prefix, suffix);
                        }

                        //Load all the parts missing.
                        req(toLoad, function () {
                            var i, partBundle, part;
                            for (i = needed.length - 1; i > -1 && needed[i]; i--) {
                                part = needed[i];
                                partBundle = master[part];
                                if (partBundle === true || partBundle === 1) {
                                    partBundle = req(prefix + part + '/' + suffix);
                                }
                                mixin(value, partBundle);
                            }

                            //All done, notify the loader.
                            onLoad(value);
                        });
                    });
                }
            }
        };
    });
}());

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/nls/messages',{
	root:true
});

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/nls/root/messages',{//Default message bundle
	"Navigator": "Navigator",
	"Sites": "Sites",
	"Shell": "Shell",
	"ShellLinkWorkspace": "Shell",
	"Get Plugins": "Get Plug-ins",
	"Global": "Global",
	"Editor": "Editor",
	"EditorRelatedLink": "Show Current Folder",
	"EditorRelatedLinkParent": "Show Enclosing Folder",
	"EditorLinkWorkspace": "Edit",
	"EditorRelatedLinkProj": "Show Project",
	"navigationBar": "Navigation Bar",
	"Filter bindings": "Filter bindings",
	"BindingPrompt": "Enter the new binding",
	"NoBinding": "---",
	"orionClientLabel": "Orion client repository",
	"Orion Editor": "Orion Editor",
	"Orion Image Viewer": "Orion Image Viewer",
	"Orion Markdown Editor": "Orion Markdown Editor",
	"Orion Markdown Viewer": "Orion Markdown Viewer",
	"Orion JSON Editor": "Orion JSON Editor",
	"View on Site": "View on Site",
	"View this file or folder on a web site hosted by Orion": "View this file or folder on a web site hosted by Orion.",
	"ShowAllKeyBindings": "Show a list of all the keybindings on this page",
	"Show Keys": "Show Keys",
	"HideShowBannerFooter": "Hide or show the page banner",
	"Toggle banner and footer": "Toggle banner",
	"ChooseFileOpenEditor": "Choose a file by name and open an editor on it",
	"FindFile": "Open File...",
	"System Configuration Details": "System Configuration Details",
	"System Config Tooltip": "Go to the System Configuration Details page",
	"Background Operations": "Background Operations",
	"Background Operations Tooltip": "Go to the Background Operations page",
	"Operation status is unknown": "Operation status is unknown",
	"Unknown item": "Unknown item",
	"NoSearchAvailableErr": "Can't search: no search service is available",
	"Related": "Related",
	"Options": "Options",
	"LOG: ": "LOG: ",
	"View": "View",
	"SplashTitle": "Setting up Workspace",
	"SplashTitleSettings": "Loading Settings",
	"SplashTitleGit": "Loading Git Repositories",
	"LoadingPage": "Loading Page",
	"LoadingPlugins": "Loading Plugins",
	"LoadingResources": "Loading Resources",
	"plugin_started": "\"${0}\" started",
	"plugin_lazy activation": "\"${0}\" lazily activated",
	"plugin_starting": "\"${0}\" starting",
	"no parent": "no parent",
	"no tree model": "no tree model",
	"no renderer": "no renderer",
	"could not find table row ": "could not find table row ",
	"Operations": "Operations",
	"Operations running": "Operations running",
	"SomeOpWarning": "Some operations finished with warning",
	"SomeOpErr": "Some operations finished with error",
	"no service registry": "no service registry",
	"Tasks": "Tasks",
	"Close": "Close",
	"Expand all": "Expand all",
	"Collapse all": "Collapse all",
	"Search" : "Search",
	"Advanced search" : "Advanced search",
	"Submit" : "Submit",
	"More" : "More",
	"Recent searches" : "Recent searches",
	"Regular expression" : "Regular expression",
	"Search options" : "Search options",
	"Global search" : "Global search",
	"Orion Home" : "Orion Home",
	"Close notification" : "Close notification",
	"OpPressSpaceMsg" : "Operations - Press spacebar to show current operations",
	"Toggle side panel" : "Toggle side panel",
	"Open or close the side panel": "Open or close the side panel",
	"Projects" : "Projects",
	"Toggle Sidebar" : "Toggle Sidebar",
	"Sample HTML5 Site": "Sample HTML5 Site",
	"Generate an HTML5 'Hello World' website, including JavaScript, HTML, and CSS files.": "Generate an HTML5 'Hello World' website, including JavaScript, HTML, and CSS files.",
	"Sample Orion Plugin": "Sample Orion Plug-in",
	"Generate a sample plugin for integrating with Orion.": "Generate a sample plug-in for integrating with Orion.",
	"Browser": "Web Browser",
	"OutlineProgress": "Getting outline for ${0} from ${1}",
	"outlineTimeout": "Outline service timed out. Try reloading the page and opening the outline again.",
	"UnknownError": "An unknown error occurred.",
	"Filter": "Filter (* = any string, ? = any character)",
	"TemplateExplorerLabel": "Templates",
	"OpenTemplateExplorer": "Open Template Explorer",
	"Edit": "Edit",
	"CentralNavTooltip": "Toggle Navigation Menu",
	"Wrote: ${0}": "Wrote: ${0}",
	"GenerateHTML": "Generate HTML file",
	"GenerateHTMLTooltip": "Write an HTML file generated from the current Markdown editor content",
	"alt text": "alt text",
	"blockquote": "blockquote",
	"code": "code",
	"code (block)": "code (block)",
	"code (span)": "code (span)",
	"emphasis": "emphasis",
	"fenced code (${0})": "fenced code (${0})",
	"header (${0})": "header (${0})",
	"horizontal rule": "horizontal rule",
	"label": "label",
	"link (auto)": "link (auto)",
	"link (image)": "link (image)",
	"link (inline)": "link (inline)",
	"link label": "link label",
	"link label (optional)": "link label (optional)",
	"link (ref)": "link (ref)",
	"list item (bullet)": "list item (bullet)",
	"list item (numbered)": "list item (numbered)",
	"strikethrough (${0})": "strikethrough (${0})",
	"strong": "strong",
	"table (${0})": "table (${0})",
	"text": "text",
	"title (optional)": "title (optional)",
	"url": "url",
	"TogglePaneOrientationTooltip": "Toggle split pane orientation",
	"WarningDuplicateLinkId": "Duplicate link ID: ${0} (link IDs are not case-sensitive)",
	"WarningHeaderTooDeep": "Header level cannot exceed 6",
	"WarningLinkHasNoText": "Link has no text",
	"WarningLinkHasNoURL": "Link has no URL",
	"WarningOrderedListItem": "Ordered list item within unordered list",
	"WarningOrderedListShouldStartAt1": "The first item in an ordered list should have index 1",
	"WarningUndefinedLinkId": "Undefined link ID: ${0}",
	"WarningUnorderedListItem": "Unordered list item within ordered list",
	"PageTitleFormat": "${0} - ${1}", // ${0} is the file or resource being edited; ${1} is the task (eg. "Editor")
	// Display names for keys:
	"KeyCTRL": "Ctrl",
	"KeySHIFT": "Shift",
	"KeyALT": "Alt",
	"KeyBKSPC": "Backspace",
	"KeyDEL": "Del",
	"KeyEND": "End",
	"KeyENTER": "Enter",
	"KeyESCAPE": "Esc",
	"KeyHOME": "Home",
	"KeyINSERT": "Ins",
	"KeyPAGEDOWN": "Page Down",
	"KeyPAGEUP": "Page Up",
	"KeySPACE": "Space",
	"KeyTAB": "Tab",
	// Display elapsed time:
	"a year": "a year",
	"years": "${0} years",
	"a month": "a month",
	"months": "${0} months",
	"a day": "a day",
	"days": "${0} days",
	"an hour": "an hour",
	"hours": "${0} hours",
	"a minute": "a minute",
	"minutes": "${0} minutes",
	"timeAgo": "${0} ago", //${0} represent the time elapsed
	"justNow": "just now" //Represent that the time elapsed is less than 1 minute
});


/*******************************************************************************
 * @license
 * Copyright (c) 2012, 2015 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors: IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
/*global requirejs*/
define('orion/i18nUtil',[], function() {
	/**
	 * Performs string substitution. Can be invoked in 2 ways:
	 *
	 * i) vargs giving numbered substition values:
	 *   formatMessage("${0} is ${1}", "foo", "bar")  // "foo is bar"
	 *
	 * ii) a map giving the substitutions:
	 *   formatMessage("${thing} is ${1}", {1: "bar", thing: "foo"})  // "foo is bar"
	 */
	function formatMessage(msg) {
		var pattern = /\$\{([^\}]+)\}/g, args = arguments;
		if (args.length === 2 && args[1] && typeof args[1] === "object") {
			return msg.replace(pattern, function(str, key) {
				return args[1][key];
			});
		}
		return msg.replace(pattern, function(str, index) {
			return args[(index << 0) + 1];
		});
	}
	return {
		formatMessage: formatMessage
	};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2009, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors: IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define('orion/uiUtils',[
	'i18n!orion/nls/messages',
	'orion/webui/littlelib',
	'orion/i18nUtil'
], function(messages, lib, i18nUtil) {
	/**
	 * This class contains static utility methods. It is not intended to be instantiated.
	 * @class This class contains static utility methods.
	 * @name orion.uiUtils
	 */

	var isMac = navigator.platform.indexOf("Mac") !== -1; //$NON-NLS-0$

	// Maps keyCode to display symbol
	var keySymbols = Object.create(null);
	keySymbols[lib.KEY.DOWN]  = "\u2193"; //$NON-NLS-0$
	keySymbols[lib.KEY.UP]    = "\u2191"; //$NON-NLS-0$
	keySymbols[lib.KEY.RIGHT] = "\u2192"; //$NON-NLS-0$
	keySymbols[lib.KEY.LEFT]  = "\u2190"; //$NON-NLS-0$
	if (isMac) {
		keySymbols[lib.KEY.BKSPC]    = "\u232b"; //$NON-NLS-0$
		keySymbols[lib.KEY.DEL]      = "\u2326"; //$NON-NLS-0$
		keySymbols[lib.KEY.END]      = "\u21f2"; //$NON-NLS-0$
		keySymbols[lib.KEY.ENTER]    = "\u23ce"; //$NON-NLS-0$
		keySymbols[lib.KEY.ESCAPE]   = "\u238b"; //$NON-NLS-0$
		keySymbols[lib.KEY.HOME]     = "\u21f1"; //$NON-NLS-0$
		keySymbols[lib.KEY.PAGEDOWN] = "\u21df"; //$NON-NLS-0$
		keySymbols[lib.KEY.PAGEUP]   = "\u21de"; //$NON-NLS-0$
		keySymbols[lib.KEY.SPACE]    = "\u2423"; //$NON-NLS-0$
		keySymbols[lib.KEY.TAB]      = "\u21e5"; //$NON-NLS-0$
	}

	function getUserKeyStrokeString(binding) {
		var userString = "";

		if (isMac) {
			if (binding.mod4) {
				userString+= "\u2303"; //Ctrl //$NON-NLS-0$
			}
			if (binding.mod3) {
				userString+= "\u2325"; //Alt //$NON-NLS-0$
			}
			if (binding.mod2) {
				userString+= "\u21e7"; //Shift //$NON-NLS-0$
			}
			if (binding.mod1) {
				userString+= "\u2318"; //Command //$NON-NLS-0$
			}
		} else {
			var PLUS = "+"; //$NON-NLS-0$;
			if (binding.mod1)
				userString += messages.KeyCTRL + PLUS;
			if (binding.mod2)
				userString += messages.KeySHIFT + PLUS;
			if (binding.mod3)
				userString += messages.KeyALT + PLUS;
		}
		
		if (binding.alphaKey) {
			return userString+binding.alphaKey;
		}
		if (binding.type === "keypress") {
			return userString+binding.keyCode; 
		}

		// Check if it has a special symbol defined
		var keyCode = binding.keyCode;
		var symbol = keySymbols[keyCode];
		if (symbol) {
			return userString + symbol;
		}

		// Check if it's a known named key from lib.KEY
		var keyName = lib.keyName(keyCode);
		if (keyName) {
			// Some key names are translated, so check for that.
			keyName = messages["Key" + keyName] || keyName; //$NON-NLS-0$
			return userString + keyName;
		}

		var character;
		switch (binding.keyCode) {
			case 59:
				character = binding.mod2 ? ":" : ";"; //$NON-NLS-1$ //$NON-NLS-0$
				break;
			case 61:
				character = binding.mod2 ? "+" : "="; //$NON-NLS-1$ //$NON-NLS-0$
				break;
			case 188:
				character = binding.mod2 ? "<" : ","; //$NON-NLS-1$ //$NON-NLS-0$
				break;
			case 190:
				character = binding.mod2 ? ">" : "."; //$NON-NLS-1$ //$NON-NLS-0$
				break;
			case 191:
				character = binding.mod2 ? "?" : "/"; //$NON-NLS-1$ //$NON-NLS-0$
				break;
			case 192:
				character = binding.mod2 ? "~" : "`"; //$NON-NLS-1$ //$NON-NLS-0$
				break;
			case 219:
				character = binding.mod2 ? "{" : "["; //$NON-NLS-1$ //$NON-NLS-0$
				break;
			case 220:
				character = binding.mod2 ? "|" : "\\"; //$NON-NLS-1$ //$NON-NLS-0$
				break;
			case 221:
				character = binding.mod2 ? "}" : "]"; //$NON-NLS-1$ //$NON-NLS-0$
				break;
			case 222:
				character = binding.mod2 ? '"' : "'"; //$NON-NLS-1$ //$NON-NLS-0$
				break;
			}
		if (character) {
			return userString+character;
		}
		if (binding.keyCode >= 112 && binding.keyCode <= 123) {
			return userString+"F"+ (binding.keyCode - 111); //$NON-NLS-0$
		}
		return userString+String.fromCharCode(binding.keyCode);
	}

	function getUserKeyString(binding) {
		var result = "";
		var keys = binding.getKeys();
		for (var i = 0; i < keys.length; i++) {
			if (i !== 0) {
				result += " "; //$NON-NLS-0$
			}
			result += getUserKeyStrokeString(keys[i]);
		}
		return result;
	}

	/**
	 * @name orion.uiUtils.getUserText
	 * @function
	 * @param {Object} options The options object
	 * @param {String} options.id
	 * @param {Element} options.refNode
	 * @param {Boolean} options.hideRefNode
	 * @param {String} options.initialText
	 * @param {Function} options.onComplete
	 * @param {Function} options.onEditDestroy
	 * @param {String} options.selectTo
	 * @param {Boolean} options.isInitialValid
	 * @param {Boolean} options.insertAsChild
	 */
	function getUserText(options) {
		var id = options.id;
		var refNode = options.refNode;
		var hideRefNode = options.hideRefNode;
		var initialText = options.initialText;
		var onComplete = options.onComplete;
		var onEditDestroy = options.onEditDestroy;
		var selectTo = options.selectTo;
		var isInitialValid = options.isInitialValid;
		var insertAsChild = options.insertAsChild;
		
		var done = false;
		var handler = function(isKeyEvent) {
			return function(event) {
				if (done) {
					return;
				}
				var editBox = lib.node(id),
					newValue = editBox.value;
				if (!editBox) {
					return;
				}
				if (isKeyEvent && event.keyCode === lib.KEY.ESCAPE) {
					if (hideRefNode) {
						refNode.style.display = "inline"; //$NON-NLS-0$
					}
					done = true;
					editBox.parentNode.removeChild(editBox);
					if (onEditDestroy) {
						onEditDestroy();
					}
					return;
				}
				if (isKeyEvent && event.keyCode !== lib.KEY.ENTER) {
					return;
				} else if (newValue.length === 0 || (!isInitialValid && newValue === initialText)) {
					if (hideRefNode) {
						refNode.style.display = "inline"; //$NON-NLS-0$
					}
					done = true;
				} else {
					onComplete(newValue);
					if (hideRefNode && refNode.parentNode) {
						refNode.style.display = "inline"; //$NON-NLS-0$
					}
					done = true;
				}
				// some clients remove temporary dom structures in the onComplete processing, so check that we are still in DOM
				if (editBox.parentNode) {
					editBox.parentNode.removeChild(editBox);
				}
				if (onEditDestroy) {
					onEditDestroy();
				}
			};
		};
	
		// Swap in an editable text field
		var editBox = document.createElement("input"); //$NON-NLS-0$
		editBox.id = id;
		editBox.value = initialText || "";
		if (insertAsChild) {
			refNode.appendChild(editBox);
		} else {
			refNode.parentNode.insertBefore(editBox, refNode.nextSibling);
		}
		editBox.classList.add("userEditBoxPrompt"); //$NON-NLS-0$
		if (hideRefNode) {
			refNode.style.display = "none"; //$NON-NLS-0$
		}				
		editBox.addEventListener("keydown", handler(true), false); //$NON-NLS-0$
		editBox.addEventListener("blur", handler(false), false); //$NON-NLS-0$
		window.setTimeout(function() { 
			editBox.focus(); 
			if (initialText) {
				var box = lib.node(id);
				var end = selectTo ? initialText.indexOf(selectTo) : -1;
				if (end > 0) {
					if(box.createTextRange) {
						var range = box.createTextRange();
						range.collapse(true);
						range.moveStart("character", 0); //$NON-NLS-0$
						range.moveEnd("character", end); //$NON-NLS-0$
						range.select();
					} else if(box.setSelectionRange) {
						box.setSelectionRange(0, end);
					} else if(box.selectionStart !== undefined) {
						box.selectionStart = 0;
						box.selectionEnd = end;
					}
				} else {
					box.select();
				}
			}
		}, 0);
	}
	
	/**
	 * Returns whether the given event should cause a reference
	 * to open in a new window or not.
	 * @param {Object} event The key event
	 * @name orion.util#openInNewWindow
	 * @function
	 */
	function openInNewWindow(event) {
		var isMac = window.navigator.platform.indexOf("Mac") !== -1; //$NON-NLS-0$
		return (isMac && event.metaKey) || (!isMac && event.ctrlKey);
	}
	
	/**
	 * Opens a link in response to some event. Whether the link
	 * is opened in the same window or a new window depends on the event
	 * @param {String} href The link location
	 * @name orion.util#followLink
	 * @function
	 */
	function followLink(href, event) {
		if (event && openInNewWindow(event)) {
			window.open(href);
		} else {
			window.location = href;
		}
	}
	
	function createButton(text, callback) {
		var button = document.createElement("button"); //$NON-NLS-0$
		button.className = "orionButton commandButton commandMargins"; //$NON-NLS-0$
		button.addEventListener("click", function(e) { //$NON-NLS-0$
			callback();
			lib.stop(e);
		}, false);
		if (text) {
			button.appendChild(document.createTextNode(text));
		}
		return button;	
	}
	
	function createDropdownButton(parent, name, populateFunction) {
	}

	/**
	 * Returns whether <code>element</code> or its parent is an HTML5 form element.
	 * @param {Element} element
	 * @param {Element} parentLimit
	 * @function
	 * @returns {Boolean}
	 */
	function isFormElement(element, parentLimit) {
		if (!element || !element.tagName) return false;
		switch (element.tagName.toLowerCase()) {
			case "button": //$NON-NLS-0$
			case "fieldset": //$NON-NLS-0$
			case "form": //$NON-NLS-0$
			case "input": //$NON-NLS-0$
			case "keygen": //$NON-NLS-0$
			case "label": //$NON-NLS-0$
			case "legend": //$NON-NLS-0$
			case "meter": //$NON-NLS-0$
			case "optgroup": //$NON-NLS-0$
			case "output": //$NON-NLS-0$
			case "progress": //$NON-NLS-0$
			case "select": //$NON-NLS-0$
			case "textarea": //$NON-NLS-0$
				return true;
		}
		if (element.parentNode === parentLimit) return false;
		return element.parentNode && isFormElement(element.parentNode, parentLimit);
	}

	/**
	 * Returns the folder name from path.
	 * @param {String} filePath
	 * @param {String} fileName
	 * @param {Boolean} keepTailSlash
	 * @returns {String}
	 */
	function path2FolderName(filePath, fileName, keepTailSlash){
		var tail = keepTailSlash ? 0: 1;
		return filePath.substring(0, filePath.length - encodeURIComponent(fileName).length - tail);
	}
	
	function _timeDifference(timeStamp) {
		var currentDate = new Date();
		var commitDate = new Date(timeStamp);
	    var difference = currentDate.getTime() - commitDate.getTime();
	    var yearDiff = Math.floor(difference/1000/60/60/24/365);
	    difference -= yearDiff*1000*60*60*24*365;
	    var monthDiff = Math.floor(difference/1000/60/60/24/30);
	    difference -= monthDiff*1000*60*60*24*30;
	    var daysDifference = Math.floor(difference/1000/60/60/24);
	    difference -= daysDifference*1000*60*60*24;
		var hoursDifference = Math.floor(difference/1000/60/60);
	    difference -= hoursDifference*1000*60*60;
	    var minutesDifference = Math.floor(difference/1000/60);
	    difference -= minutesDifference*1000*60;
	    var secondsDifference = Math.floor(difference/1000);
	    return {year: yearDiff, month: monthDiff, day: daysDifference, hour: hoursDifference, minute: minutesDifference, second: secondsDifference};
	}
	
	function _generateTimeString(number, singleTerm, term) {
		if(number > 0) {
			if(number === 1) {
				return messages[singleTerm];
			}
			return i18nUtil.formatMessage(messages[term], number);
		}
		return "";
	}
	
	/**
	 * Returns the time duration passed by now. E.g. "2 minutes", "an hour", "a day", "3 months", "2 years"
	 * @param {String} timeStamp
	 * @returns {String} If the duration is less than 1 minute, it returns empty string "". Otherwise it returns a duration value.
	 */
	function timeElapsed(timeStamp) {
		var diff = _timeDifference(timeStamp);
		var yearStr = _generateTimeString(diff.year, "a year", "years");
		var monthStr = _generateTimeString(diff.month, "a month", "months");
		var dayStr = _generateTimeString(diff.day, "a day", "days");
		var hourStr = _generateTimeString(diff.hour, "an hour", "hours");
		var minuteStr = _generateTimeString(diff.minute, "a minute", "minutes");
		var disPlayStr = "";
		if(yearStr) {
			disPlayStr = diff.year > 0 ? yearStr : yearStr + monthStr;
		} else if(monthStr) {
			disPlayStr = diff.month > 0 ? monthStr : monthStr + dayStr;
		} else if(dayStr) {
			disPlayStr = diff.day > 0 ? dayStr : dayStr + hourStr;
		} else if(hourStr) {
			disPlayStr = diff.hour > 0 ? hourStr : hourStr + minuteStr;
		} else if(minuteStr) {
			disPlayStr = minuteStr;
		}
		return disPlayStr;	
	}
	/**
	 * Returns the displayable time duration passed by now. E.g. "just now", "2 minutes ago", "an hour ago", "a day ago", "3 months ago", "2 years ago"
	 * @param {String} timeStamp
	 * @returns {String} If the duration is less than 1 minute, it returns empty string "just now". Otherwise it returns a duration value.
	 */
	function displayableTimeElapsed(timeStamp) {
		var duration = timeElapsed(timeStamp);
		if(duration) {
			return i18nUtil.formatMessage(messages["timeAgo"], duration);
		}
		return messages["justNow"];
	}
	//return module exports
	return {
		getUserKeyString: getUserKeyString,
		getUserText: getUserText,
		openInNewWindow: openInNewWindow,
		followLink: followLink,
		createButton: createButton,
		createDropdownButton: createDropdownButton,
		isFormElement: isFormElement,
		path2FolderName: path2FolderName,
		timeElapsed: timeElapsed,
		displayableTimeElapsed: displayableTimeElapsed
	};
});


define('text!orion/webui/submenutriggerbutton.html',[],function () { return '<li class="dropdownSubMenu"><span class="dropdownTrigger dropdownMenuItem" role="menuitem" tabindex="0"><span class="dropdownCommandName">${ButtonText}</span><span class="dropdownArrowRight core-sprite-closedarrow"></span></span><ul class="dropdownMenu"></ul></li>';});

/*******************************************************************************
 * @license
 * Copyright (c) 2010,2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
 
define('orion/commandRegistry',[
	'orion/commands',
	'orion/keyBinding',
	'orion/explorers/navigationUtils',
	'orion/PageUtil',
	'orion/uiUtils',
	'orion/webui/littlelib',
	'orion/webui/dropdown',
	'orion/webui/tooltip',
	'text!orion/webui/submenutriggerbutton.html',
	'orion/metrics',
	'orion/Deferred',
	'orion/EventTarget'
], function(Commands, mKeyBinding, mNavUtils, PageUtil, UIUtil, lib, mDropdown, mTooltip, SubMenuButtonFragment, mMetrics, mDeferred, mEventTarget) {

	/**
	 * Constructs a new command registry with the given options.
	 * @class CommandRegistry can render commands appropriate for a particular scope and DOM element.
	 * @name orion.commandregistry.CommandRegistry
	 * @param {Object} options The registry options object
	 * @param {orion.selection.Selection} [options.selection] Optional selection service.
	 */
	function CommandRegistry(options) {
		this._commandList = {};
		this._contributionsByScopeId = {};
		this._activeBindings = {};
		this._urlBindings = {};
		this._pendingBindings = {}; // bindings for as-yet-unknown commands
		this._parameterCollector = null;
		this._init(options || {});
	}
	CommandRegistry.prototype = /** @lends orion.commandregistry.CommandRegistry.prototype */ {
		_init: function(options) {
			this._selectionService = options.selection;
			var self = this;
			Commands.setKeyBindingProvider(function() { return self._activeBindings; });

			// Make the CommandRegistry an EventTarget. This is somewhat different from the normal pattern
			// so that we can override the normal 'addEventTarget' processing (see below)
			mEventTarget.attach(CommandRegistry.prototype);

			// Add a listener so our bindings get updated immediately
			this.addEventListener("bindingChanged", function(info) {
				this._handleBindingChanges(info);
			}.bind(this));

		
			/**
			 * @name addEventListener
			 * 
			 * @description  This is an override of the normal addEventListener to allow it to
			 * keep listeners synch'd with the current binding overrides whether or not they get added before or
			 * after the actual binding overrides have been retrieved from the preference store.
			 * 
			 * Once the overrides preference is loaded any currently registered listeners are informed of the current
			 * binding overrides. Subsequent listeners are also imformed when they get added.
			 * @param eventType The type of event being listened on.
			 * @param listener The listener to call when the event is dispatched.
			 */
			this.addEventListener = function(eventType, listener) {
				// if we've already received the overrides from the preference store then broadcast them to the new listener
				if (this._bindingOverrides && eventType === "bindingChanged") {
					this._updateBindingOverrides(listener);
				}
				
				// hook the listener for future changes
				CommandRegistry.prototype.addEventListener.call(this, eventType, listener);
			}
		},
		
		/**
		 * Process the provided URL to determine whether any commands should be invoked.  Note that we never
		 * invoke a command callback by URL, only its parameter collector.  If a parameter collector is not
		 * specified, commands in the URL will be ignored.
		 *
		 * @param {String} url a url that may contain URL bindings.
		 */
		processURL: function(url) {
			for (var id in this._urlBindings) {
				if (this._urlBindings[id] && this._urlBindings[id].urlBinding && this._urlBindings[id].command) {
					var match = this._urlBindings[id].urlBinding.match(url);
					if (match) {
						var urlBinding = this._urlBindings[id];
						var command = urlBinding.command;
						var invocation = urlBinding.invocation;
						// If the command has not rendered (visibleWhen=false, etc.) we don't have an invocation.
						if (invocation && invocation.parameters && command.callback) {
							invocation.parameters.setValue(match.parameterName, match.parameterValue);
							var self = this;
							window.setTimeout(function() {
								self._invoke(invocation);
							}, 0);
							return;
						}
					}
				}
			}
		},
		
		/**
		 * @param {String} commandId
		 * @returns {orion.commands.Command}
		 */
		findCommand: function(commandId) {
			return this._commandList[commandId];
		}, 
		
		/**
		 * Run the command with the specified commandId.
		 *
		 * @param {String} commandId the id of the command to run.
		 * @param {Object} item the item on which the command should run.
		 * @param {Object} handler the handler for the command.
		 * @param {orion.commands.ParametersDescription} [parameters] Parameters used on this invocation. Optional.
		 * @param {Object} [userData] Optional user data that should be attached to generated command callbacks.
		 * @param {DOMElement} [parent] Optional parent for the parameter collector.
		 *
		 * Note:  The current implementation will only run the command if a URL binding has been
		 * specified, or if an item to run the command against has been specified.  
		 */
		runCommand: function(commandId, item, handler, parameters, userData, parent) {
			var self = this;
			if (item) {
				var command = this._commandList[commandId];
				var enabled = command && (command.visibleWhen ? command.visibleWhen(item) : true);
				if (enabled && command.callback) {
					var commandInvocation = new Commands.CommandInvocation(handler, item, userData, command, self);
					commandInvocation.domParent = parent;
					return self._invoke(commandInvocation, parameters);
				}
			} else {
				//TODO should we be keeping invocation context for commands without bindings? 
				var binding = this._urlBindings[commandId];
				if (binding && binding.command) {
					if (binding.command.callback) {
						return self._invoke(binding.invocation, parameters);
					}
				}
			}
		},
		
		/**
		 * Return the default selection service that is being used when commands should apply against a selection.
		 */
		getSelectionService: function() {
			return this._selectionService;
		},


		/**
		 * Interface for a parameter collector.
		 * @name orion.commandregistry.ParameterCollector
		 * @class
		 */
		/**
		 * Open a parameter collector and return the dom node where parameter information should be inserted.
		 * @name orion.commandregistry.ParameterCollector#open
		 * @function
		 * @param {String|DOMElement} commandNode the node containing the triggering command
		 * @param {Function} fillFunction a function that will fill the parameter area
		 * @param {Function} onClose a function that will be called when the parameter area is closed
		 * @returns {Boolean} Whether the node is open.
		 */
		/**
		 * Closes any active parameter collectors.
		 * @name orion.commandregistry.ParameterCollector#close
		 * @function
		 */
		/**
		 * Returns a function that can be used to fill a specified parent node with parameter information.
		 * @name orion.commandregistry.ParameterCollector#getFillFunction
		 * @function
		 * @param {orion.commands.CommandInvocation} the command invocation used when gathering parameters
		 * @param {Function} closeFunction an optional function called when the area must be closed. 
		 * @returns {Function} a function that can fill the specified dom node with parameter collection behavior
		 */
		/**
		 * Collect parameters for the given command.
		 * @name orion.commandregistry.ParameterCollector#collectParameters
		 * @function
		 * @param {orion.commands.CommandInvocation} commandInvocation The command invocation
		 * @returns {Boolean} Whether or not required parameters were collected.
		 */
		/**
		 * Provide an object that can collect parameters for a given "tool" command.  When a command that
		 * describes its required parameters is shown in a toolbar (as an image, button, or link), clicking
		 * the command will invoke any registered parameterCollector before calling the command's callback.
		 * This hook allows a page to define a standard way for collecting required parameters that is 
		 * appropriate for the page architecture.  If no parameterCollector is specified, then the command callback
		 * will be responsible for collecting parameters.
		 *
		 * @param {orion.commandregistry.ParameterCollector} parameterCollector
		 */
		setParameterCollector: function(parameterCollector) {
			this._parameterCollector = parameterCollector;
		},

		/**
		 * Open a parameter collector suitable for collecting information about a command.
		 * Once a collector is created, the specified function is used to fill it with
		 * information needed by the command.  This method is used for commands that cannot
		 * rely on a simple parameter description to collect parameters.  Commands that describe
		 * their required parameters do not need to use this method because the command framework
		 * will open and close parameter collectors as needed and call the command callback with
		 * the values of those parameters.
		 *
		 * @param {DOMElement} node the dom node that is displaying the command, or a node in the parameter collector area
		 * @param {Function} fillFunction a function that will fill the parameter area
		 * @param {Function} onClose a function that will be called when the user closes the collector
		 */
		openParameterCollector: function(node, fillFunction, onClose) {
			if (this._parameterCollector) {
				this._parameterCollector.close();
				this._parameterCollector.open(node, fillFunction, onClose);
			}
		},
		
		/**
		 * Open a parameter collector to confirm a command.
		 *
		 * @param {DOMElement} node the dom node that is displaying the command
		 * @param {String} message the message to show when confirming the command
		 * @param {String} yesString the label to show on a yes/true choice
		 * @param {String} noString the label to show on a no/false choice
		 * @param {Boolean} modal indicates whether the confirmation prompt should be modal.
		 * @param {Function} onConfirm a function that will be called when the user confirms the command.  The function
		 * will be called with boolean indicating whether the command was confirmed.
		 */
		confirm: function(node, message, yesString, noString, modal, onConfirm) {
			var result = false;
			if (this._parameterCollector && !modal) {
				var self = this;
				var okCallback = function() {onConfirm(result);};
				var closeFunction = function(){self._parameterCollector.close();}
				var fillFunction = function(parent, buttonParent) {
					var label = document.createElement("span"); //$NON-NLS-0$
					label.classList.add("parameterPrompt"); //$NON-NLS-0$
					label.textContent = message;
					
					parent.appendChild(label);
					var yesButton = document.createElement("button"); //$NON-NLS-0$
					yesButton.addEventListener("click", function(event) { //$NON-NLS-0$
						result = true;
						okCallback();
						closeFunction();
					}, false);
					buttonParent.appendChild(yesButton);
					yesButton.appendChild(document.createTextNode(yesString)); //$NON-NLS-0$
					yesButton.className = "dismissButton"; //$NON-NLS-0$
					var button = document.createElement("button"); //$NON-NLS-0$
					button.addEventListener("click", function(event) { //$NON-NLS-0$
						result = false;
						closeFunction();
					}, false);
					buttonParent.appendChild(button);
					button.appendChild(document.createTextNode(noString)); //$NON-NLS-0$
					button.className = "dismissButton"; //$NON-NLS-0$
					return yesButton;
				};
				this._parameterCollector.close();
				var opened = this._parameterCollector.open(node, fillFunction, function(){});
				if (!opened) {
					var tooltip = new mTooltip.Tooltip({
						node: node,
						afterHiding: function() {
							this.destroy();
						},
						trigger: "click", //$NON-NLS-0$
						position: ["below", "right", "above", "left"] //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$
					});
					var parameterArea = tooltip.contentContainer();
					parameterArea.classList.add("parameterPopup"); //$NON-NLS-0$
					var originalFocusNode = window.document.activeElement;
					closeFunction = function() {
						if (originalFocusNode) {
							originalFocusNode.focus();
						}
						tooltip.destroy();
					};
					var messageArea = document.createElement("div"); //$NON-NLS-0$
					messageArea.classList.add("parameterMessage"); //$NON-NLS-0$
					parameterArea.appendChild(messageArea);
					
					var buttonArea = document.createElement("div"); //$NON-NLS-0$
					parameterArea.appendChild(buttonArea);
					buttonArea.classList.add("layoutRight"); //$NON-NLS-0$
					buttonArea.classList.add("parametersDismiss"); //$NON-NLS-0$
				
					var focusNode = fillFunction(messageArea, buttonArea);
					tooltip.show();
					if (focusNode) {
						window.setTimeout(function() {
								focusNode.focus();
								if (focusNode.select) {
									focusNode.select();
								}
						}, 0);	
					}
				}
				return;
			} 
			result = window.confirm(message);
			onConfirm(result);
		},
		
		/**
		 * Close any active parameter collector.  This method should be used to deactivate a
		 * parameter collector that was opened with <code>openParameterCollector</code>.
		 * Commands that describe their required parameters do not need to use this method 
		 * because the command framework will open and close parameter collectors as needed and 
		 * call the command callback with the values of those parameters.
		 */
		closeParameterCollector: function() {
			if (this._parameterCollector) {
				this._parameterCollector.close();
			}
		},
		
		/**
		 * Returns whether this registry has been configured to collect command parameters
		 *
		 * @returns {Boolean} whether or not this registry is configured to collect parameters.
		 */
		collectsParameters: function() {
			return this._parameterCollector;
		},
		
		/*
		 * Invoke the specified command, collecting parameters if necessary.  This is used inside the framework
		 * when the user invokes a command. If parameters are specified, then these parameters should be used
		 * in lieu of the invocation's parameters.
		 *
		 */
		_invoke: function(commandInvocation, parameters) {
			return this._collectAndInvoke(commandInvocation.makeCopy(parameters), false);
		},
		
		/*
		 * This method is the actual implementation for collecting parameters and invoking a callback.
		 * "forceCollect" specifies whether we should always collect parameters or consult the parameters description to see if we should.
		 */
		_collectAndInvoke: function(commandInvocation, forceCollect, cancelCallback) {
			if (commandInvocation) {
				// Establish whether we should be trying to collect parameters. 
				if (this._parameterCollector && commandInvocation.parameters && commandInvocation.parameters.hasParameters() && 
					(forceCollect || commandInvocation.parameters.shouldCollectParameters())) {
					var collecting = false;
					commandInvocation.parameters.updateParameters(commandInvocation);
					// Consult shouldCollectParameters() again to verify we still need collection. Due to updateParameters(), the CommandInvocation
					// could have dynamically set its parameters to null (meaning no collection should be done).
					if (commandInvocation.parameters.shouldCollectParameters()) {
						collecting = this._parameterCollector.collectParameters(commandInvocation,cancelCallback);
						// The parameter collector cannot collect.  We will do a default implementation using a popup.
						if (!collecting) {
							var tooltip = new mTooltip.Tooltip({
								node: commandInvocation.domNode || commandInvocation.domParent,
								afterHiding: function() {
									this.destroy();
								},
								trigger: "click", //$NON-NLS-0$
								position: ["below", "right", "above", "left"] //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$
							});
							var parameterArea = tooltip.contentContainer();
							parameterArea.classList.add("parameterPopup"); //$NON-NLS-0$
							var originalFocusNode = window.document.activeElement;
							var focusNode = this._parameterCollector.getFillFunction(commandInvocation, function() {
								if (originalFocusNode) {
									originalFocusNode.focus();
								}
								tooltip.destroy();
							}, cancelCallback)(parameterArea);
							tooltip.show();
							if (focusNode) {
								window.setTimeout(function() {
										focusNode.focus();
										if (focusNode.select) {
											focusNode.select();
										}
								}, 0);
							}
							collecting = true;
						}
					}
					if (!collecting) {
						mMetrics.logEvent("command", "invoke", commandInvocation.command.id); //$NON-NLS-2$ //$NON-NLS-1$
						// Just call the callback with the information we had.
						return commandInvocation.command.callback.call(commandInvocation.handler || window, commandInvocation);
					}
				} else {
					mMetrics.logEvent("command", "invoke", commandInvocation.command.id); //$NON-NLS-2$ //$NON-NLS-1$
					// We should not be trying to collect parameters, just call the callback.
					return commandInvocation.command.callback.call(commandInvocation.handler || window, commandInvocation);
				}
			} else {
				window.console.log("Client attempted to invoke command without an available (rendered) command invocation"); //$NON-NLS-0$
			}
		},
		
		/**
		 * Collect the parameters specified in the given command invocation.  If parameters are
		 * collected successfully, invoke the command's callback. This method is used by clients who want to 
		 * control the timing of parameter collection.  For example, if a command must be executed before it can
		 * be determined what parameters are known, the client can try the command in the callback and then call
		 * this function if parameters are needed.  In this case, clients typically configure the parameters description
		 * options with "options.clientWillCollect" set to true.
		 *
		 * @see orion.commands.ParametersDescription
		 *
		 * @param {orion.commands.CommandInvocation} commandInvocation the current invocation of the command 
		 */
		collectParameters: function(commandInvocation,cancelCallback) {
			this._collectAndInvoke(commandInvocation, true, cancelCallback); 
		},
		
		/**
		 * Show the keybindings that are registered with the command registry inside the specified target node.
		 * @param {KeyAssistPanel} keyAssist the key assist panel
		 */
		showKeyBindings: function(keyAssist) {
			var scopes = {};
			var binding;
			// see commands.js _processKey
			function execute(activeBinding) {
				return function() {
					Commands.executeBinding(activeBinding);
				};
			}
			
			var bindings = [];
			for (var aBinding in this._activeBindings) {
				binding = this._activeBindings[aBinding];
				if (binding && binding.keyBinding && binding.command && (binding.command.name || binding.command.tooltip)) {
					bindings.push(binding);
				}
			}
			bindings.sort(function (a, b) {
				var ta = a.command.name || a.command.tooltip;
				var tb = b.command.name || b.command.tooltip;
				return ta.localeCompare(tb);
			});
			for (var i=0; i<bindings.length; i++) {
				binding = bindings[i];
				// skip scopes and process at end
				if (binding.keyBinding.scopeName) {
					if (!scopes[binding.keyBinding.scopeName]) {
						scopes[binding.keyBinding.scopeName] = [];
					}
					scopes[binding.keyBinding.scopeName].push(binding);
				} else {
					keyAssist.createItem(binding.keyBinding, binding.command.name || binding.command.tooltip, binding.command.id, execute(binding));
				}
			}
			for (var scopedBinding in scopes) {
				if (scopes[scopedBinding].length && scopes[scopedBinding].length > 0) {
					keyAssist.createHeader(scopedBinding);
					scopes[scopedBinding].forEach(function(binding) {
						keyAssist.createItem(binding.keyBinding, binding.command.name || binding.command.tooltip, binding.command.id, execute(binding));
					});
				}	
			}
		},
		
		_handleBindingChanges: function(args) {
			// Change the command's current binding
			var command = this.findCommand(args.id);
			if (command) {
				// Add the new binding (overrides any existing one)
				var curBinding = this._activeBindings[args.id];
				if (curBinding) {
					curBinding.keyBinding = args.newBinding;
				} else {
					this._addBinding(command, "key", args.newBinding); //$NON-NLS-1$
				}
			}
			
			// Now see if we have any menu items to update
			if (this._renderedCommands && this._renderedCommands[args.id]) {
				var scopes = this._renderedCommands[args.id];
				var keys = Object.keys(scopes);
				for (var i=0; i<keys.length; i++) {
					var invocation = scopes[keys[i]];
					var bindingString = UIUtil.getUserKeyString(args.newBinding);
					var newElement = Commands.createCommandMenuItem(invocation.domParent, invocation.command, invocation, null, invocation.onClick, bindingString);
					invocation.domNode.parentNode.replaceChild(newElement, invocation.domNode);
					invocation.domNode = newElement;
				}
			}
		},
		
		/**
		 * @name createBindingOverride
		 * @description Creates an override for the binding of a particular Command or Text Action.
		 * @param id The id of the Command or Text Action being overridden
		 * @param newBinding The new binding to use
		 * @param prevBinding The binding (if any) being overridden. Note that this is needed to ensure that
		 * we override the correct Text Action (which can havemultiple bindings for the same id).
		 */
		createBindingOverride: function(id, newBinding, prevBinding) {
			if (!this._bindingOverrides) {
				this._bindingOverrides = [];
			}

			// Capture this change			
			this._bindingOverrides.push ({id: id, newBinding: newBinding, prevBinding: prevBinding});

			// Update the preference store
			if (this._prefService) {
				var overridesStr = JSON.stringify(this._bindingOverrides);
				this._prefService.getPreferences("/KeyBindings").then(function(prefs) { //$NON-NLS-1$
					prefs.put("overridesJSON", overridesStr); //$NON-NLS-1$
				});
			}
			
			// Handle the change locally
			var bindingChangeEvent = {type: "bindingChanged", id: id, newBinding: newBinding, prevBinding: prevBinding}; //$NON-NLS-1$
			this.dispatchEvent(bindingChangeEvent);
		},

		/**
		 * @name _registerRenderedCommand
		 * @description Keeps a record of the 'invocation' object for a renderred command. The invocation contains
		 * a lot of information about the rendered item (including the DOM element created to show it). This is currently
		 * used to update the menu item's key binding info if its binding changes.
		 * 
		 * @param actionID The actionID of the menu item
		 * @param scopeID The scopeID of this particular invocation (two different menus can be created using teh same commands)
		 * @param invocation The invocation record to store
		 */
		_registerRenderedCommand: function(actionID, scopeID, invocation) {
			if (!this._renderedCommands) {
				this._renderedCommands = {};
			}
			var scopes = this._renderedCommands[actionID];
			if (!scopes) {
				scopes = this._renderedCommands[actionID] = {};
			}
			scopes[scopeID] = invocation;
		},
		
		_getBindingOverrides: function() {
			// Get the key binding overrides from the preference store
			return this._prefService.getPreferences("/KeyBindings").then(function(bindingPrefs) { //$NON-NLS-1$
				var overrides = [];
				this.bindingPrefs = bindingPrefs;
				
				var prefVal = bindingPrefs.get("overridesJSON"); //$NON-NLS-1$
				if (prefVal) {
					// Sometimes we get the parsing done for us, sometimes not...wtf?
					if (typeof prefVal === "string") {
						overrides = JSON.parse(prefVal);
					} else {
						overrides = prefVal;
					}
				}
				
				// Turn the Objects back into KeyStrokes
				for (var i = 0; i < overrides.length; i++) {
					var bindingChange = overrides[i];
					if (bindingChange.newBinding) {
						var nb = bindingChange.newBinding;
						overrides[i].newBinding = new mKeyBinding.KeyStroke(nb.keyCode, nb.mod1, nb.mod2, nb.mod3, nb.mod4, nb.type);
					}
					if (bindingChange.prevBinding) {
						var pb = bindingChange.prevBinding;
						overrides[i].prevBinding = new mKeyBinding.KeyStroke(pb.keyCode, pb.mod1, pb.mod2, pb.mod3, pb.mod4, pb.type);
					}
				}
				
				return new mDeferred().resolve(overrides);
			}.bind(this));
		},
		
		/**
		 * @name getBindingOverride
		 * @description returns the binding that the given id should be overridden with (or null if no override exists). Note that
		 * this method is only valid for Commands, not Text Actions,
		 * @param cmdId The id of the Command to check
		 * @returns returns the new KeyStroke if there's an override else null
		 */
		getBindingOverride: function(cmdId) {
			// If we haven't recieved the overrides yet
			if (!this._bindingOverrides)
				return null;
			
			// Here we want the *last* override so we iterate backwards
			for (var i = (this._bindingOverrides.length - 1); i >= 0; i--) {
				var override = this._bindingOverrides[i];
				if (override.id === cmdId) {
					return override.newBinding;
				}
			}
			return null;
		},
		
		/** 
		 * Add a command to the command registry.  Nothing will be shown in the UI
		 * until this command is referenced in a contribution.
		 * @param {orion.commands.Command} command The command being added.
		 * @see registerCommandContribution
		 */
		addCommand: function(command) {
			this._commandList[command.id] = command;
			// Resolve any pending key/url bindings against this command
			var pending = this._pendingBindings[command.id];
			if (pending) {
				var _self = this;
				pending.forEach(function(binding) {
					_self._addBinding(command, binding.type, binding.binding, binding.bindingOnly);
				});
				delete this._pendingBindings[command.id];
			}
		},
		
		/**
		 * Registers a command group and specifies visual information about the group.
		 * @param {String} scopeId The id of a DOM element in which the group should be visible.  Required.
		 *  When commands are rendered for a particular element, the group will be shown only if its scopeId
		 *  matches the id being rendered.
		 * @param {String} groupId The id of the group, must be unique.  May be used for a dom node id of
		 *  the element representing the group
		 * @param {Number} position The relative position of the group within its parent.  Required.
		 * @param {String} [title] The title of the group, optional
		 * @param {String} [parentPath] The path of parent groups, separated by '/'.  For example,
		 *  a path of "group1Id/group2Id" indicates that the group belongs as a child of 
		 *  group2Id, which is itself a child of group1Id.  Optional.
		 * @param {String} [emptyGroupMessage] A message to show if the group is empty and the user activates the UI element
		 *  representing the group.  Optional.  If not specified, then the group UI element won't be shown when it is empty.
		 * @param {String} [imageClass] CSS class of an image to use for this group.
		 * @param {String} [tooltip] Tooltip to show on this group. If not provided, and the group uses an <code>imageClass</code>,
		 * the <code>title</code> will be used as the tooltip.
		 * @param {String} [selectionClass] CSS class to be appended when the command button is selected. Optional.
		 * @param {String} or {boolean} [defaultActionId] Id of an action from this group that should be invoked when the group is selected. This will add an
		 * arrow to the grup that will open the dropdown. Optionally this can be set to <code>true</code> instead of adding a particular action.
		 * If set to <code>true</code> the group will be renderer as if there was a default action, but instead of invoking the default action it will
		 * open the dropdown. Optional.
		 * @param {String} [extraClasses] A string containing space separated css classes that will be applied to group button
		 */	
		addCommandGroup: function(scopeId, groupId, position, title, parentPath, emptyGroupMessage, imageClass, tooltip, selectionClass, defaultActionId, extraClasses) {
			if (!this._contributionsByScopeId[scopeId]) {
				this._contributionsByScopeId[scopeId] = {};
			}
			var parentTable = this._contributionsByScopeId[scopeId];
			if (parentPath) {
				parentTable = this._createEntryForPath(parentTable, parentPath);		
			}
			
			if (parentTable[groupId]) {
				// update existing group definition if info has been supplied
				if (title) {
					parentTable[groupId].title = title;
				}
				if (position) {
					parentTable[groupId].position = position;
				}
				if (imageClass) {
					parentTable[groupId].imageClass = imageClass;
				}
				if (tooltip) {
					parentTable[groupId].tooltip = tooltip;
				}
				if (selectionClass) {
					parentTable[groupId].selectionClass = selectionClass;
				}
				
				if (extraClasses) {
					parentTable[groupId].extraClass = extraClasses;
				}
				
				if(defaultActionId === true){
					parentTable[groupId].pretendDefaultActionId = true;
				} else {
					parentTable[groupId].defaultActionId = defaultActionId;
				}
				

				parentTable[groupId].emptyGroupMessage = emptyGroupMessage;
			} else {
				// create new group definition
				parentTable[groupId] = {title: title, 
										position: position, 
										emptyGroupMessage: emptyGroupMessage,
										imageClass: imageClass,
										tooltip: tooltip,
										selectionClass: selectionClass,
										defaultActionId: defaultActionId === true ? null : defaultActionId,
										pretendDefaultActionId: defaultActionId === true,
										children: {},
										extraClasses: extraClasses};
				parentTable.sortedContributions = null;
			}
		},
		
		_createEntryForPath: function(parentTable, parentPath) {
			if (parentPath) {
				var segments = parentPath.split("/"); //$NON-NLS-0$
				segments.forEach(function(segment) {
					if (segment.length > 1) {
						if (!parentTable[segment]) {
							// empty slot with children
							parentTable[segment] = {position: 0, children: {}};
							parentTable.sortedContributions = null;
						} 
						parentTable = parentTable[segment].children;
					}
				});
			}
			return parentTable;	
		},
		
		/**
		 * Register a selection service that should be used for certain command scopes.
		 * @param {String} scopeId The id describing the scope for which this selection service applies.  Required.
		 *  Only contributions made to this scope will use the selection service.
		 * @param {orion.selection.Selection} selectionService the selection service for the scope.
		 */
		registerSelectionService: function(scopeId, selectionService) {
			if (!this._contributionsByScopeId[scopeId]) {
				this._contributionsByScopeId[scopeId] = {};
			}
			this._contributionsByScopeId[scopeId].localSelectionService = selectionService;
		},
		
		/**
		 * Register the serviceRegistry. Use this gain access to any other services we need (i.e. the preferenceService)
		 * @param serviceRegistry {orion.serviceregistry.ServiceRegistry} the current service registry.
		 */
		setServiceRegistry: function(serviceRegistry) {
			this._serviceRegistry = serviceRegistry;
			this._prefService = serviceRegistry.getService("orion.core.preference"); //$NON-NLS-1$

			if (this._prefService) {
				// Get the overrides from the preference store
				this._getBindingOverrides().then(function(overrides) {
					this._bindingOverrides = overrides;
					
					// Update all folks that are listening
					this._updateBindingOverrides();
				}.bind(this));
	
				// listen for changes form other pages
				var storageKey;
				this._prefService.listenForChangedSettings("/KeyBindings", function (e) { //$NON-NLS-1$
					if (e.key === storageKey) {
						// Refresh the binding overrides
						this._getBindingOverrides().then(function(overrides) {
							if (overrides.length > this._bindingOverrides.length) {
								// handle any new changes
								for (var i = this._bindingOverrides.length; i < overrides.length; i++) {
									var override = overrides[i];
									var bindingChangeEvent = {type: "bindingChanged", id: override.id, newBinding: override.newBinding,  //$NON-NLS-1$
																prevBinding: override.prevBinding};
									this.dispatchEvent(bindingChangeEvent);
								}
							}
							this._bindingOverrides = overrides;
						}.bind(this));
					}
				}.bind(this)).then(function(key) {
					storageKey = key;
				}.bind(this));
			}
		},

		_updateBindingOverrides: function(listener) {
			for(var i = 0; i < this._bindingOverrides.length; i++) {
				var override = this._bindingOverrides[i];
				var bindingChangeEvent = {type: "bindingChanged", id: override.id, newBinding: override.newBinding,  //$NON-NLS-1$
					prevBinding: override.prevBinding};
				if (listener) {
					listener(bindingChangeEvent);
				} else {
					this.dispatchEvent(bindingChangeEvent);
				}
			}
		},
		
		/**
		 * Register a command contribution, which identifies how a command appears
		 * on a page and how it is invoked.
		 * @param {String} scopeId The id describing the scope of the command.  Required.
		 *  This scope id is used when rendering commands.
		 * @param {String} commandId the id of the command.  Required.
		 * @param {Number} position the relative position of the command within its parent.  Required.
		 * @param {String} [parentPath=null] the path of any parent groups, separated by '/'.  For example,
		 *  a path of "group1Id/group2Id/command" indicates that the command belongs as a child of 
		 *  group2Id, which is itself a child of group1Id.  Optional.
		 * @param {boolean} [bindingOnly=false] if true, then the command is never rendered, but the key or URL binding is hooked.
		 * @param {orion.KeyBinding} [keyBinding] a keyBinding for the command.  Optional.
		 * @param {orion.commands.URLBinding} [urlBinding] a url binding for the command.  Optional.
		 * @param {Object} [handler] the object that should perform the command for this contribution.  Optional.
		 */
		registerCommandContribution: function(scopeId, commandId, position, parentPath, bindingOnly, keyBinding, urlBinding, handler) {
			if (!this._contributionsByScopeId[scopeId]) {
				this._contributionsByScopeId[scopeId] = {};
			}
			var parentTable = this._contributionsByScopeId[scopeId];
			if (parentPath) {
				parentTable = this._createEntryForPath(parentTable, parentPath);		
			} 
			
			// store the contribution
			parentTable[commandId] = {position: position, handler: handler};
			
			var command;
			if (this._bindingOverrides) {
				var bindingOverride = this.getBindingOverride(commandId, keyBinding);
				if (bindingOverride) {
					keyBinding = bindingOverride;
				}
			}
			// add to the bindings table now
			if (keyBinding) {
				command = this._commandList[commandId];
				if (command) {
					this._addBinding(command, "key", keyBinding, bindingOnly); //$NON-NLS-0$
				} else {
					this._addPendingBinding(commandId, "key", keyBinding, bindingOnly); //$NON-NLS-0$
				}
			}
			
			// add to the url key table
			if (urlBinding) {
				command = this._commandList[commandId];
				if (command) {
					this._addBinding(command, "url", urlBinding, bindingOnly); //$NON-NLS-0$
				} else {
					this._addPendingBinding(commandId, "url", urlBinding, bindingOnly); //$NON-NLS-0$
				}
			}
			// get rid of sort cache because we have a new contribution
			parentTable.sortedContributions = null;
		},
		
		unregisterCommandContribution: function(scopeId, commandId, parentPath){
			if (!this._contributionsByScopeId[scopeId]) {
				// scope does not exist
				return;
			}
			delete this._commandList[commandId];
			delete this._activeBindings[commandId];
			delete this._urlBindings[commandId];
			delete this._pendingBindings[commandId];
			var parentTable = this._contributionsByScopeId[scopeId];
			if(parentPath){
				var segments = parentPath.split("/"); //$NON-NLS-0$
				segments.forEach(function(segment) {
					if (segment.length > 1) {
						if (!parentTable[segment]) {
							// command does not exist in given path
							return;
						} 
						parentTable = parentTable[segment].children;
					}
				});
			}
			delete parentTable[commandId];
			
			parentTable.sortedContributions = null;
		},

		/**
		 * @param {String} type One of <code>"key"</code>, <code>"url"</code>.
		 */
		_addBinding: function(command, type, binding, bindingOnly) {
			if (!command.id) {
				throw new Error("No command id: " + command); //$NON-NLS-1$
			}
			if (type === "key") { //$NON-NLS-0$
				this._activeBindings[command.id] = {command: command, keyBinding: binding, bindingOnly: bindingOnly};
			} else if (type === "url") { //$NON-NLS-0$
				this._urlBindings[command.id] = {command: command, urlBinding: binding, bindingOnly: bindingOnly};
			}
		},

		/**
		 * Remembers a key or url binding that has not yet been resolved to a command.
		 * @param {String} type One of <code>"key"</code>, <code>"url"</code>.
		 */
		_addPendingBinding: function(commandId, type, binding, bindingOnly) {
			this._pendingBindings[commandId] = this._pendingBindings[commandId] || [];
			this._pendingBindings[commandId].push({
				type: type,
				binding: binding,
				bindingOnly: bindingOnly
			});
		},

		_checkForTrailingSeparator: function(parent, style, autoRemove) {
			var last;
			if (style === "tool" || style === "button") { //$NON-NLS-1$ //$NON-NLS-0$
				last = parent.childNodes.length > 0 ? parent.childNodes[parent.childNodes.length-1] : null;
				if (last && last.classList.contains("commandSeparator")) { //$NON-NLS-0$
					if (autoRemove) {
						parent.removeChild(last);
						return false;
					} 
					return true;
				}
			}
			if (style === "menu") { //$NON-NLS-0$
				var items = lib.$$array("li > *", parent); //$NON-NLS-0$
				if (items.length > 0 && items[items.length - 1].classList.contains("dropdownSeparator")) { //$NON-NLS-0$
					last = items[items.length - 1];
					if (autoRemove) {
						// reachy reachy.  Remove the anchor's li parent
						last.parentNode.parentNode.removeChild(last.parentNode);
						return false;
					} else {
						return true;
					}
				}
			}
			return false;
		},

		/**
		 * Render the commands that are appropriate for the given scope.
		 * @param {String} scopeId The id describing the scope for which we are rendering commands.  Required.
		 *  Only contributions made to this scope will be rendered.
		 * @param {DOMElement} parent The element in which commands should be rendered.  If commands have been
		 *  previously rendered into this element, it is up to the caller to empty any previously generated content.
		 * @param {Object} [items] An item or array of items to which the command applies.  Optional.  If no
		 *  items are specified and a selection service was specified at creation time, then the selection
		 *  service will be used to determine which items are involved. 
		 * @param {Object} handler The object that should perform the command
		 * @param {String} renderType The style in which the command should be rendered.  "tool" will render
		 *  a tool image in the dom.  "button" will render a text button.  "menu" will render menu items.  
		 * @param {Object} [userData] Optional user data that should be attached to generated command callbacks
		 * @param {Object[]} [domNodeWrapperList] Optional an array used to record any DOM nodes that are rendered during this call.
		 *  If an array is provided, then as commands are rendered, an object will be created to represent the command's node.  
		 *  The object will always have the property "domNode" which contains the node created for the command.  If the command is
		 *  rendered using other means (toolkit widget) then the optional property "widget" should contain the toolkit
		 *  object that represents the specified dom node.
		 */	
		renderCommands: function(scopeId, parent, items, handler, renderType, userData, domNodeWrapperList) {
			if (typeof(scopeId) !== "string") { //$NON-NLS-0$
				throw "a scope id for rendering must be specified"; //$NON-NLS-0$
			}
			parent = lib.node(parent);
			if (!parent) { 
				throw "no parent";  //$NON-NLS-0$
			}

			var contributions = this._contributionsByScopeId[scopeId];

			if (!items && contributions) {
				var selectionService = contributions.localSelectionService || this._selectionService;
				var self = this;
				if (selectionService) {
					selectionService.getSelections(function(selections) {
						self.renderCommands(scopeId, parent, selections, handler, renderType, userData);
					});
				}
				return;
			} 
			if (contributions) {
				this._render(scopeId, contributions, parent, items, handler, renderType || "button", userData, domNodeWrapperList); //$NON-NLS-0$
				
				// If the last thing we rendered was a group, it's possible there is an unnecessary trailing separator.
				this._checkForTrailingSeparator(parent, renderType, true);
			}
		},
		
		/**
		 * Destroy all DOM nodes and any other resources used by rendered commands.
		 * This call does not remove the commands from the command registry.  Clients typically call this
		 * function to empty a command area when a client wants to render the commands again due to some 
		 * change in state.  
		 * @param {String|DOMElement} parent The id or DOM node that should be emptied.
		 */
		destroy: function(parent) {
			parent = lib.node(parent);
			if (!parent) { 
				throw "no parent";  //$NON-NLS-0$
			}
			while (parent.hasChildNodes()) {
				var node = parent.firstChild;
				if (node.commandTooltip) {
					node.commandTooltip.destroy();
				}
				if (node.emptyGroupTooltip) {
					node.emptyGroupTooltip.destroy();
				}
				this.destroy(node);
				parent.removeChild(node);
			}
		},
		
		_render: function(scopeId, contributions, parent, items, handler, renderType, userData, domNodeWrapperList, extraClasses) {
			// sort the items
			var sortedByPosition = contributions.sortedContributions;
			
			if (!sortedByPosition) {
				sortedByPosition = [];
				var pushedItem = false;
				for (var key in contributions) {
					if (Object.prototype.hasOwnProperty.call(contributions, key)) {
						var item = contributions[key];
						if (item && typeof(item.position) === "number") { //$NON-NLS-0$
							item.id = key;
							sortedByPosition.push(item);
							pushedItem = true;
						}
					}
				}
				if (pushedItem) {
					sortedByPosition.sort(function(a,b) {
						return a.position-b.position;
					});
					contributions.sortedContributions = sortedByPosition;
				}
			}
			// now traverse the sorted contributions and render as we go
			var index = 0;
			var self = this;
			sortedByPosition.forEach(function(contribution) {
				var id, invocation;
				
				if( !contribution.imageClass ){
					contribution.imageClass = null;
				}
				
				if (contribution.children && Object.getOwnPropertyNames(contribution.children).length > 0) {
					
					var childContributions = contribution.children;
					var created;
					if (renderType === "tool" || renderType === "button") { //$NON-NLS-0$ //$NON-NLS-1$
						if (contribution.title) {
							// We need a named menu button.  We used to first render into the menu and only 
							// add a menu button in the dom when we knew items were actually rendered.
							// For performance, though, we need to be asynchronous in traversing children, so we will 
							// add the menu button always and then remove it if we don't need it.  
							// If we wait until the end of asynch processing to add the menu button, the layout will have 
							// to be redone. The down side to always adding the menu button is that we may find out we didn't
							// need it after all, which could cause layout to change.
							var defaultInvocation;
							if(contribution.defaultActionId){
								contribution.pretendDefaultActionId = contribution.defaultActionId === true;
								var defaultChild = self._commandList[contribution.defaultActionId];
								if(defaultChild && (defaultChild.visibleWhen ? defaultChild.visibleWhen(items) : true)){
									defaultInvocation = new Commands.CommandInvocation(handler, items, userData, defaultChild, self);
									defaultInvocation.domParent = parent;
								} else {
									contribution.pretendDefaultActionId = true;
								}
							}
						
							created = self._createDropdownMenu(parent, contribution.title, null /*nested*/, null /*populateFunc*/, contribution.imageClass, contribution.tooltip, contribution.selectionClass, null, defaultInvocation, contribution.pretendDefaultActionId, contribution.extraClasses);
							if(domNodeWrapperList){
								mNavUtils.generateNavGrid(domNodeWrapperList, created.menuButton);
							}

							// render the children asynchronously
							if (created) {
//								window.setTimeout(function() {
									self._render(scopeId, contribution.children, created.menu, items, handler, "menu", userData, domNodeWrapperList);  //$NON-NLS-0$
									// special post-processing when we've created a menu in an image bar.  We want to get rid 
									// of a trailing separator in the menu first, and then decide if our menu is necessary
									self._checkForTrailingSeparator(created.menu, "menu", true);  //$NON-NLS-0$
									// now determine if we actually needed the menu or not
									
									if (created.menu.childNodes.length === 0) {
										if (contribution.emptyGroupMessage) {
											if (!created.menuButton.emptyGroupTooltip) {
												created.menuButton.emptyGroupTooltip = new mTooltip.Tooltip({
													node: created.menuButton,
													text: contribution.emptyGroupMessage,
													trigger: "click", //$NON-NLS-0$
													position: ["below", "right", "above", "left"] //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
												});
											}
										} else {
											if(domNodeWrapperList){
												mNavUtils.removeNavGrid(domNodeWrapperList, created.menuButton);
											}
											function remove(child) {
												if (child && child.parentNode) {
													child.parentNode.removeChild(child);
												}
											}
											remove(created.menu);
											remove(created.menuButton);
											remove(created.destroyButton);
										}
									} else {
										created.menuButton.style.visibility = "visible";  //$NON-NLS-0$
									}
//								}, 0);
							}
						} else {  
							// rendering a group using a separator on each end. We do it synchronously because order matters with
							// non grouped items.
							var sep;
							// Only draw a separator if there is a non-separator preceding it.
							if (parent.childNodes.length > 0 && !self._checkForTrailingSeparator(parent, renderType)) {
								sep = self.generateSeparatorImage(parent);
							}
							self._render(scopeId, childContributions, parent, items, handler, renderType, userData, domNodeWrapperList); 
	
							// make sure that more than just the separator got rendered before rendering a trailing separator
							if (parent.childNodes.length > 0) {
								var lastRendered = parent.childNodes[parent.childNodes.length - 1];
								if (lastRendered !== sep) {
									sep = self.generateSeparatorImage(parent);
								}
							}
						}
					} else {
						// group within a menu
						if (contribution.title) {
							var subMenu = self._createDropdownMenu(parent, contribution.title, true, null, null, contribution.imageClass);
							if (subMenu) {
								self._render(scopeId, childContributions, subMenu.menu, items, handler, "menu", userData, domNodeWrapperList);  //$NON-NLS-0$
								// special post-processing when we've created a menu in an image bar.  We want to get rid 
								// of a trailing separator in the menu first, and then decide if our menu is necessary
								self._checkForTrailingSeparator(subMenu.menu, "menu", true);  //$NON-NLS-0$
								// If no items rendered in the submenu, we don't need it.
								if (subMenu.menu.childNodes.length === 0 && subMenu.destroyButton) {
									parent.removeChild(subMenu.destroyButton);
								}
							}
						} else {  
							// menu items with leading and trailing separators
							// don't render a separator if there is nothing preceding
							if (parent.childNodes.length > 0) {
								self._generateMenuSeparator(parent);
							}
							// synchronously render the children since order matters
							self._render(scopeId, childContributions, parent, items, handler, renderType, userData, domNodeWrapperList); 
							// Add a trailing separator if children rendered.
							if (parent.childNodes.length > 0) {
								self._generateMenuSeparator(parent);
							}
						}
					}
				} else {
					// processing atomic commands
					var command = self._commandList[contribution.id];
					var render = command ? true : false;
					var keyBinding = null;
					var urlBinding = null;
					if (command) {
						invocation = new Commands.CommandInvocation(contribution.handler || handler, items, userData, command, self);
						invocation.domParent = parent;
						var enabled = false;
						try {
							enabled = render && (command.visibleWhen ? command.visibleWhen(items, invocation) : true);
						} catch (e) {
							console.log(e);
							throw e;
						}
						// ensure that keybindings are bound to the current handler, items, and user data
						if (self._activeBindings[command.id] && self._activeBindings[command.id].keyBinding) {
							keyBinding = self._activeBindings[command.id];
							if (enabled) {
								keyBinding.invocation = invocation;
							} else {
								keyBinding.invocation = null;
							}
							// if it is a binding only, don't render the command.
							if (keyBinding.bindingOnly) {
								render = false;
							}
						}
						
						// same for url bindings
						if (self._urlBindings[command.id] && self._urlBindings[command.id].urlBinding) {
							urlBinding = self._urlBindings[command.id];
							if (enabled) {
								urlBinding.invocation = invocation;
							} else {
								urlBinding.invocation = null;
							}
							if (urlBinding.bindingOnly) {
								render = false;
							}
						}
						render = render && enabled;
					}
					if (render) {
						if (command.choiceCallback) {
							// special case.  The item wants to provide a set of choices
							var menuParent;
							var nested;
							if (renderType === "tool" || renderType === "button") { //$NON-NLS-1$ //$NON-NLS-0$
								menuParent = parent;
								nested = false;
								if (parent.nodeName.toLowerCase() === "ul") { //$NON-NLS-0$
									menuParent = document.createElement("li"); //$NON-NLS-0$
									parent.appendChild(menuParent);
								}
							} else {
								menuParent = parent;
								nested = true;
							}
							// dropdown button
							var populateFunction = function(menu) {
								command.populateChoicesMenu(menu, items, handler, userData, self);
							};
							self._createDropdownMenu(menuParent, command.name, nested, populateFunction.bind(command), command.imageClass, command.tooltip || command.title, command.selectionClass, command.positioningNode);
						} else {
							// Rendering atomic commands as buttons or menus
							invocation.handler = invocation.handler || this;
							invocation.domParent = parent;
							var element;
							var onClick = function(event) {
								self._invoke(invocation);
							};
							if (renderType === "menu") { //$NON-NLS-0$
								var bindingString = null;
								if (keyBinding && keyBinding.keyBinding) {
									bindingString = UIUtil.getUserKeyString(keyBinding.keyBinding);
								}
								element = Commands.createCommandMenuItem(parent, command, invocation, null, onClick, bindingString);
								
								// Register this command as being rendered (do we want to register all the commands ?)
								invocation.onClick = onClick;  // cache the handler
								self._registerRenderedCommand(command.id, scopeId, invocation);
							} else if (renderType === "quickfix") { //$NON-NLS-0$
								id = renderType + command.id + index; //$NON-NLS-0$ // using the index ensures unique ids within the DOM when a command repeats for each item
								var commandDiv = document.createElement("div"); //$NON-NLS-0$
								parent.appendChild(commandDiv);
								element = Commands.createCommandItem(commandDiv, command, invocation, id, null, renderType === "button", onClick); //$NON-NLS-0$
							} else {
								id = renderType + command.id + index;  //$NON-NLS-0$ // using the index ensures unique ids within the DOM when a command repeats for each item
								element = Commands.createCommandItem(parent, command, invocation, id, null, renderType === "tool", onClick); //$NON-NLS-0$
							} 
							mNavUtils.generateNavGrid(domNodeWrapperList, element);
							invocation.domNode = element;
							index++;
						}
					} 
				}
			});
		},
		
		/*
		 * private.  Parent must exist in the DOM.
		 */
		_createDropdownMenu: function(parent, name, nested, populateFunction, icon, tooltip, selectionClass, positioningNode, defaultInvocation, pretendDefaultActionId, extraClasses) {
			parent = lib.node(parent);
			// We create dropdowns asynchronously so it's possible that the parent has been removed from the document 
			// by the time we are called.  If so, don't bother building a submenu for an orphaned menu.
			if (!parent || !lib.contains(document.body, parent)) {
				return null;
			}
			var menuButton, newMenu, dropdownArrow;
			var destroyButton, menuParent = parent;
			if (nested) {
				var range = document.createRange();
				range.selectNode(parent);
				var buttonFragment = range.createContextualFragment(SubMenuButtonFragment);
				// bind name to fragment variable
				lib.processTextNodes(buttonFragment, {ButtonText: name});
				parent.appendChild(buttonFragment);
				destroyButton = parent.lastChild;
				newMenu = destroyButton.lastChild;
				menuButton = newMenu.previousSibling;
				menuButton.dropdown = new mDropdown.Dropdown({dropdown: newMenu, populate: populateFunction, parentDropdown: parent.dropdown});
				newMenu.dropdown = menuButton.dropdown;
			} else {
				if (parent.nodeName.toLowerCase() === "ul") { //$NON-NLS-0$
					menuParent = document.createElement("li"); //$NON-NLS-0$
					parent.appendChild(menuParent);
					destroyButton = menuParent;
				}
				var buttonCss = null;
				if (icon) {
					buttonCss = "dropdownButtonWithIcon"; //$NON-NLS-0$ // This class distinguishes dropdown buttons with an icon from those without
					tooltip = tooltip || name; // No text and no tooltip => fallback to name
				}
				tooltip = icon ? (tooltip || name) : tooltip;
				var created = Commands.createDropdownMenu(menuParent, name, populateFunction, buttonCss, icon, false, selectionClass, positioningNode, defaultInvocation || pretendDefaultActionId, extraClasses);
				dropdownArrow = created.dropdownArrow;
				menuButton = created.menuButton;
				if (dropdownArrow) {
					if (defaultInvocation) {
						defaultInvocation.domNode = created.menuButton;
					}
					var self = this;
					menuButton.onclick = function(evt){
						var bounds = lib.bounds(dropdownArrow);
						if ((evt.clientX >= bounds.left || pretendDefaultActionId === true) && created.dropdown) {
							created.dropdown.toggle(evt);
						} else {
							self._invoke(defaultInvocation);
						}
					};
					if (created.dropdown) {
						menuButton.onkeydown = function(evt) {
							if (lib.KEY.DOWN === evt.keyCode) {
								created.dropdown.toggle(evt);
								lib.stop(evt);
							}
						};
					}
				}
				newMenu = created.menu;
				var tooltipText, hasDefault = defaultInvocation && defaultInvocation.command && (defaultInvocation.command.tooltip || defaultInvocation.command.name);
				if (hasDefault) {
					tooltipText = defaultInvocation.command.tooltip || defaultInvocation.command.name;
				} else {
					tooltipText = tooltip;
				}
				if (tooltipText) {
					menuButton.commandTooltip = new mTooltip.Tooltip({
						node: menuButton,
						text: tooltipText,
						position: ["above", "below", "right", "left"] //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
					});					
				}
			}
			
			return {menuButton: menuButton, menu: newMenu, dropdown: menuButton.dropdown, destroyButton: destroyButton, dropdownArrow: dropdownArrow};
		},
		
		_generateMenuSeparator: function(dropdown) {
			if (!this._checkForTrailingSeparator(dropdown, "menu")) { //$NON-NLS-0$
				var item = document.createElement("li"); //$NON-NLS-0$
				item.classList.add("dropdownSeparator"); //$NON-NLS-0$
				var sep = document.createElement("span"); //$NON-NLS-0$
				sep.classList.add("dropdownSeparator"); //$NON-NLS-0$
				item.appendChild(sep);
				dropdown.appendChild(item);
			}
		},
		
				
		/**
		 * Add a dom node appropriate for using a separator between different groups
		 * of commands.  This function is useful when a page is precisely arranging groups of commands
		 * (in a table or contiguous spans) and needs to use the same separator that the command registry
		 * would use when rendering different groups of commands.
		 * @param {DOMElement} parent
		 */
		generateSeparatorImage: function(parent) {
			var sep;
			if (parent.nodeName.toLowerCase() === "ul") { //$NON-NLS-0$
				sep = document.createElement("li"); //$NON-NLS-0$
				parent.appendChild(sep);
			} else {
				sep = document.createElement("span"); //$NON-NLS-0$
				parent.appendChild(sep);
			}
			sep.classList.add("core-sprite-sep");  // location in sprite //$NON-NLS-0$
			sep.classList.add("imageSprite");  // sets sprite background //$NON-NLS-0$
			sep.classList.add("commandSeparator"); //$NON-NLS-0$
			return sep;
		}

	};  // end command registry prototype
	CommandRegistry.prototype.constructor = CommandRegistry;

	/**
	 * A URL binding defines how a URL token is bound to a command, and what parameter
	 * is provided
	 * @param {String} token the token in a URL query parameter that identifies the command
	 * @param {String} parameterName the name of the parameter being specified in the value of the query 
	 * 
	 * @name orion.commands.URLBinding
	 * @class
	 */
	function URLBinding (token, parameterName) {
		this.token = token;
		this.parameterName = parameterName;
	}
	URLBinding.prototype = /** @lends orion.commands.URLBinding.prototype */ {
		/**
		 * Returns whether this URL binding matches the given URL
		 * 
		 * @param url the URL.
		 * @returns {Boolean} whether this URL binding matches
		 */
		match: function (url) {
			//ensure this is only the hash portion
			var params = PageUtil.matchResourceParameters(url);
			if (typeof params[this.token] !== "undefined") { //$NON-NLS-0$
				this.parameterValue = params[this.token];
				return this;
			}
			return null;
		}
	};
	URLBinding.prototype.constructor = URLBinding;
	
	/**
	 * A CommandEventListener defines an (optional) UI event listener.
	 * 
	 * @param {String} name the name of the event
	 * @param {Function} handler the event handler function. The handler is provided two parameters on invocation, i. e.
	 * 			the DOM event and the undergoing commandInvocation objects.
	 * @param {Boolean} [capture] the (optional) flag used to determine whether to caputre the event or not
	 */
	function CommandEventListener (event, handler, capture) {
		this.event = event;
		this.handler = handler;
		this.capture = capture || false;
	}
	CommandEventListener.prototype.constructor = CommandEventListener;
	
	
	/**
	 * A CommandParameter defines a parameter that is required by a command.
	 *
	 * @param {String} name the name of the parameter
	 * @param {String} type the type of the parameter, one of the HTML5 input types, or "boolean"
	 * @param {String} [label] the (optional) label that should be used when showing the parameter
	 * @param {String} [value] the (optional) default value for the parameter
	 * @param {Number} [lines] the (optional) number of lines that should be shown when collecting the value.  Valid for type "text" only.
	 * @param {Object|Array} [eventListeners] the (optional) array or single command event listener
	 * @param {Function} [validator] a (optional) validator function
	 * 
	 * @name orion.commands.CommandParameter
	 * @class
	 */
	function CommandParameter (name, type, label, value, lines, eventListeners, validator) {
		this.name = name;
		this.type = type;
		this.label = label;
		this.value = value;
		this.lines = lines || 1;
		this.validator = validator;
		
		this.eventListeners = (Array.isArray(eventListeners)) ?
			eventListeners : (eventListeners ? [eventListeners] : []);
	}
	CommandParameter.prototype = /** @lends orion.commands.CommandParameter.prototype */ {
		/**
		 * Returns whether the user has requested to assign values to optional parameters
		 * 
		 * @returns {Boolean} whether the user has requested optional parameters
		 */
		optionsRequested: function () {
			return this.optionsRequested;
		}
	};
	CommandParameter.prototype.constructor = CommandParameter;
	
	/**
	 * A ParametersDescription defines the parameters required by a command, and whether there are additional
	 * optional parameters that can be specified.  The command registry will attempt to collect required parameters
	 * before calling a command callback.  The command is expected to provide UI for optional parameter, when the user has
	 * signalled a desire to provide optional information.
	 *
	 * @param {orion.commands.CommandParameter[]} parameters an array of CommandParameters that are required
	 * @param {Object} options The parameters description options object.
	 * @param {Boolean} options.hasOptionalParameters specifies whether there are additional optional parameters
	 *			that could be collected.  If true, then the collector will show an affordance for invoking an 
	 *			additional options collector and the client can use the optionsRequested flag to determine whether
	 *			additional parameters should be shown.  Default is false.
	 * @param {Boolean} options.clientCollect specifies whether the client will collect the parameters in its
	 *			callback.  Default is false, which means the callback will not be called until an attempt has
	 *			been made to collect parameters.
	 * @param {Function} options.getParameterElement a function used to look up the DOM element for a given parameter.
	 * @param {Function} options.getSubmitName a function used to return a name to use for the Submit button.
	 *
	 * @param {Function} [getParameters] a function used to define the parameters just before the command is invoked.  This is used
	 *			when a particular invocation of the command will change the parameters. The function will be passed
	 *          the CommandInvocation as a parameter. Any stored parameters will be ignored, and
	 *          replaced with those returned by this function. If no parameters (empty array or <code>null</code>) are returned,
	 *          then it is assumed that the command should not try to obtain parameters before invoking the command's callback
	 *          (similar to <code>options.clientCollect === true</code>).
	 * @name orion.commands.ParametersDescription
	 * @class
	 */
	function ParametersDescription (parameters, options, getParameters) {
		this._storeParameters(parameters);
		this._hasOptionalParameters = options && options.hasOptionalParameters;
		this._options = options;  // saved for making a copy
		this.optionsRequested = false;
		this.getParameters = getParameters;
		this.clientCollect = options && options.clientCollect;
		this.getParameterElement = options && options.getParameterElement;
		this.getSubmitName = options && options.getSubmitName;
		this.getCancelName = options && options.getCancelName;
		this.message = options && options.message;
	}
	ParametersDescription.prototype = /** @lends orion.commands.ParametersDescription.prototype */ {	
	
		_storeParameters: function(parameterArray) {
			this.parameterTable = null;
			if (parameterArray) {
				var table = this.parameterTable = {};
				parameterArray.forEach(function(parameter) {
					table[parameter.name] = parameter;
				});
			}
		},
		
		/**
		 * Update the stored parameters by running the stored function if one has been supplied.
		 */
		updateParameters: function(commandInvocation) {
			if (typeof this.getParameters === "function") { //$NON-NLS-0$
				this._storeParameters(this.getParameters(commandInvocation));
			}
		},
		
		/**
		 * Returns a boolean indicating whether any parameters have been specified.
		 *
		 * @returns {Boolean} whether there are parameters to collect.
		 */
		hasParameters: function() {
			return this.parameterTable !== null;
		},
		
		/**
		 * Returns a boolean indicating whether a collector should try to collect parameters.  If there
		 * are no parameters specified, or the client is expecting to collect them, this will return
		 * <code>false</code>.
		 *
		 * @returns {Boolean} indicating whether the caller should attempt to collect the parameters.
		 */
		shouldCollectParameters: function() {
			return !this.clientCollect && this.hasParameters();
		},
				
		/**
		 * Returns the CommandParameter with the given name, or <code>null</code> if there is no parameter
		 * by that name.
		 *
		 * @param {String} name the name of the parameter
		 * @returns {orion.commands.CommandParameter} the parameter with the given name
		*/
		parameterNamed: function(name) {
			return this.parameterTable[name];
		},
		
		/**
		 * Returns the value of the parameter with the given name, or <code>null</code> if there is no parameter
		 * by that name, or no value for that parameter.
		 *
		 * @param {String} name the name of the parameter
		 * @returns {String} the value of the parameter with the given name
		 */
		valueFor: function(name) {
			var parm = this.parameterTable[name];
			if (parm) {
				return parm.value;
			}
			return null;
		},
		
		/**
		 * Sets the value of the parameter with the given name.
		 *
		 * @param {String} name the name of the parameter
		 * @param {String} value the value of the parameter with the given name
		 */
		setValue: function(name, value) {
			var parm = this.parameterTable[name];
			if (parm) {
				parm.value = value;
			}
		},
		 
		/**
		 * Evaluate the specified function for each parameter.
		 *
		 * @param {Function} func a function which operates on a provided command parameter
		 *
		 */
		forEach: function(func) {
			for (var key in this.parameterTable) {
				if (this.parameterTable[key].type && this.parameterTable[key].name) {
					func(this.parameterTable[key]);
				}
			}
		},
		
		validate: function(name, value) {
			var parm = this.parameterTable[name];
			if (parm && parm.validator) {
				return parm.validator(value);
			}
			return true;
		},
		
		/**
		 * Make a copy of this description.  Used for collecting values when a client doesn't want
		 * the values to be persisted across different objects.
		 *
		 */
		 makeCopy: function() {
			var parameters = [];
			this.forEach(function(parm) {
				var newParm = new CommandParameter(parm.name, parm.type, parm.label, parm.value, parm.lines, parm.eventListeners, parm.validator);
				parameters.push(newParm);
			});
			var copy = new ParametersDescription(parameters, this._options, this.getParameters);
			// this value may have changed since the options
			copy.clientCollect = this.clientCollect;
			copy.message = this.message;
			return copy;
			
		 },
		 /**
		  * Return a boolean indicating whether additional optional parameters are available.
		  */
		 hasOptionalParameters: function() {
			return this._hasOptionalParameters;
		 }
	};
	ParametersDescription.prototype.constructor = ParametersDescription;

	//return the module exports
	return {
		CommandRegistry: CommandRegistry,
		URLBinding: URLBinding,
		ParametersDescription: ParametersDescription,
		CommandParameter: CommandParameter,
		CommandEventListener: CommandEventListener
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/edit/nls/messages',{
	root:true
});

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/edit/nls/root/messages',{//Default message bundle
	"Editor": "Editor", //$NON-NLS-1$ //$NON-NLS-0$
	"switchEditor": "Switch Editor", //$NON-NLS-1$ //$NON-NLS-0$
	"Fetching": "Fetching: ${0}", //$NON-NLS-1$ //$NON-NLS-0$
	"confirmUnsavedChanges": "There are unsaved changes. Do you still want to navigate away?", //$NON-NLS-1$ //$NON-NLS-0$
	"searchFilesCommand": "Quick Search...", //$NON-NLS-1$ //$NON-NLS-0$
	"searchFiles": "Quick Search in ${0}", //$NON-NLS-1$ //$NON-NLS-0$
	"searchTerm": "Enter search term:", //$NON-NLS-1$ //$NON-NLS-0$
	"unsavedChanges": "There are unsaved changes.", //$NON-NLS-1$ //$NON-NLS-0$
	"unsavedAutoSaveChanges": "Please stay on the page until Auto Save is complete.", //$NON-NLS-1$ //$NON-NLS-0$
	"Save": "Save", //$NON-NLS-1$ //$NON-NLS-0$
	"Saved": "Saved", //$NON-NLS-1$ //$NON-NLS-0$
	"Blame": "Blame", //$NON-NLS-1$ //$NON-NLS-0$
	"BlameTooltip":"Show blame annotations", //$NON-NLS-1$ //$NON-NLS-0$
	"Diff": "Diff", //$NON-NLS-1$//$NON-NLS-0$
	"DiffTooltip":"Show diff annotations", //$NON-NLS-1$//$NON-NLS-0$
	"saveOutOfSync": "Resource is out of sync with the server. Do you want to save it anyway?", //$NON-NLS-1$ //$NON-NLS-0$
	"loadOutOfSync": "Resource is out of sync with the server. Do you want to load it anyway? This will overwrite your local changes.", //$NON-NLS-1$ //$NON-NLS-0$
	"ReadingMetadata": "Reading metadata of ${0}", //$NON-NLS-1$ //$NON-NLS-0$
	"ReadingMetadataError": "Cannot get metadata of ${0}", //$NON-NLS-1$ //$NON-NLS-0$
	"Reading": "Reading ${0}", //$NON-NLS-1$ //$NON-NLS-0$
	"readonly": "Read Only.", //$NON-NLS-1$ //$NON-NLS-0$
	"saveFile": "Save this file", //$NON-NLS-1$ //$NON-NLS-0$
	"toggleZoomRuler": "Toggle Zoom Ruler", //$NON-NLS-1$ //$NON-NLS-0$
	"gotoLine": "Go to Line...", //$NON-NLS-1$ //$NON-NLS-0$
	"gotoLineTooltip": "Go to specified line number", //$NON-NLS-1$ //$NON-NLS-0$
	"gotoLinePrompt": "Go to line:", //$NON-NLS-1$ //$NON-NLS-0$
	"Undo": "Undo", //$NON-NLS-1$ //$NON-NLS-0$
	"Redo": "Redo", //$NON-NLS-1$ //$NON-NLS-0$
	"Cut": "Cut", //$NON-NLS-1$ //$NON-NLS-0$
	"Copy": "Copy", //$NON-NLS-1$ //$NON-NLS-0$
	"Paste": "Paste", //$NON-NLS-1$ //$NON-NLS-0$
	"Find": "Find...", //$NON-NLS-1$ //$NON-NLS-0$
	"noResponse": "No response from server. Check your internet connection and try again.", //$NON-NLS-1$ //$NON-NLS-0$
	"savingFile": "Saving file ${0}", //$NON-NLS-1$ //$NON-NLS-0$
	"running": "Running ${0}", //$NON-NLS-1$ //$NON-NLS-0$
	"Saving..." : "Saving...", //$NON-NLS-1$ //$NON-NLS-0$
	"View": "View", //$NON-NLS-1$ //$NON-NLS-0$
	"SplitSinglePage": "Single Page", //$NON-NLS-1$ //$NON-NLS-0$
	"SplitVertical": "Split Vertical", //$NON-NLS-1$ //$NON-NLS-0$
	"SplitHorizontal": "Split Horizontal", //$NON-NLS-1$ //$NON-NLS-0$
	"SplitPipInPip": "Picture in Picture", //$NON-NLS-1$ //$NON-NLS-0$
	"SplitModeTooltip": "Change split editor mode", //$NON-NLS-1$ //$NON-NLS-0$
	"SidePanel": "Side Panel", //$NON-NLS-1$ //$NON-NLS-0$
	"SidePanelTooltip": "Choose what to show in the side panel.", //$NON-NLS-1$ //$NON-NLS-0$
	"Slideout": "Slideout", //$NON-NLS-1$ //$NON-NLS-0$
	"Actions": "Actions", //$NON-NLS-1$ //$NON-NLS-0$
	"Navigator": "Navigator", //$NON-NLS-1$ //$NON-NLS-0$
	"FolderNavigator": "Folder Navigator", //$NON-NLS-1$ //$NON-NLS-0$
	"Project": "Project", //$NON-NLS-1$ //$NON-NLS-0$
	"New": "New", //$NON-NLS-1$ //$NON-NLS-0$
	"File": "File", //$NON-NLS-1$ //$NON-NLS-0$
	"Edit": "Edit", //$NON-NLS-1$ //$NON-NLS-0$
	"Tools": "Tools", //$NON-NLS-1$ //$NON-NLS-0$
	"Add": "Add", //$NON-NLS-1$ //$NON-NLS-0$
	"noActions": "There are no actions for the current selection.", //$NON-NLS-1$ //$NON-NLS-0$
	"NoFile": "Use the ${0} to create new files and folders. Click a file to start coding.", //$NON-NLS-1$ //$NON-NLS-0$
	"LocalEditorSettings": "Local Editor Settings", //$NON-NLS-1$ //$NON-NLS-0$
	"NoProject": "${0} is not a project. To convert it to a project use ${1}.", //$NON-NLS-1$ //$NON-NLS-0$
	"NoProjects": "There are no projects in your workspace. Use the ${0} menu to create projects.", //$NON-NLS-1$ //$NON-NLS-0$
	"Disconnected": "${0} (disconnected)", //$NON-NLS-1$ //$NON-NLS-0$
	"ChooseFS": "Choose Filesystem", //$NON-NLS-1$ //$NON-NLS-0$
	"ChooseFSTooltip": "Choose the filesystem you want to view.", //$NON-NLS-1$ //$NON-NLS-0$
	"FSTitle": "${0} (${1})", //$NON-NLS-1$ //$NON-NLS-0$
	"Deploy": "Deploy", //$NON-NLS-1$ //$NON-NLS-0$
	"Deploy As": "Deploy As", //$NON-NLS-1$ //$NON-NLS-0$
	"Import": "Import", //$NON-NLS-1$ //$NON-NLS-0$
	"Export": "Export", //$NON-NLS-1$ //$NON-NLS-0$
	"OpenWith": "Open With", //$NON-NLS-1$ //$NON-NLS-0$
	"OpenRelated": "Open Related", //$NON-NLS-1$ //$NON-NLS-0$
	"Dependency": "Dependency", //$NON-NLS-1$ //$NON-NLS-0$
	"UnnamedCommand": "Unnamed", //$NON-NLS-1$ //$NON-NLS-0$
	"searchInFolder": "Folder Search...",  //$NON-NLS-1$ //$NON-NLS-0$
	"Global Search": "Global Search...", //$NON-NLS-1$ //$NON-NLS-0$
	"ClickEditLabel": "Click to edit", //$NON-NLS-1$ //$NON-NLS-0$
	"ProjectInfo": "Project Information", //$NON-NLS-1$ //$NON-NLS-0$
	"Name": "Name", //$NON-NLS-1$ //$NON-NLS-0$
	"Description": "Description", //$NON-NLS-1$ //$NON-NLS-0$
	"Site": "Site", //$NON-NLS-1$ //$NON-NLS-0$
	'projectsSectionTitle': 'Projects',  //$NON-NLS-0$  //$NON-NLS-1$
	'listingProjects': 'Listing projects...',  //$NON-NLS-0$  //$NON-NLS-1$
	'gettingWorkspaceInfo': 'Getting workspace information...',  //$NON-NLS-0$  //$NON-NLS-1$
	"showProblems": "Show Problems...",  //$NON-NLS-1$ //$NON-NLS-0$
	"showTooltip": "Show Tooltip", //$NON-NLS-1$ //$NON-NLS-0$
	"showTooltipTooltip": "Shows the tooltip immediately based on the caret position", //$NON-NLS-1$ //$NON-NLS-0$
	"emptyDeploymentInfoMessage": "Use the Launch Configurations dropdown to deploy this project" //$NON-NLS-1$ //$NON-NLS-0$
});

/*******************************************************************************
 * @license
 * Copyright (c) 2014 IBM Corporation and others. All rights reserved. This
 * program and the accompanying materials are made available under the terms of
 * the Eclipse Public License v1.0 (http://www.eclipse.org/legal/epl-v10.html),
 * and the Eclipse Distribution License v1.0
 * (http://www.eclipse.org/org/documents/edl-v10.html).
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/explorers/navigatorRenderer',[], function() {
	return {
		getClickedItem: function() {
			return null;
		}
	};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/objects',[], function() {
	function mixin(target/*, source..*/) {
		var hasOwnProperty = Object.prototype.hasOwnProperty;
		for (var j = 1, len = arguments.length; j < len; j++) {
			var source = arguments[j];
			for (var key in source) {
				if (hasOwnProperty.call(source, key)) {
					target[key] = source[key];
				}
			}
		}
		return target;
	}

	/**
	 * @name orion.objects
	 * @class Object-oriented helpers.
	 */
	return {
		/**
		 * Creates a shallow clone of the given <code>object</code>.
		 * @name orion.objects.clone
		 * @function
		 * @static
		 * @param {Object|Array} object The object to clone. Must be a "normal" Object or Array. Other built-ins,
		 * host objects, primitives, etc, will not work.
		 * @returns {Object|Array} A clone of <code>object</code>.
		 */
		clone: function(object) {
			if (Array.isArray(object)) {
				return Array.prototype.slice.call(object);
			}
			var clone = Object.create(Object.getPrototypeOf(object));
			mixin(clone, object);
			return clone;
		},
		/**
		 * Mixes all <code>source</code>'s own enumerable properties into <code>target</code>. Multiple source objects
		 * can be passed as varargs.
		 * @name orion.objects.mixin
		 * @function
		 * @static
		 * @param {Object} target
		 * @param {Object} source
		 */
		mixin: mixin,
		/**
		 * Wraps an object into an Array if necessary.
		 * @name orion.objects.toArray
		 * @function
		 * @static
		 * @param {Object} obj An object.
		 * @returns {Array} Returns <code>obj</code> unchanged, if <code>obj</code> is an Array. Otherwise returns a 1-element Array
		 * whose sole element is <code>obj</code>.
		 */
		toArray: function(o) {
			return Array.isArray(o) ? o : [o];
		}
	};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/*eslint-env browser, amd*/
define('orion/inputManager',[
	'i18n!orion/edit/nls/messages',
	'orion/explorers/navigatorRenderer',
	'orion/i18nUtil',
	'orion/Deferred',
	'orion/EventTarget',
	'orion/objects',
	'orion/PageUtil',
	'orion/metrics'
], function(messages, mNavigatorRenderer, i18nUtil, Deferred, EventTarget, objects, PageUtil, mMetrics) {

	function Idle(options){
		this._document = options.document || document;
		this._timeout = options.timeout;
		//TODO: remove listeners if there are no clients
		//TODO: add support for multiple clients with different timeouts
		var events = ["mousedown", "keypress","keydown","keyup"]; //$NON-NLS-0$ //$NON-NLS-1$ //$NON-NLS-2$
		var reset = function () { this._resetTimer(); }.bind(this);
		for (var i = 0; i < events.length; i++) {
			var evt = events[i];
			this._document.addEventListener(evt, reset, true);
		}
		EventTarget.attach(this);
	}

	Idle.prototype = {
		_resetTimer: function() {
			var win = this._document.defaultView || this._document.parentWindow;
			if (this._timer) {
				win.clearTimeout(this._timer);
				this._timer = null;
			}
			if (this._timeout !== -1) {
				this._timer = win.setTimeout(function() {
					this.onIdle({type:"Idle"});	//$NON-NLS-0$
					this._timer = null;
					this._resetTimer();
				}.bind(this), this._timeout);
			}
		},
		onIdle: function (idleEvent) {
			return this.dispatchEvent(idleEvent);
		},
		setTimeout: function(timeout) {
			this._timeout = timeout;
			this._resetTimer();
		}
	};

	function _makeError(error) {
		var newError = {
			Severity: "Error", //$NON-NLS-0$
			Message: messages.noResponse
		};
		if(error.name === "Cancel") {
			return {Severity: "Warning", Message: error.name, Cancel: true};
		} else if (error.status === 0) {
			newError.Cancel = true;
			return newError; // might do better here
		} else if (error.responseText) {
			var responseText = error.responseText;
			try {
				var parsedError = JSON.parse(responseText);
				newError.Severity = parsedError.Severity || newError.Severity;
				newError.Message = parsedError.Message || newError.Message;
			} catch (e) {
				newError.Message = responseText;
			}
		} else {
			try {
				newError.Message = JSON.stringify(error);
			} catch (e) {
				// best effort - fallthrough
			}
		}
		return newError;
	}

	function handleError(statusService, error) {
		if (!statusService) {
			window.console.log(error);
			return;
		}
		if (!error.Severity) {
			error = _makeError(error);
		}
		statusService.setProgressResult(error);
		return error;
	}
	
	function decode(contents, charset, done, errorHandler) {
		if (window.TextDecoder) {
			done(new TextDecoder(charset).decode(contents));
		} else {
			var mimeType = 'text/plain; charset=' + charset; //$NON-NLS-0$
			var blob = new Blob([contents], { type: mimeType });
			var reader = new FileReader();
			reader.onload = function() {
				done(reader.result);
			}.bind(this);
			reader.onerror = errorHandler;
			reader.readAsText(blob, charset);
		}
	}
	
	var UTF8 = "UTF-8"; //$NON-NLS-0$
	function isUTF8(charset) {
		return !charset || charset === UTF8; //$NON-NLS-0$
	}

	/**
	 * @name orion.editor.InputManager
	 * @class
	 */
	function InputManager(options) {
		EventTarget.attach(this);
		this.serviceRegistry = options.serviceRegistry;
		this.statusService = options.statusService;
		this.fileClient = options.fileClient;
		this.progressService = options.progressService;
		this.contentTypeRegistry = options.contentTypeRegistry;
		this.selection = options.selection;
		this._input = this._title = "";
	}
	objects.mixin(InputManager.prototype, /** @lends orion.editor.InputManager.prototype */ {
		/**
		 * @returns {orion.Promise} Promise resolving to the new Location we should use
		 */
		_maybeLoadWorkspace: function(resource) {
			var fileClient = this.fileClient;
			// If it appears to be a workspaceRootURL we cannot load it directly, have to get the workspace first
			var root = resource;
			if (root.indexOf("?")) root = root.split("?")[0];
			if (root === fileClient.fileServiceRootURL(root)) {
				return fileClient.loadWorkspace(root).then(function(workspace) {
					return workspace.Location;
				});
			}
			return new Deferred().resolve(resource);
		},
		/**
		 * Wrapper for fileClient.read() that tolerates a filesystem root URL passed as location. If location is indeed
		 * a filesystem root URL, the original read() operation is instead performed on the workspace.
		 */
		_read: function(loc /**, readArgs*/) {
			var cachedMetadata = this.cachedMetadata || mNavigatorRenderer.getClickedItem();
			if (cachedMetadata && cachedMetadata.Location === loc &&
				cachedMetadata.Parents && cachedMetadata.Attributes &&
				cachedMetadata.ETag
			) {
				return new Deferred().resolve(cachedMetadata);
			}
			var fileClient = this.fileClient;
			var readArgs = Array.prototype.slice.call(arguments, 1);
			return this._maybeLoadWorkspace(loc).then(function(newLocation) {
				return fileClient.read.apply(fileClient, [newLocation].concat(readArgs));
			});
		},
		_isSameParent: function(loc) {
			if (this._lastMetadata && this._lastMetadata.Parents && this._lastMetadata.Parents.length > 0) {
				var parentLocation = loc.substring(0, loc.lastIndexOf("/", loc.length - (loc[loc.length - 1] === "/" ? 2 : 1)) + 1); 
				return this._lastMetadata.Parents[0].Location === parentLocation;
			}
			return false;
		},
		load: function() {
			var fileURI = this.getInput();
			if (!fileURI) { return; }
			var fileClient = this.fileClient;
			var resource = this._parsedLocation.resource;
			var progressService = this.progressService;
			var progress = function(deferred, msgKey, uri) {
				if (!progressService) { return deferred; }
				return progressService.progress(deferred, i18nUtil.formatMessage(msgKey, uri));
			};
			var editor = this.getEditor();
			if (this._fileMetadata) {
				//Reload if out of sync, unless we are already in the process of saving
				if (!this._fileMetadata._saving && !this._fileMetadata.Directory && !this.getReadOnly()) {
					progress(fileClient.read(resource, true), messages.ReadingMetadata, fileURI).then(function(data) {
						if (this._fileMetadata && !this._fileMetadata._saving && this._fileMetadata.Location === data.Location && this._fileMetadata.ETag !== data.ETag) {
							this._fileMetadata = objects.mixin(this._fileMetadata, data);
							if (!editor.isDirty() || window.confirm(messages.loadOutOfSync)) {
								progress(fileClient.read(resource), messages.Reading, fileURI).then(function(contents) {
									editor.setInput(fileURI, null, contents);
									this._clearUnsavedChanges();
								}.bind(this));
							}
						}
					}.bind(this));
				}
			} else {
				var progressTimeout = window.setTimeout(function() {
					progressTimeout = null;
					this.reportStatus(i18nUtil.formatMessage(messages.Fetching, fileURI));
				}.bind(this), 800);
				var clearProgressTimeout = function() {
					this.reportStatus("");
					if (progressTimeout) {
						window.clearTimeout(progressTimeout);
					}
				}.bind(this);
				var errorHandler = function(error) {
					clearProgressTimeout();
					var statusService = null;
					if(this.serviceRegistry) {
						statusService = this.serviceRegistry.getService("orion.page.message"); //$NON-NLS-0$
					} else if(this.statusService) {
						statusService = this.statusService;
					}
					handleError(statusService, error);
					this._setNoInput();
				}.bind(this);
				this._acceptPatch = null;
				// Read metadata
				var metadataURI = resource;
				if (!this._isSameParent(metadataURI)) {
					var uri = new URL(metadataURI);
					uri.query.set("tree", localStorage.useCompressedTree ? "compressed" : "decorated"); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$
					metadataURI = uri.href;
				}
				progress(this._read(metadataURI, true), messages.ReadingMetadata, resource).then(function(metadata) {
					if(!metadata) {
						errorHandler({responseText: i18nUtil.formatMessage(messages.ReadingMetadataError, resource)});
					} else if (metadata.Directory) {
						// Fetch children
						Deferred.when(metadata.Children || progress(fileClient.fetchChildren(metadata.ChildrenLocation), messages.Reading, fileURI), function(contents) {
							clearProgressTimeout();
							metadata.Children = contents;
							this._setInputContents(this._parsedLocation, fileURI, contents, metadata);
						}.bind(this), errorHandler);
					} else {
						var charset = this._charset;
						// Read contents if this is a text file and encoding is UTF-8
						var isText = this._isText(metadata);
						if (isUTF8(charset) && isText) {
							// Read text contents
							progress(fileClient.read(resource, false, true), messages.Reading, fileURI).then(function(contents) {
								clearProgressTimeout();
								if (typeof contents !== "string") { //$NON-NLS-0$
									this._acceptPatch = contents.acceptPatch;
									contents = contents.result;
								}
								this._setInputContents(this._parsedLocation, fileURI, contents, metadata);
							}.bind(this), errorHandler);
						} else {
							progress(fileClient._getService(resource).readBlob(resource), messages.Reading, fileURI).then(function(contents) {
								clearProgressTimeout();
								if (isText) {
									decode(contents, charset, function(result) {
										this._setInputContents(this._parsedLocation, fileURI, result, metadata);
									}.bind(this), errorHandler);
									return;
								}
								this._setInputContents(this._parsedLocation, fileURI, contents, metadata);
							}.bind(this), errorHandler);
						}
					}
				}.bind(this), errorHandler);
			}
		},
		processParameters: function(input) {
			var editor = this.getEditor();
			if (editor && editor.processParameters) {
				return editor.processParameters(input);
			}
			return false;
		},
		getAutoLoadEnabled: function() {
			return this._autoLoadEnabled;
		},
		getAutoSaveEnabled: function() {
			return this._autoSaveEnabled;
		},
		getEditor: function() {
			return this.editor;
		},
		getEncodingCharset: function() {
			return this._charset || UTF8;
		},
		getInput: function() {
			return this._input;
		},
		getLocation: function() {
			return this._location;
		},
		getTitle: function() {
			return this._title;
		},
		getFileMetadata: function() {
			return this._fileMetadata;
		},
		isSaveEnabled: function() {
			return !this.getReadOnly();
		},
		getReadOnly: function() {
			var data = this._fileMetadata;
			return this._readonly || !data || (data.Attributes && data.Attributes.ReadOnly);
		},
		getContentType: function() {
			return this._contentType;
		},
		onFocus: function() {
			// If there was an error while auto saving, auto save is temporarily disabled and
			// we retry saving every time the editor gets focus
			if (this._autoSaveEnabled && this._errorSaving) {
				this.save();
				return;
			}
			if (this._autoLoadEnabled && this._fileMetadata) {
				this.load();
			}
		},
		reportStatus: function(msg) {
			if (this.statusReporter) {
				this.statusReporter(msg);
			} else if (this.editor) {
				this.editor.reportStatus(msg);
			}
		},
		save: function(closing) {
			var metadata = this.getFileMetadata();
			if (!metadata) return new Deferred().reject();
			if (metadata._saving) { return metadata._savingDeferred; }
			var that = this;
			metadata._savingDeferred = new Deferred();
			metadata._saving = true;
			function done(result) {
				var deferred = metadata._savingDeferred;
				deferred.resolve(result);
				metadata._savingDeferred = null;
				metadata._saving = false;
				return deferred;
			}
			var editor = this.getEditor();
			if (!editor || !editor.isDirty() || this.getReadOnly()) { return done(); }
			var failedSaving = this._errorSaving;
			var input = this.getInput();
			this.reportStatus(messages['Saving...']);

			if (!this._saveEventLogged) {
				this._logMetrics("save"); //$NON-NLS-0$
				this._saveEventLogged = true;
			}

			this.dispatchEvent({ type: "Saving", inputManager: this}); //$NON-NLS-0$

			editor.markClean();
			var contents = editor.getText();
			var data = contents;
			if (this._getSaveDiffsEnabled() && !this._errorSaving) {
				var changes = this._getUnsavedChanges();
				if (changes) {
					var len = 0;
					for (var i = 0; i < changes.length; i++) {
						len += changes[i].text.length;
					}
					if (contents.length > len) {
						data = {
							diff: changes
						};
					}
				}
			}
			this._clearUnsavedChanges();
			this._errorSaving = false;

			var etag = metadata.ETag;
			var args = { "ETag" : etag }; //$NON-NLS-0$
			var resource = this._parsedLocation.resource;
			var def = this.fileClient.write(resource, data, args);
			var progress = this.progressService;
			var statusService = null;
			if(this.serviceRegistry){
				statusService = this.serviceRegistry.getService("orion.page.message"); //$NON-NLS-0$
			}
			if (progress) {
				def = progress.progress(def, i18nUtil.formatMessage(messages.savingFile, input));
			}
			function successHandler(result) {
				if (input === that.getInput()) {
					metadata.ETag = result.ETag;
					editor.setInput(input, null, contents, true);
				}
				that.reportStatus("");
				if (failedSaving && statusService) {
					statusService.setProgressResult({Message:messages.Saved, Severity:"Normal"}); //$NON-NLS-0$
				}
				if (that.postSave) {
					that.postSave(closing);
				}
				return done(result);
			}
			function errorHandler(error) {
				that.reportStatus("");
				var errorMsg = handleError(statusService, error);
				mMetrics.logEvent("status", "exception", (that._autoSaveActive ? "Auto-save: " : "Save: ") + errorMsg.Message); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				that._errorSaving = true;
				return done();
			}
			def.then(successHandler, function(error) {
				// expected error - HTTP 412 Precondition Failed
				// occurs when file is out of sync with the server
				if (error.status === 412) {
					var forceSave = window.confirm(messages.saveOutOfSync);
					if (forceSave) {
						// repeat save operation, but without ETag
						var redef = that.fileClient.write(resource, contents);
						if (progress) {
							redef = progress.progress(redef, i18nUtil.formatMessage(messages.savingFile, input));
						}
						redef.then(successHandler, errorHandler);
					} else {
						return done();
					}
				} else {
					// unknown error
					errorHandler(error);
				}
			});
			return metadata._savingDeferred;
		},
		setAutoLoadEnabled: function(enabled) {
			this._autoLoadEnabled = enabled;
		},
		/**
		 * Set the autosave timeout. If the timeout is <code>-1</code>, autosave is
		 * disabled.
		 * @param {Number} timeout - the autosave timeout in milliseconds
		 */
		setAutoSaveTimeout: function(timeout) {
			this._autoSaveEnabled = timeout !== -1;
			this._autoSaveActive = false;
			if (!this._idle) {
				var options = {
					document: document,
					timeout: timeout
				};
				this._idle = new Idle(options);
				this._idle.addEventListener("Idle", function () { //$NON-NLS-0$
					if (!this._errorSaving) {
						this._autoSaveActive = true;
						this.save().then(function() {
							this._autoSaveActive = false;
						});
					}
				}.bind(this));
			} else {
				this._idle.setTimeout(timeout);
			}
		},
		setContentType: function(contentType) {
			this._contentType = contentType;
		},
		setEncodingCharset: function(charset) {
			this._charset = charset;
		},
		setInput: function(loc) {
			if (this._ignoreInput) { return; }
			if (!loc) {
				loc = PageUtil.hash();
			}
			if (typeof loc !== "string") { //$NON-NLS-0$
				return;
			}
			var editor = this.getEditor();
			if (loc && loc[0] !== "#") { //$NON-NLS-0$
				loc = "#" + loc; //$NON-NLS-0$
			}
			var input = PageUtil.matchResourceParameters(loc), oldInput = this._parsedLocation || {};
			if (editor && editor.isDirty()) {
				var oldLocation = this._location;
				var oldResource = oldInput.resource;
				var newResource = input.resource;
				if (oldResource !== newResource) {
					if (this._autoSaveEnabled) {
						this.save();
					} else if (!window.confirm(messages.confirmUnsavedChanges)) {
						window.location.hash = oldLocation;
						return;
					}
				}
			}
			var editorChanged = editor && oldInput.editor !== input.editor;
			this._location = loc;
			this._parsedLocation = input;
			this._ignoreInput = true;
			if(this.selection) {
				this.selection.setSelections(loc);
			}
			this._ignoreInput = false;
			var evt = {
				type: "InputChanging", //$NON-NLS-0$
				input: input
			};
			this.dispatchEvent(evt);
			function saveSession() {
				if (evt.session) {
					evt.session.save();
				}
			}
			var fileURI = input.resource;
			if (evt.metadata) {
				saveSession();
				this.reportStatus("");
				this._input = fileURI;
				var metadata = evt.metadata;
				this._setInputContents(input, fileURI, null, metadata);
				return;
			}
			if (fileURI) {
				if (fileURI === this._input) {
					if (editorChanged) {
						this.reportStatus("");
						this._setInputContents(input, fileURI, null, this._fileMetadata, this._isText(this._fileMetadata));
					} else {
						if (!this.processParameters(input)) {
							if (evt.session) {
								evt.session.apply(true);
							}
						}
					}
				} else {
					saveSession();
					this._input = fileURI;
					this._readonly = false;
					this._lastMetadata = this._fileMetadata;
					this._fileMetadata = null;
					this.load();
				}
			} else {
				saveSession();
				this._setNoInput(true);
			}
		},
		setTitle: function(title) {
			var indexOfSlash = title.lastIndexOf("/"); //$NON-NLS-0$
			var shortTitle = title;
			if (indexOfSlash !== -1) {
				shortTitle = shortTitle.substring(indexOfSlash + 1);
			}
			this._title = shortTitle;
		},
		setSaveDiffsEnabled: function(enabled) {
			this._saveDiffsEnabled = enabled;
			var editor = this.editor;
			if (editor && !editor.isDirty()) {
				this._clearUnsavedChanges();
			}
		},
		_getSaveDiffsEnabled: function() {
			return this._saveDiffsEnabled && this._acceptPatch && this._acceptPatch.indexOf("application/json-patch") !== -1; //$NON-NLS-0$
		},
		_logMetrics: function(type) {
			var label = "(none)"; //$NON-NLS-0$
			var contentType = this.getContentType();
			var metadata = this.getFileMetadata();
			if (contentType) {
				label = contentType.id;
			} else if (metadata) {
				var _name = metadata.Name;
				var index = _name.lastIndexOf("."); //$NON-NLS-0$
				if (index >= 0) {
					label = "unregistered: " + _name.substring(index); //$NON-NLS-0$
				} else {
					switch (_name) {
						case "AUTHORS": //$NON-NLS-0$
						case "config": //$NON-NLS-0$
						case "LICENSE": //$NON-NLS-0$
						case "make": //$NON-NLS-0$
						case "Makefile": { //$NON-NLS-0$ 
							label = "unregistered: " + _name; //$NON-NLS-0$
							break;
						}
					}
				}
			}
			mMetrics.logEvent("editor", type, label); //$NON-NLS-1$ //$NON-NLS-0$
		},
		_unknownContentTypeAsText: function() {// Return true if we think unknown content type is text type
			return true;
		},
		_isText: function(metadata) {
			var contentType = this.contentTypeRegistry.getFileContentType(metadata);
			// Allow unkownn content types to be loaded as text files
			if (!contentType) { return this._unknownContentTypeAsText(); }
			var textPlain = this.contentTypeRegistry.getContentType("text/plain"); //$NON-NLS-0$
			return this.contentTypeRegistry.isExtensionOf(contentType, textPlain);
		},
		_setNoInput: function(loadRoot) {
			if (loadRoot) {
				this.fileClient.loadWorkspace("").then(function(root) {
					this._input = root.ChildrenLocation;
					this._setInputContents(root.ChildrenLocation, null, root, root);
				}.bind(this));
				return;
			}
			// No input, no editor.
			this._input = this._title = this._fileMetadata = null;
			this.setContentType(null);
			this.dispatchEvent({ type: "InputChanged", input: null }); //$NON-NLS-0$
		},
		_setInputContents: function(input, title, contents, metadata, noSetInput) {
			var _name, isDir = false;
			if (metadata) {
				this._fileMetadata = metadata;
				this.setTitle(metadata.Location || String(metadata));
				this.setContentType(this.contentTypeRegistry.getFileContentType(metadata));
				_name = metadata.Name;
				isDir = metadata.Directory;
			} else {
				// No metadata
				this._fileMetadata = null;
				this.setTitle(title);
				this.setContentType(this.contentTypeRegistry.getFilenameContentType(this.getTitle()));
				_name = this.getTitle();
			}
			var editor = this.getEditor();
			if (this._focusListener) {
				if (editor && editor.getTextView && editor.getTextView()) {
					editor.getTextView().removeEventListener("Focus", this._focusListener); //$NON-NLS-0$
				}
				this._focusListener = null;
			}
			var evt = {
				type: "InputChanged", //$NON-NLS-0$
				input: input,
				name: _name,
				title: title,
				contentType: this.getContentType(),
				metadata: metadata,
				location: window.location,
				contents: contents
			};
			this._logMetrics("open"); //$NON-NLS-0$
			this.dispatchEvent(evt);
			this.editor = editor = evt.editor;
			if (!isDir) {
				if (!noSetInput) {
					editor.setInput(title, null, contents);
				}
				if (editor && editor.getTextView && editor.getTextView()) {
					var textView = editor.getTextView();
					textView.addEventListener("Focus", this._focusListener = this.onFocus.bind(this)); //$NON-NLS-0$
				}
				this._clearUnsavedChanges();
				if (!this.processParameters(input)) {
					if (evt.session) {
						evt.session.apply();
					}
				}
			}

			this._saveEventLogged = false;
			mMetrics.logPageLoadTiming("interactive", window.location.pathname); //$NON-NLS-0$
		},
		_getUnsavedChanges: function() {
			var editor = this.editor;
			if (editor && editor.getUndoStack && editor.getUndoStack()) {
				return editor.getUndoStack()._unsavedChanges;
			}
			return null;
		},
		_clearUnsavedChanges: function() {
			var editor = this.editor;
			if (editor && editor.getUndoStack && editor.getUndoStack()) {
				editor.getUndoStack()._unsavedChanges = this._getSaveDiffsEnabled() ? [] : null;
			}
		}
	});
	return {
		handleError: handleError,
		InputManager: InputManager
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/navigate/nls/messages',{
	root:true
});

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/navigate/nls/root/messages',{//Default message bundle
	"Navigator": "Navigator",
	"Strings Xtrnalizr": "Strings Xtrnalizr",
	"Externalize strings": "Externalize strings from JavaScript files in this folder.",
	"NotSupportFileSystem":"${0} is not supported in this file system",
	"SrcNotSupportBinRead":"Source file service does not support binary read",
	"TargetNotSupportBinWrite":"Target file service does not support binary write",
	"NoFileSrv": "No matching file service for location: ${0}",
	"Choose a Folder": "Choose a Folder",
	"Copy of ${0}": "Copy of ${0}",
	"EnterName": "Enter a new name for '${0}'",
	"ChooseFolder": "Choose folder...",
	"Rename": "Rename",
	"RenameFilesFolders": "Rename the selected files or folders",
	"CompareEach": "Compare with each other",
	"Compare 2 files": "Compare the selected 2 files with each other",
	"Compare with...": "Compare With...",
	"CompareFolders": "Compare the selected folder with a specified folder",
	"Delete": "Delete",
	"Unknown item": "Unknown item",
	"delete item msg": "Are you sure you want to delete these ${0} items?",
	"DeleteTrg": "Are you sure you want to delete '${0}'?",
	"Zip": "Zip",
	"ZipDL": "Create a zip file of the folder contents and download it",
	"New File": "File",
	"Create a new file": "Create a new file",
	"FailedToCreateProject":"Failed to create project: ${0}",
	"FailedToCreateFile": "Failed to create file: ${0}",
	"CopyFailed": "Copy operation failed",
	"MoveFailed": "Move operation failed",
	"Name:": "Name:",
	"New Folder": "Folder",
	"Folder name:": "Folder name:",
	"Create a new folder": "Create a new folder",
	"Creating folder": "Creating folder",
	"Folder": "Folder",
	"Create an empty folder": "Create an empty folder",
	"CreateEmptyMsg": "Create an empty folder on the Orion server. You can import, upload, or create content in the editor.",
	"Sample HTML5 Site": "Sample HTML5 Site",
	"Generate a sample": "Generate a sample",
	"Generate an HTML5 \"Hello World\" website, including JavaScript, HTML, and CSS files.": "Generate an HTML5 \"Hello World\" website, including JavaScript, HTML, and CSS files.",
	"Creating a folder for ${0}": "Creating a folder for ${0}",
	"SFTP Import": "SFTP Import",
	"Import content from SFTP": "Import content from SFTP",
	"Imported Content": "Imported Content",
	"Upload a Zip": "Upload a Zip",
	"Upload content from a local zip file": "Upload content from a local zip file",
	"Uploaded Content": "Uploaded Content",
	"Clone Git Repository": "Clone Git Repository",
	"Clone a git repository": "Clone a git repository",
	"Link to Server": "Link to Server",
	"LinkContent": "Link to existing content on the server",
	"CreateLinkedFolder": "Create a folder that links to an existing folder on the server.",
	"Server path:": "Server path:",
	"NameLocationNotClear": "The name and server location were not specified.",
	"Go Up": "Go Up",
	"GoUpToParent": "Move up to the parent folder",
	"Go Into": "Go Into",
	"GoSelectedFolder": "Move into the selected folder",
	"File or zip archive": "File or Zip Archive",
	"ImportLcFile": "Import a file or zip archive from your local file system",
	"SFTP from...": "SFTP",
	"CpyFrmSftp": "Copy files and folders from a specified SFTP connection",
	"Importing from ${0}": "Importing from ${0}",
	"SFTP to...": "SFTP",
	"CpyToSftp": "Copy files and folders to a specified SFTP location",
	"Exporting": "Exporting to ${0}",
	"Pasting ${0}": "Pasting ${0}",
	"Copy to": "Copy to",
	"Move to": "Move to",
	"Copying ${0}": "Copying ${0}",
	"Moving ${0}": "Moving ${0}",
	"Renaming ${0}": "Renaming ${0}",
	"Deleting ${0}": "Deleting ${0}",
	"Creating ${0}": "Creating ${0}",
	"Linking to ${0}": "Linking to ${0}",
	"MvToLocation": "Move files and folders to a new location",
	"Cut": "Cut",
	"Copy": "Copy",
	"Fetching children of ": "Fetching children of ",
	"Paste": "Paste",
	"Open With": "Open With",
	"Loading ": "Loading ",
	"New": "New",
	"File": "File",
	"Actions": "Actions",
	"Orion Content": "Orion Content",
	"Create new content": "Create new content",
	"Import from HTTP...": "HTTP",
	"File URL:": "File URL:",
	"ImportURL": "Import a file from a URL and optionally unzip it",
	"Unzip *.zip files:": "Unzip *.zip files:",
	"Extracted from:": "Extracted from:",
	"FolderDropNotSupported": "Did not drop ${0}. Folder drop is not supported in this browser.",
	"CreateFolderErr": "You cannot copy files directly into the workspace. Create a folder first.",
	"Unzip ${0}?": "Unzip ${0}?",
	"Upload progress: ": "Upload progress: ",
	"Uploading ": "Uploading ",
	"Cancel upload": "Cancel upload",
	"UploadingFileErr": "Uploading the following file failed: ",
	"Enter project name:": "Enter project name:",
	"Create new project" : "Create new project",
	"Creating project ${0}": "Creating project ${0}",
	"NoFile": "Use the ${0} menu to create new files and folders. Click a file to start coding.",
	"Download": "Download",
	"Download_tooltips": "Download the file contents as the displayed name",
	"Downloading...": "Reading file contents...",
	"Download not supported": "Contents download is not supported in this browser.",
	"gettingContentFrom": "Getting content from ",
	"confirmLaunchDelete": "Delete Launch Configuration \"${0}\" ?",
	"deletingLaunchConfiguration": "Deleting launch configuration...",
	"deployTo": "Deploy to ",
	"deploy": "Deploy ",
	"connect": "Connect",
	"fetchContent": "Fetch content",
	"fetchContentOf": "Fetch content of ",
	"disconnectFromProject": "Disconnect from project",
	"doNotTreatThisFolder": "Do not treat this folder as a part of the project",
	"checkStatus": "Check status",
	"checkApplicationStatus": "Check application status",
	"checkApplicationState": "Check application state",
	"stop": "Stop",
	"start": "Start",
	"stopApplication": "Stop the App",
	"startApplication": "Start the application",
	"manage": "Manage",
	"manageThisApplicationOnRemote": "Manage this application on remote server",
	"deleteLaunchConfiguration": "Delete this launch configuration",
	"editLaunchConfiguration": "Edit this launch configuration",
	"deployThisApplication": "Deploy the App from the Workspace",
	"associatedFolder": "Associated Folder",
	"associateAFolderFromThe": "Associate a folder from the workspace with this project.",
	"convertToProject": "Convert to project",
	"convertThisFolderIntoA": "Convert this folder into a project",
	"thisFolderIsAProject": "This folder is a project already.",
	"basic": "Basic",
	"createAnEmptyProject.": "Create an empty project.",
	"sFTP": "SFTP",
	"createAProjectFromAn": "Create a project from an SFTP site.",
	'readMeCommandName': 'Readme File',  //$NON-NLS-0$  //$NON-NLS-1$
	'readMeCommandTooltip': 'Create a README.md file in this project',  //$NON-NLS-0$  //$NON-NLS-1$
	'zipArchiveCommandName': 'Zip Archive',  //$NON-NLS-0$  //$NON-NLS-1$
	'zipArchiveCommandTooltip': 'Create a project from a local zip archive.',  //$NON-NLS-0$  //$NON-NLS-1$
	'Url:': 'Url:',  //$NON-NLS-0$  //$NON-NLS-1$
	'notZip' : 'The following files are not zip files: ${0}. Would you like to continue the import?', //$NON-NLS-0$  //$NON-NLS-1$
	'notZipMultiple' : 'There are multiple non-zip files being uploaded. Would you like to continue the import?', //$NON-NLS-0$  //$NON-NLS-1$
	"Cancel": "Cancel", //$NON-NLS-0$  //$NON-NLS-1$
	"Ok": "Ok", //$NON-NLS-0$  //$NON-NLS-1$
	"missingCredentials": "Enter the ${0} authentication credentials associated with ${1} to check its status.", //$NON-NLS-0$  //$NON-NLS-1$
	"deploying": "deploying", //$NON-NLS-0$  //$NON-NLS-1$
	"starting": "restarting", //$NON-NLS-0$  //$NON-NLS-1$
	"stopping": "stopping", //$NON-NLS-0$  //$NON-NLS-1$
	"checkingStateShortMessage": "checking status" //$NON-NLS-0$  //$NON-NLS-1$
});


/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/

/*eslint-env browser, amd*/
/** @namespace The global container for eclipse APIs. */

define('orion/fileClient',['i18n!orion/navigate/nls/messages', "orion/Deferred", "orion/i18nUtil"], function(messages, Deferred, i18nUtil){
	/**
	 * This helper method implements invocation of the service call,
	 * with retry on authentication error if needed.
	 * @private
	 */
	function _doServiceCall(fileService, funcName, funcArgs) {
		//if the function is not implemented in the file service, we throw an exception to the caller
		if(!fileService[funcName]){
			throw new Error(i18nUtil.formatMessage(messages["NotSupportFileSystem"], funcName));
		}
		return fileService[funcName].apply(fileService, funcArgs);
	}
	
	function _copy(sourceService, sourceLocation, targetService, targetLocation) {
		
		if (!sourceService.readBlob) {
			throw new Error(messages["SrcNotSupportBinRead"]);
		}

		if (!targetService.writeBlob) {
			throw new Error(messages["TargetNotSupportBinWrite"]);
		}
	
		if (sourceLocation[sourceLocation.length -1] !== "/") { //$NON-NLS-0$
			return _doServiceCall(sourceService, "readBlob", [sourceLocation]).then(function(contents) { //$NON-NLS-0$
				return _doServiceCall(targetService, "writeBlob", [targetLocation, contents]); //$NON-NLS-0$
			});
		}

		var temp = targetLocation.substring(0, targetLocation.length - 1);
		var name = decodeURIComponent(temp.substring(temp.lastIndexOf("/")+1)); //$NON-NLS-0$
		var parentLocation = temp.substring(0, temp.lastIndexOf("/")+1);  //$NON-NLS-0$

		return _doServiceCall(targetService, "createFolder", [parentLocation, name]).then(function() { //$NON-NLS-0$
			return;
		}, function() {
			return;
		}).then(function() {
			return _doServiceCall(sourceService, "fetchChildren", [sourceLocation]).then(function(children) { //$NON-NLS-0$
				var results = [];
				for(var i = 0; i < children.length; ++i) {
					var childSourceLocation = children[i].Location;
					var childTemp =  childSourceLocation;
					if (children[i].Directory) {
						childTemp = childSourceLocation.substring(0, childSourceLocation.length - 1);
					}
					var childName = decodeURIComponent(childTemp.substring(childTemp.lastIndexOf("/")+1)); //$NON-NLS-0$
					
					var childTargetLocation = targetLocation + encodeURIComponent(childName);
					if (children[i].Directory) {
						childTargetLocation += "/"; //$NON-NLS-0$
					}
					results[i] = _copy(sourceService, childSourceLocation, targetService, childTargetLocation);
				}
				return Deferred.all(results);
			});
		});
	}
	
	
	/**
	 * Creates a new file client.
	 * @class The file client provides a convenience API for interacting with file services
	 * provided by plugins. This class handles authorization, and authentication-related
	 * error handling.
	 * @name orion.fileClient.FileClient
	 */
	function FileClient(serviceRegistry, filter) {
		var allReferences = serviceRegistry.getServiceReferences("orion.core.file"); //$NON-NLS-0$
		var _references = allReferences;
		if (filter) {
			_references = [];
			for(var i = 0; i < allReferences.length; ++i) {
				if (filter(allReferences[i])) {
					_references.push(allReferences[i]);
				}
			}
		}
		_references.sort(function (ref1, ref2) {
			var ranking1 = ref1.getProperty("ranking") || 0;
			var ranking2 = ref2.getProperty("ranking")  || 0;
			return ranking1 - ranking2;
		});
		var _patterns = [];
		var _services = [];
		var _names = [];
		
		function _noMatch(location) {
			var d = new Deferred();
			d.reject(messages["No Matching FileService for location:"] + location);
			return d;
		}
		
		var _fileSystemsRoots = [];
		var _allFileSystemsService =  {
			fetchChildren: function() {
				var d = new Deferred();
				d.resolve(_fileSystemsRoots);
				return d;
			},
			createWorkspace: function() {
				var d = new Deferred();
				d.reject(messages["no file service"]);
				return d;
			},
			loadWorkspaces: function() {
				var d = new Deferred();
				d.reject(messages['no file service']);
				return d;
			},
			loadWorkspace: function(location) {
				var d = new Deferred();
				window.setTimeout(function() {
					d.resolve({
						Directory: true, 
						Length: 0, 
						LocalTimeStamp: 0,
						Name: messages["File Servers"],
						Location: "/",  //$NON-NLS-0$
						Children: _fileSystemsRoots,
						ChildrenLocation: "/" //$NON-NLS-0$
					});
				}, 100);
				return d;
			},
			search: _noMatch,
			createProject: _noMatch,
			createFolder: _noMatch,
			createFile: _noMatch,
			deleteFile: _noMatch,
			moveFile: _noMatch,
			copyFile: _noMatch,
			read: _noMatch,
			write: _noMatch
		};
				
		for(var j = 0; j < _references.length; ++j) {
			_fileSystemsRoots[j] = {
				Directory: true, 
				Length: 0, 
				LocalTimeStamp: 0,
				Location: _references[j].getProperty("top"), //$NON-NLS-0$
				ChildrenLocation: _references[j].getProperty("top"), //$NON-NLS-0$
				Name: _references[j].getProperty("Name") || _references[j].getProperty("NameKey")		 //$NON-NLS-0$
			};

			var filetop = _references[j].getProperty("top");
			var patternStringArray = _references[j].getProperty("pattern") || (filetop ? filetop.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1") : ""); //$NON-NLS-1$ //$NON-NLS-0$
			if (!Array.isArray(patternStringArray)) {
				patternStringArray = [patternStringArray];
			}
			var patterns = [];
			for (var k = 0; k < patternStringArray.length; k++) {
				var patternString = patternStringArray[k];
				if (patternString[0] !== "^") { //$NON-NLS-0$
					patternString = "^" + patternString; //$NON-NLS-0$
				}
				patterns.push(new RegExp(patternString));
			}
			_patterns[j] = patterns;			
			_services[j] = serviceRegistry.getService(_references[j]);
			_names[j] = _references[j].getProperty("Name") || _references[j].getProperty("NameKey"); //$NON-NLS-0$
		}
				
		this._getServiceIndex = function(location) {
			// client must specify via "/" when a multi file service tree is truly wanted
			if (location === "/") { //$NON-NLS-0$
				return -1;
			} else if (!location || (location.length && location.length === 0)) {
				// TODO we could make the default file service a preference but for now we use the first one
				return _services[0] ? 0 : -1;
			}
			for(var i = 0; i < _patterns.length; ++i) {
				for (var j = 0; j < _patterns[i].length; j++) {
					if (_patterns[i][j].test(location)) {
						return i;
					}
				}
			}
			throw new Error(i18nUtil.formatMessage(messages['NoFileSrv'], location));
		};
		
		this._getService = function(location) {
			var i = this._getServiceIndex(location);
			return i === -1 ? _allFileSystemsService : _services[i];
		};
		
		this._getServiceName = function(location) {
			var i = this._getServiceIndex(location);
			return i === -1 ? _allFileSystemsService.Name : _names[i];
		};
		
		this._getServiceRootURL = function(location) {
			var i = this._getServiceIndex(location);
			return i === -1 ? _allFileSystemsService.Location : _fileSystemsRoots[i].Location;
		};
	}
	
	FileClient.prototype = /**@lends orion.fileClient.FileClient.prototype */ {
		/**
		 * Returns the file service managing this location
		 * @param location The location of the item 
		 */
		getService: function(location) {
			return this._getService(location);
		},
		 
		/**
		 * Returns the name of the file service managing this location
		 * @param location The location of the item 
		 */
		fileServiceName: function(location) {
			return this._getServiceName(location);
		},
		 
		/**
		 * Returns the root url of the file service managing this location
		 * @param location The location of the item 
		 */
		fileServiceRootURL: function(location) {
			return this._getServiceRootURL(location);
		},
		 
		/**
		 * Obtains the children of a remote resource
		 * @param location The location of the item to obtain children for
		 * @return A deferred that will provide the array of child objects when complete
		 */
		fetchChildren: function(location) {
			return _doServiceCall(this._getService(location), "fetchChildren", arguments); //$NON-NLS-0$
		},

		/**
		 * Creates a new workspace with the given name. The resulting workspace is
		 * passed as a parameter to the provided onCreate function.
		 * @param {String} name The name of the new workspace
		 */
		createWorkspace: function(name) {
			return _doServiceCall(this._getService(), "createWorkspace", arguments); //$NON-NLS-0$
		},

		/**
		 * Loads all the user's workspaces. Returns a deferred that will provide the loaded
		 * workspaces when ready.
		 */
		loadWorkspaces: function() {
			return _doServiceCall(this._getService(), "loadWorkspaces", arguments); //$NON-NLS-0$
		},
		
		/**
		 * Loads the workspace with the given id and sets it to be the current
		 * workspace for the IDE. The workspace is created if none already exists.
		 * @param {String} location the location of the workspace to load
		 * @param {Function} onLoad the function to invoke when the workspace is loaded
		 */
		loadWorkspace: function(location) {
			return _doServiceCall(this._getService(location), "loadWorkspace", arguments); //$NON-NLS-0$
		},
		
		/**
		 * Adds a project to a workspace.
		 * @param {String} url The workspace location
		 * @param {String} projectName the human-readable name of the project
		 * @param {String} serverPath The optional path of the project on the server.
		 * @param {Boolean} create If true, the project is created on the server file system if it doesn't already exist
		 */
		createProject: function(url, projectName, serverPath, create) {
			return _doServiceCall(this._getService(url), "createProject", arguments); //$NON-NLS-0$
		},
		/**
		 * Creates a folder.
		 * @param {String} parentLocation The location of the parent folder
		 * @param {String} folderName The name of the folder to create
		 * @return {Object} JSON representation of the created folder
		 */
		createFolder: function(parentLocation, folderName) {
			return _doServiceCall(this._getService(parentLocation), "createFolder", arguments); //$NON-NLS-0$
		},
		/**
		 * Create a new file in a specified location. Returns a deferred that will provide
		 * The new file object when ready.
		 * @param {String} parentLocation The location of the parent folder
		 * @param {String} fileName The name of the file to create
		 * @return {Object} A deferred that will provide the new file object
		 */
		createFile: function(parentLocation, fileName) {
			return _doServiceCall(this._getService(parentLocation), "createFile", arguments); //$NON-NLS-0$
		},
		/**
		 * Deletes a file, directory, or project.
		 * @param {String} location The location of the file or directory to delete.
		 */
		deleteFile: function(location) {
			return _doServiceCall(this._getService(location), "deleteFile", arguments); //$NON-NLS-0$
		},
		
		/**		 
		 * Moves a file or directory.
		 * @param {String} sourceLocation The location of the file or directory to move.
		 * @param {String} targetLocation The location of the target folder.
		 * @param {String} [name] The name of the destination file or directory in the case of a rename
		 */
		moveFile: function(sourceLocation, targetLocation, name) {
			var sourceService = this._getService(sourceLocation);
			var targetService = this._getService(targetLocation);
			
			if (sourceService === targetService) {
				return _doServiceCall(sourceService, "moveFile", arguments);				 //$NON-NLS-0$
			}
			
			var isDirectory = sourceLocation[sourceLocation.length -1] === "/"; //$NON-NLS-0$
			var target = targetLocation;
			
			if (target[target.length -1] !== "/") { //$NON-NLS-0$
				target += "/"; //$NON-NLS-0$
			}
			
			if (name) {
				target += encodeURIComponent(name);
			} else {
				var temp = sourceLocation;
				if (isDirectory) {
					temp = temp.substring(0, temp.length - 1);
				}
				target += temp.substring(temp.lastIndexOf("/")+1); //$NON-NLS-0$
			}
			
			if (isDirectory && target[target.length -1] !== "/") { //$NON-NLS-0$
				target += "/"; //$NON-NLS-0$
			}
	
			return _copy(sourceService, sourceLocation, targetService, target).then(function() {
				return _doServiceCall(sourceService, "deleteFile", [sourceLocation]); //$NON-NLS-0$
			});
			
		},
				
		/**
		 * Copies a file or directory.
		 * @param {String} sourceLocation The location of the file or directory to copy.
		 * @param {String} targetLocation The location of the target folder.
		 * @param {String} [name] The name of the destination file or directory in the case of a rename
		 */
		copyFile: function(sourceLocation, targetLocation, name) {
			var sourceService = this._getService(sourceLocation);
			var targetService = this._getService(targetLocation);
			
			if (sourceService === targetService) {
				return _doServiceCall(sourceService, "copyFile", arguments);				 //$NON-NLS-0$
			}
			
			var isDirectory = sourceLocation[sourceLocation.length -1] === "/"; //$NON-NLS-0$
			var target = targetLocation;
			
			if (target[target.length -1] !== "/") { //$NON-NLS-0$
				target += "/"; //$NON-NLS-0$
			}
			
			if (name) {
				target += encodeURIComponent(name);
			} else {
				var temp = sourceLocation;
				if (isDirectory) {
					temp = temp.substring(0, temp.length - 1);
				}
				target += temp.substring(temp.lastIndexOf("/")+1); //$NON-NLS-0$
			}
			
			if (isDirectory && target[target.length -1] !== "/") { //$NON-NLS-0$
				target += "/"; //$NON-NLS-0$
			}

			return _copy(sourceService, sourceLocation, targetService, target);
		},

		/**
		 * Returns the contents or metadata of the file at the given location.
		 *
		 * @param {String} location The location of the file to get contents for
		 * @param {Boolean} [isMetadata] If defined and true, returns the file metadata, 
		 *   otherwise file contents are returned
		 * @return A deferred that will be provided with the contents or metadata when available
		 */
		read: function(location, isMetadata) {
			return _doServiceCall(this._getService(location), "read", arguments); //$NON-NLS-0$
		},

		/**
		 * Returns the blob contents of the file at the given location.
		 *
		 * @param {String} location The location of the file to get contents for
		 * @return A deferred that will be provided with the blob contents when available
		 */
		readBlob: function(location) {
			return _doServiceCall(this._getService(location), "readBlob", arguments); //$NON-NLS-0$
		},

		/**
		 * Writes the contents or metadata of the file at the given location.
		 *
		 * @param {String} location The location of the file to set contents for
		 * @param {String|Object} contents The content string, or metadata object to write
		 * @param {String|Object} args Additional arguments used during write operation (i.e. ETag) 
		 * @return A deferred for chaining events after the write completes with new metadata object
		 */		
		write: function(location, contents, args) {
			return _doServiceCall(this._getService(location), "write", arguments); //$NON-NLS-0$
		},

		/**
		 * Imports file and directory contents from another server
		 *
		 * @param {String} targetLocation The location of the folder to import into
		 * @param {Object} options An object specifying the import parameters
		 * @return A deferred for chaining events after the import completes
		 */		
		remoteImport: function(targetLocation, options) {
			return _doServiceCall(this._getService(targetLocation), "remoteImport", arguments); //$NON-NLS-0$
		},

		/**
		 * Exports file and directory contents to another server
		 *
		 * @param {String} sourceLocation The location of the folder to export from
		 * @param {Object} options An object specifying the export parameters
		 * @return A deferred for chaining events after the export completes
		 */		
		remoteExport: function(sourceLocation, options) {
			return _doServiceCall(this._getService(sourceLocation), "remoteExport", arguments); //$NON-NLS-0$
		},
		
		/**
		 * Performs a search with the given search parameters.
		 * @param {Object} searchParams The JSON object that describes all the search parameters.
		 * @param {String} searchParams.resource Required. The location where search is performed. Required. Normally a sub folder of the file system. Empty string means the root of the file system.
		 * @param {String} searchParams.keyword The search keyword. Required but can be empty string.  If fileType is a specific type and the keyword is empty, then list up all the files of that type. If searchParams.regEx is true then the keyword has to be a valid regular expression. 
		 * @param {String} searchParams.sort Required. Defines the order of the return results. Should be either "Path asc" or "Name asc". Extensions are possible but not currently supported.  
		 * @param {boolean} searchParams.nameSearch Optional. If true, the search performs only file name search. 
		 * @param {String} searchParams.fileType Optional. The file type. If specified, search will be performed under this file type. E.g. "*.*" means all file types. "html" means html files.
		 * @param {Boolean} searchParams.regEx Optional. The option of regular expression search.
		 * @param {integer} searchParams.start Optional. The zero based start number for the range of the returned hits. E.g if there are 1000 hits in total, then 5 means the 6th hit.
		 * @param {integer} searchParams.rows Optional. The number of hits of the range. E.g if there are 1000 hits in total and start=5 and rows=40, then the return range is 6th-45th.
		 */
		search: function(searchParams) {
			return _doServiceCall(this._getService(searchParams.resource), "search", arguments); //$NON-NLS-0$
		}
	};//end FileClient prototype
	FileClient.prototype.constructor = FileClient;

	//return the module exports
	return {FileClient: FileClient};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/serviceTracker',[], function() {

	var CLOSED = 0, OPENED = 1;
	/**
	 * @name orion.ServiceTracker
	 * @class Simplifies the use of services within a service registry.
	 * @description Creates a <code>ServiceTracker</code> against the given service registry.
	 * The returned <code>ServiceTracker</code> will track services whose <code>objectClass</code> property contains the
	 * given <code>objectClass</code> parameter.
	 *
	 * <p>After creating a <code>ServiceTracker</code>, it can then be {@link #open}ed to begin tracking services.</p>
	 * <p>The {@link #addingService} and {@link #removedService} methods can be overridden to customize the service objects
	 * being tracked.</p>
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry The service registry to track services of.
	 * @param {String} objectClass The objectClass of services to be tracked.
	 */
	function ServiceTracker(serviceRegistry, objectClass) {
		this.serviceRegistry = serviceRegistry;
		var refs = {};
		var services = {};
		var state = CLOSED;
		var addedListener, removedListener;

		function add(serviceRef) {
			var id = serviceRef.getProperty('service.id');
			var serviceObject = this.addingService(serviceRef);
			if (serviceObject) {
				refs[id] = serviceRef;
				services[id] = serviceObject;
			}
		}
		function remove(serviceRef) {
			var id = serviceRef.getProperty('service.id');
			var service = services[id];
			delete refs[id];
			delete services[id];
			this.removedService(serviceRef, service);
		}
		function isTrackable(serviceRef) {
			return serviceRef.getProperty('objectClass').indexOf(objectClass) !== -1; //$NON-NLS-0$
		}

		/**
		 * Stops tracking services.
		 * @name orion.ServiceTracker#close
		 * @function
		 */
		this.close = function() {
			if (state !== OPENED) {
				throw new Error('Already closed'); //$NON-NLS-0$
			}
			state = CLOSED;
			serviceRegistry.removeEventListener('registered', addedListener); //$NON-NLS-0$
			serviceRegistry.removeEventListener('unregistering', removedListener); //$NON-NLS-0$
			addedListener = null;
			removedListener = null;
			var self = this;
			this.getServiceReferences().forEach(function(serviceRef) {
				remove.call(self, serviceRef);
			});
			if (typeof this.onClose === 'function') {
				this.onClose();
			}
		};
		/**
		 * Returns service references to the services that are being tracked.
		 * @name orion.ServiceTracker#getServiceReferences
		 * @function
		 * @returns {orion.serviceregistry.ServiceReference[]} References to all services that are being tracked by this ServiceTracker.
		 */
		this.getServiceReferences = function() {
			var keys = Object.keys(refs);
			if (!keys.length) {
				return null;
			}
			return keys.map(function(serviceId) {
				return refs[serviceId];
			});
		};
		/**
		 * Begins tracking services.
		 * @name orion.ServiceTracker#open
		 * @function
		 * @param {Boolean} [all=true] When <tt>true</tt>, this ServiceTracker receives an initial block of serviceAdded
		 * calls for any services currently present in the registry, and then for any subsequent registrations.
		 * When <tt>false</tt>, this ServiceTracker is invoked only for subsequent serviceAdded events.
		 *
		 * <p>Clients can use <tt>all === false</tt> when they wish to respond only to future events, and
		 * <tt>all === true</tt> when they wish to quickly "catch up" with trackable services that were
		 * registered prior to {@link #open} being called.</p>
		 */
		this.open = function(all) {
			if (typeof all === 'undefined') { //$NON-NLS-0$
				all = true;
			}
			if (state !== CLOSED) {
				throw new Error('Already open'); //$NON-NLS-0$
			}
			state = OPENED;
			var self = this;
			addedListener = /** @ignore */ function(event) {
				if (isTrackable(event.serviceReference)) {
					add.call(self, event.serviceReference);
					if (typeof self.onServiceAdded === 'function') { //$NON-NLS-0$
						return self.onServiceAdded(event.serviceReference, self.serviceRegistry.getService(event.serviceReference));
					}
				}
			};
			removedListener = /** @ignore */ function(event) {
				if (isTrackable(event.serviceReference)) {
					remove.call(self, event.serviceReference);
				}
			};
			serviceRegistry.addEventListener('registered', addedListener); //$NON-NLS-0$
			serviceRegistry.addEventListener('unregistering', removedListener); //$NON-NLS-0$
			if (all) {
				serviceRegistry.getServiceReferences(objectClass).forEach(function(serviceRef) {
					add.call(self, serviceRef);
					if (typeof self.onServiceAdded === 'function') { //$NON-NLS-0$
						return self.onServiceAdded(serviceRef, serviceRegistry.getService(serviceRef));
					}
				});
			}
			if (typeof this.onOpen === 'function') {
				this.onOpen();
			}
		};
	}
	ServiceTracker.prototype = /** @lends orion.ServiceTracker.prototype */ {
		/**
		 * Called to customize a service object being added to this ServiceTracker. Subclasses may override this method.
		 * The default implementation returns the result of calling {@link orion.serviceregistry.ServiceRegistry#getService}
		 * passing the service reference.
		 * @param {orion.serviceregistry.ServiceReference} serviceRef The reference to the service being added.
		 * @returns {Object} The service object to be tracked for the given service reference. If <code>null</code> 
		 * is returned, the service reference will not be tracked.
		 */
		addingService: function(serviceRef) {
			return this.serviceRegistry.getService(serviceRef);
		},
		/**
		 * Called when this ServiceTracker has been opened. Subclasses can override this method.
		 * @function
		 */
		onOpen: null,
		/**
		 * Called when this ServiceTracker has been closed. Subclasses can override this method.
		 * @function
		 */
		onClose: null,
		/**
		 * Called when a service is being added to this ServiceTracker. Subclasses can override this method to take part
		 * in the service's <code>'serviceAdded'</code> phase.
		 * @function
		 * @param {orion.serviceregistry.ServiceReference} serviceRef The service reference for the service that is being added.
		 * @param {Object} service The service implementation object that is being added.
		 * @returns {orion.Promise|undefined} This method can optionally return a deferred. If it does, the returned deferred
		 * will be added to the service's <code>serviceAdded</code> listener queue; in other words, the returned deferred
		 * must resolve before any calls to the service's methods can proceed.
		 */
		onServiceAdded: null,
		/**
		 * Called when a service has been removed from this ServiceTracker. Subclasses may override this method.
		 * The default implementation does nothing.
		 * @function
		 * @param {orion.serviceregistry.ServiceReference} serviceRef The reference to the removed service.
		 * @param {Object} service The service implementation object for the removed service.
		 */
		removedService: function(serviceRef, service) {
		}
	};

	return ServiceTracker;
});
/*******************************************************************************
 * @license
 * Copyright (c) 2011, 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define('orion/contentTypes',["orion/serviceTracker"], function(ServiceTracker) {
	var SERVICE_ID = "orion.core.contentTypeRegistry"; //$NON-NLS-0$
	var EXTENSION_ID = "orion.core.contenttype"; //$NON-NLS-0$
	var OLD_EXTENSION_ID = "orion.file.contenttype"; // backwards compatibility //$NON-NLS-0$

	/**
	 * @name orion.core.ContentType
	 * @class Represents a content type known to Orion.
	 * @property {String} id Unique identifier of this ContentType.
	 * @property {String} name User-readable name of this ContentType.
	 * @property {String} extends Optional; Gives the ID of another ContentType that is this one's parent.
	 * @property {String[]} extension Optional; List of file extensions characterizing this ContentType. Extensions are not case-sensitive.
	 * @property {String[]} filename Optional; List of filenames characterizing this ContentType.
	 */

	function contains(array, item) {
		return array.indexOf(item) !== -1;
	}

	function isImage(contentType) {
		switch (contentType && contentType.id) {
			case "image/jpeg": //$NON-NLS-0$
			case "image/png": //$NON-NLS-0$
			case "image/gif": //$NON-NLS-0$
			case "image/ico": //$NON-NLS-0$
			case "image/tiff": //$NON-NLS-0$
			case "image/svg": //$NON-NLS-0$
				return true;
		}
		return false;
	}
	
	function isBinary(cType) {
		if(!cType) {
			return false;
		}
		return (cType.id === "application/octet-stream" || cType['extends'] === "application/octet-stream"); //$NON-NLS-0$ //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	/**
	 * @name getFilenameContentType
	 * @description Return the best contentType match to the given filename or null if no match. Filename pattern checked first, then extension
	 * @param filename the filename to compare against contentTypes
	 * @param contentTypes the array of possible contentTypes to check
	 * @returns returns ContentType that is the best match or null
	 */
	function getFilenameContentType(/**String*/ filename, contentTypes) {
		if (typeof filename !== "string") { //$NON-NLS-0$
			return null;
		}
		
		var best = null;
		var current;
		
		var extStart = filename.indexOf('.'); //$NON-NLS-0$
		extStart++; // leading period not included in extension
		var extension = filename.substring(extStart).toLowerCase();
		
		// Check the most common cases, exact filename match or full extension match
		for (var i=0; i < contentTypes.length; i++) {
			current = contentTypes[i];
			if (current.filename.indexOf(filename) >= 0){
				best = current;
				break;
			}
			
			if (contains(current.extension, extension)){
				// A filename match is considered better than a perfect extension match
				best = current;
				continue;
			}
		}
		
		// Check the less common case where the filename contains periods (foo.bar.a.b check 'bar.a.b' then 'a.b' then 'b')
		if (!best){
			extStart = extension.indexOf('.'); //$NON-NLS-0$
			while (!best && extStart >= 0){
				extStart++; // leading period not included in extension
				extension = extension.substring(extStart);
				for (i=0; i < contentTypes.length; i++) {
					current = contentTypes[i];
					if (contains(current.extension, extension)){
						best = current;
						break;
					}
				}
				extStart = extension.indexOf('.'); //$NON-NLS-0$
			}
		}
		
		return best;		
	}

	function array(obj) {
		if (obj === null || typeof obj === "undefined") { return []; } //$NON-NLS-0$
			return (Array.isArray(obj)) ? obj : [obj];
		}

	function arrayLowerCase(obj) {
		return array(obj).map(function(str) { return String.prototype.toLowerCase.call(str); });
	}

	function process(contentTypeData) {
		return {
			id: contentTypeData.id,
			name: contentTypeData.name,
			image: contentTypeData.image,
			imageClass: contentTypeData.imageClass,
			"extends": contentTypeData["extends"], //$NON-NLS-1$ //$NON-NLS-0$
			extension: arrayLowerCase(contentTypeData.extension),
			filename: array(contentTypeData.filename)
		};
	}
	
	function add2Map(map, contentTypeDatas) {
		contentTypeDatas.map(process).forEach(function(contentType) {
			if (!Object.prototype.hasOwnProperty.call(map, contentType.id)) {
				map[contentType.id] = contentType;
			}
		});
	}
	
	function deleteFromMap(map, contentTypeDatas) {
		contentTypeDatas.map(process).forEach(function(contentType) {
			if (Object.prototype.hasOwnProperty.call(map, contentType.id)) {
				delete map[contentType.id];
			}
		});
	}
	
	function add2TypeData(serviceRef, contentTypeDatas) {
		var types = array(serviceRef.getProperty("contentTypes")); //$NON-NLS-0$
		for (var j=0; j < types.length; j++) {
			contentTypeDatas.push(types[j]);
		}
	}

	function buildMap(contentTypeDatas) {
		var map = Object.create(null);
		add2Map(map, contentTypeDatas);
		return map;
	}

	function buildMapFromServiceRegistry(serviceRegistry) {
		var serviceReferences = serviceRegistry.getServiceReferences(EXTENSION_ID).concat(
				serviceRegistry.getServiceReferences(OLD_EXTENSION_ID));
		var contentTypeDatas = [];
		for (var i=0; i < serviceReferences.length; i++) {
			var serviceRef = serviceReferences[i];
			add2TypeData(serviceRef, contentTypeDatas);
		}
		return buildMap(contentTypeDatas);
	}

	/**
	 * @name orion.core.ContentTypeRegistry
	 * @class A service for querying {@link orion.core.ContentType}s.
	 * @description A registry that provides information about {@link orion.core.ContentType}s.
	 *
	 * <p>If a {@link orion.serviceregistry.ServiceRegistry} is available, clients should request the service with
	 * objectClass <code>"orion.core.contentTypeRegistry"</code> from the registry rather than instantiate this 
	 * class directly. This constructor is intended for use only by page initialization code.</p>
	 *
	 * @param {orion.serviceregistry.ServiceRegistry|orion.core.ContentType[]} dataSource The service registry
	 * to use for looking up available content types and for registering this ContentTypeRegistry.
	 * 
	 * <p>Alternatively, an array of ContentType data may be passed instead, which allows clients to use this
	 * ContentTypeRegistry without a service registry.</p>
	 */
	function ContentTypeRegistry(dataSource) {
		if (dataSource && dataSource.registerService) {
			this.serviceRegistry = dataSource;
			var tracker = new ServiceTracker(this.serviceRegistry, "orion.core.contenttype"); //$NON-NLS-0$
			var _self = this;
			tracker.onServiceAdded = function(serviceRef) {
				var contentTypeDatas = [];
				add2TypeData(serviceRef, contentTypeDatas);
				add2Map(_self.map, contentTypeDatas);
			};
			tracker.removedService = function(serviceRef) {
				var contentTypeDatas = [];
				add2TypeData(serviceRef, contentTypeDatas);
				deleteFromMap(_self.map, contentTypeDatas);
			};
			tracker.open(false);
			this.map = buildMapFromServiceRegistry(dataSource);
			dataSource.registerService(SERVICE_ID, this);
		} else if (Array.isArray(dataSource)) {
			this.serviceRegistry = null;
			this.map = buildMap(dataSource);
		} else {
			throw new Error("Invalid parameter"); //$NON-NLS-0$
		}
	}
	ContentTypeRegistry.prototype = /** @lends orion.core.ContentTypeRegistry.prototype */ {
		/**
		 * Gets all the ContentTypes in the registry.
		 * @returns {orion.core.ContentType[]} An array of all registered ContentTypes.
		 */
		getContentTypes: function() {
			var map = this.getContentTypesMap();
			var types = [];
			for (var type in map) {
				if (Object.prototype.hasOwnProperty.call(map, type)) {
					types.push(map[type]);
				}
			}
			return types;
		},
		/**
		 * Gets a map of all ContentTypes.
		 * @return {Object} A map whose keys are ContentType IDs and values are the {@link orion.core.ContentType} having that ID.
		 */
		getContentTypesMap: function() {
			return this.map;
		},
		/**
		 * Looks up the ContentType for a file or search result, given the metadata.
		 * @param {Object} fileMetadata Metadata for a file or search result.
		 * @returns {orion.core.ContentType} The ContentType for the file, or <code>null</code> if none could be found.
		 */
		getFileContentType: function(fileMetadata) {
			return getFilenameContentType(fileMetadata.Name, this.getContentTypes());
		},
		/**
		 * Looks up the ContentType, given a filename.
		 * @param {String} filename The filename.
		 * @returns {orion.core.ContentType} The ContentType for the file, or <code>null</code> if none could be found.
		 */
		getFilenameContentType: function(filename) {
			return getFilenameContentType(filename, this.getContentTypes());
		},
		/**
		 * Gets a ContentType by ID.
		 * @param {String} id The ContentType ID.
		 * @returns {orion.core.ContentType} The ContentType having the given ID, or <code>null</code>.
		 */
		getContentType: function(id) {
			return this.map[id] || null;
		},
		/**
		 * Determines whether a ContentType is an extension of another.
		 * @param {orion.core.ContentType|String} contentTypeA ContentType or ContentType ID.
		 * @param {orion.core.ContentType|String} contentTypeB ContentType or ContentType ID.
		 * @returns {Boolean} Returns <code>true</code> if <code>contentTypeA</code> equals <code>contentTypeB</code>,
		 *  or <code>contentTypeA</code> descends from <code>contentTypeB</code>.
		 */
		isExtensionOf: function(contentTypeA, contentTypeB) {
			contentTypeA = (typeof contentTypeA === "string") ? this.getContentType(contentTypeA) : contentTypeA; //$NON-NLS-0$
			contentTypeB = (typeof contentTypeB === "string") ? this.getContentType(contentTypeB) : contentTypeB; //$NON-NLS-0$
			if (!contentTypeA || !contentTypeB) { return false; }
			if (contentTypeA.id === contentTypeB.id) { return true; }
			else {
				var parent = contentTypeA, seen = {};
				while (parent && (parent = this.getContentType(parent['extends']))) { //$NON-NLS-0$
					if (parent.id === contentTypeB.id) { return true; }
					if (seen[parent.id]) { throw new Error("Cycle: " + parent.id); } //$NON-NLS-0$
					seen[parent.id] = true;
				}
			}
			return false;
		},
		/**
		 * Similar to {@link #isExtensionOf}, but works on an array of contentTypes.
		 * @param {orion.core.ContentType|String} contentType ContentType or ContentType ID.
		 * @param {orion.core.ContentType[]|String[]} contentTypes Array of ContentTypes or ContentType IDs.
		 * @returns {Boolean} <code>true</code> if <code>contentType</code> equals or descends from any of the
		 * ContentTypes in <code>contentTypes</code>.
		 */
		isSomeExtensionOf: function(contentType, contentTypes) {
			for (var i=0; i < contentTypes.length; i++) {
				if (this.isExtensionOf(contentType, contentTypes[i])) {
					return true;
				}
			}
			return false;
		}
	};
	return {
		ContentTypeRegistry: ContentTypeRegistry,
		isImage: isImage,
		isBinary: isBinary,
		getFilenameContentType: getFilenameContentType
	};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: 
 *		Felipe Heidrich (IBM Corporation) - initial API and implementation
 *		Silenio Quarti (IBM Corporation) - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/editor/nls/messages',{
	root:true
});

/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: 
 *		Felipe Heidrich (IBM Corporation) - initial API and implementation
 *		Silenio Quarti (IBM Corporation) - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/editor/nls/root/messages',{//Default message bundle
	"multipleAnnotations": "Multiple annotations:", //$NON-NLS-1$ //$NON-NLS-0$
	"line": "Line: ${0}", //$NON-NLS-1$ //$NON-NLS-0$
	"breakpoint": "Breakpoint", //$NON-NLS-1$ //$NON-NLS-0$
	"bookmark": "Bookmark", //$NON-NLS-1$ //$NON-NLS-0$
	"task": "Task", //$NON-NLS-1$ //$NON-NLS-0$
	"error": "Error", //$NON-NLS-1$ //$NON-NLS-0$
	"warning": "Warning", //$NON-NLS-1$ //$NON-NLS-0$
	"currentSearch": "Current Search", //$NON-NLS-1$ //$NON-NLS-0$
	"currentLine": "Current Line", //$NON-NLS-1$ //$NON-NLS-0$
	"matchingBracket": "Matching Bracket", //$NON-NLS-1$ //$NON-NLS-0$
	"currentBracket": "Current Bracket", //$NON-NLS-1$ //$NON-NLS-0$
	"diffAdded": "Diff Added Lines", //$NON-NLS-1$ //$NON-NLS-0$
	"diffDeleted": "Diff Deleted Lines", //$NON-NLS-1$ //$NON-NLS-0$
	"diffModified": "Diff Modified Lines", //$NON-NLS-1$ //$NON-NLS-0$
	
	"lineUp": "Line Up", //$NON-NLS-1$ //$NON-NLS-0$
	"lineDown": "Line Down", //$NON-NLS-1$ //$NON-NLS-0$
	"lineStart": "Line Start", //$NON-NLS-1$ //$NON-NLS-0$
	"lineEnd": "Line End", //$NON-NLS-1$ //$NON-NLS-0$
	"charPrevious": "Previous Character", //$NON-NLS-1$ //$NON-NLS-0$
	"charNext": "Next Character", //$NON-NLS-1$ //$NON-NLS-0$
	"pageUp": "Page Up", //$NON-NLS-1$ //$NON-NLS-0$
	"pageDown": "Page Down", //$NON-NLS-1$ //$NON-NLS-0$
	"scrollPageUp": "Scroll Page Up", //$NON-NLS-1$ //$NON-NLS-0$
	"scrollPageDown": "Scroll Page Down", //$NON-NLS-1$ //$NON-NLS-0$
	"scrollLineUp": "Scroll Line Up", //$NON-NLS-1$ //$NON-NLS-0$
	"scrollLineDown": "Scroll Line Down", //$NON-NLS-1$ //$NON-NLS-0$
	"wordPrevious": "Previous Word", //$NON-NLS-1$ //$NON-NLS-0$
	"wordNext": "Next Word", //$NON-NLS-1$ //$NON-NLS-0$
	"textStart": "Document Start", //$NON-NLS-1$ //$NON-NLS-0$
	"textEnd": "Document End", //$NON-NLS-1$ //$NON-NLS-0$
	"scrollTextStart": "Scroll Document Start", //$NON-NLS-1$ //$NON-NLS-0$
	"scrollTextEnd": "Scroll Document End", //$NON-NLS-1$ //$NON-NLS-0$
	"centerLine": "Center Line", //$NON-NLS-1$ //$NON-NLS-0$
	
	"selectLineUp": "Select Line Up", //$NON-NLS-1$ //$NON-NLS-0$
	"selectLineDown": "Select Line Down", //$NON-NLS-1$ //$NON-NLS-0$
	"selectWholeLineUp": " Select Whole Line Up", //$NON-NLS-1$ //$NON-NLS-0$
	"selectWholeLineDown": "Select Whole Line Down", //$NON-NLS-1$ //$NON-NLS-0$
	"selectLineStart": "Select Line Start", //$NON-NLS-1$ //$NON-NLS-0$
	"selectLineEnd": "Select Line End", //$NON-NLS-1$ //$NON-NLS-0$
	"selectCharPrevious": "Select Previous Character", //$NON-NLS-1$ //$NON-NLS-0$
	"selectCharNext": "Select Next Character", //$NON-NLS-1$ //$NON-NLS-0$
	"selectPageUp": "Select Page Up", //$NON-NLS-1$ //$NON-NLS-0$
	"selectPageDown": "Select Page Down", //$NON-NLS-1$ //$NON-NLS-0$
	"selectWordPrevious": "Select Previous Word", //$NON-NLS-1$ //$NON-NLS-0$
	"selectWordNext": "Select Next Word", //$NON-NLS-1$ //$NON-NLS-0$
	"selectTextStart": "Select Document Start", //$NON-NLS-1$ //$NON-NLS-0$
	"selectTextEnd": "Select Document End", //$NON-NLS-1$ //$NON-NLS-0$

	"deletePrevious": "Delete Previous Character", //$NON-NLS-1$ //$NON-NLS-0$
	"deleteNext": "Delete Next Character", //$NON-NLS-1$ //$NON-NLS-0$
	"deleteWordPrevious": "Delete Previous Word", //$NON-NLS-1$ //$NON-NLS-0$
	"deleteWordNext": "Delete Next Word", //$NON-NLS-1$ //$NON-NLS-0$
	"deleteLineStart": "Delete Line Start", //$NON-NLS-1$ //$NON-NLS-0$
	"deleteLineEnd": "Delete Line End", //$NON-NLS-1$ //$NON-NLS-0$
	"tab": "Insert Tab", //$NON-NLS-1$ //$NON-NLS-0$
	"enter": "Insert Line Delimiter", //$NON-NLS-1$ //$NON-NLS-0$
	"enterNoCursor": "Insert Line Delimiter", //$NON-NLS-1$ //$NON-NLS-0$
	"escape": "Escape", //$NON-NLS-1$ //$NON-NLS-0$
	"selectAll": "Select All", //$NON-NLS-1$ //$NON-NLS-0$
	"copy": "Copy", //$NON-NLS-1$ //$NON-NLS-0$
	"cut": "Cut", //$NON-NLS-1$ //$NON-NLS-0$
	"paste": "Paste", //$NON-NLS-1$ //$NON-NLS-0$
	
	"uppercase": "To Upper Case", //$NON-NLS-1$ //$NON-NLS-0$
	"lowercase": "To Lower Case", //$NON-NLS-1$ //$NON-NLS-0$
	"capitalize": "Capitalize", //$NON-NLS-1$ //$NON-NLS-0$
	"reversecase" : "Reverse Case", //$NON-NLS-1$ //$NON-NLS-0$
	
	"toggleWrapMode": "Toggle Wrap Mode", //$NON-NLS-1$ //$NON-NLS-0$
	"toggleTabMode": "Toggle Tab Mode", //$NON-NLS-1$ //$NON-NLS-0$
	"toggleOverwriteMode": "Toggle Overwrite Mode", //$NON-NLS-1$ //$NON-NLS-0$
	
	"committerOnTime": "${0} on ${1}", //$NON-NLS-1$ //$NON-NLS-0$
	
	//Emacs
	"emacs": "Emacs", //$NON-NLS-1$ //$NON-NLS-0$
	"exchangeMarkPoint": "Exchange Mark and Point", //$NON-NLS-1$ //$NON-NLS-0$
	"setMarkCommand": "Set Mark", //$NON-NLS-1$ //$NON-NLS-0$
	"clearMark": "Clear Mark", //$NON-NLS-1$ //$NON-NLS-0$
	"digitArgument": "Digit Argument ${0}", //$NON-NLS-1$ //$NON-NLS-0$
	"negativeArgument": "Negative Argument", //$NON-NLS-1$ //$NON-NLS-0$
			
	"Comment": "Comment", //$NON-NLS-1$ //$NON-NLS-0$
	"Flat outline": "Flat outline", //$NON-NLS-1$ //$NON-NLS-0$
	"incrementalFindStr": "Incremental find: ${0}", //$NON-NLS-1$ //$NON-NLS-0$
	"incrementalFindStrNotFound": "Incremental find: ${0} (not found)", //$NON-NLS-1$ //$NON-NLS-0$
	"incrementalFindReverseStr": "Reverse Incremental find: ${0}", //$NON-NLS-1$ //$NON-NLS-0$
	"incrementalFindReverseStrNotFound": "Reverse Incremental find: ${0} (not found)", //$NON-NLS-1$ //$NON-NLS-0$
	"find": "Find...", //$NON-NLS-1$ //$NON-NLS-0$
	"undo": "Undo", //$NON-NLS-1$ //$NON-NLS-0$
	"redo": "Redo", //$NON-NLS-1$ //$NON-NLS-0$
	"cancelMode": "Cancel Current Mode", //$NON-NLS-1$ //$NON-NLS-0$
	"findNext": "Find Next Occurrence", //$NON-NLS-1$ //$NON-NLS-0$
	"findPrevious": "Find Previous Occurrence", //$NON-NLS-1$ //$NON-NLS-0$
	"incrementalFind": "Incremental Find", //$NON-NLS-1$ //$NON-NLS-0$
	"incrementalFindReverse": "Incremental Find Reverse", //$NON-NLS-1$ //$NON-NLS-0$
	"indentLines": "Indent Lines", //$NON-NLS-1$ //$NON-NLS-0$
	"unindentLines": "Unindent Lines", //$NON-NLS-1$ //$NON-NLS-0$
	"moveLinesUp": "Move Lines Up", //$NON-NLS-1$ //$NON-NLS-0$
	"moveLinesDown": "Move Lines Down", //$NON-NLS-1$ //$NON-NLS-0$
	"copyLinesUp": "Copy Lines Up", //$NON-NLS-1$ //$NON-NLS-0$
	"copyLinesDown": "Copy Lines Down", //$NON-NLS-1$ //$NON-NLS-0$
	"deleteLines": "Delete Lines", //$NON-NLS-1$ //$NON-NLS-0$
	"gotoLine": "Goto Line...", //$NON-NLS-1$ //$NON-NLS-0$
	"gotoLinePrompty": "Goto Line:", //$NON-NLS-1$ //$NON-NLS-0$
	"nextAnnotation": "Next Annotation", //$NON-NLS-1$ //$NON-NLS-0$
	"prevAnnotation": "Previous Annotation", //$NON-NLS-1$ //$NON-NLS-0$
	"expand": "Expand", //$NON-NLS-1$ //$NON-NLS-0$
	"collapse": "Collapse", //$NON-NLS-1$ //$NON-NLS-0$
	"expandAll": "Expand All", //$NON-NLS-1$ //$NON-NLS-0$
	"collapseAll": "Collapse All", //$NON-NLS-1$ //$NON-NLS-0$
	"lastEdit": "Last Edit Location", //$NON-NLS-1$ //$NON-NLS-0$
	"trimTrailingWhitespaces": "Trim Trailing Whitespaces", //$NON-NLS-1$ //$NON-NLS-0$
	"toggleLineComment": "Toggle Line Comment", //$NON-NLS-1$ //$NON-NLS-0$
	"addBlockComment": "Add Block Comment", //$NON-NLS-1$ //$NON-NLS-0$
	"removeBlockComment": "Remove Block Comment", //$NON-NLS-1$ //$NON-NLS-0$
	"linkedModeEntered": "Linked Mode: Enter or ESC to exit", //$NON-NLS-1$ //$NON-NLS-0$
	"linkedModeExited": "Linked Mode exited", //$NON-NLS-1$ //$NON-NLS-0$
	"syntaxError": "Syntax Error", //$NON-NLS-1$ //$NON-NLS-0$
	"contentAssist": "Content Assist", //$NON-NLS-1$ //$NON-NLS-0$
	"noProposals": "No proposals found",
	"computingProposals": "Computing content assist proposals...",
	"lineColumn": "Line ${0} : Column ${1}", //$NON-NLS-1$ //$NON-NLS-0$
	"multiSelections": "${0} selection regions", //$NON-NLS-1$ //$NON-NLS-0$
	
	//vi
	"vi": "vi", //$NON-NLS-1$ //$NON-NLS-0$
	"vimove": "(Move)", //$NON-NLS-1$ //$NON-NLS-0$
	"viyank": "(Yank)", //$NON-NLS-1$ //$NON-NLS-0$
	"videlete": "(Delete)", //$NON-NLS-1$ //$NON-NLS-0$
	"vichange": "(Change)", //$NON-NLS-1$ //$NON-NLS-0$
	"viLeft": "${0} Left", //$NON-NLS-1$ //$NON-NLS-0$
	"viRight": "${0} Right", //$NON-NLS-1$ //$NON-NLS-0$
	"viUp": "${0} Up", //$NON-NLS-1$ //$NON-NLS-0$
	"viDown": "${0} Down", //$NON-NLS-1$ //$NON-NLS-0$
	"viw": "${0} Next Word", //$NON-NLS-1$ //$NON-NLS-0$
	"vib": "${0} Beginning of Word", //$NON-NLS-1$ //$NON-NLS-0$
	"viW": "${0} Next Word (ws stop)", //$NON-NLS-1$ //$NON-NLS-0$
	"viB": "${0} Beginning of Word (ws stop)", //$NON-NLS-1$ //$NON-NLS-0$
	"vie": "${0} End of Word", //$NON-NLS-1$ //$NON-NLS-0$
	"viE": "${0} End of Word (ws stop)", //$NON-NLS-1$ //$NON-NLS-0$
	"vi$": "${0} End of the line", //$NON-NLS-1$ //$NON-NLS-0$
	"vi^_": "${0} First non-blank Char Current Line", //$NON-NLS-1$ //$NON-NLS-0$
	"vi+": "${0} First Char Next Line", //$NON-NLS-1$ //$NON-NLS-0$
	"vi-": "${0} First Char Previous Line", //$NON-NLS-1$ //$NON-NLS-0$
	"vi|": "${0} nth Column in Line", //$NON-NLS-1$ //$NON-NLS-0$
	"viH": "${0} Top of Page", //$NON-NLS-1$ //$NON-NLS-0$
	"viM": "${0} Middle of Page", //$NON-NLS-1$ //$NON-NLS-0$
	"viL": "${0} Bottom of Page", //$NON-NLS-1$ //$NON-NLS-0$
	"vi/": "${0} Search Forward", //$NON-NLS-1$ //$NON-NLS-0$
	"vi?": "${0} Search Backward", //$NON-NLS-1$ //$NON-NLS-0$
	"vin": "${0} Next Search", //$NON-NLS-1$ //$NON-NLS-0$
	"viN": "${0} Previous Search", //$NON-NLS-1$ //$NON-NLS-0$
	"vif": "${0} Search Char Fwd", //$NON-NLS-1$ //$NON-NLS-0$
	"viF": "${0} Search Char Bckwd", //$NON-NLS-1$ //$NON-NLS-0$
	"vit": "${0} Search Before Char Fwd", //$NON-NLS-1$ //$NON-NLS-0$
	"viT": "${0} Search Before Char Bckwd", //$NON-NLS-1$ //$NON-NLS-0$
	"vi,": "${0} Repeat Reverse Char Search", //$NON-NLS-1$ //$NON-NLS-0$
	"vi;": "${0} Repeat Char Search", //$NON-NLS-1$ //$NON-NLS-0$
	"viG": "${0} Go to Line", //$NON-NLS-1$ //$NON-NLS-0$
	"viycd": "${0} Current Line", //$NON-NLS-1$ //$NON-NLS-0$
	"via": "Append After Cursor", //$NON-NLS-1$ //$NON-NLS-0$
	"viA": "Append to End of Line", //$NON-NLS-1$ //$NON-NLS-0$
	"vii": "Insert Before Cursor", //$NON-NLS-1$ //$NON-NLS-0$
	"viI": "Insert at Beginning of Line", //$NON-NLS-1$ //$NON-NLS-0$
	"viO": "Insert Line Above", //$NON-NLS-1$ //$NON-NLS-0$
	"vio": "Insert Line Below", //$NON-NLS-1$ //$NON-NLS-0$
	"viR": "Begin Overwriting Text", //$NON-NLS-1$ //$NON-NLS-0$
	"vis": "Substitute a Character", //$NON-NLS-1$ //$NON-NLS-0$
	"viS": "Substitute Entire Line", //$NON-NLS-1$ //$NON-NLS-0$
	"viC": "Change Text Until Line End", //$NON-NLS-1$ //$NON-NLS-0$
	"vip": "Paste After Char or Line", //$NON-NLS-1$ //$NON-NLS-0$
	"viP": "Paste Before Char or Line", //$NON-NLS-1$ //$NON-NLS-0$
	"viStar": "Search Word Under Cursor", //$NON-NLS-1$ //$NON-NLS-0$
	
	"next": "Next", //$NON-NLS-1$ //$NON-NLS-0$
	"previous": "Previous", //$NON-NLS-1$ //$NON-NLS-0$
	"replace": "Replace", //$NON-NLS-1$ //$NON-NLS-0$
	"replaceAll": "Replace All", //$NON-NLS-1$ //$NON-NLS-0$
	"findWith": "Find With", //$NON-NLS-1$ //$NON-NLS-0$
	"replaceWith": "Replace With", //$NON-NLS-1$ //$NON-NLS-0$
	"caseInsensitive": "Aa", //$NON-NLS-1$ //$NON-NLS-0$
	"regex": "/.*/", //$NON-NLS-1$ //$NON-NLS-0$
	"wholeWord": "\\b", //$NON-NLS-1$ //$NON-NLS-0$
	"caseInsensitiveTooltip": "Toggle Case Insensitive", //$NON-NLS-1$ //$NON-NLS-0$
	"regexTooltip": "Toggle Regex", //$NON-NLS-1$ //$NON-NLS-0$
	"wholeWordTooltip": "Toggle Whole Word", //$NON-NLS-1$ //$NON-NLS-0$
	"closeTooltip": "Close", //$NON-NLS-1$ //$NON-NLS-0$

	"replacingAll": "Replacing all...", //$NON-NLS-1$ //$NON-NLS-0$
	"replacedMatches": "Replaced ${0} matches", //$NON-NLS-1$ //$NON-NLS-0$
	"nothingReplaced": "Nothing replaced", //$NON-NLS-1$ //$NON-NLS-0$
	"notFound": "Not found" //$NON-NLS-1$ //$NON-NLS-0$
});


/*******************************************************************************
 * @license
 * Copyright (c) 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
 
/*eslint-env browser, amd*/
define("orion/editor/keyModes", [ //$NON-NLS-0$
		"orion/keyBinding", //$NON-NLS-0$
		"orion/util" //$NON-NLS-0$
], function(mKeyBinding, util) {

	function KeyMode(view) {
		if (!view) {
			return;
		}
		this._view = view;
		this._keyBindings = this.createKeyBindings();
		this._keyBindingIndex = 0;
	}
	KeyMode.prototype = /** @lends orion.editor.KeyMode.prototype */ {
		createKeyBindings: function () {
			return [];
		},
		/**
		 * Returns all the key bindings associated to the given action ID.
		 *
		 * @param {String} actionID the action ID.
		 * @returns {orion.KeyBinding[]} the array of key bindings associated to the given action ID.
		 *
		 * @see orion.editor.KeyModesetKeyBinding
		 * @see orion.editor.KeyModesetAction
		 */
		getKeyBindings: function (actionID) {
			var result = [];
			var keyBindings = this._keyBindings;
			for (var i = 0; i < keyBindings.length; i++) {
				if (keyBindings[i].actionID === actionID) {
					result.push(keyBindings[i].keyBinding);
				}
			}
			return result;
		},
		getView: function() {
			return this._view;
		},
		isActive: function () {
			return this._view.getKeyModes().indexOf(this) !== -1;
		},
		match: function(e) {
			if (e.type === "keydown") { //$NON-NLS-0$
				switch (e.keyCode) {
					case 16: /* Shift */
					case 17: /* Control */
					case 18: /* Alt */
					case 91: /* Command */
						return undefined;
				}
			}
			var keyBindingIndex = this._keyBindingIndex;
			var keyBindings = this._matchingKeyBindings || this._keyBindings;
			var matchingKeyBindings = [];
			for (var i = 0; i < keyBindings.length; i++) {
				var kb = keyBindings[i];
				var keyBinding = kb.keyBinding;
				var match = keyBinding.match(e, keyBindingIndex);
				if (match === true) {
					this._keyBindingIndex = 0;
					this._matchingKeyBindings = null;
					return kb.actionID;
				} else if (typeof match === "number") { //$NON-NLS-0$
					matchingKeyBindings.push(kb);
				}
			}
			if (matchingKeyBindings.length === 0) {
				this._keyBindingIndex = 0;
				this._matchingKeyBindings = null;
			} else {
				this._keyBindingIndex++;
				this._matchingKeyBindings = matchingKeyBindings;
				return "noop"; //$NON-NLS-0$
			}
			return undefined;
		},
		/**
		 * Associates a key binding with the given action ID. Any previous
		 * association with the specified key binding is overwriten. If the
		 * action ID is <code>null</code>, the association is removed.
		 * 
		 * @param {orion.KeyBinding} keyBinding the key binding
		 * @param {String} actionID the action ID
		 */
		setKeyBinding: function(keyBinding, actionID) {
			var keyBindings = this._keyBindings;
			for (var i = 0; i < keyBindings.length; i++) {
				var kb = keyBindings[i]; 
				if (kb.keyBinding.equals(keyBinding)) {
					if (actionID) {
						kb.actionID = actionID;
					} else {
						if (kb.predefined) {
							kb.actionID = "noop"; //$NON-NLS-0$
						} else {
							keyBindings.splice(i, 1);
						}
					}
					return;
				}
			}
			if (actionID) {
				keyBindings.push({keyBinding: keyBinding, actionID: actionID});
			}
		},
		setView: function(view) {
			this._view = view;
		}
	};
	
	function DefaultKeyMode(view) {
		KeyMode.call(this, view);
	}
	DefaultKeyMode.prototype = new KeyMode();
	DefaultKeyMode.prototype.createKeyBindings = function () {
		var KeyBinding = mKeyBinding.KeyBinding;
		//no duplicate keybindings
		var bindings = [];

		// Cursor Navigation
		bindings.push({actionID: "lineUp",		keyBinding: new KeyBinding(38), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "lineDown",	keyBinding: new KeyBinding(40), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "charPrevious",	keyBinding: new KeyBinding(37), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "charNext",	keyBinding: new KeyBinding(39), predefined: true}); //$NON-NLS-0$
		if (util.isMac) {
			bindings.push({actionID: "scrollPageUp",		keyBinding: new KeyBinding(33), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "scrollPageDown",	keyBinding: new KeyBinding(34), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "pageUp",		keyBinding: new KeyBinding(33, null, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "pageDown",	keyBinding: new KeyBinding(34, null, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "lineStart",	keyBinding: new KeyBinding(37, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "lineEnd",		keyBinding: new KeyBinding(39, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "wordPrevious",	keyBinding: new KeyBinding(37, null, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "wordNext",	keyBinding: new KeyBinding(39, null, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "scrollTextStart",	keyBinding: new KeyBinding(36), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "scrollTextEnd",		keyBinding: new KeyBinding(35), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "textStart",	keyBinding: new KeyBinding(38, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "textEnd",		keyBinding: new KeyBinding(40, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "scrollPageUp",	keyBinding: new KeyBinding(38, null, null, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "scrollPageDown",		keyBinding: new KeyBinding(40, null, null, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "lineStart",	keyBinding: new KeyBinding(37, null, null, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "lineEnd",		keyBinding: new KeyBinding(39, null, null, null, true), predefined: true}); //$NON-NLS-0$
			//TODO These two actions should be changed to paragraph start and paragraph end  when word wrap is implemented
			bindings.push({actionID: "lineStart",	keyBinding: new KeyBinding(38, null, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "lineEnd",		keyBinding: new KeyBinding(40, null, null, true), predefined: true}); //$NON-NLS-0$
		} else {
			bindings.push({actionID: "pageUp",		keyBinding: new KeyBinding(33), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "pageDown",	keyBinding: new KeyBinding(34), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "lineStart",	keyBinding: new KeyBinding(36), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "lineEnd",		keyBinding: new KeyBinding(35), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "wordPrevious",	keyBinding: new KeyBinding(37, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "wordNext",	keyBinding: new KeyBinding(39, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "textStart",	keyBinding: new KeyBinding(36, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "textEnd",		keyBinding: new KeyBinding(35, true), predefined: true}); //$NON-NLS-0$
		}
		if (util.isFirefox && util.isLinux) {
			bindings.push({actionID: "lineUp",		keyBinding: new KeyBinding(38, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "lineDown",	keyBinding: new KeyBinding(40, true), predefined: true}); //$NON-NLS-0$
		}
		if (util.isWindows) {
			bindings.push({actionID: "scrollLineUp",	keyBinding: new KeyBinding(38, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "scrollLineDown",	keyBinding: new KeyBinding(40, true), predefined: true}); //$NON-NLS-0$
		}

		// Select Cursor Navigation
		bindings.push({actionID: "selectLineUp",		keyBinding: new KeyBinding(38, null, true), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "selectLineDown",		keyBinding: new KeyBinding(40, null, true), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "selectCharPrevious",	keyBinding: new KeyBinding(37, null, true), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "selectCharNext",		keyBinding: new KeyBinding(39, null, true), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "selectPageUp",		keyBinding: new KeyBinding(33, null, true), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "selectPageDown",		keyBinding: new KeyBinding(34, null, true), predefined: true}); //$NON-NLS-0$
		if (util.isMac) {
			bindings.push({actionID: "selectLineStart",	keyBinding: new KeyBinding(37, true, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectLineEnd",		keyBinding: new KeyBinding(39, true, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectWordPrevious",	keyBinding: new KeyBinding(37, null, true, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectWordNext",	keyBinding: new KeyBinding(39, null, true, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectTextStart",	keyBinding: new KeyBinding(36, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectTextEnd",		keyBinding: new KeyBinding(35, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectTextStart",	keyBinding: new KeyBinding(38, true, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectTextEnd",		keyBinding: new KeyBinding(40, true, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectLineStart",	keyBinding: new KeyBinding(37, null, true, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectLineEnd",		keyBinding: new KeyBinding(39, null, true, null, true), predefined: true}); //$NON-NLS-0$
			//TODO These two actions should be changed to select paragraph start and select paragraph end  when word wrap is implemented
			bindings.push({actionID: "selectLineStart",	keyBinding: new KeyBinding(38, null, true, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectLineEnd",		keyBinding: new KeyBinding(40, null, true, true), predefined: true}); //$NON-NLS-0$
		} else {
			if (util.isLinux) {
				bindings.push({actionID: "selectWholeLineUp",		keyBinding: new KeyBinding(38, true, true), predefined: true}); //$NON-NLS-0$
				bindings.push({actionID: "selectWholeLineDown",		keyBinding: new KeyBinding(40, true, true), predefined: true}); //$NON-NLS-0$
			}
			bindings.push({actionID: "selectLineStart",		keyBinding: new KeyBinding(36, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectLineEnd",		keyBinding: new KeyBinding(35, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectWordPrevious",	keyBinding: new KeyBinding(37, true, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectWordNext",		keyBinding: new KeyBinding(39, true, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectTextStart",		keyBinding: new KeyBinding(36, true, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "selectTextEnd",		keyBinding: new KeyBinding(35, true, true), predefined: true}); //$NON-NLS-0$
		}
		
		//Undo stack
		bindings.push({actionID: "undo", keyBinding: new mKeyBinding.KeyBinding('z', true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
		if (util.isMac) {
			bindings.push({actionID: "redo", keyBinding: new mKeyBinding.KeyBinding('z', true, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
		} else {
			bindings.push({actionID: "redo", keyBinding: new mKeyBinding.KeyBinding('y', true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
		}

		//Misc
		bindings.push({actionID: "deletePrevious",		keyBinding: new KeyBinding(8), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "deletePrevious",		keyBinding: new KeyBinding(8, null, true), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "deleteNext",		keyBinding: new KeyBinding(46), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "deleteWordPrevious",	keyBinding: new KeyBinding(8, true), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "deleteWordPrevious",	keyBinding: new KeyBinding(8, true, true), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "deleteWordNext",		keyBinding: new KeyBinding(46, true), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "tab",			keyBinding: new KeyBinding(9), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "shiftTab",			keyBinding: new KeyBinding(9, null, true), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "enter",			keyBinding: new KeyBinding(13), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "enter",			keyBinding: new KeyBinding(13, null, true), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "escape",			keyBinding: new KeyBinding(27), predefined: true}); //$NON-NLS-0$
		bindings.push({actionID: "selectAll",		keyBinding: new KeyBinding('a', true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "toggleTabMode",	keyBinding: new KeyBinding('m', true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
		if (util.isMac) {
			bindings.push({actionID: "deleteNext",		keyBinding: new KeyBinding(46, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "deleteWordPrevious",	keyBinding: new KeyBinding(8, null, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "deleteWordNext",		keyBinding: new KeyBinding(46, null, null, true), predefined: true}); //$NON-NLS-0$
		}
		
		bindings.push({actionID: "toggleWrapMode",		keyBinding: new mKeyBinding.KeyBinding('w', true, false, true)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "toggleOverwriteMode",		keyBinding: new mKeyBinding.KeyBinding(45)}); //$NON-NLS-0$
		
		/*
		* Feature in IE/Chrome: prevent ctrl+'u', ctrl+'i', and ctrl+'b' from applying styles to the text.
		*
		* Note that Chrome applies the styles on the Mac with Ctrl instead of Cmd.
		*/
		if (!util.isFirefox) {
			var isMacChrome = util.isMac && util.isChrome;
			bindings.push({actionID: "noop", keyBinding: new KeyBinding('u', !isMacChrome, false, false, isMacChrome), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "noop", keyBinding: new KeyBinding('i', !isMacChrome, false, false, isMacChrome), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "noop", keyBinding: new KeyBinding('b', !isMacChrome, false, false, isMacChrome), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
		}

		if (util.isFirefox) {
			bindings.push({actionID: "copy", keyBinding: new KeyBinding(45, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "paste", keyBinding: new KeyBinding(45, null, true), predefined: true}); //$NON-NLS-0$
			bindings.push({actionID: "cut", keyBinding: new KeyBinding(46, null, true), predefined: true}); //$NON-NLS-0$
		}

		// Add the emacs Control+ ... key bindings.
		if (util.isMac) {
			bindings.push({actionID: "lineStart", keyBinding: new KeyBinding("a", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "lineEnd", keyBinding: new KeyBinding("e", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "lineUp", keyBinding: new KeyBinding("p", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "lineDown", keyBinding: new KeyBinding("n", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "charPrevious", keyBinding: new KeyBinding("b", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "charNext", keyBinding: new KeyBinding("f", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "deletePrevious", keyBinding: new KeyBinding("h", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "deleteNext", keyBinding: new KeyBinding("d", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "deleteLineEnd", keyBinding: new KeyBinding("k", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
			if (util.isFirefox) {
				bindings.push({actionID: "scrollPageDown", keyBinding: new KeyBinding("v", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
				bindings.push({actionID: "deleteLineStart", keyBinding: new KeyBinding("u", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
				bindings.push({actionID: "deleteWordPrevious", keyBinding: new KeyBinding("w", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
			} else {
				bindings.push({actionID: "pageDown", keyBinding: new KeyBinding("v", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
				bindings.push({actionID: "centerLine", keyBinding: new KeyBinding("l", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
				bindings.push({actionID: "enterNoCursor", keyBinding: new KeyBinding("o", false, false, false, true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
				//TODO implement: y (yank), t (transpose)
			}
		}
		return bindings;
	};
	
	return {
		KeyMode: KeyMode,
		DefaultKeyMode: DefaultKeyMode
	};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2013,2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
 
/*eslint-env browser, amd*/
define("orion/editor/textTheme", //$NON-NLS-0$
[
	'require', //$NON-NLS-0$
	'orion/editor/eventTarget', //$NON-NLS-0$
	'orion/util' //$NON-NLS-0$
], function(require, mEventTarget, util) {
	var THEME_PREFIX = "orion-theme-"; //$NON-NLS-0$
	
	var Themes = {};

	/**
	 * Constructs a new text theme. 
	 * 
	 * @class A TextTheme is a class used to specify an editor theme.
	 * @name orion.editor.TextTheme
	 * @borrows orion.editor.EventTarget#addEventListener as #addEventListener
	 * @borrows orion.editor.EventTarget#removeEventListener as #removeEventListener
	 * @borrows orion.editor.EventTarget#dispatchEvent as #dispatchEvent
	 */
	function TextTheme(options) {
		options = options || {};
		this._document = options.document || document;
	}

	/**
	 * Gets an instance of <code>orion.editor.TextTheme</code> by name. If the name
	 * paramenter is not speficed the default text theme instance is returned.
	 * Subsequent calls of <code>getTheme</code> with the same name will return
	 * the same instance.
	 */
	TextTheme.getTheme = function(name) {
		name = name || "default"; //$NON-NLS-0$
		var theme = Themes[name];
		if (!theme) {
			theme = Themes[name] = new TextTheme();
		}
		return theme;
	};

	TextTheme.prototype = /** @lends orion.editor.TextTheme.prototype */ {
		/**
		 * Returns the theme className.
		 *
		 * @see orion.editor.TextTheme#setThemeClass
		 */
		getThemeClass: function() {
			return this._themeClass;
		},
		/**
		 * @class This object represents a style sheet for a theme manager.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextTheme#setThemeClass}
		 * </p>
		 * @name orion.editor.ThemeStyleSheet
		 * 
		 * @property {String} href The href of the stylesheet
		 */
		/**
		 * Sets the theme className and style sheet.
		 * <p>
		 * If the <code>stylesheet</code> parameter is a string, it represents an inline
		 * CSS and it will be added to the document as a <i>STYLE</i> tag element.  If the
		 * <code>stylesheet</code> parameter is a <code>orion.editor.ThemeStyleSheet</code>,
		 * its href property is loaded as either a <i>STYLE</i> tag element or as a <i>LINK</i>
		 * tag element.
		 * </p>
		 * <p>
		 * Listeners of the ThemeChanged event are notify once the styled sheet is loaded
		 * into the document.
		 * </p>
		 *
		 * @param {String} className the new theme className.
		 * @param {String|orion.editor.ThemeStyleSheet} styleSheet the CSS stylesheet for the new theme className.
		 *
		 * @see orion.editor.TextTheme#getThemeClass
		 * @see orion.editor.TextTheme#onThemeChanged
		 */
		 setThemeClass: function(className, styleSheet) {
			var self = this;
			var oldThemeClass = self._themeClass;	
			self._themeClass = className;
			this._load(className, styleSheet, function() {
				self.onThemeChanged({
					type: "ThemeChanged", //$NON-NLS-0$
					oldValue: oldThemeClass,
					newValue: self.getThemeClass()
				});
			});
		},
		/**
		 * @class This is the event sent when the theme className or style sheet has changed.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextTheme}<br/>
		 * {@link orion.editor.TextTheme#event:onThemeChanged}
		 * </p>
		 * @name orion.editor.ThemeChangedEvent
		 * 
		 * @property {String} oldValue The old theme clasName.
		 * @property {String} newValue The new theme className.
		 */
		/**
		 * This event is sent when the theme clasName has changed and its style sheet has been loaded in the document.
		 *
		 * @event
		 * @param {orion.editor.ThemeChangedEvent} themeChangedEvent the event
		 */
		onThemeChanged: function(themeChangedEvent) {
			return this.dispatchEvent(themeChangedEvent);
		},
		buildStyleSheet: function(themeClass, settings) {
			var convertCSSname = function(name) {
				return name.replace(this._capitalRegEx, function(match) {
					return "-" + match; //$NON-NLS-0$
				}.bind(this)).toLowerCase();
			}.bind(this);

			var parseStyles = function(object, ancestors, className, isTopLevel, result) {
				var localResult = [];
				var keys = Object.keys(object);
				keys.forEach(function(key) {
					var value = object[key];
					if (typeof(value) === "string") { //$NON-NLS-0$
						localResult.push("\t" + convertCSSname(key) + ": " + value + ";"); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
					} else {
						parseStyles(
							value,
							className === key ? ancestors : ancestors + (isTopLevel ? " ." : ".") + key, //$NON-NLS-1$ //$NON-NLS-0$
							className,
							false,
							result);
					}
				});
				if (localResult.length) {
					result.push(ancestors + (isTopLevel ? ".textview" : "") + " {"); //$NON-NLS-0$
					result.push.apply(result, localResult);
					result.push("}"); //$NON-NLS-0$
				}
			};

			var result = [""];
			parseStyles(settings.styles, "." + themeClass, settings.className, true, result); //$NON-NLS-0$
			return result.join("\n"); //$NON-NLS-0$
		},

		/**
		 * @private
		 */
		_createStyle: function(className, styleSheet, callback, link) {
			var document = this._document;
			var id = THEME_PREFIX + className;
			var node = document.getElementById(id);
			if (node) {
				if (link || node.firstChild.data === styleSheet) {
					return;
				}
				node.removeChild(node.firstChild);
				node.appendChild(document.createTextNode(styleSheet));
			} else {
				if (link) {
					node = util.createElement(document, "link"); //$NON-NLS-0$
					node.rel = "stylesheet"; //$NON-NLS-0$
					node.type = "text/css"; //$NON-NLS-0$
					node.href = styleSheet;
					node.addEventListener("load", function() { //$NON-NLS-0$
						callback();
					});
				} else {
					node = util.createElement(document, "style"); //$NON-NLS-0$
					node.appendChild(document.createTextNode(styleSheet));
				}
				node.id = id;
				var head = document.getElementsByTagName("head")[0] || document.documentElement; //$NON-NLS-0$
				head.appendChild(node);
			}
			if (!link) {
				callback();
			}
		},
		/**
		 * @private
		 */
		_load: function (className, styleSheet, callback) {
			if (!className) {
				callback();
				return;
			}
			if (typeof styleSheet === "string") { //$NON-NLS-0$
				this._createStyle(className, styleSheet, callback);
				return;
			}
			var href = styleSheet.href;
			var extension = ".css"; //$NON-NLS-0$
			if (href.substring(href.length - extension.length) !== extension) {
				href += extension;
			}
			if (/^\//.test(href) || /[a-zA-Z0-9]+:\/\//i.test(href) || !require.toUrl /* almond cannot load dynamically */) {
				this._createStyle(className, href, callback, true);
			} else {
				var self = this;
				require(["text!" + href], function(cssText) { //$NON-NLS-0$
					self._createStyle(className, cssText, callback, false);
				});
			}
		},
		_capitalRegEx: /[A-Z]/g
	};
	mEventTarget.EventTarget.addMixin(TextTheme.prototype);
	
	return {
		TextTheme: TextTheme
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
 
/*eslint-env browser, amd*/
define("orion/editor/util", [], function() { //$NON-NLS-0$
	
	/** @private */
	function addEventListener(node, type, handler, capture) {
		if (typeof node.addEventListener === "function") { //$NON-NLS-0$
			node.addEventListener(type, handler, capture === true);
		} else {
			node.attachEvent("on" + type, handler); //$NON-NLS-0$
		}
	}
	/** @private */
	function removeEventListener(node, type, handler, capture) {
		if (typeof node.removeEventListener === "function") { //$NON-NLS-0$
			node.removeEventListener(type, handler, capture === true);
		} else {
			node.detachEvent("on" + type, handler); //$NON-NLS-0$
		}
	}
	/** @private */
	function contains(topNode, node) {
		if (!node) { return false; }
		if (!topNode.compareDocumentPosition) {
			var temp = node;
			while (temp) {
				if (topNode === temp) {
					return true;
				}
				temp = temp.parentNode;
			}
			return false;
		}
		return topNode === node || (topNode.compareDocumentPosition(node) & 16) !== 0;
	}
	/** @private */
	function getNodeStyle(node, prop, defaultValue) {
		var value;
		if (node) {
			value = node.style[prop];
			if (!value) {
				if (node.currentStyle) {
					var index = 0, p = prop;
					while ((index = p.indexOf("-", index)) !== -1) { //$NON-NLS-0$
						p = p.substring(0, index) + p.substring(index + 1, index + 2).toUpperCase() + p.substring(index + 2);
					}
					value = node.currentStyle[p];
				} else {
					var css = node.ownerDocument.defaultView.getComputedStyle(node, null);
					value = css ? css.getPropertyValue(prop) : null;
				}
			}
		}
		return value || defaultValue;
	}

	/**
	 * @class
	 * @private
	 * @name orion.editor.Animation
	 * @description Creates an animation.
	 * @param {Object} options Options controlling the animation.
	 * @param {Array} options.curve Array of 2 values giving the start and end points for the animation.
	 * @param {Number} [options.duration=350] Duration of the animation, in milliseconds.
	 * @param {Function} [options.easing]
	 * @param {Function} [options.onAnimate]
	 * @param {Function} [options.onEnd]
	 * @param {Number} [options.rate=20] The time between frames, in milliseconds.
	 */
	var Animation = /** @ignore */ (function() {
		function Animation(options) {
			this.options = options;
		}
		/**
		 * Plays this animation.
		 * @function
		 * @memberOf orion.editor.Animation.prototype
		 * @name play
		 */
		Animation.prototype.play = function() {
			var duration = (typeof this.options.duration === "number") ? this.options.duration : 350, //$NON-NLS-0$
			    rate = (typeof this.options.rate === "number") ? this.options.rate : 20, //$NON-NLS-0$
			    easing = this.options.easing || this.defaultEasing,
			    onAnimate = this.options.onAnimate || function() {},
			    start = this.options.curve[0],
			    end = this.options.curve[1],
			    range = (end - start),
			    startedAt = -1,
				propertyValue,
				self = this;

			function onFrame() {
				startedAt = (startedAt === -1) ? new Date().getTime() : startedAt;
				var now = new Date().getTime(),
				    percentDone = (now - startedAt) / duration;
				if (percentDone < 1) {
					var eased = easing(percentDone);
					propertyValue = start + (eased * range);
					onAnimate(propertyValue);
				} else {
					onAnimate(end);
					self.stop();
				}
			}
			this.interval = this.options.window.setInterval(onFrame, rate);
		};
		/**
		 * Stops this animation.
		 * @function
		 * @memberOf orion.editor.Animation.prototype
		 */
		Animation.prototype.stop = function() {
			this.options.window.clearInterval(this.interval);
		    var onEnd = this.options.onEnd || function () {};
			onEnd();
		};
		Animation.prototype.defaultEasing = function(x) {
			return Math.sin(x * (Math.PI / 2));
		};
		return Animation;
	}());

	return {
		contains: contains,
		getNodeStyle: getNodeStyle,
		addEventListener: addEventListener,
		removeEventListener: removeEventListener,
		Animation: Animation
	};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2015 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: 
 *		Felipe Heidrich (IBM Corporation) - initial API and implementation
 *		Silenio Quarti (IBM Corporation) - initial API and implementation
 *		Mihai Sucan (Mozilla Foundation) - fix for Bug#334583 Bug#348471 Bug#349485 Bug#350595 Bug#360726 Bug#361180 Bug#362835 Bug#362428 Bug#362286 Bug#354270 Bug#361474 Bug#363945 Bug#366312 Bug#370584
 ******************************************************************************/

/*eslint-env browser, amd*/
define("orion/editor/textView", [  //$NON-NLS-1$
	'i18n!orion/editor/nls/messages', //$NON-NLS-1$
	'orion/editor/textModel', //$NON-NLS-1$
	'orion/editor/keyModes', //$NON-NLS-1$
	'orion/editor/eventTarget', //$NON-NLS-1$
	'orion/editor/textTheme', //$NON-NLS-1$
	'orion/editor/util', //$NON-NLS-1$
	'orion/util', //$NON-NLS-1$
	'orion/metrics' //$NON-NLS-1$
], function(messages, mTextModel, mKeyModes, mEventTarget, mTextTheme, textUtil, util, mMetrics) {

	/** @private */
	function getWindow(doc) {
		return doc.defaultView || doc.parentWindow;
	}
	function newArray(len) {
		return new Array(len);
	}
	var addHandler = textUtil.addEventListener;
	var removeHandler = textUtil.removeEventListener;
	/** @private */
	function applyStyle(style, node, reset) {
		if (reset) {
			node.className = "";
			var attrs = node.attributes;
			for (var i= attrs.length; i-->0;) {
				if (!util.isIE || util.isIE >= 9 || (util.isIE < 9 && attrs[i].specified)) {
					node.removeAttribute(attrs[i].name); 
				}
			}
		}
		if (!style) {
			return;
		}
		if (style.styleClass) {
			node.className = style.styleClass;
		}
		var properties = style.style;
		if (properties) {
			for (var s in properties) {
				if (properties.hasOwnProperty(s)) {
					node.style[s] = properties[s];
				}
			}
		}
		var attributes = style.attributes;
		if (attributes) {
			for (var a in attributes) {
				if (attributes.hasOwnProperty(a)) {
					node.setAttribute(a, attributes[a]);
				}
			}
		}
	}
	/** @private */
	function clone(obj) {
		/*Note that this code only works because of the limited types used in TextViewOptions */
		if (obj instanceof Array) {
			return obj.slice(0);
		}
		return obj;
	}
	/**	@private */
	function merge(obj1, obj2) {
		if (!obj1) {
			return obj2;
		}
		if (!obj2) {
			return obj1;
		}
		for (var p in obj2) {
			if (obj2.hasOwnProperty(p)) {
				if (!obj1.hasOwnProperty(p)) {
					obj1[p] = obj2[p];
				}
			}
		}
		return obj1;
	}
	/** @private */
	function compare(s1, s2) {
		if (s1 === s2) { return true; }
		if (s1 && !s2 || !s1 && s2) { return false; }
		if ((s1 && s1.constructor === String) || (s2 && s2.constructor === String)) { return false; }
		if (s1 instanceof Array || s2 instanceof Array) {
			if (!(s1 instanceof Array && s2 instanceof Array)) { return false; }
			if (s1.length !== s2.length) { return false; }
			for (var i = 0; i < s1.length; i++) {
				if (!compare(s1[i], s2[i])) {
					return false;
				}
			}
			return true;
		}
		if (!(s1 instanceof Object) || !(s2 instanceof Object)) { return false; }
		var p;
		for (p in s1) {
			if (s1.hasOwnProperty(p)) {
				if (!s2.hasOwnProperty(p)) { return false; }
				if (!compare(s1[p], s2[p])) {return false; }
			}
		}
		for (p in s2) {
			if (!s1.hasOwnProperty(p)) { return false; }
		}
		return true;
	}
	/** @private */
	function convertDelimiter(text, addTextFunc, addDelimiterFunc) {
		var cr = 0, lf = 0, index = 0, len = text.length;
		while (index < len) {
			if (cr !== -1 && cr <= index) { cr = text.indexOf("\r", index); } //$NON-NLS-1$
			if (lf !== -1 && lf <= index) { lf = text.indexOf("\n", index); } //$NON-NLS-1$
			var start = index, end;
			if (lf === -1 && cr === -1) {
				addTextFunc(text.substring(index));
				break;
			}
			if (cr !== -1 && lf !== -1) {
				if (cr + 1 === lf) {
					end = cr;
					index = lf + 1;
				} else {
					end = cr < lf ? cr : lf;
					index = (cr < lf ? cr : lf) + 1;
				}
			} else if (cr !== -1) {
				end = cr;
				index = cr + 1;
			} else {
				end = lf;
				index = lf + 1;
			}
			addTextFunc(text.substring(start, end));
			if (addDelimiterFunc) {
				addDelimiterFunc();
			} else {
				if (index === len) addTextFunc("");
			}
		}
	}
	/** @private */
	function getBorder(node) {
		var left,_top,right,bottom;
		var win = getWindow(node.ownerDocument);
		if (win.getComputedStyle) {
			var style = win.getComputedStyle(node, null);
			left = style.getPropertyValue("border-left-width"); //$NON-NLS-1$
			_top = style.getPropertyValue("border-top-width"); //$NON-NLS-1$
			right = style.getPropertyValue("border-right-width"); //$NON-NLS-1$
			bottom = style.getPropertyValue("border-bottom-width"); //$NON-NLS-1$
		} else if (node.currentStyle) {
			left = node.currentStyle.borderLeftWidth;
			_top = node.currentStyle.borderTopWidth;
			right = node.currentStyle.borderRightWidth;
			bottom = node.currentStyle.borderBottomWidth;
		}
		return {
			left: parseInt(left, 10) || 0,
			top: parseInt(_top, 10) || 0,
			right: parseInt(right, 10) || 0,
			bottom: parseInt(bottom, 10) || 0
		};
	}
	/** @private */
	function getPadding(node) {
		var left,_top,right,bottom;
		var win = getWindow(node.ownerDocument);
		if (win.getComputedStyle) {
			var style = win.getComputedStyle(node, null);
			left = style.getPropertyValue("padding-left"); //$NON-NLS-1$
			_top = style.getPropertyValue("padding-top"); //$NON-NLS-1$
			right = style.getPropertyValue("padding-right"); //$NON-NLS-1$
			bottom = style.getPropertyValue("padding-bottom"); //$NON-NLS-1$
		} else if (node.currentStyle) {
			left = node.currentStyle.paddingLeft;
			_top = node.currentStyle.paddingTop;
			right = node.currentStyle.paddingRight;
			bottom = node.currentStyle.paddingBottom;
		}
		return {
			left: parseInt(left, 10) || 0, 
			top: parseInt(_top, 10) || 0,
			right: parseInt(right, 10) || 0,
			bottom: parseInt(bottom, 10) || 0
		};
	}
	/** @private */
	function getLineTrim(line) {
		var trim = line._trim;
		if (!trim) {
			trim = getPadding(line);
			var border = getBorder(line);
			trim.left += border.left;
			trim.top += border.top;
			trim.right += border.right;
			trim.bottom += border.bottom;
			line._trim = trim;
		}
		return trim;
	}
	/** @private */
	function DOMReady(doc, _parent, className, callback) {
		className = "_" + className + "DOMReady"; //$NON-NLS-1$ //$NON-NLS-1$
		_parent.className = _parent.className ? _parent.className + " " + className : className; //$NON-NLS-1$
		_parent.__DOMReady = callback;
		var id = className + "Style"; //$NON-NLS-1$
		if (doc.getElementById(id)) { return; }
		var animationName = className + "Animation"; //$NON-NLS-1$
		function insertListener(evt) {
			if (evt.animationName === animationName) {
				var target = evt.target;
				if (typeof target.__DOMReady === "function") { //$NON-NLS-1$
					getWindow(doc).setTimeout(function() {
						target.__DOMReady();
					}, 0);
				}
			}
		}
		function template(className, animationName) {
			var props = ["", "-webkit-", "-moz-", "-ms-", "-o-"]; //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-4$
			var _frames = "", classRule = "body ." + className + " {\n"; //$NON-NLS-1$ //$NON-NLS-2$
			for (var i=0; i<props.length; i++) {
				_frames +=
				"@" + props[i] + "keyframes " + animationName + " {\n" + //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-1$
				"from { opacity: 0.99; }\n" + //$NON-NLS-1$
				"to { opacity: 1; }\n" + //$NON-NLS-1$
				"}\n"; //$NON-NLS-1$
				classRule +=
				props[i] + "animation-duration: 0.001s;\n" + //$NON-NLS-1$
				props[i] + "animation-name: " + animationName + ";\n"; //$NON-NLS-1$ //$NON-NLS-2$
			}
			classRule += "}"; //$NON-NLS-1$
			return _frames + classRule;
		}
		addHandler(doc, "animationstart", insertListener, false); //$NON-NLS-1$
		addHandler(doc, "MSAnimationStart", insertListener, false);  //$NON-NLS-1$
		addHandler(doc, "webkitAnimationStart", insertListener, false); //$NON-NLS-1$
		var style = doc.createElement("style"); //$NON-NLS-1$
		style.id = id;
		var head = doc.getElementsByTagName("head")[0] || doc.documentElement; //$NON-NLS-1$
		style.appendChild(doc.createTextNode(template(className, animationName)));
		head.insertBefore(style, head.firstChild);
	}
	
	var Animation = textUtil.Animation;
	
	/** 
	 * Constructs a new Selection object.
	 * 
	 * @class A Selection represents a range of selected text in the view.
	 * @name orion.editor.Selection
	 */
	function Selection (start, end, caret) {
		/**
		 * The selection start offset.
		 *
		 * @name orion.editor.Selection#start
		 */
		this.start = start;
		/**
		 * The selection end offset.
		 *
		 * @name orion.editor.Selection#end
		 */
		this.end = end;
		/** @private */
		this.caret = caret; //true if the start, false if the caret is at end
		/** @private */
		this._columnX = -1;
	}
	/** @private */
	Selection.compare = function(s1, s2) {
		if (s1.length !== s2.length) return false;
		for (var i = 0; i < s1.length; i++) {
			if (!s1[i].equals(s2[i])) return false;
		}
		return true;
	};
	Selection.editing = function(selections, back) {
		var i;
		if (back) {
			for (i = selections.length - 1; i >= 0; i--) {
				if (selections[i]._editing) return selections[i];
			}
			return selections[selections.length - 1];
		}
		for (i = 0; i < selections.length; i++) {
			if (selections[i]._editing) return selections[i];
		}
		return selections[0];
	};
	/** @private */
	Selection.convert = function(selections) {
		if (selections.length === 1) return selections[0];
		return selections;
	};
	/** @private */
	Selection.contains = function(selections, offset) {
		return selections.some(function(selection) {
			return selection.contains(offset);
		});
	};
	/** @private */
	Selection.merge = function(selections) {
		if (selections.length <= 1) return selections;
		selections.sort(function(a, b) {
			return a.start - b.start;
		});
		var result = [];
		var current = selections[0];
		for (var i = 1; i < selections.length; i++) {
			if (selections[i].start >= current.end || current._editing || selections[i]._editing) {
				result.push(current);
				current = selections[i];
			} else {
				current.end = Math.max(current.end, selections[i].end);
			}
		}
		result.push(current);
		return result;
	};
	Selection.prototype = /** @lends orion.editor.Selection.prototype */ {
		/** @private */
		clone: function() {
			var result = new Selection(this.start, this.end, this.caret);
			result._columnX = this._columnX;
			result._editing = this._editing;
			result._docX = this._docX;
			return result;
		},
		/** @private */
		contains: function(offset) {
			if (this.start <= offset && offset < this.end) {
				return true;
			}
			return false;
		},
		/** @private */
		collapse: function() {
			if (this.caret) {
				this.end = this.start;
			} else {
				this.start = this.end;
			}
		},
		/** @private */
		extend: function (offset) {
			if (this.caret) {
				this.start = offset;
			} else {
				this.end = offset;
			}
			if (this.start > this.end) {
				var tmp = this.start;
				this.start = this.end;
				this.end = tmp;
				this.caret = !this.caret;
			}
		},
		/** @private */
		setCaret: function(offset) {
			this.start = offset;
			this.end = offset;
			this.caret = false;
		},
		/** @private */
		getCaret: function() {
			return this.caret ? this.start : this.end;
		},
		/** @private */
		getAnchor: function() {
			return this.caret ? this.end : this.start;
		},
		/** @private */
		getOrientedSelection: function() {
			return {start: this.getAnchor(), end: this.getCaret()};
		},
		/** @private */
		toString: function() {
			return "start=" + this.start + " end=" + this.end + (this.caret ? " caret is at start" : " caret is at end"); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-4$
		},
		/** @private */
		isEmpty: function() {
			return this.start === this.end;
		},
		/** @private */
		equals: function(object) {
			return this.caret === object.caret && this.start === object.start && this.end === object.end && this._editing === object._editing;
		}
	};
	/** @private */
	function DOMSelection (view) {
		this._view = view;
		this._divs = [];
		var _parent = view._clipDiv || view._rootDiv;
		for (var i=0; i<3; i++) {
			var div = view._createSelectionDiv();
			_parent.appendChild(div);
			this._divs.push(div);
		}
	}
	DOMSelection.prototype = /** @lends orion.editor.DOMSelection.prototype */ {
		/** @private */
		destroy: function() {
			if (!this._divs) return;
			this._divs.forEach(function(div) {
				div.parentNode.removeChild(div);
			});
			this._divs = null;
		},
		/** @private */
		setPrimary: function(enabled) {
			this.primary = enabled;
		},
		/** @private */
		update: function() {
			var view = this._view;
			var primary = this.primary;
			var focused = view._hasFocus;
			var visible = view._cursorVisible;
			var cursor = !this.primary && this._selection && this._selection.isEmpty();
			var className;
			if (cursor) {
				className = "textviewSelectionCaret"; //$NON-NLS-1$
			} else {
				className = focused ? "textviewSelection" : "textviewSelectionUnfocused"; //$NON-NLS-1$ //$NON-NLS-2$
			}
			this._divs[0].style.visibility = (cursor && visible && focused) || !cursor ? "visible" : "hidden"; //$NON-NLS-1$ //$NON-NLS-2$
			this._divs[0].style.zIndex = visible && cursor ? "2" : "0"; //$NON-NLS-1$ //$NON-NLS-2$
			this._divs.forEach(function(div) {
				div.className = className;
				if (util.isWebkit && primary) {
					div.style.background = focused ? "transparent" : ""; //$NON-NLS-1$
				}
			});
		},
		/** @private */
		setSelection: function (selection) {
			this._selection = selection;
			this.update();
			var view = this._view;
			var model = view._model;
			var startLine = model.getLineAtOffset(selection.start);
			var endLine = model.getLineAtOffset(selection.end);
			var firstNode = view._getLineNext();
			/*
			* Bug in Firefox. For some reason, after a update page sometimes the 
			* firstChild returns null incorrectly. The fix is to ignore show selection.
			*/
			if (!firstNode) { return; }
			var lastNode = view._getLinePrevious();
			
			var topNode, bottomNode, topOffset, bottomOffset;
			if (startLine < firstNode.lineIndex) {
				topNode = firstNode;
				topOffset = model.getLineStart(firstNode.lineIndex);
			} else if (startLine > lastNode.lineIndex) {
				topNode = lastNode;
				topOffset = model.getLineStart(lastNode.lineIndex);
			} else {
				topNode = view._getLineNode(startLine);
				topOffset = selection.start;
			}

			if (endLine < firstNode.lineIndex) {
				bottomNode = firstNode;
				bottomOffset = model.getLineStart(firstNode.lineIndex);
			} else if (endLine > lastNode.lineIndex) {
				bottomNode = lastNode;
				bottomOffset = model.getLineStart(lastNode.lineIndex);
			} else {
				bottomNode = view._getLineNode(endLine);
				bottomOffset = selection.end;
			}
			this._setDOMSelection(topNode, topOffset, bottomNode, bottomOffset, selection.caret);
		},
		/** @private */
		_setDOMSelection: function (startNode, startOffset, endNode, endOffset, startCaret) {
			this._setDOMFullSelection(startNode, startOffset, endNode, endOffset);
			if (!this.primary) { return; }
			var view = this._view;
			var start = startNode._line.getNodeOffset(startOffset);
			var end = endNode._line.getNodeOffset(endOffset);
			if (!start.node || !end.node) return;
			var range;
			var win = view._getWindow();
			var doc = view._parent.ownerDocument;
			if (win.getSelection) {
				//W3C
				var sel = win.getSelection();
				range = doc.createRange();
				range.setStart(start.node, start.offset);
				range.setEnd(end.node, end.offset);
				if (view._hasFocus && (
					sel.anchorNode !== start.node || sel.anchorOffset !== start.offset ||
					sel.focusNode !== end.node || sel.focusOffset !== end.offset ||
					sel.anchorNode !== end.node || sel.anchorOffset !== end.offset ||
					sel.focusNode !== start.node || sel.focusOffset !== start.offset))
				{
					view._anchorNode = start.node;
					view._anchorOffset = start.offset;
					view._focusNode = end.node;
					view._focusOffset = end.offset;
					view._ignoreSelect = false;
					if (sel.rangeCount > 0) { sel.removeAllRanges(); }
					sel.addRange(range);
					view._ignoreSelect = true;
				}
				if (view._cursorDiv) {
					range = doc.createRange();
					if (startCaret) {
						range.setStart(start.node, start.offset);
						range.setEnd(start.node, start.offset);
					} else {
						range.setStart(end.node, end.offset);
						range.setEnd(end.node, end.offset);
					}
					var rect = range.getClientRects()[0];
					var cursorParent = view._cursorDiv.parentNode;
					var clientRect = cursorParent.getBoundingClientRect();
					if (rect && clientRect) {
						view._cursorDiv.style.top = (rect.top - clientRect.top + cursorParent.scrollTop) + "px"; //$NON-NLS-1$
						view._cursorDiv.style.left = (rect.left - clientRect.left + cursorParent.scrollLeft) + "px"; //$NON-NLS-1$
					}
				}
			} else if (doc.selection) {
				if (!view._hasFocus) { return; }
				//IE < 9
				var body = doc.body;

				/*
				* Bug in IE. For some reason when text is deselected the overflow
				* selection at the end of some lines does not get redrawn.  The
				* fix is to create a DOM element in the body to force a redraw.
				*/
				var child = util.createElement(doc, "div"); //$NON-NLS-1$
				body.appendChild(child);
				body.removeChild(child);
				
				range = body.createTextRange();
				range.moveToElementText(start.node.parentNode);
				range.moveStart("character", start.offset); //$NON-NLS-1$
				var endRange = body.createTextRange();
				endRange.moveToElementText(end.node.parentNode);
				endRange.moveStart("character", end.offset); //$NON-NLS-1$
				range.setEndPoint("EndToStart", endRange); //$NON-NLS-1$
				view._ignoreSelect = false;
				range.select();
				view._ignoreSelect = true;
			}
		},
		/** @private */
		_setDOMFullSelection: function(startNode, startOffset, endNode, endOffset) {
			this._divs.forEach(function(div) {
				div.style.width = div.style.height = "0px"; //$NON-NLS-1$
			});
			var view = this._view;
			if (!view._fullSelection) { return; }
			if (util.isIOS) { return; }
			if (startNode === endNode && startOffset === endOffset && this.primary) { return; }
			var viewPad = view._getViewPadding();
			var clientRect = view._clientDiv.getBoundingClientRect();
			var viewRect = view._viewDiv.getBoundingClientRect();
			var left = viewRect.left + viewPad.left;
			var right = clientRect.right;
			var _top = viewRect.top + viewPad.top;
			var bottom = clientRect.bottom;
			var hd = 0, vd = 0;
			if (view._clipDiv) {
				var clipRect = view._clipDiv.getBoundingClientRect();
				hd = clipRect.left - view._clipDiv.scrollLeft;
				vd = clipRect.top;
			} else {
				var rootpRect = view._rootDiv.getBoundingClientRect();
				hd = rootpRect.left;
				vd = rootpRect.top;
			}
			view._ignoreDOMSelection = true;
			var startLine = new TextLine(view, startNode.lineIndex, startNode);
			var startRect = startLine.getBoundingClientRect(startOffset, false);
			var l = startRect.left, endLine, endRect;
			if (startNode === endNode && startOffset === endOffset) {
				endLine = startLine;
				endRect = startRect;
			} else {
				endLine = new TextLine(view, endNode.lineIndex, endNode);
				endRect = endLine.getBoundingClientRect(endOffset, false);
			}
			var r = endRect.left;
			view._ignoreDOMSelection = false;
			var sel1Div = this._divs[0];
			var sel1Left = Math.min(right, Math.max(left, l));
			var sel1Top = Math.min(bottom, Math.max(_top, startRect.top));
			var sel1Right = right;
			var sel1Bottom = Math.min(bottom, Math.max(_top, startRect.bottom));
			sel1Div.style.left = (sel1Left - hd) + "px"; //$NON-NLS-1$
			sel1Div.style.top = (sel1Top - vd) + "px"; //$NON-NLS-1$
			sel1Div.style.width = Math.max(0, sel1Right - sel1Left) + "px"; //$NON-NLS-1$
			sel1Div.style.height = Math.max(0, sel1Bottom - sel1Top) + "px"; //$NON-NLS-1$
			if (startNode.lineIndex === endNode.lineIndex) {
				sel1Right = Math.min(r, right);
				sel1Div.style.width = Math.max(this.primary ? 0 : 1, sel1Right - sel1Left) + "px"; //$NON-NLS-1$
			} else {
				var sel3Left = left;
				var sel3Top = Math.min(bottom, Math.max(_top, endRect.top));
				var sel3Right = Math.min(right, Math.max(left, r));
				var sel3Bottom = Math.min(bottom, Math.max(_top, endRect.bottom));
				var sel3Div = this._divs[2];
				sel3Div.style.left = (sel3Left - hd) + "px"; //$NON-NLS-1$
				sel3Div.style.top = (sel3Top - vd) + "px"; //$NON-NLS-1$
				sel3Div.style.width = Math.max(0, sel3Right - sel3Left) + "px"; //$NON-NLS-1$
				sel3Div.style.height = Math.max(0, sel3Bottom - sel3Top) + "px"; //$NON-NLS-1$
				if (Math.abs(startNode.lineIndex - endNode.lineIndex) > 1) {
					var sel2Div = this._divs[1];
					sel2Div.style.left = (left - hd)  + "px"; //$NON-NLS-1$
					sel2Div.style.top = (sel1Bottom - vd) + "px"; //$NON-NLS-1$
					sel2Div.style.width = Math.max(0, right - left) + "px"; //$NON-NLS-1$
					sel2Div.style.height = Math.max(0, sel3Top - sel1Bottom) + "px"; //$NON-NLS-1$
				}
			}
		}
	};
	/** @private */
	function TextRect (rect) {
		this.left = rect.left;
		this.top = rect.top;
		this.right = rect.right;
		this.bottom = rect.bottom;
	}
	TextRect.prototype = /** @lends orion.editor.TextRect.prototype */ {
		/** @private */
		toString: function() {
			return "{l=" + this.left + ", t=" + this.top + ", r=" + this.right + ", b=" + this.bottom + "}"; //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-1$
		}
	};
	/** 
	 * Constructs a new TextLine object.
	 * 
	 * @class A TextLine represents a line of text in the view.
	 * @name orion.editor.TextLine
	 * @private
	 */
	function TextLine (view, lineIndex, lineDiv) {
		/**
		 * The view.
		 *
		 * @name orion.editor.TextLine#view
		 * @private
		 */
		this.view = view;
		/**
		 * The line index.
		 *
		 * @name orion.editor.TextLine#lineIndex
		 * @private
		 */
		this.lineIndex = lineIndex;
		
		this._lineDiv = lineDiv;
	}
	TextLine.prototype = /** @lends orion.editor.TextLine.prototype */ {
		/** @private */
		create: function(_parent, div) {
			if (this._lineDiv) { return; }
			var child = this._lineDiv = this._createLine(_parent, div, this.lineIndex);
			child._line = this;
			return child;
		},
		_createLine: function(_parent, div, lineIndex) {
			var view = this.view;
			var model = view._model;
			var lineText = model.getLine(lineIndex);
			var lineStart = model.getLineStart(lineIndex);
			var e = {type:"LineStyle", textView: view, lineIndex: lineIndex, lineText: lineText, lineStart: lineStart}; //$NON-NLS-1$
			view.onLineStyle(e);
			var doc = _parent.ownerDocument;
			var lineDiv = div || util.createElement(doc, "div"); //$NON-NLS-1$
			if (!div || !compare(div.viewStyle, e.style)) {
				applyStyle(e.style, lineDiv, div);
				if (div) { div._trim = null; }
				lineDiv.viewStyle = e.style;
				lineDiv.setAttribute("role", "presentation"); //$NON-NLS-1$ //$NON-NLS-2$
			}
			lineDiv.lineIndex = lineIndex;
			
			if (div && lineDiv.viewLineText === lineText && compare(e.ranges, lineDiv.viewRanges)) {
				return lineDiv;
			}
			lineDiv.viewRanges = e.ranges;
			lineDiv.viewLineText = lineText;
			
			var ranges = [];
			var data = {tabOffset: 0, ranges: ranges};
			this._createRanges(e.ranges, lineText, 0, lineText.length, lineStart, data);
			
			/*
			* A trailing span with a whitespace is added for three different reasons:
			* 1. Make sure the height of each line is the largest of the default font
			* in normal, italic, bold, and italic-bold.
			* 2. When full selection is off, Firefox, Opera and IE9 do not extend the 
			* selection at the end of the line when the line is fully selected. 
			* 3. The height of a div with only an empty span is zero.
			*/
			var c = " "; //$NON-NLS-1$
			if (!view._fullSelection && util.isIE < 9) {
				/* 
				* IE8 already selects extra space at end of a line fully selected,
				* adding another space at the end of the line causes the selection 
				* to look too big. The fix is to use a zero-width space (\uFEFF) instead. 
				*/
				c = "\uFEFF"; //$NON-NLS-1$
			}
			var range = {text: c, style: view._metrics.largestFontStyle, ignoreChars: 1};
			if (ranges.length === 0 || !ranges[ranges.length - 1].style || ranges[ranges.length - 1].style.tagName !== "div") { //$NON-NLS-1$
				ranges.push(range);
			} else {
				ranges.splice(ranges.length - 1, 0, range);
			}
		
			var span, style, oldSpan, oldStyle, text, oldText, end = 0, oldEnd = 0, next, i;
			if (util.isFirefox && lineText.length > 2000) {
				if (div) {
					lineDiv.innerHTML = "";
					div.lineWidth = undefined;
				}
				var frag = doc.createDocumentFragment();
				for (i = 0; i < ranges.length; i++) {
					range = ranges[i];
					text = range.text;
					style = range.style;
					span = this._createSpan(lineDiv, text, style, range.ignoreChars);
					frag.appendChild(span);
				}
				lineDiv.appendChild(frag);
			} else {
				var changeCount, changeStart;
				if (div) {
					var modelChangedEvent = div.modelChangedEvent;
					if (modelChangedEvent) {
						if (modelChangedEvent.removedLineCount === 0 && modelChangedEvent.addedLineCount === 0) {
							changeStart = modelChangedEvent.start - lineStart;
							changeCount = modelChangedEvent.addedCharCount - modelChangedEvent.removedCharCount;
						} else {
							changeStart = -1;
						}
						div.modelChangedEvent = undefined;
					}
					oldSpan = div.firstChild;
				}
				for (i = 0; i < ranges.length; i++) {
					range = ranges[i];
					text = range.text;
					end += text.length;
					style = range.style;
					if (oldSpan) {
						oldText = oldSpan.firstChild.data;
						oldStyle = oldSpan.viewStyle;
						if (oldText === text && compare(style, oldStyle)) {
							oldEnd += oldText.length;
							oldSpan._rectsCache = undefined;
							span = oldSpan = oldSpan.nextSibling;
							continue;
						} else {
							while (oldSpan) {
								if (changeStart !== -1) {
									var spanEnd = end;
									if (spanEnd >= changeStart) {
										spanEnd -= changeCount;
									}
									var t = oldSpan.firstChild.data;
									var len = t ? t.length : 0;
									if (oldEnd + len > spanEnd) { break; }
									oldEnd += len;
								}
								next = oldSpan.nextSibling;
								lineDiv.removeChild(oldSpan);
								oldSpan = next;
							}
						}
					}
					span = this._createSpan(lineDiv, text, style, range.ignoreChars);
					if (oldSpan) {
						lineDiv.insertBefore(span, oldSpan);
					} else {
						lineDiv.appendChild(span);
					}
					if (div) {
						div.lineWidth = undefined;
					}
				}
				if (div) {
					var tmp = span ? span.nextSibling : null;
					while (tmp) {
						next = tmp.nextSibling;
						div.removeChild(tmp);
						tmp = next;
					}
				}
			}
			if (!lineDiv.parentNode) {
				_parent.appendChild(lineDiv);
			}
			return lineDiv;
		},
		_createRanges: function(ranges, text, start, end, lineStart, data) {
			if (start > end) { return; }
			if (ranges) {
				for (var i = 0; i < ranges.length; i++) {
					var range = ranges[i];
					if (range.end < lineStart + start) { continue; }
					var styleStart = Math.max(lineStart + start, range.start) - lineStart;
					if (styleStart > end) { break; }
					var styleEnd = Math.min(lineStart + end, range.end) - lineStart;
					if (styleStart <= styleEnd) {
						styleStart = Math.max(start, styleStart);
						styleEnd = Math.min(end, styleEnd);
						if (start < styleStart) {
							this._createRange(text, start, styleStart, null, data);
						}
						if (!range.style || !range.style.unmergeable) {
							while (i + 1 < ranges.length && ranges[i + 1].start - lineStart === styleEnd && compare(range.style, ranges[i + 1].style)) {
								range = ranges[i + 1];
								styleEnd = Math.min(lineStart + end, range.end) - lineStart;
								i++;
							}
						}
						this._createRange(text, styleStart, styleEnd, range.style, data);
						start = styleEnd;
					}
				}
			}
			if (start < end) {
				this._createRange(text, start, end, null, data);
			}
		},
		_createRange: function(text, start, end, style, data) {
			if (start > end) { return; }
			var tabSize = this.view._customTabSize, range;
			if (tabSize && tabSize !== 8) {
				var tabIndex = text.indexOf("\t", start); //$NON-NLS-1$
				while (tabIndex !== -1 && tabIndex < end) {
					if (start < tabIndex) {
						range = {text: text.substring(start, tabIndex), style: style};
						data.ranges.push(range);
						data.tabOffset += range.text.length;
					}
					var spacesCount = tabSize - (data.tabOffset % tabSize);
					if (spacesCount > 0) {
						//TODO hack to preserve tabs in getDOMText()
						var spaces = "\u00A0"; //$NON-NLS-1$
						for (var i = 1; i < spacesCount; i++) {
							spaces += " "; //$NON-NLS-1$
						}
						range = {text: spaces, style: style, ignoreChars: spacesCount - 1};
						data.ranges.push(range);
						data.tabOffset += range.text.length;
					}
					start = tabIndex + 1;
					if (start === end) {
						return;
					}
					tabIndex = text.indexOf("\t", start); //$NON-NLS-1$
				}
			}
			if (start <= end) {
				range = {text: text.substring(start, end), style: style};
				data.ranges.push(range);
				data.tabOffset += range.text.length;
			}
		},
		_createSpan: function(_parent, text, style, ignoreChars) {
			var view = this.view;
			var tagName = "span"; //$NON-NLS-1$
			if (style && style.tagName) {
				tagName = style.tagName.toLowerCase();
			}
			var isLink = tagName === "a"; //$NON-NLS-1$
			if (isLink) { this.hasLink = true; }
			if (isLink && !view._linksVisible) {
				tagName = "span"; //$NON-NLS-1$
			}
			var doc = _parent.ownerDocument;
			var child = util.createElement(_parent.ownerDocument, tagName);
			child.appendChild(doc.createTextNode(style && style.text ? style.text : text));
			if (style && style.html) {
				child.innerHTML = style.html;
				child.ignore = true;
			} else if (style && style.node) {
				child.appendChild(style.node);
				child.ignore = true;
			}
			applyStyle(style, child);
			if (tagName === "a") { //$NON-NLS-1$
				var win = view._getWindow();
				addHandler(child, "click", function(e) { return view._handleLinkClick(e ? e : win.event); }, false); //$NON-NLS-1$
			}
			child.viewStyle = style;
			if (ignoreChars) {
				child.ignoreChars = ignoreChars;
			}
			return child;
		},
		_ensureCreated: function() {
			if (this._lineDiv) { return this._lineDiv; }
			return (this._createdDiv = this.create(this.view._clientDiv, null));
		},
		/** @private */
		getBoundingClientRect: function(offset, absolute) {
			var child = this._ensureCreated();
			var view = this.view;
			if (offset === undefined) {
				return this._getLineBoundingClientRect(child, true);
			}
			var model = view._model;
			var doc = child.ownerDocument;
			var lineIndex = this.lineIndex;
			var result = null;
			if (offset < model.getLineEnd(lineIndex)) {
				var lineOffset = model.getLineStart(lineIndex);
				this.forEach(function(lineChild) {
					var textNode = lineChild.firstChild;
					var nodeLength = this._nodeLength(lineChild); 
					if (lineOffset + nodeLength > offset) {
						var index = offset - lineOffset;
						var range;
						if (textNode.length === 1) {
							result = new TextRect(lineChild.getBoundingClientRect());
						} else if (view._isRangeRects) {
							range = doc.createRange();
							range.setStart(textNode, index);
							range.setEnd(textNode, index + 1);
							result = new TextRect(range.getBoundingClientRect());
						} else if (util.isIE) {
							range = doc.body.createTextRange();
							range.moveToElementText(lineChild);
							range.collapse();
							/*
							* Bug in IE8. TextRange.getClientRects() and TextRange.getBoundingClientRect() fails
							* if the line child is not the first element in the line and if the start offset is 0. 
							* The fix is to use Node.getClientRects() left edge instead.
							*/
							var fixIE8 = index === 0 && util.isIE === 8;
							if (fixIE8) { index = 1; }
							range.moveEnd("character", index + 1); //$NON-NLS-1$
							range.moveStart("character", index); //$NON-NLS-1$
							result = new TextRect(range.getBoundingClientRect());
							if (fixIE8) {
								result.left = lineChild.getClientRects()[0].left;
							}
						} else {
							var text = textNode.data;
							lineChild.removeChild(textNode);
							lineChild.appendChild(doc.createTextNode(text.substring(0, index)));
							var span = util.createElement(doc, "span"); //$NON-NLS-1$
							span.appendChild(doc.createTextNode(text.substring(index, index + 1)));
							lineChild.appendChild(span);
							lineChild.appendChild(doc.createTextNode(text.substring(index + 1)));
							result = new TextRect(span.getBoundingClientRect());
							lineChild.innerHTML = "";
							lineChild.appendChild(textNode);
							if (!this._createdDiv) {
								/*
								 * Removing the element node that holds the selection start or end
								 * causes the selection to be lost. The fix is to detect this case
								 * and restore the selection. 
								 */
								var s = view._getSelections()[0];
								if ((lineOffset <= s.start && s.start < lineOffset + nodeLength) ||  (lineOffset <= s.end && s.end < lineOffset + nodeLength)) {
									view._updateDOMSelection();
								}
							}
						}
						if (util.isIE < 11) {
							var win = getWindow(child.ownerDocument);
							var xFactor = win.screen.logicalXDPI / win.screen.deviceXDPI;
							var yFactor = win.screen.logicalYDPI / win.screen.deviceYDPI;
							result.left = result.left * xFactor;
							result.right = result.right * xFactor;
							result.top = result.top * yFactor;
							result.bottom = result.bottom * yFactor;
						}
						return false;
					}
					lineOffset += nodeLength;
					return true;
				});
			}
			var rect = this.getBoundingClientRect();
			if (!result) {
				if (view._wrapMode) {
					var rects = this.getClientRects();
					result = rects[rects.length - 1];
					result.left = result.right;
					result.left += rect.left;
					result.top += rect.top;
					result.right += rect.left;
					result.bottom += rect.top;
				} else {
					result = new TextRect(rect);
					result.left = result.right;
				}
			}
			if (absolute || absolute === undefined) {
				result.left -= rect.left;
				result.top -= rect.top;
				result.right -= rect.left;
				result.bottom -= rect.top;
			}
			return result;
		},
		forEach: function(callback) {
			var child = this._ensureCreated();
			var lineChild = child.firstChild;
			while (lineChild) {
				var next = lineChild.nextSibling;
				if (!lineChild.ignore) {
					if (!callback.call(this, lineChild)) {
						break;
					}
				}
				lineChild = next;
			}
		},
		/** @private */
		_getClientRects: function(element, parentRect) {
			var rects, newRects, rect, i;
			if (!element._rectsCache) {
				rects = element.getClientRects();
				newRects = newArray(rects.length);
				for (i = 0; i<rects.length; i++) {
					rect = newRects[i] = new TextRect(rects[i]);
					rect.left -= parentRect.left;
					rect.top -= parentRect.top;
					rect.right -= parentRect.left;
					rect.bottom -= parentRect.top;
				}
				element._rectsCache = newRects;
			}
			rects = element._rectsCache;
			newRects = [rects.length];
			for (i = 0; i<rects.length; i++) {
				newRects[i] = new TextRect(rects[i]);
			}
			return newRects;
		},
		getClientRects: function(lineIndex) {
			if (!this.view._wrapMode) { return [this.getBoundingClientRect()]; }
			var child = this._ensureCreated();
			//TODO [perf] cache rects
			var result = [];
			var parentRect = child.getBoundingClientRect();
			this.forEach(function(lineChild) {
				var rects = this._getClientRects(lineChild, parentRect);
				for (var i = 0; i < rects.length; i++) {
					var rect = rects[i], j, r;
					if (rect.top === rect.bottom) { continue; }
					var center = rect.top + (rect.bottom - rect.top) / 2;
					for (j = 0; j < result.length; j++) {
						r = result[j];
						if ((r.top <= center && center < r.bottom)) {
							break;
						}
					}
					if (j === result.length) {
						result.push(rect);
					} else {
						if (rect.left < r.left) { r.left = rect.left; }
						if (rect.top < r.top) { r.top = rect.top; }
						if (rect.right > r.right) { r.right = rect.right; }
						if (rect.bottom > r.bottom) { r.bottom = rect.bottom; }
					}
				}
				return true;
			});
			if (lineIndex !== undefined) {
				return result[lineIndex];
			}
			return result;
		},
		/** @private */
		_getLineBoundingClientRect: function (child, noTrim) {
			var rect = new TextRect(child.getBoundingClientRect());
			if (this.view._wrapMode) {
			} else {
				rect.right = rect.left;
				var lastChild = child.lastChild;
				//Remove any artificial trailing whitespace in the line
				while (lastChild && lastChild.ignoreChars === lastChild.firstChild.length) {
					lastChild = lastChild.previousSibling;
				}
				if (lastChild) {
					var lastRect = lastChild.getBoundingClientRect();
					rect.right = lastRect.right + getLineTrim(child).right;
				}
			}
			if (noTrim) {
				var padding = getLineTrim(child);
				rect.left = rect.left + padding.left;
				rect.right = rect.right - padding.right;
			}
			return rect;
		},
		/** @private */
		getLineCount: function () {
			if (!this.view._wrapMode) { return 1; }
			return this.getClientRects().length;
		},
		/** @private */
		getLineIndex: function(offset) {
			if (!this.view._wrapMode) { return 0; }
			var rects = this.getClientRects();
			var rect = this.getBoundingClientRect(offset);
			var center = rect.top + ((rect.bottom - rect.top) / 2);
			for (var i = 0; i < rects.length; i++) {
				if (rects[i].top <= center && center < rects[i].bottom) {
					return i;
				}
			}
			return rects.length - 1;
		},
		/** @private */
		getLineStart: function (lineIndex) {
			if (!this.view._wrapMode || lineIndex === 0) {
				return this.view._model.getLineStart(this.lineIndex);
			}
			var rects = this.getClientRects();
			return this.getOffset(rects[lineIndex].left + 1, rects[lineIndex].top + 1);
		},
		_nodeLength: function(lineChild) {
			if (!lineChild || lineChild.ignore) return 0;
			var len = lineChild.firstChild.length; 
			if (lineChild.ignoreChars) {
				len -= lineChild.ignoreChars;
			}
			return len;
		},
		getModelOffset: function(node, offset) {
			if (!node) { return 0; }
			var lineOffset = 0;
			this.forEach(function(lineChild) {
				var textNode = lineChild.firstChild;
				if (textNode === node) {
					if (lineChild.ignoreChars) { lineOffset -= lineChild.ignoreChars; }
					lineOffset += offset;
					return false;
				}
				if (lineChild.ignoreChars) { lineOffset -= lineChild.ignoreChars; }
				lineOffset += textNode.data.length;
				return true;
			});
			return Math.max(0, lineOffset) + this.view._model.getLineStart(this.lineIndex);
		},
		getNodeOffset: function(modelOffset) {
			var offset = 0;
			var lineNode, lineNodeOffset;
			var model = this.view._model;
			var lineStart = model.getLineStart(this.lineIndex);
			var lineOffset = modelOffset - lineStart;
			var end = model.getLineEnd(this.lineIndex) - lineStart;
			this.forEach(function(lineChild) {
				var node = lineChild.firstChild;
				var nodeLength = this._nodeLength(lineChild);
				if (nodeLength + offset > lineOffset || offset + nodeLength >= end) {
					lineNode = node;
					lineNodeOffset = lineOffset - offset;
					if (lineChild.ignoreChars && nodeLength > 0 && lineNodeOffset === nodeLength) {
						lineNodeOffset += lineChild.ignoreChars; 
					}
					return false;
				}
				offset += nodeLength;
				return true;
			});
			return {node: lineNode, offset: lineNodeOffset};
		},
		getText: function(offsetNode) {
			var text = "", offset = 0;
			this.forEach(function(lineChild) {
				var textNode;
				if (lineChild.ignoreChars) {
					textNode = lineChild.lastChild;
					var ignored = 0, childText = [], childOffset = -1;
					while (textNode) {
						var data = textNode.data;
						if (data) {
							for (var i = data.length - 1; i >= 0; i--) {
								var ch = data.substring(i, i + 1);
								if (ignored < lineChild.ignoreChars && (ch === " " || ch === "\uFEFF")) { //$NON-NLS-1$ //$NON-NLS-1$
									ignored++;
								} else {
									childText.push(ch === "\u00A0" ? "\t" : ch); //$NON-NLS-1$ //$NON-NLS-1$
								}
							}
						}
						if (offsetNode === textNode) {
							childOffset = childText.length;
						}
						textNode = textNode.previousSibling;
					}
					childText = childText.reverse().join("");
					if (childOffset !== -1) {
						offset = text.length + childText.length - childOffset;
					}
					text += childText;
				} else {
					textNode = lineChild.firstChild;
					while (textNode) {
						if (offsetNode === textNode) {
							offset = text.length;
						}
						text += textNode.data;
						textNode = textNode.nextSibling;
					}
				}
				return true;
			});
			return {text: text, offset: offset};
		},
		/** @private */
		getOffset: function(x, y) {
			var view = this.view;
			var model = view._model;
			var lineIndex = this.lineIndex;
			var lineStart = model.getLineStart(lineIndex);
			var lineEnd = model.getLineEnd(lineIndex);
			if (lineStart === lineEnd) {
				return lineStart;
			}
			var child = this._ensureCreated();
			var lineRect = this.getBoundingClientRect();
			
			var that = this;
			function hitChild(lineChild, offset, rect) {
				var textNode = lineChild.firstChild;
				var nodeLength = that._nodeLength(lineChild);
				var doc = child.ownerDocument;
				var win = getWindow(doc);
				var xFactor = util.isIE < 11 ? win.screen.logicalXDPI / win.screen.deviceXDPI : 1;
				var yFactor = util.isIE < 11 ? win.screen.logicalYDPI / win.screen.deviceYDPI : 1;
				var rangeLeft, rangeTop, rangeRight, rangeBottom;
				var range, start, end;
				var rl = rect.left + lineRect.left, fixIE8, rects1;
				if (util.isIE || view._isRangeRects) {
					range = view._isRangeRects ? doc.createRange() : doc.body.createTextRange();
					var high = nodeLength;
					var low = -1;
					while ((high - low) > 1) {
						var mid = Math.floor((high + low) / 2);
						start = low + 1;
						end = mid === nodeLength - 1 && lineChild.ignoreChars ? textNode.length : mid + 1;
						/*
						* Bug in IE8. TextRange.getClientRects() and TextRange.getBoundingClientRect() fails
						* if the line child is not the first element in the line and if the start offset is 0. 
						* The fix is to use Node.getClientRects() left edge instead.
						*/
						fixIE8 = start === 0 && util.isIE === 8;
						if (view._isRangeRects) {
							range.setStart(textNode, start);
							range.setEnd(textNode, end);
						} else {
							if (fixIE8) { start = 1; } 
							range.moveToElementText(lineChild);
							range.move("character", start); //$NON-NLS-1$
							range.moveEnd("character", end - start); //$NON-NLS-1$
						}
						rects1 = range.getClientRects();
						var found = false;
						for (var k = 0; k < rects1.length; k++) {
							rect = rects1[k];
							rangeLeft = (fixIE8 ? rl : rect.left) * xFactor - lineRect.left;
							rangeRight = rect.right * xFactor - lineRect.left;
							rangeTop = rect.top * yFactor - lineRect.top;
							rangeBottom = rect.bottom * yFactor - lineRect.top;
							if (rangeLeft <= x && x < rangeRight && (!view._wrapMode || (rangeTop <= y && y <= rangeBottom))) {
								found = true;
								break;
							}
						}
						if (found) {
							high = mid;
						} else {
							low = mid;
						}
					}
					offset += high;
					start = high;
					end = high === nodeLength - 1 && lineChild.ignoreChars ? textNode.length : Math.min(high + 1, textNode.length);
					if (view._isRangeRects) {
						range.setStart(textNode, start);
						range.setEnd(textNode, end);
					} else {
						range.moveToElementText(lineChild);
						range.move("character", start); //$NON-NLS-1$
						range.moveEnd("character", end - start); //$NON-NLS-1$
					}
					rects1 = range.getClientRects();
					var trailing = false;
					if (rects1.length > 0) {
						rect = rects1[0];
						rangeLeft = (fixIE8 ? rl : rect.left) * xFactor - lineRect.left;
						rangeRight = rect.right * xFactor - lineRect.left;
						//TODO test for character trailing (wrong for bidi)
						trailing = x > (rangeLeft + (rangeRight - rangeLeft) / 2);
					}
					// Handle Unicode surrogates
					var offsetInLine = offset - lineStart;
					var lineText = model.getLine(lineIndex);
					var c = lineText.charCodeAt(offsetInLine);
					if (0xD800 <= c && c <= 0xDBFF && trailing) {
						if (offsetInLine < lineText.length) {
							c = lineText.charCodeAt(offsetInLine + 1);
							if (0xDC00 <= c && c <= 0xDFFF) {
								offset += 1;
							}
						}
					} else if (0xDC00 <= c && c <= 0xDFFF && !trailing) {
						if (offsetInLine > 0) {
							c = lineText.charCodeAt(offsetInLine - 1);
							if (0xD800 <= c && c <= 0xDBFF) {
								offset -= 1;
							}
						}
					}
					if (trailing) {
						offset++;
					}
				} else {
					var newText = [];
					for (var q = 0; q < nodeLength; q++) {
						newText.push("<span>"); //$NON-NLS-1$
						if (q === nodeLength - 1) {
							newText.push(textNode.data.substring(q));
						} else {
							newText.push(textNode.data.substring(q, q + 1));
						}
						newText.push("</span>"); //$NON-NLS-1$
					}
					lineChild.innerHTML = newText.join("");
					var rangeChild = lineChild.firstChild;
					while (rangeChild) {
						rect = rangeChild.getBoundingClientRect();
						rangeLeft = rect.left - lineRect.left;
						rangeRight = rect.right - lineRect.left;
						if (rangeLeft <= x && x < rangeRight) {
							//TODO test for character trailing (wrong for bidi)
							if (x > rangeLeft + (rangeRight - rangeLeft) / 2) {
								offset++;
							}
							break;
						}
						offset++;
						rangeChild = rangeChild.nextSibling;
					}
					if (!that._createdDiv) {
						lineChild.innerHTML = "";
						lineChild.appendChild(textNode);
						/*
						 * Removing the element node that holds the selection start or end
						 * causes the selection to be lost. The fix is to detect this case
						 * and restore the selection. 
						 */
						var s = view._getSelections()[0];
						if ((offset <= s.start && s.start < offset + nodeLength) || (offset <= s.end && s.end < offset + nodeLength)) {
							view._updateDOMSelection();
						}
					}
				}
				return offset;
			}
			
			var rects, rect;
			if (view._wrapMode) {
				rects = this.getClientRects();
				if (y < rects[0].top) {
					y = rects[0].top;
				}
				for (var i = 0; i < rects.length; i++) {
					rect = rects[i];
					if (rect.top <= y && y < rect.bottom) {
						break;
					}
				}
				if (x < rect.left) { x = rect.left; }
				if (x > rect.right) { x = rect.right - 1; }
			} else {
				if (x < 0) { x = 0; }
				if (x > (lineRect.right - lineRect.left)) { x = lineRect.right - lineRect.left; }
			}
			
			function hitRects(child) {
				if (child.ignore) return null;
				var rects1 = that._getClientRects(child, lineRect);
				for (var j = 0; j < rects1.length; j++) {
					var rect1 = rects1[j];
					if (rect1.left <= x && x < rect1.right && (!view._wrapMode || (rect1.top <= y && y <= rect1.bottom))) {
						return rect1;
					}
				}
				return null;
			}
			
			var offset, lineChild;
			if (this._lastHitChild && this._lastHitChild.parentNode) {
				// Search last hit child first, then search around the last hit child
				offset = this._lastHitOffset;
				lineChild = this._lastHitChild;
				rect = hitRects(lineChild);
				if (!rect ) {
					var previousOffset = offset, nextOffset = offset + this._nodeLength(lineChild);
					var previousChild = lineChild.previousSibling, nextChild = lineChild.nextSibling;
					while (previousChild || nextChild) {
						if (previousChild) {
							previousOffset -= this._nodeLength(previousChild);
							if ((rect = hitRects(previousChild))) {
								lineChild = previousChild;
								offset = previousOffset;
								break;
							}
							previousChild = previousChild.previousSibling;
						}
						if (nextChild) {
							if ((rect = hitRects(nextChild))) {
								lineChild = nextChild;
								offset = nextOffset;
								break;
							}
							nextOffset += this._nodeLength(nextChild);
							nextChild = nextChild.nextSibling;
						}
					}
				}
			} else {
				// Start searching from the beginning of the line
				offset = lineStart;
				this.forEach(function(c) {
					lineChild = c;
					if ((rect = hitRects(lineChild))) {
						return false;
					}
					offset += this._nodeLength(lineChild);
					return true;
				});
			}
			
			if (lineChild && rect) {
				// Cache the last hit child
				this._lastHitChild = lineChild;
				this._lastHitOffset = offset;

				offset = hitChild(lineChild, offset, rect);
			}

			return Math.min(lineEnd, Math.max(lineStart, offset));
		},
		/** @private */
		getNextOffset: function (offset, data) {
			if (data.unit === "line") { //$NON-NLS-1$
				var view = this.view;
				var model = view._model;
				var lineIndex = model.getLineAtOffset(offset);
				if (data.count > 0) {
					data.count--;
					return model.getLineEnd(lineIndex);
				}
				data.count++;
				return model.getLineStart(lineIndex);
			}
			if (data.unit === "wordend" || data.unit === "wordWS" || data.unit === "wordendWS") { //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-1$
				return this._getNextOffset_W3C(offset, data);
			}
			return util.isIE ? this._getNextOffset_IE(offset, data) : this._getNextOffset_W3C(offset, data);
		},
		/** @private */
		_getNextOffset_W3C: function (offset, data) {
			function _isPunctuation(c) {
				return (33 <= c && c <= 47) || (58 <= c && c <= 64) || (91 <= c && c <= 94) || c === 96 || (123 <= c && c <= 126);
			}
			function _isWhitespace(c) {
				return c === 32 || c === 9;
			}
			var view = this.view;
			var model = view._model;
			var lineIndex = model.getLineAtOffset(offset);
			var lineText = model.getLine(lineIndex);
			var lineStart = model.getLineStart(lineIndex);
			var lineEnd = model.getLineEnd(lineIndex);
			var lineLength = lineText.length;
			var offsetInLine = offset - lineStart;
			var c;
			var step = data.count < 0 ? -1 : 1;
			if (data.unit === "word" || data.unit === "wordend" || data.unit === "wordWS" || data.unit === "wordendWS") { //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-1$
				var previousPunctuation, previousLetterOrDigit, punctuation, letterOrDigit;
				while (data.count !== 0) {
					if (data.count > 0) {
						if (offsetInLine === lineLength) { return lineEnd; }
						c = lineText.charCodeAt(offsetInLine);
						previousPunctuation = _isPunctuation(c); 
						previousLetterOrDigit = !previousPunctuation && !_isWhitespace(c);
						offsetInLine++;
						while (offsetInLine < lineLength) {
							c = lineText.charCodeAt(offsetInLine);
							if (data.unit !== "wordWS" && data.unit !== "wordendWS") { //$NON-NLS-1$ //$NON-NLS-1$
								punctuation = _isPunctuation(c);
								if (data.unit === "wordend") { //$NON-NLS-1$
									if (!punctuation && previousPunctuation) { break; }
								} else {
									if (punctuation && !previousPunctuation) { break; }
								}
								letterOrDigit  = !punctuation && !_isWhitespace(c);
							} else {
								letterOrDigit  = !_isWhitespace(c);
							}
							if (data.unit === "wordend" || data.unit === "wordendWS") { //$NON-NLS-1$ //$NON-NLS-1$
								if (!letterOrDigit && previousLetterOrDigit) { break; }
							} else {
								if (letterOrDigit && !previousLetterOrDigit) { break; }
							}
							previousLetterOrDigit = letterOrDigit;
							previousPunctuation = punctuation;
							offsetInLine++;
						}
					} else {
						if (offsetInLine === 0) { return lineStart; }
						offsetInLine--;
						c = lineText.charCodeAt(offsetInLine);
						previousPunctuation = _isPunctuation(c); 
						previousLetterOrDigit = !previousPunctuation && !_isWhitespace(c);
						while (0 < offsetInLine) {
							c = lineText.charCodeAt(offsetInLine - 1);
							if (data.unit !== "wordWS" && data.unit !== "wordendWS") { //$NON-NLS-1$ //$NON-NLS-1$ 
								punctuation = _isPunctuation(c);
								if (data.unit === "wordend") { //$NON-NLS-1$
									if (punctuation && !previousPunctuation) { break; }
								} else {
									if (!punctuation && previousPunctuation) { break; }
								}
								letterOrDigit  = !punctuation && !_isWhitespace(c);
							} else {
								letterOrDigit  = !_isWhitespace(c);
							}
							if (data.unit === "wordend" || data.unit === "wordendWS") { //$NON-NLS-1$ //$NON-NLS-1$
								if (letterOrDigit && !previousLetterOrDigit) { break; }
							} else {
								if (!letterOrDigit && previousLetterOrDigit) { break; }
							}
							previousLetterOrDigit = letterOrDigit;
							previousPunctuation = punctuation;
							offsetInLine--;
						}
						if (offsetInLine === 0) {
							//get previous line
						}
					}
					data.count -= step;
				}
			} else {
				while (data.count !== 0 && (0 <= offsetInLine + step && offsetInLine + step <= lineLength)) {
					offsetInLine += step;
					c = lineText.charCodeAt(offsetInLine);
					// Handle Unicode surrogates
					if (0xDC00 <= c && c <= 0xDFFF) {
						if (offsetInLine > 0) {
							c = lineText.charCodeAt(offsetInLine - 1);
							if (0xD800 <= c && c <= 0xDBFF) {
								offsetInLine += step;
							}
						}
					}
					data.count -= step;
				}
			}
			return lineStart + offsetInLine;
		},
		/** @private */
		_getNextOffset_IE: function (offset, data) {
			var child = this._ensureCreated();
			var view = this.view;
			var model = view._model;
			var lineIndex = this.lineIndex;
			var result = 0, range, len;
			var lineOffset = model.getLineStart(lineIndex);
			var lineText = model.getLine(lineIndex);
			var lineStart = model.getLineStart(lineIndex);
			var doc = child.ownerDocument;
			var lineChild;
			var step = data.count < 0 ? -1 : 1;
			if (offset === model.getLineEnd(lineIndex)) {
				lineChild = child.lastChild;
				while (lineChild && lineChild.ignoreChars === lineChild.firstChild.length) {
					lineChild = lineChild.previousSibling;
				}
				if (!lineChild) {
					return lineOffset;
				}
				range = doc.body.createTextRange();
				range.moveToElementText(lineChild);
				len = range.text.length;
				range.moveEnd(data.unit, step);
				result = offset + range.text.length - len;
			} else if (offset === lineOffset && data.count < 0) {
				result = lineOffset;
			} else {
				lineChild = child.firstChild;
				while (lineChild) {
					var nodeLength = this._nodeLength(lineChild);
					if (lineOffset + nodeLength > offset) {
						range = doc.body.createTextRange();
						if (offset === lineOffset && data.count < 0) {
							var temp = lineChild.previousSibling;
							// skip empty nodes
							while (temp) {
								if (temp.firstChild && temp.firstChild.length) {
									break;
								}
								temp = temp.previousSibling;
							}
							range.moveToElementText(temp ? temp : lineChild.previousSibling);
						} else {
							range.moveToElementText(lineChild);
							range.collapse();
							range.moveEnd("character", offset - lineOffset); //$NON-NLS-1$
						}
						len = range.text.length;
						range.moveEnd(data.unit, step);
						result = offset + range.text.length - len;
						break;
					}
					lineOffset = nodeLength + lineOffset;
					lineChild = lineChild.nextSibling;
				}
			}
			var offsetInLine = result - lineStart;
			var c = lineText.charCodeAt(offsetInLine);
			// Handle Unicode surrogates
			if (0xDC00 <= c && c <= 0xDFFF) {
				if (offsetInLine > 0) {
					c = lineText.charCodeAt(offsetInLine - 1);
					if (0xD800 <= c && c <= 0xDBFF) {
						offsetInLine += step;
					}
				}
			}
			result = offsetInLine + lineStart;
			data.count -= step;
			return result;
		},
		updateLinks: function() {
			var child = this._ensureCreated();
			if (!this.hasLink) { return; }
			var that = this;
			this.forEach(function(span) {
				var style = span.viewStyle;
				if (style && style.tagName && style.tagName.toLowerCase() === "a") { //$NON-NLS-1$
					child.replaceChild(that._createSpan(child, span.firstChild.data, style), span);
				}
				return true;
			});
		},
		/** @private */
		destroy: function() {
			var div = this._createdDiv;
			if (div) {
				div.parentNode.removeChild(div);
				this._createdDiv = null;
			}
		}
	};
	
	/**
	 * @class This object describes the options for the text view.
	 * <p>
	 * <b>See:</b><br/>
	 * {@link orion.editor.TextView}<br/>
	 * {@link orion.editor.TextView#setOptions}
	 * {@link orion.editor.TextView#getOptions}	 
	 * </p>		 
	 * @name orion.editor.TextViewOptions
	 *
	 * @property {String|DOMElement} parent the parent element for the view, it can be either a DOM element or an ID for a DOM element.
	 * @property {orion.editor.TextModel} [model] the text model for the view. If it is not set the view creates an empty {@link orion.editor.TextModel}.
	 * @property {Boolean} [readonly=false] whether or not the view is read-only.
	 * @property {Boolean} [fullSelection=true] whether or not the view is in full selection mode.
	 * @property {Boolean} [tabMode=true] whether or not the tab keypress is consumed by the view or is used for focus traversal.
	 * @property {Boolean} [expandTab=false] whether or not the tab key inserts white spaces.
	 * @property {orion.editor.TextTheme} [theme=orion.editor.TextTheme.getTheme()] the TextTheme manager. TODO more info on this
	 * @property {orion.editor.UndoStack} [undoStack] the Undo Stack.
	 * @property {String} [themeClass] the CSS class for the view theming.
	 * @property {Number} [tabSize=8] The number of spaces in a tab.
	 * @property {Boolean} [overwriteMode=false] whether or not the view is in insert/overwrite mode.
	 * @property {Boolean} [singleMode=false] whether or not the editor is in single line mode.
	 * @property {Number} [marginOffset=0] the offset in a line where the print margin should be displayed. <code>0</code> means no print margin.
	 * @property {Number} [wrapOffset=0] the offset in a line where text should wrap. <code>0</code> means wrap at the client area right edge.
	 * @property {Boolean} [wrapMode=false] whether or not the view wraps lines.
	 * @property {Boolean} [wrapable=false] whether or not the view is wrappable.
	 * @property {Number} [scrollAnimation=0] the time duration in miliseconds for scrolling animation. <code>0</code> means no animation.
	 * @property {Boolean} [blockCursorVisible=false] whether or not to show the block cursor.
	 */
	/**
	 * Constructs a new text view.
	 * 
	 * @param {orion.editor.TextViewOptions} options the view options.
	 * 
	 * @class A TextView is a user interface for editing text.
	 * @name orion.editor.TextView
	 * @borrows orion.editor.EventTarget#addEventListener as #addEventListener
	 * @borrows orion.editor.EventTarget#removeEventListener as #removeEventListener
	 * @borrows orion.editor.EventTarget#dispatchEvent as #dispatchEvent
	 */
	function TextView (options) {
		this._init(options || {});
	}
	
	TextView.prototype = /** @lends orion.editor.TextView.prototype */ {
		/**
		 * Adds a keyMode to the text view at the specified position.
		 *
		 * @param {orion.editor.KeyMode} mode the editor keyMode.
		 * @param {Number} [index=length] the index.
		 */
		addKeyMode: function(mode, index) {
			var keyModes = this._keyModes;
			if (index !== undefined) {
				keyModes.splice(index, 0, mode);
			} else {
				keyModes.push(mode);
			}
			//TODO: API needed for this
			if (mode._modeAdded) {
				mode._modeAdded();
			}
		},
		/**
		 * Adds a ruler to the text view at the specified position.
		 * <p>
		 * The position is relative to the ruler location.
		 * </p>
		 *
		 * @param {orion.editor.Ruler} ruler the ruler.
		 * @param {Number} [index=length] the ruler index.
		 */
		addRuler: function (ruler, index) {
			var rulers = this._rulers;
			if (index !== undefined) {
				var i, sideIndex;
				for (i = 0, sideIndex=0; i < rulers.length && sideIndex < index; i++) {
					if (ruler.getLocation() === rulers[i].getLocation()) {
						sideIndex++;
					}
				}
				rulers.splice(sideIndex, 0, ruler);
				index = sideIndex;
			} else {
				rulers.push(ruler);
			}
			this._createRuler(ruler, index);
			ruler.setView(this);
			this._update();
		},
		computeSize: function() {
			var w = 0, h = 0;
			var model = this._model, clientDiv = this._clientDiv;
			if (!clientDiv) { return {width: w, height: h}; }
			var clientWidth = clientDiv.style.width;
			/*
			* Feature in WekKit. Webkit limits the width of the lines
			* computed below to the width of the client div.  This causes
			* the lines to be wrapped even though "pre" is set.  The fix
			* is to set the width of the client div to a "0x7fffffffpx"
			* before computing the lines width.  Note that this value is
			* reset to the appropriate value further down.
			*/
			if (util.isWebkit) {
				clientDiv.style.width = "0x7fffffffpx"; //$NON-NLS-1$
			}
			var lineCount = model.getLineCount();
			for (var lineIndex=0; lineIndex<lineCount; lineIndex++) {
				var line = this._getLine(lineIndex);
				var rect = line.getBoundingClientRect();
				w = Math.max(w, rect.right - rect.left);
				h += rect.bottom - rect.top;
				line.destroy();
			}
			if (util.isWebkit) {
				clientDiv.style.width = clientWidth;
			}
			var viewPadding = this._getViewPadding();
			w += viewPadding.right + viewPadding.left + this._metrics.scrollWidth;
			h += viewPadding.bottom + viewPadding.top + this._metrics.scrollWidth;
			return {width: w, height: h};
		},
		/**
		 * Converts the given rectangle from one coordinate spaces to another.
		 * <p>The supported coordinate spaces are:
		 * <ul>
		 *   <li>"document" - relative to document, the origin is the top-left corner of first line</li>
		 *   <li>"page" - relative to html page that contains the text view</li>
		 * </ul>
		 * </p>
		 * <p>All methods in the view that take or return a position are in the document coordinate space.</p>
		 *
		 * @param rect the rectangle to convert.
		 * @param rect.x the x of the rectangle.
		 * @param rect.y the y of the rectangle.
		 * @param rect.width the width of the rectangle.
		 * @param rect.height the height of the rectangle.
		 * @param {String} from the source coordinate space.
		 * @param {String} to the destination coordinate space.
		 *
		 * @see orion.editor.TextView#getLocationAtOffset
		 * @see orion.editor.TextView#getOffsetAtLocation
		 * @see orion.editor.TextView#getTopPixel
		 * @see orion.editor.TextView#setTopPixel
		 */
		convert: function(rect, from, to) {
			if (!this._clientDiv) { return rect; }
			var _scroll = this._getScroll();
			var viewPad = this._getViewPadding();
			var viewRect = this._viewDiv.getBoundingClientRect();
			if (from === "document") { //$NON-NLS-1$
				if (rect.x !== undefined) {
					rect.x += - _scroll.x + viewRect.left + viewPad.left;
				}
				if (rect.y !== undefined) {
					rect.y += - _scroll.y + viewRect.top + viewPad.top;
				}
			}
			//At this point rect is in the widget coordinate space
			if (to === "document") { //$NON-NLS-1$
				if (rect.x !== undefined) {
					rect.x += _scroll.x - viewRect.left - viewPad.left;
				}
				if (rect.y !== undefined) {
					rect.y += _scroll.y - viewRect.top - viewPad.top;
				}
			}
			return rect;
		},
		/**
		 * Copies the selected text to the clipboard in plain text format.
		 * @returns {Boolean} <code>true</code> if the operation succeded.
		 * @since 10.0
		 */
		copy: function() {
			if (!this._clientDiv) { return false; }
			return this._doCopy();
		},
		/**
		 * Moves the selected text to the clipboard in plain text format.
		 * @returns {Boolean} <code>true</code> if the operation succeded.
		 * @since 10.0
		 */
		cut: function() {
			if (!this._clientDiv) { return false; }
			return this._doCut();
		},
		/**
		 * Destroys the text view. 
		 * <p>
		 * Removes the view from the page and frees all resources created by the view.
		 * Calling this function causes the "Destroy" event to be fire so that all components
		 * attached to view can release their references.
		 * </p>
		 *
		 * @see orion.editor.TextView#onDestroy
		 */
		destroy: function() {
			/* Destroy rulers*/
			for (var i=0; i< this._rulers.length; i++) {
				this._rulers[i].setView(null);
			}
			this.rulers = null;
			
			this._destroyView();

			var e = {type: "Destroy"}; //$NON-NLS-1$
			this.onDestroy(e);

			this._parent = null;
			if (this._model && this._model.destroy) {
				this._model.destroy();
			}
			this._model = null;
			this._theme = null;
			this._selection = null;
			this._doubleClickSelection = null;
			this._keyModes = null;
			this._actions = null;
		},
		/**
		 * Gives focus to the text view.
		 */
		focus: function() {
			if (!this._clientDiv) { return; }
			/*
			* Feature in Chrome. When focus is called in the clientDiv without
			* setting selection the browser will set the selection to the first dom 
			* element, which can be above the client area. When this happen the 
			* browser also scrolls the window to show that element.
			* The fix is to call _updateDOMSelection() before calling focus().
			*/
			this._updateDOMSelection();
			this._clientDiv.focus();
			/*
			* Feature in Safari. When focus is called the browser selects the clientDiv
			* itself. The fix is to call _updateDOMSelection() after calling focus().
			*/
			this._updateDOMSelection();
		},
		/**
		 * Check if the text view has focus.
		 *
		 * @returns {Boolean} <code>true</code> if the text view has focus, otherwise <code>false</code>.
		 */
		hasFocus: function() {
			return this._hasFocus;
		},
		/**
		 * Returns the action description for a given action ID.
		 *
		 * @returns {orion.editor.ActionDescrition} the action description
		 */
		getActionDescription: function(actionID) {
			var action = this._actions[actionID];
			if (action) {
				return action.actionDescription;
			}
			return undefined;
		},
		/**
		 * Returns all action IDs defined in the text view.
		 * <p>
		 * There are two types of actions, the predefined actions of the view 
		 * and the actions added by application code.
		 * </p>
		 * <p>
		 * The predefined actions are:
		 * <ul>
		 *   <li>Navigation actions. These actions move the caret collapsing the selection.</li>
		 *     <ul>
		 *       <li>"lineUp" - moves the caret up by one line</li>
		 *       <li>"lineDown" - moves the caret down by one line</li>
		 *       <li>"lineStart" - moves the caret to beginning of the current line</li>
		 *       <li>"lineEnd" - moves the caret to end of the current line </li>
		 *       <li>"charPrevious" - moves the caret to the previous character</li>
		 *       <li>"charNext" - moves the caret to the next character</li>
		 *       <li>"pageUp" - moves the caret up by one page</li>
		 *       <li>"pageDown" - moves the caret down by one page</li>
		 *       <li>"wordPrevious" - moves the caret to the previous word</li>
		 *       <li>"wordNext" - moves the caret to the next word</li>
		 *       <li>"textStart" - moves the caret to the beginning of the document</li>
		 *       <li>"textEnd" - moves the caret to the end of the document</li>
		 *     </ul>
		 *   <li>Selection actions. These actions move the caret extending the selection.</li>
		 *     <ul>
		 *       <li>"selectLineUp" - moves the caret up by one line</li>
		 *       <li>"selectLineDown" - moves the caret down by one line</li>
		 *       <li>"selectLineStart" - moves the caret to beginning of the current line</li>
		 *       <li>"selectLineEnd" - moves the caret to end of the current line </li>
		 *       <li>"selectCharPrevious" - moves the caret to the previous character</li>
		 *       <li>"selectCharNext" - moves the caret to the next character</li>
		 *       <li>"selectPageUp" - moves the caret up by one page</li>
		 *       <li>"selectPageDown" - moves the caret down by one page</li>
		 *       <li>"selectWordPrevious" - moves the caret to the previous word</li>
		 *       <li>"selectWordNext" - moves the caret to the next word</li>
		 *       <li>"selectTextStart" - moves the caret to the beginning of the document</li>
		 *       <li>"selectTextEnd" - moves the caret to the end of the document</li>
		 *       <li>"selectAll" - selects the entire document</li>
		 *     </ul>
		 *   <li>Edit actions. These actions modify the text view text</li>
		 *     <ul>
		 *       <li>"deletePrevious" - deletes the character preceding the caret</li>
		 *       <li>"deleteNext" - deletes the charecter following the caret</li>
		 *       <li>"deleteWordPrevious" - deletes the word preceding the caret</li>
		 *       <li>"deleteWordNext" - deletes the word following the caret</li>
		 *       <li>"deleteLineStart" - deletes characteres to the beginning of the line</li>
		 *       <li>"deleteLineEnd" - deletes characteres to the end of the line</li>
		 *       <li>"tab" - inserts a tab character at the caret</li>
		 *       <li>"shiftTab" - noop</li>
		 *       <li>"enter" - inserts a line delimiter at the caret</li>
		 *       <li>"uppercase" - upper case the text at the caret</li>
		 *       <li>"lowercase" - lower case the text at the caret</li>
		 *       <li>"capitalize" - capitilize case the text at the caret</li>
		 *       <li>"reversecase" - reverse the case the text at the caret</li>
		 *     </ul>
		 *   <li>Clipboard actions. These actions modify the view text as well</li>
		 *     <ul>
		 *       <li>"copy" - copies the selected text to the clipboard</li>
		 *       <li>"cut" - copies the selected text to the clipboard and deletes the selection</li>
		 *       <li>"paste" - replaces the selected text with the clipboard contents</li>
		 *     </ul>
		 *   <li>Scrolling actions.</li>
		 *     <ul>
		 *       <li>"scrollLineUp" - scrolls the view up by one line</li>
		 *       <li>"scrollLineDown" - scrolls the view down by one line</li>
		 *       <li>"scrollPageUp" - scrolls the view up by one page</li>
		 *       <li>"scrollPageDown" - scrolls the view down by one page</li>
		 *       <li>"scrollTextStart" - scrolls the view to the beginning of the document</li>
		 *       <li>"scrollTextEnd" - scrolls the view to the end of the document</li>
		 *     </ul>
		 *   <li>Mode actions.</li>
		 *     <ul>
		 *       <li>"toggleTabMode" - toggles tab mode.</li>
		 *       <li>"toggleWrapMode" - toggles wrap mode.</li>
		 *       <li>"toggleOverwriteMode" - toggles overwrite mode.</li>
		 *     </ul>
		 * </ul>
		 * </p>
		 * 
		 * @param {Boolean} [defaultAction=false] whether or not the predefined actions are included.
		 * @returns {String[]} an array of action IDs defined in the text view.
		 *
		 * @see orion.editor.TextView#invokeAction
		 * @see orion.editor.TextView#setAction
		 * @see orion.editor.TextView#setKeyBinding
		 * @see orion.editor.TextView#getKeyBindings
		 */
		getActions: function (defaultAction) {
			var result = [];
			var actions = this._actions;
			for (var i in actions) {
				if (actions.hasOwnProperty(i)) {
					if (!defaultAction && actions[i].defaultHandler) { continue; }
					result.push(i);
				}
			}
			return result;
		},
		/**
		 * Returns the bottom index.
		 * <p>
		 * The bottom index is the line that is currently at the bottom of the view.  This
		 * line may be partially visible depending on the vertical scroll of the view. The parameter
		 * <code>fullyVisible</code> determines whether to return only fully visible lines. 
		 * </p>
		 *
		 * @param {Boolean} [fullyVisible=false] if <code>true</code>, returns the index of the last fully visible line. This
		 *    parameter is ignored if the view is not big enough to show one line.
		 * @returns {Number} the index of the bottom line.
		 *
		 * @see orion.editor.TextView#getTopIndex
		 * @see orion.editor.TextView#setTopIndex
		 */
		getBottomIndex: function(fullyVisible) {
			if (!this._clientDiv) { return 0; }
			return this._getBottomIndex(fullyVisible);
		},
		/**
		 * Returns the bottom pixel.
		 * <p>
		 * The bottom pixel is the pixel position that is currently at
		 * the bottom edge of the view.  This position is relative to the
		 * beginning of the document.
		 * </p>
		 *
		 * @returns {Number} the bottom pixel.
		 *
		 * @see orion.editor.TextView#getTopPixel
		 * @see orion.editor.TextView#setTopPixel
		 * @see orion.editor.TextView#convert
		 */
		getBottomPixel: function() {
			if (!this._clientDiv) { return 0; }
			return this._getScroll().y + this._getClientHeight();
		},
		/**
		 * Returns the caret offset relative to the start of the document.
		 *
		 * @returns {Number} the caret offset relative to the start of the document.
		 *
		 * @see orion.editor.TextView#setCaretOffset
		 * @see orion.editor.TextView#setSelection
		 * @see orion.editor.TextView#getSelection
		 */
		getCaretOffset: function () {
			var s = this._getSelection();
			return s.getCaret();
		},
		/**
		 * Returns the client area.
		 * <p>
		 * The client area is the portion in pixels of the document that is visible. The
		 * client area position is relative to the beginning of the document.
		 * </p>
		 *
		 * @returns {Object} the client area rectangle {x, y, width, height}.
		 *
		 * @see orion.editor.TextView#getTopPixel
		 * @see orion.editor.TextView#getBottomPixel
		 * @see orion.editor.TextView#getHorizontalPixel
		 * @see orion.editor.TextView#convert
		 */
		getClientArea: function() {
			if (!this._clientDiv) { return {x: 0, y: 0, width: 0, height: 0}; }
			var _scroll = this._getScroll();
			return {x: _scroll.x, y: _scroll.y, width: this._getClientWidth(), height: this._getClientHeight()};
		},
		/**
		 * Returns the horizontal pixel.
		 * <p>
		 * The horizontal pixel is the pixel position that is currently at
		 * the left edge of the view.  This position is relative to the
		 * beginning of the document.
		 * </p>
		 *
		 * @returns {Number} the horizontal pixel.
		 *
		 * @see orion.editor.TextView#setHorizontalPixel
		 * @see orion.editor.TextView#convert
		 */
		getHorizontalPixel: function() {
			if (!this._clientDiv) { return 0; }
			return this._getScroll().x;
		},
		/**
		 * Returns all the key bindings associated to the given action ID.
		 *
		 * @param {String} actionID the action ID.
		 * @returns {orion.KeyBinding[]} the array of key bindings associated to the given action ID.
		 *
		 * @see orion.editor.TextView#setKeyBinding
		 * @see orion.editor.TextView#setAction
		 */
		getKeyBindings: function (actionID) {
			var result = [];
			var keyModes = this._keyModes;
			for (var i = 0; i < keyModes.length; i++) {
				result = result.concat(keyModes[i].getKeyBindings(actionID));
			}
			return result;
		},
		/**
		 * Returns all the key modes added to text view.
		 *
		 * @returns {orion.editor.KeyMode[]} the array of key modes.
		 *
		 * @see orion.editor.TextView#addKeyMode
		 * @see orion.editor.TextView#removeKeyMode
		 */
		getKeyModes: function() {
			return this._keyModes.slice(0);
		},
		/**
		 * Returns the line height for a given line index.  Returns the default line
		 * height if the line index is not specified.
		 *
		 * @param {Number} [lineIndex] the line index.
		 * @returns {Number} the height of the line in pixels.
		 *
		 * @see orion.editor.TextView#getLinePixel
		 */
		getLineHeight: function(lineIndex) {
			if (!this._clientDiv) { return 0; }
			return this._getLineHeight(lineIndex);
		},
		/**
		 * Returns the line index for a given line pixel position relative to the document.
		 *
		 * @param {Number} [y] the line pixel.
		 * @returns {Number} the line index for the specified pixel position.
		 *
		 * @see orion.editor.TextView#getLinePixel
		 */
		getLineIndex: function(y) {
			if (!this._clientDiv) { return 0; }
			return this._getLineIndex(y);
		},
		/**
		 * @name isValidTextPosition
		 * @description Return whether the given x/y pixel position, relative to the document, is inside of document text. 
		 * 				This tests both whether the y position is below the text lines of the document as we as whether the
		 * 				x position is within the text of the line.
		 * @function
		 * @param x {Number} [x] the x pixel position
		 * @param y {Number} [y] the line pixel position
		 * @returns returns {Boolean} true if the pixel position is over text content
		 */
		isValidTextPosition: function(x, y){
			if (!this._clientDiv) { return false; }
			// Check if we are within a valid line
			var lineIndex = this._getLineIndex(y, true);
			if (lineIndex < 0){
				return false;
			}
			// Get the closest offset to the position
			var line = this._getLine(lineIndex);
			var offset = this.getOffsetAtLocation(x, y);
			// If the closest offset is to the left of the character's bounds then position is outside the text on the line
			var bounds = line.getBoundingClientRect(offset);
			line.destroy();
			if (x > bounds.right){
				return false;
			}
			return true;
		},
		/**
		 * Returns the top pixel position of a given line index relative to the beginning
		 * of the document.
		 * <p>
		 * Clamps out of range indices.
		 * </p>
		 *
		 * @param {Number} lineIndex the line index.
		 * @returns {Number} the pixel position of the line.
		 *
		 * @see orion.editor.TextView#setTopPixel
		 * @see orion.editor.TextView#getLineIndex
		 * @see orion.editor.TextView#convert
		 */
		getLinePixel: function(lineIndex) {
			if (!this._clientDiv) { return 0; }
			return this._getLinePixel(lineIndex);
		},
		/**
		 * Returns the {x, y} pixel location of the top-left corner of the character
		 * bounding box at the specified offset in the document.  The pixel location
		 * is relative to the document.
		 * <p>
		 * Clamps out of range offsets.
		 * </p>
		 *
		 * @param {Number} offset the character offset
		 * @returns {Object} the {x, y} pixel location of the given offset.
		 *
		 * @see orion.editor.TextView#getOffsetAtLocation
		 * @see orion.editor.TextView#convert
		 */
		getLocationAtOffset: function(offset) {
			if (!this._clientDiv) { return {x: 0, y: 0}; }
			var model = this._model;
			offset = Math.min(Math.max(0, offset), model.getCharCount());
			var lineIndex = model.getLineAtOffset(offset);
			var line = this._getLine(lineIndex);
			var rect = line.getBoundingClientRect(offset);
			line.destroy();
			var x = rect.left;
			var y = this._getLinePixel(lineIndex) + rect.top;
			return {x: x, y: y};
		},
		/**
		 * Returns the next character offset after the given offset and options
		 *
		 * @param {Number} offset the offset to start from
		 * @param {Object} options
		 *   { unit: the type of unit to advance to (eg "character", "word", "wordend", "wordWS", "wordendWS"),
		 *    count: the number of units to advance (negative to advance backwards) }
		 * @returns {Number} the next character offset
		 */
		getNextOffset: function(offset, options) {
			var selection = new Selection(offset, offset, false);
			this._doMove(options, selection);
			return selection.getCaret();
		},
		/**
		 * Returns the specified view options.
		 * <p>
		 * The returned value is either a <code>orion.editor.TextViewOptions</code> or an option value. An option value is returned when only one string parameter
		 * is specified. A <code>orion.editor.TextViewOptions</code> is returned when there are no paremeters, or the parameters are a list of options names or a
		 * <code>orion.editor.TextViewOptions</code>. All view options are returned when there no paremeters.
		 * </p>
		 *
		 * @param {String|orion.editor.TextViewOptions} [options] The options to return.
		 * @return {Object|orion.editor.TextViewOptions} The requested options or an option value.
		 *
		 * @see orion.editor.TextView#setOptions
		 */
		getOptions: function() {
			var options;
			if (arguments.length === 0) {
				options = this._defaultOptions();
			} else if (arguments.length === 1) {
				var arg = arguments[0];
				if (typeof arg === "string") { //$NON-NLS-1$
					return clone(this["_" + arg]); //$NON-NLS-1$
				}
				options = arg;
			} else {
				options = {};
				for (var index in arguments) {
					if (arguments.hasOwnProperty(index)) {
						options[arguments[index]] = undefined;
					}
				}
			}
			for (var option in options) {
				if (options.hasOwnProperty(option)) {
					options[option] = clone(this["_" + option]); //$NON-NLS-1$
				}
			}
			return options;
		},
		/**
		 * Returns the text model of the text view.
		 *
		 * @returns {orion.editor.TextModel} the text model of the view.
		 */
		getModel: function() {
			return this._model;
		},
		/**
		 * Returns the character offset nearest to the given pixel location.  The
		 * pixel location is relative to the document.
		 *
		 * @param x the x of the location
		 * @param y the y of the location
		 * @returns {Number} the character offset at the given location.
		 *
		 * @see orion.editor.TextView#getLocationAtOffset
		 */
		getOffsetAtLocation: function(x, y) {
			if (!this._clientDiv) { return 0; }
			var lineIndex = this._getLineIndex(y);
			var line = this._getLine(lineIndex);
			var offset = line.getOffset(x, y - this._getLinePixel(lineIndex));
			line.destroy();
			return offset;
		},
		/**
		 * @name getLineAtOffset
		 * @description Compute the editor line number for the given offset
		 * @function
		 * @public
		 * @memberof orion.editor.TextView
		 * @param {Number} offset The offset into the editor
		 * @returns {Number} Returns the line number in the editor corresponding to the given offset or <code>-1</code> if the offset is 
		 * out of range
		 * @since 5.0
		 */
		getLineAtOffset: function(offset) {
			return this.getModel().getLineAtOffset(offset);
		},
		/**
		 * @name getLineStart
		 * @description Compute the editor start offset of the given line number
		 * @function
		 * @public
		 * @memberof orion.editor.TextView
		 * @param {Number} line The line number in the editor
		 * @returns {Number} Returns the start offset of the given line number in the editor.
		 * @since 5.0
		 */
		getLineStart: function(line) {
			return this.getModel().getLineStart(line);
		},
		/**
		 * Get the view rulers.
		 *
		 * @returns {orion.editor.Ruler[]} the view rulers
		 *
		 * @see orion.editor.TextView#addRuler
		 */
		getRulers: function() {
			return this._rulers.slice(0);
		},
		/**
		 * Returns the text view selection.
		 * <p>
		 * The selection is defined by a start and end character offset relative to the
		 * document. The character at end offset is not included in the selection.
		 * </p>
		 * 
		 * @returns {orion.editor.Selection} the view selection
		 *
		 * @see orion.editor.TextView#setSelection
		 */
		getSelection: function () {
			return this._getSelection();
		},
		getSelections: function () {
			return this._getSelections();
		},
		/**
		 * Returns the text view selection text.
		 * <p>
		 * If there are multiple selection ranges, the result is concatenated with the specified delimiter.
		 * </p>
		 * 
		 * @param {String} delimiter The offset into the editor
		 * @returns {String} the view selection text
		 * @since 10.0
		 * @see orion.editor.TextView#setSelection
		 */
		getSelectionText: function(delimiter) {
			var text = [];
			var that = this;
			var selections = this._getSelections();
			selections.forEach(function(selection) {
				if (!selection.isEmpty()) {
					text.push(that._getBaseText(selection.start, selection.end));
				}
			});
			return text.join(delimiter !== undefined ? delimiter : this._model.getLineDelimiter());
		},
		/**
		 * Returns the text for the given range.
		 * <p>
		 * The text does not include the character at the end offset.
		 * </p>
		 *
		 * @param {Number} [start=0] the start offset of text range.
		 * @param {Number} [end=char count] the end offset of text range.
		 *
		 * @see orion.editor.TextView#setText 	
		 */
		getText: function(start, end) {
			var model = this._model;
			return model.getText(start, end);
		},
		/**
		 * Returns the top index.
		 * <p>
		 * The top index is the line that is currently at the top of the view.  This
		 * line may be partially visible depending on the vertical scroll of the view. The parameter
		 * <code>fullyVisible</code> determines whether to return only fully visible lines. 
		 * </p>
		 *
		 * @param {Boolean} [fullyVisible=false] if <code>true</code>, returns the index of the first fully visible line. This
		 *    parameter is ignored if the view is not big enough to show one line.
		 * @returns {Number} the index of the top line.
		 *
		 * @see orion.editor.TextView#getBottomIndex
		 * @see orion.editor.TextView#setTopIndex
		 */
		getTopIndex: function(fullyVisible) {
			if (!this._clientDiv) { return 0; }
			return this._getTopIndex(fullyVisible);
		},
		/**
		 * Returns the top pixel.
		 * <p>
		 * The top pixel is the pixel position that is currently at
		 * the top edge of the view.  This position is relative to the
		 * beginning of the document.
		 * </p>
		 *
		 * @returns {Number} the top pixel.
		 *
		 * @see orion.editor.TextView#getBottomPixel
		 * @see orion.editor.TextView#setTopPixel
		 * @see orion.editor.TextView#convert
		 */
		getTopPixel: function() {
			if (!this._clientDiv) { return 0; }
			return this._getScroll().y;
		},
		/**
		 * Executes the action handler associated with the given action ID.
		 * <p>
		 * The application defined action takes precedence over predefined actions unless
		 * the <code>defaultAction</code> paramater is <code>true</code>.
		 * </p>
		 * <p>
		 * If the application defined action returns <code>false</code>, the text view predefined
		 * action is executed if present.
		 * </p>
		 *
		 * @param {String} actionID the action ID.
		 * @param {Boolean} [defaultAction] whether to always execute the predefined action only.
		 * @param {Object} [actionOptions] action specific options to be passed to the action handlers.
		 * @returns {Boolean} <code>true</code> if the action was executed.
		 *
		 * @see orion.editor.TextView#setAction
		 * @see orion.editor.TextView#getActions
		 */
		invokeAction: function (actionID, defaultAction, actionOptions) {
			if (!this._clientDiv) { return; }
			var action = this._actions[actionID];
			if (action) {
				if (action.actionDescription && action.actionDescription.id) {
					mMetrics.logEvent("editor", "action", action.actionDescription.id); //$NON-NLS-1$ //$NON-NLS-2$
				}
				if (!defaultAction && action.handler) {
					if (action.handler(actionOptions)) {
						return true;
					}
				}
				if (action.defaultHandler) {
					return typeof action.defaultHandler(actionOptions) === "boolean"; //$NON-NLS-1$
				}
			}
			return false;
		},
		/**
		* Returns if the view is destroyed.
		* @returns {Boolean} <code>true</code> if the view is destroyed.
		*/
		isDestroyed: function () {
			return !this._clientDiv;
		},
		/** 
		 * @class This is the event sent when the user right clicks or otherwise invokes the context menu of the view. 
		 * <p> 
		 * <b>See:</b><br/> 
		 * {@link orion.editor.TextView}<br/> 
		 * {@link orion.editor.TextView#event:onContextMenu} 
		 * </p> 
		 * 
		 * @name orion.editor.ContextMenuEvent 
		 * 
		 * @property {Number} x The pointer location on the x axis, relative to the document the user is editing. 
		 * @property {Number} y The pointer location on the y axis, relative to the document the user is editing. 
		 * @property {Number} screenX The pointer location on the x axis, relative to the screen. This is copied from the DOM contextmenu event.screenX property. 
		 * @property {Number} screenY The pointer location on the y axis, relative to the screen. This is copied from the DOM contextmenu event.screenY property. 
		 * @property {Boolean} defaultPrevented Determines whether the user agent context menu should be shown. It is shown by default.
		 * @property {Function} preventDefault If called prevents the user agent context menu from showing.
		 */ 
		/** 
		 * This event is sent when the user invokes the view context menu. 
		 * 
		 * @event 
		 * @param {orion.editor.ContextMenuEvent} contextMenuEvent the event 
		 */ 
		onContextMenu: function(contextMenuEvent) {
			return this.dispatchEvent(contextMenuEvent); 
		}, 
		onDragStart: function(dragEvent) {
			return this.dispatchEvent(dragEvent);
		},
		onDrag: function(dragEvent) {
			return this.dispatchEvent(dragEvent);
		},
		onDragEnd: function(dragEvent) {
			return this.dispatchEvent(dragEvent);
		},
		onDragEnter: function(dragEvent) {
			return this.dispatchEvent(dragEvent);
		},
		onDragOver: function(dragEvent) {
			return this.dispatchEvent(dragEvent);
		},
		onDragLeave: function(dragEvent) {
			return this.dispatchEvent(dragEvent);
		},
		onDrop: function(dragEvent) {
			return this.dispatchEvent(dragEvent);
		},
		/**
		 * @class This is the event sent when the text view is destroyed.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onDestroy}
		 * </p>
		 * @name orion.editor.DestroyEvent
		 */
		/**
		 * This event is sent when the text view has been destroyed.
		 *
		 * @event
		 * @param {orion.editor.DestroyEvent} destroyEvent the event
		 *
		 * @see orion.editor.TextView#destroy
		 */
		onDestroy: function(destroyEvent) {
			return this.dispatchEvent(destroyEvent);
		},
		/**
		 * @description This event is sent when the file is being saved
		 * @function
		 * @param {Object} savingEvent the event
		 * @since 8.0
		 */
		onSaving: function onSaving(savingEvent) {
		    return this.dispatchEvent(savingEvent);
		},
		/**
		 * @description This event is sent when the file has been saved
		 * @function
		 * @param {Object} inputChangedEvent the event
		 * @since 8.0
		 */
		onInputChanged: function onInputChanged(inputChangedEvent) {
		    return this.dispatchEvent(inputChangedEvent);
		},
		/**
		 * @class This object is used to define style information for the text view.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onLineStyle}
		 * </p>		 
		 * @name orion.editor.Style
		 * 
		 * @property {String} styleClass A CSS class name.
		 * @property {Object} style An object with CSS properties.
		 * @property {String} tagName A DOM tag name.
		 * @property {Object} attributes An object with DOM attributes.
		 */
		/**
		 * @class This object is used to style range.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onLineStyle}
		 * </p>		 
		 * @name orion.editor.StyleRange
		 * 
		 * @property {Number} start The start character offset, relative to the document, where the style should be applied.
		 * @property {Number} end The end character offset (exclusive), relative to the document, where the style should be applied.
		 * @property {orion.editor.Style} style The style for the range.
		 */
		/**
		 * @class This is the event sent when the text view needs the style information for a line.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onLineStyle}
		 * </p>		 
		 * @name orion.editor.LineStyleEvent
		 * 
		 * @property {orion.editor.TextView} textView The text view.		 
		 * @property {Number} lineIndex The line index.
		 * @property {String} lineText The line text.
		 * @property {Number} lineStart The character offset, relative to document, of the first character in the line.
		 * @property {orion.editor.Style} style The style for the entire line (output argument).
		 * @property {orion.editor.StyleRange[]} ranges An array of style ranges for the line (output argument).		 
		 */
		/**
		 * This event is sent when the text view needs the style information for a line.
		 *
		 * @event
		 * @param {orion.editor.LineStyleEvent} lineStyleEvent the event
		 */
		onLineStyle: function(lineStyleEvent) {
			return this.dispatchEvent(lineStyleEvent);
		},
		/**
		 * @class This is the event sent for all keyboard events.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onKeyDown}<br/>
		 * {@link orion.editor.TextView#event:onKeyPress}<br/>
		 * {@link orion.editor.TextView#event:onKeyUp}<br/>
		 * </p>
		 * @name orion.editor.KeyEvent
		 * 
		 * @property {String} type The type of event.
		 * @property {DOMEvent} event The key DOM event.
		 * @property {Boolean} defaultPrevented Determines whether the user agent context menu should be shown. It is shown by default.
		 * @property {Function} preventDefault If called prevents the user agent context menu from showing.
		 */
		/**
		 * This event is sent for key down events.
		 *
		 * @event
		 * @param {orion.editor.KeyEvent} keyEvent the event
		 */
		onKeyDown: function(keyEvent) {
			return this.dispatchEvent(keyEvent);
		},
		/**
		 * This event is sent for key press events. Key press events are only sent
		 * for printable characters.
		 *
		 * @event
		 * @param {orion.editor.KeyEvent} keyEvent the event
		 */
		onKeyPress: function(keyEvent) {
			return this.dispatchEvent(keyEvent);
		},
		/**
		 * This event is sent for key up events.
		 *
		 * @event
		 * @param {orion.editor.KeyEvent} keyEvent the event
		 */
		onKeyUp: function(keyEvent) {
			return this.dispatchEvent(keyEvent);
		},
		/**
		 * @class This is the event sent when the text in the model has changed.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onModelChanged}<br/>
		 * {@link orion.editor.TextModel#onChanged}
		 * </p>
		 * @name orion.editor.ModelChangedEvent
		 * 
		 * @property {Number} start The character offset in the model where the change has occurred.
		 * @property {Number} removedCharCount The number of characters removed from the model.
		 * @property {Number} addedCharCount The number of characters added to the model.
		 * @property {Number} removedLineCount The number of lines removed from the model.
		 * @property {Number} addedLineCount The number of lines added to the model.
		 */
		/**
		 * This event is sent when the text in the model has changed.
		 *
		 * @event
		 * @param {orion.editor.ModelChangedEvent} modelChangedEvent the event
		 */
		onModelChanged: function(modelChangedEvent) {
			return this.dispatchEvent(modelChangedEvent);
		},
		/**
		 * @class This is the event sent when the text in the model is about to change.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onModelChanging}<br/>
		 * {@link orion.editor.TextModel#onChanging}
		 * </p>
		 * @name orion.editor.ModelChangingEvent
		 * 
		 * @property {String} text The text that is about to be inserted in the model.
		 * @property {Number} start The character offset in the model where the change will occur.
		 * @property {Number} removedCharCount The number of characters being removed from the model.
		 * @property {Number} addedCharCount The number of characters being added to the model.
		 * @property {Number} removedLineCount The number of lines being removed from the model.
		 * @property {Number} addedLineCount The number of lines being added to the model.
		 */
		/**
		 * This event is sent when the text in the model is about to change.
		 *
		 * @event
		 * @param {orion.editor.ModelChangingEvent} modelChangingEvent the event
		 */
		onModelChanging: function(modelChangingEvent) {
			return this.dispatchEvent(modelChangingEvent);
		},
		/**
		 * @class This is the event sent when the text is modified by the text view.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onModify}
		 * </p>
		 * @name orion.editor.ModifyEvent
		 */
		/**
		 * This event is sent when the text view has changed text in the model.
		 * <p>
		 * If the text is changed directly through the model API, this event
		 * is not sent.
		 * </p>
		 *
		 * @event
		 * @param {orion.editor.ModifyEvent} modifyEvent the event
		 */
		onModify: function(modifyEvent) {
			return this.dispatchEvent(modifyEvent);
		},
		onMouseDown: function(mouseEvent) {
			return this.dispatchEvent(mouseEvent);
		},
		onMouseUp: function(mouseEvent) {
			return this.dispatchEvent(mouseEvent);
		},
		onMouseMove: function(mouseEvent) {
			return this.dispatchEvent(mouseEvent);
		},
		onMouseOver: function(mouseEvent) {
			return this.dispatchEvent(mouseEvent);
		},
		onMouseOut: function(mouseEvent) {
			return this.dispatchEvent(mouseEvent);
		},
		onTouchStart: function(touchEvent) {
			return this.dispatchEvent(touchEvent);
		},
		onTouchMove: function(touchEvent) {
			return this.dispatchEvent(touchEvent);
		},
		onTouchEnd: function(touchEvent) {
			return this.dispatchEvent(touchEvent);
		},
		onOptions: function(optionsEvent) {
			return this.dispatchEvent(optionsEvent);
		},
		/**
		 * @class This is the event sent when the selection changes in the text view.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onSelection}
		 * </p>		 
		 * @name orion.editor.SelectionEvent
		 * 
		 * @property {orion.editor.Selection} oldValue The old selection.
		 * @property {orion.editor.Selection} newValue The new selection.
		 */
		/**
		 * This event is sent when the text view selection has changed.
		 *
		 * @event
		 * @param {orion.editor.SelectionEvent} selectionEvent the event
		 */
		onSelection: function(selectionEvent) {
			return this.dispatchEvent(selectionEvent);
		},
		/**
		 * @class This is the event sent when the text view scrolls.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onScroll}
		 * </p>		 
		 * @name orion.editor.ScrollEvent
		 * 
		 * @property {Object} oldValue The old scroll {x,y}.
		 * @property {Object} newValue The new scroll {x,y}.
		 */
		/**
		 * This event is sent when the text view scrolls vertically or horizontally.
		 *
		 * @event
		 * @param {orion.editor.ScrollEvent} scrollEvent the event
		 */
		onScroll: function(scrollEvent) {
			return this.dispatchEvent(scrollEvent);
		},
		/**
		 * @class This is the event sent when the text is about to be modified by the text view.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onVerify}
		 * </p>
		 * @name orion.editor.VerifyEvent
		 * 
		 * @property {String} text The text being inserted.
		 * @property {Number} start The start offset of the text range to be replaced.
		 * @property {Number} end The end offset (exclusive) of the text range to be replaced.
		 */
		/**
		 * This event is sent when the text view is about to change text in the model.
		 * <p>
		 * If the text is changed directly through the model API, this event
		 * is not sent.
		 * </p>
		 * <p>
		 * Listeners are allowed to change these parameters. Setting text to null
		 * or undefined stops the change.
		 * </p>
		 *
		 * @event
		 * @param {orion.editor.VerifyEvent} verifyEvent the event
		 */
		onVerify: function(verifyEvent) {
			return this.dispatchEvent(verifyEvent);
		},
		/**
		 * @class This is the event sent when the text view is focused.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onFocus}<br/>
		 * </p>
		 * @name orion.editor.FocusEvent
		 */
		/**
		 * This event is sent when the text view is focused.
		 *
		 * @event
		 * @param {orion.editor.FocusEvent} focusEvent the event
		 */
		onFocus: function(focusEvent) {
			return this.dispatchEvent(focusEvent);
		},
		/**
		 * @class This is the event sent when the text view goes out of focus.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#event:onBlur}<br/>
		 * </p>
		 * @name orion.editor.BlurEvent
		 */
		/**
		 * This event is sent when the text view goes out of focus.
		 *
		 * @event
		 * @param {orion.editor.BlurEvent} blurEvent the event
		 */
		onBlur: function(blurEvent) {
			return this.dispatchEvent(blurEvent);
		},
		/**
		 * Replaces the selection with the text on the clipboard or, if there is no selection, inserts the text at the current caret offset.
		 * <p>
		 * If the single mode is on and the clipboard text contains more than one line, all lines will be concatenated.
		 * </p>
		 * @returns {Boolean} <code>true</code> if the operation succeded.
		 * @since 10.0
		 */
		paste: function() {
			if (!this._clientDiv) { return false; }
			return this._doPaste();
		},
		/**
		 * Redraws the entire view, including rulers.
		 *
		 * @see orion.editor.TextView#redrawLines
		 * @see orion.editor.TextView#redrawRange
		 * @see orion.editor.TextView#setRedraw
		 */
		redraw: function() {
			if (this._redrawCount > 0) { return; }
			var lineCount = this._model.getLineCount();
			this.redrawRulers(0, lineCount);
			this.redrawLines(0, lineCount); 
		},
		redrawRulers: function(startLine, endLine) {
			if (this._redrawCount > 0) { return; }
			var rulers = this.getRulers();
			for (var i = 0; i < rulers.length; i++) {
				this.redrawLines(startLine, endLine, rulers[i]);
			}
		},
		/**
		 * Redraws the text in the given line range.
		 * <p>
		 * The line at the end index is not redrawn.
		 * </p>
		 *
		 * @param {Number} [startLine=0] the start line
		 * @param {Number} [endLine=line count] the end line
		 *
		 * @see orion.editor.TextView#redraw
		 * @see orion.editor.TextView#redrawRange
		 * @see orion.editor.TextView#setRedraw
		 */
		redrawLines: function(startLine, endLine, ruler) {
			if (this._redrawCount > 0) { return; }
			if (startLine === undefined) { startLine = 0; }
			if (endLine === undefined) { endLine = this._model.getLineCount(); }
			if (startLine === endLine) { return; }
			var div = this._clientDiv;
			if (!div) { return; }
			if (ruler) {
				var divRuler = this._getRulerParent(ruler);
				div = divRuler.firstChild;
				while (div) {
					if (div._ruler === ruler) {
						break;
					}
					div = div.nextSibling;
				}
			}
			if (ruler) {
				div.rulerChanged = true;
			} else {
				if (this._lineHeight) {
					this._resetLineHeight(startLine, endLine);
				}
			}
			var imeLineIndex = -1;
			if (!ruler && this._imeOffset !== -1) {
				imeLineIndex = this._model.getLineAtOffset(this._imeOffset);
			}
			if (!ruler || ruler.getOverview() === "page") { //$NON-NLS-1$
				var child = div.firstChild;
				while (child) {
					var lineIndex = child.lineIndex;
					if (startLine <= lineIndex && lineIndex < endLine && lineIndex !== imeLineIndex) {
						child.lineChanged = true;
					}
					child = child.nextSibling;
				}
			}
			if (!ruler) {
				if (!this._wrapMode) {
					if (startLine <= this._maxLineIndex && this._maxLineIndex < endLine) {
						this._checkMaxLineIndex = this._maxLineIndex;
						this._maxLineIndex = -1;
						this._maxLineWidth = 0;
					}
				}
			}
			this.dispatchEvent({type: "Redraw", startLine: startLine, endLine: endLine, ruler: ruler}); //$NON-NLS-1$
			this._queueUpdate();
		},
		/**
		 * Redraws the text in the given range.
		 * <p>
		 * The character at the end offset is not redrawn.
		 * </p>
		 *
		 * @param {Number} [start=0] the start offset of text range
		 * @param {Number} [end=char count] the end offset of text range
		 *
		 * @see orion.editor.TextView#redraw
		 * @see orion.editor.TextView#redrawLines
		 * @see orion.editor.TextView#setRedraw
		 */
		redrawRange: function(start, end) {
			if (this._redrawCount > 0) { return; }
			var model = this._model;
			if (start === undefined) { start = 0; }
			if (end === undefined) { end = model.getCharCount(); }
			var startLine = model.getLineAtOffset(start);
			var endLine = model.getLineAtOffset(Math.max(start, end - 1)) + 1;
			this.redrawLines(startLine, endLine);
		},	
		/**
		 * Removes a key mode from the text view.
		 *
		 * @param {orion.editor.KeyMode} mode the key mode.
		 */
		removeKeyMode: function (mode) {
			var keyModes = this._keyModes;
			for (var i=0; i<keyModes.length; i++) {
				if (keyModes[i] === mode) {
					keyModes.splice(i, 1);
					break;
				}
			}
			//TODO: API needed for this
			if (mode._modeRemoved) {
				mode._modeRemoved();
			}
		},
		/**
		 * Removes a ruler from the text view.
		 *
		 * @param {orion.editor.Ruler} ruler the ruler.
		 */
		removeRuler: function (ruler) {
			var rulers = this._rulers;
			for (var i=0; i<rulers.length; i++) {
				if (rulers[i] === ruler) {
					rulers.splice(i, 1);
					ruler.setView(null);
					this._destroyRuler(ruler);
					this._update();
					break;
				}
			}
		},
		resize: function() {
			if (!this._clientDiv) { return; }
			this._handleResize(null);
		},
		/**
		 * @class This object describes an action for the text view.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView}<br/>
		 * {@link orion.editor.TextView#setAction}
		 * </p>		 
		 * @name orion.editor.ActionDescription
		 *
		 * @property {String} [name] the name to be used when showing the action as text.
		 */
		/**
		 * Associates an application defined handler to an action ID.
		 * <p>
		 * If the action ID is a predefined action, the given handler executes before
		 * the default action handler.  If the given handler returns <code>true</code>, the
		 * default action handler is not called.
		 * </p>
		 *
		 * @param {String} actionID the action ID.
		 * @param {Function} handler the action handler.
		 * @param {orion.editor.ActionDescription} [actionDescription=undefined] the action description.
		 *
		 * @see orion.editor.TextView#getActions
		 * @see orion.editor.TextView#invokeAction
		 */
		setAction: function(actionID, handler, actionDescription) {
			if (!actionID) { return; }
			var actions = this._actions;
			var action = actions[actionID];
			if (!action) { 
				action = actions[actionID] = {};
			}
			action.handler = handler;
			if (actionDescription !== undefined) {
				action.actionDescription = actionDescription;
			}
		},
		/**
		 * Associates a key binding with the given action ID. Any previous
		 * association with the specified key binding is overwriten. If the
		 * action ID is <code>null</code>, the association is removed.
		 * 
		 * @param {orion.KeyBinding} keyBinding the key binding
		 * @param {String} actionID the action ID
		 */
		setKeyBinding: function(keyBinding, actionID) {
			this._keyModes[0].setKeyBinding(keyBinding, actionID);
		},
		/**
		 * Sets the caret offset relative to the start of the document.
		 *
		 * @param {Number} caret the caret offset relative to the start of the document.
		 * @param {Boolean|Number|orion.editor.TextViewShowOptions} [show=true]
		 * 					if <code>true</code>, the view will scroll the minimum amount necessary to show the caret location. If
		 *					<code>show</code> is a <code>Number</code>, the view will scroll the minimum amount necessary to show the caret location plus a
		 *					percentage of the client area height. The parameter is clamped to the [0,1] range.  In either case, the view will only scroll
		 *					if the new caret location is not visible already.  The <code>show</code> parameter can also be a <code>orion.editor.TextViewShowOptions</code> object. See
		 * 					{@link orion.editor.TextViewShowOptions} for further information in how the options can be used to control the scrolling behavior.
		 * @param {Function} [callback] if callback is specified and <code>scrollAnimation</code> is not zero, view scrolling is animated and
		 *					the callback is called when the animation is done. Otherwise, callback is callback right away.
		 *
		 * @see orion.editor.TextView#getCaretOffset
		 * @see orion.editor.TextView#setSelection
		 * @see orion.editor.TextView#getSelection
		 */
		setCaretOffset: function(offset, show, callback) {
			var charCount = this._model.getCharCount();
			offset = Math.max(0, Math.min (offset, charCount));
			var selection = new Selection(offset, offset, false);
			this._setSelection (selection, show === undefined || show, true, callback);
		},
		/**
		 * Sets the horizontal pixel.
		 * <p>
		 * The horizontal pixel is the pixel position that is currently at
		 * the left edge of the view.  This position is relative to the
		 * beginning of the document.
		 * </p>
		 *
		 * @param {Number} pixel the horizontal pixel.
		 *
		 * @see orion.editor.TextView#getHorizontalPixel
		 * @see orion.editor.TextView#convert
		 */
		setHorizontalPixel: function(pixel) {
			if (!this._clientDiv) { return; }
			pixel = Math.max(0, pixel);
			this._scrollView(pixel - this._getScroll().x, 0);
		},
		/**
		 * Sets whether the view should update the DOM.
		 * <p>
		 * This can be used to improve the performance.
		 * </p><p>
		 * When the flag is set to <code>true</code>,
		 * the entire view is marked as needing to be redrawn. 
		 * Nested calls to this method are stacked.
		 * </p>
		 *
		 * @param {Boolean} redraw the new redraw state
		 * 
		 * @see orion.editor.TextView#redraw
		 */
		setRedraw: function(redraw) {
			if (redraw) {
				if (--this._redrawCount === 0) {
					this.redraw();
				}
			} else {
				this._redrawCount++;
			}
		},
		/**
		 * Sets the text model of the text view.
		 *
		 * @param {orion.editor.TextModel} model the text model of the view.
		 */
		setModel: function(model) {
			if (model === this._model) { return; }
			model = model || new mTextModel.TextModel();
			this._model.removeEventListener("preChanging", this._modelListener.onChanging); //$NON-NLS-1$
			this._model.removeEventListener("postChanged", this._modelListener.onChanged); //$NON-NLS-1$
			var oldLineCount = this._model.getLineCount();
			var oldCharCount = this._model.getCharCount();
			var newLineCount = model.getLineCount();
			var newCharCount = model.getCharCount();
			var newText = model.getText();
			var e = {
				type: "ModelChanging", //$NON-NLS-1$
				text: newText,
				start: 0,
				removedCharCount: oldCharCount,
				addedCharCount: newCharCount,
				removedLineCount: oldLineCount,
				addedLineCount: newLineCount
			};
			this.onModelChanging(e);
			this._model = model;
			e = {
				type: "ModelChanged", //$NON-NLS-1$
				start: 0,
				removedCharCount: oldCharCount,
				addedCharCount: newCharCount,
				removedLineCount: oldLineCount,
				addedLineCount: newLineCount
			};
			this.onModelChanged(e); 
			this._model.addEventListener("preChanging", this._modelListener.onChanging); //$NON-NLS-1$
			this._model.addEventListener("postChanged", this._modelListener.onChanged); //$NON-NLS-1$
			this._reset();
			this._update();
		},
		/**
		 * Sets the view options for the view.
		 *
		 * @param {orion.editor.TextViewOptions} options the view options.
		 * 
		 * @see orion.editor.TextView#getOptions
		 */
		setOptions: function (options) {
			var defaultOptions = this._defaultOptions();
			for (var option in options) {
				if (options.hasOwnProperty(option)) {
					var newValue = options[option], oldValue = this["_" + option]; //$NON-NLS-1$
					if (compare(oldValue, newValue)) { continue; }
					var update = defaultOptions[option] ? defaultOptions[option].update : null;
					if (update) {
						update.call(this, newValue);
						continue;
					}
					this["_" + option] = clone(newValue); //$NON-NLS-1$
				}
			}
			this.onOptions({type: "Options", options: options}); //$NON-NLS-1$
		},
		/**
		 * @class This object describes the selection show options.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.TextView#setSelection}
		 * {@link orion.editor.TextView#setCaretOffset}	 
		 * {@link orion.editor.TextView#showSelection}	 
		 * </p>		 
		 * @name orion.editor.TextViewShowOptions
		 *
		 * @property {String} viewAnchor the view anchor.  The view anchor can be one of these values:
		 * <p>
		 * <ul>
		 *   <li>"top" - align the selection to the top of the view client area.</li>
		 *   <li>"bottom" - align the selection to the bottom of the view client area.</li>
		 *   <li>"center" - align the selection to the center of the view client area.</li>
		 *   <li> by default - align the selection to the top or bottom of the client area depending on whether the caret is above or below the client area respectively. </li>
		 * </ul>
		 * </p>
		 * @property {Number} [viewAnchorOffset=0] an offset from the view anchor. The offset is a percentage of the client area height and it is clamped to [0-1] range.
		 * @property {String} [selectionAnchor=caret] the selection anchor. The seleciton anchor can be one of these values:
		 * <p>
		 * <ul>
		 *   <li>"top" - align the top of the selection to the view anchor.</li>
		 *   <li>"bottom" - align the bottom of the selection to the view anchor.</li>
		 *   <li>"center" - align the center of the selection to the view anchor.</li>
		 *   <li> by default - align the top or bottom of the selection to the view anchor depending on whether the caret is at the start or end of the selection. </li>
		 * </ul>
		 * </p>
		 * @property {String} [scrollPolicy] the scroll policy. The scroll policy can be one of these values:
		 * <p>
		 * <ul>
		 *   <li>"always" - always scroll vertically to the desired pixel offset even if the caret is already visible.</li>
		 *   <li> by default - only scroll if the caret is not visible. </li>
		 * </ul>
		 * </p>
		 */
		/**
		 * Sets the text view selection.
		 * <p>
		 * The selection is defined by a start and end character offset relative to the
		 * document. The character at end offset is not included in the selection.
		 * </p>
		 * <p>
		 * The caret is always placed at the end offset. The start offset can be
		 * greater than the end offset to place the caret at the beginning of the
		 * selection.
		 * </p>
		 * <p>
		 * Clamps out of range offsets.
		 * </p>
		 * 
		 * @param {Number} start the start offset of the selection
		 * @param {Number} end the end offset of the selection
		 * @param {Boolean|Number|orion.editor.TextViewShowOptions} [show=true]
		 * 					if <code>true</code>, the view will scroll the minimum amount necessary to show the caret location. If
		 *					<code>show</code> is a <code>Number</code>, the view will scroll the minimum amount necessary to show the caret location plus a
		 *					percentage of the client area height. The parameter is clamped to the [0,1] range.  In either case, the view will only scroll
		 *					if the new caret location is not visible already.  The <code>show</code> parameter can also be a <code>orion.editor.TextViewShowOptions</code> object. See
		 * 					{@link orion.editor.TextViewShowOptions} for further information in how the options can be used to control the scrolling behavior.
		 * @param {Function} [callback] if callback is specified and <code>scrollAnimation</code> is not zero, view scrolling is animated and
		 *					the callback is called when the animation is done. Otherwise, callback is callback right away.
		 *
		 * @see orion.editor.TextView#getSelection
		 */
		setSelection: function (start, end, show, callback) {
			var caret = start > end;
			if (caret) {
				var tmp = start;
				start = end;
				end = tmp;
			}
			var charCount = this._model.getCharCount();
			start = Math.max(0, Math.min (start, charCount));
			end = Math.max(0, Math.min (end, charCount));
			var selection = new Selection(start, end, caret);
			this._setSelection(selection, show === undefined || show, true, callback);
		},
		setSelections: function (ranges, show, callback) {
			var selections = this._rangesToSelections(ranges);
			this._setSelection(selections, show === undefined || show, true, callback);
		},
		/**
		 * Replaces the text in the given range with the given text.
		 * <p>
		 * The character at the end offset is not replaced.
		 * </p>
		 * <p>
		 * When both <code>start</code> and <code>end</code> parameters
		 * are not specified, the text view places the caret at the beginning
		 * of the document and scrolls to make it visible.
		 * </p>
		 *
		 * @param {String} text the new text.
		 * @param {Number} [start=0] the start offset of text range.
		 * @param {Number} [end=char count] the end offset of text range.
		 * @param {Boolean|Number|orion.editor.TextViewShowOptions} [show=true]
		 * 					if <code>true</code>, the view will scroll the minimum amount necessary to show the caret location. If
		 *					<code>show</code> is a <code>Number</code>, the view will scroll the minimum amount necessary to show the caret location plus a
		 *					percentage of the client area height. The parameter is clamped to the [0,1] range.  In either case, the view will only scroll
		 *					if the new caret location is not visible already.  The <code>show</code> parameter can also be a <code>orion.editor.TextViewShowOptions</code> object. See
		 * 					{@link orion.editor.TextViewShowOptions} for further information in how the options can be used to control the scrolling behavior.
		 * @param {Function} [callback] if callback is specified and <code>scrollAnimation</code> is not zero, view scrolling is animated and
		 *					the callback is called when the animation is done. Otherwise, callback is callback right away.
		 *
		 * @see orion.editor.TextView#getText
		 */
		setText: function (text, start, end, show, callback) {
			var isSingle = typeof text === "string"; //$NON-NLS-1$
			var reset = start === undefined && end === undefined && isSingle;
			var edit;
			if (isSingle) {
				if (start === undefined) { start = 0; }
				if (end === undefined) { end = this._model.getCharCount(); }
				edit = {text: text, selection: [new Selection(start, end, false)]};
			} else {
				edit = text;
				edit.selection = this._rangesToSelections(edit.selection);
			}
			edit._code = true;
			if (reset) {
				this._variableLineHeight = false;
			}
			this._modifyContent(edit, !reset, show === undefined || show, callback);
			if (reset) {
				/*
				* Bug in Firefox.  For some reason, the caret does not show after the
				* view is refreshed.  The fix is to toggle the contentEditable state and
				* force the clientDiv to loose and receive focus if it is focused.
				*/
				if (util.isFirefox < 13) {
					this._fixCaret();
				}
			}
		},
		/**
		 * Sets the top index.
		 * <p>
		 * The top index is the line that is currently at the top of the text view.  This
		 * line may be partially visible depending on the vertical scroll of the view.
		 * </p>
		 *
		 * @param {Number} topIndex the index of the top line.
		 * @param {Function} [callback] if callback is specified and <code>scrollAnimation</code> is not zero, view scrolling is animated and
		 *					the callback is called when the animation is done. Otherwise, callback is callback right away.
		 *
		 * @see orion.editor.TextView#getBottomIndex
		 * @see orion.editor.TextView#getTopIndex
		 */
		setTopIndex: function(topIndex, callback) {
			if (!this._clientDiv) { return; }
			this._scrollViewAnimated(0, this._getLinePixel(Math.max(0, topIndex)) - this._getScroll().y, callback);
		},
		/**
		 * Sets the top pixel.
		 * <p>
		 * The top pixel is the pixel position that is currently at
		 * the top edge of the view.  This position is relative to the
		 * beginning of the document.
		 * </p>
		 *
		 * @param {Number} pixel the top pixel.
		 * @param {Function} [callback] if callback is specified and <code>scrollAnimation</code> is not zero, view scrolling is animated and
		 *					the callback is called when the animation is done. Otherwise, callback is callback right away.
		 *
		 * @see orion.editor.TextView#getBottomPixel
		 * @see orion.editor.TextView#getTopPixel
		 * @see orion.editor.TextView#convert
		 */
		setTopPixel: function(pixel, callback) {
			if (!this._clientDiv) { return; }
			this._scrollViewAnimated(0, Math.max(0, pixel) - this._getScroll().y, callback);
		},
		/**
		 * Scrolls the selection into view if needed.
		 *
 		 * @param {Number|orion.editor.TextViewShowOptions} [show=0]
		 * 					If <code>show</code> is a <code>Number</code>, the view will scroll the minimum amount necessary to show the caret location plus a
		 *					percentage of the client area height. The parameter is clamped to the [0,1] range.  The view will only scroll
		 *					if the new caret location is not visible already.  The <code>show</code> parameter can also be a <code>orion.editor.TextViewShowOptions</code> object. See
		 * 					{@link orion.editor.TextViewShowOptions} for further information in how the options can be used to control the scrolling behavior.
		 * @param {Function} [callback] if callback is specified and <code>scrollAnimation</code> is not zero, view scrolling is animated and
		 *					the callback is called when the animation is done. Otherwise, callback is callback right away.
		 *
		 * @returns {Boolean} true if the view was scrolled.
		 *
		 * @see orion.editor.TextView#getSelection
		 * @see orion.editor.TextView#setSelection
		 * @see orion.editor.TextView#setCaretOffset
		 */
		showSelection: function(show, callback) {
			return this._showCaret(show ? false : true, callback, show);
		},
		update: function(styleChanged, sync) {
			if (!this._clientDiv) { return; }
			if (styleChanged || this._metrics.invalid) {
				this._updateStyle();
			}
			if (sync === undefined || sync) {
				this._update();
			} else {
				this._queueUpdate();
			}
		},
		
		/**************************************** Event handlers *********************************/
		_handleRootMouseDown: function (e) {
			this._cancelCheckSelection();
			if (this._ignoreEvent(e)) { return; }
			if (util.isFirefox < 13 && e.which === 1) {
				this._clientDiv.contentEditable = false;
				(this._overlayDiv || this._clientDiv).draggable = true;
				this._ignoreBlur = true;
			}
			
			/* Prevent clicks outside of the client div from taking focus away. */
			var topNode = this._overlayDiv || this._clientDiv;
			/* Use view div on IE 8 otherwise it is not possible to scroll. */
			if (util.isIE < 9) { topNode = this._viewDiv; }
			var temp = e.target ? e.target : e.srcElement;
			while (temp) {
				if (topNode === temp) {
					return;
				}
				if (temp.className && temp.className.indexOf("textViewFind") !== -1) { //$NON-NLS-1$
					return;
				}
				temp = temp.parentNode;
			}
			if (e.preventDefault) { e.preventDefault(); }
			if (e.stopPropagation){ e.stopPropagation(); }
			if (!this._isW3CEvents) {
				/*
				* In IE 8 is not possible to prevent the default handler from running
				* during mouse down event using usual API. The workaround is to give
				* focus back to the client div.
				*/ 
				var that = this;
				var win = this._getWindow();
				win.setTimeout(function() {
					that._clientDiv.focus();
				}, 0);
			}
		},
		_handleRootMouseUp: function (e) {
			if (this._ignoreEvent(e)) { return; }
			if (util.isFirefox < 13 && e.which === 1) {
				this._clientDiv.contentEditable = true;
				(this._overlayDiv || this._clientDiv).draggable = false;
			}
			if (util.isFirefox && e.which === 1) {
				
				/*
				* Bug in Firefox.  For some reason, Firefox stops showing the caret
				* in some cases. For example when the user cancels a drag operation 
				* by pressing ESC.  The fix is to detect that the drag operation was
				* cancelled,  toggle the contentEditable state and force the clientDiv
				* to loose and receive focus if it is focused.
				*/
				this._fixCaret();
				this._ignoreBlur = false;
			}
		},
		_handleBlur: function () {
			this._cancelCheckSelection();
			if (this._ignoreBlur) { return; }
			this._commitIME();
			this._hasFocus = false;
			/*
			* Bug in IE 8 and earlier. For some reason when text is deselected
			* the overflow selection at the end of some lines does not get redrawn.
			* The fix is to create a DOM element in the body to force a redraw.
			*/
			if (util.isIE < 9) {
				if (!this._getSelections()[0].isEmpty()) {
					var rootDiv = this._rootDiv;
					var child = util.createElement(rootDiv.ownerDocument, "div"); //$NON-NLS-1$
					rootDiv.appendChild(child);
					rootDiv.removeChild(child);
				}
			}
			if (this._cursorDiv) {
				this._cursorDiv.style.display = "none"; //$NON-NLS-1$
			}
			if (this._domSelection) {
				this._domSelection.forEach(function(domSelection) { domSelection.update(); });
				/* Clear browser selection if selection is within clientDiv */
				var temp;
				var win = this._getWindow();
				var doc = this._parent.ownerDocument;
				if (win.getSelection) {
					var sel = win.getSelection();
					temp = sel.anchorNode;
					while (temp) {
						if (temp === this._clientDiv) {
							if (sel.rangeCount > 0) { sel.removeAllRanges(); }
							break;
						}
						temp = temp.parentNode;
					}
				} else if (doc.selection) {
					this._ignoreSelect = false;
					temp = doc.selection.createRange().parentElement();
					while (temp) {
						if (temp === this._clientDiv) {
							doc.selection.empty();
							break;
						}
						temp = temp.parentNode;
					}
					this._ignoreSelect = true;
				}
			}
			if (!this._ignoreFocus) {
				this.onBlur({type: "Blur"}); //$NON-NLS-1$
			}
		},
		_handleCompositionStart: function (e) {
			if (this._ignoreEvent(e)) { return; }
			if (this._imeTimeout) {
				var win = this._getWindow();
				win.clearTimeout(this._imeTimeout);
				this._imeTimeout = null;
			}
			if (this._imeText) {
				this._commitIME(this._imeText);
				this._imeText = null;
			}
			this._startIME();
			if (this._mutationObserver) {
				this._mutationObserver.disconnect();
				this._mutationObserver = null;
			}
		},
		_handleCompositionUpdate: function(e) {
			if (this._ignoreEvent(e)) { return; }
			this._imeText = e.data;
		},
		_handleCompositionEnd: function (e) {
			if (this._ignoreEvent(e)) { return; }
			this._imeText = e.data;
			var win = this._getWindow();
			this._imeTimeout = win.setTimeout(function() {
				this._commitIME(this._imeText);
				this._imeText = this._imeTimeout = null;
			}.bind(this), 0);
		},
		_handleContextMenu: function (e) {
			if (this._ignoreEvent(e)) { return; }
			if (util.isIE && this._lastMouseButton === 3) {
				// We need to update the DOM selection, because on
				// right-click the caret moves to the mouse location.
				// See bug 366312 and 376508.
				this._updateDOMSelection();
			}
			var preventDefault = false;
			if (this.isListening("ContextMenu")) { //$NON-NLS-1$
				var evt = this._createMouseEvent("ContextMenu", e); //$NON-NLS-1$
				evt.screenX = e.screenX;
				evt.screenY = e.screenY;
				this.onContextMenu(evt);
				preventDefault = evt.defaultPrevented;
			} else if (util.isMac && util.isFirefox && e.button === 0) {
				// hack to prevent CTRL+Space from showing the browser context menu
				preventDefault = true;
			}
			if (preventDefault) {
				if (e.preventDefault) { e.preventDefault(); }
				return false;
			} else {
				this._contextMenuOpen = true;
				if (util.isFirefox) {
					this._checkSelectionChange = true;
					this._pollSelectionChange(true);
				}
			}
		},
		_handleCopy: function (e) {
			this._cancelCheckSelection();
			if (this._ignoreEvent(e)) { return; }
			if (this._ignoreCopy) { return; }
			if (this._doCopy(e)) {
				if (e.preventDefault) { e.preventDefault(); }
				return false;
			}
		},
		_handleCut: function (e) {
			this._cancelCheckSelection();
			if (this._ignoreEvent(e)) { return; }
			if (this._doCut(e)) {
				if (e.preventDefault) { e.preventDefault(); }
				return false;
			}
		},
		_handleDataModified: function(e) {
			if (this._ignoreEvent(e)) { return; }
			this._startIME();
		},
		_handleDblclick: function (e) {
			if (this._ignoreEvent(e)) { return; }
			var time = e.timeStamp ? e.timeStamp : new Date().getTime();
			this._lastMouseTime = time;
			if (this._clickCount !== 2) {
				this._clickCount = 2;
				this._handleMouse(e);
			}
		},
		_handleDragStart: function (e) {
			if (this._ignoreEvent(e)) { return; }
			if (util.isFirefox < 13) {
				var that = this;
				var win = this._getWindow();
				win.setTimeout(function() {
					that._clientDiv.contentEditable = true;
					that._clientDiv.draggable = false;
					that._ignoreBlur = false;
				}, 0);
			}
			if (this.isListening("DragStart") && this._dragOffset !== -1) { //$NON-NLS-1$
				this._isMouseDown = false;
				this.onDragStart(this._createMouseEvent("DragStart", e)); //$NON-NLS-1$
				this._dragOffset = -1;
			} else {
				if (e.preventDefault) { e.preventDefault(); }
				return false;
			}
		},
		_handleDrag: function (e) {
			if (this._ignoreEvent(e)) { return; }
			if (this.isListening("Drag")) { //$NON-NLS-1$
				this.onDrag(this._createMouseEvent("Drag", e)); //$NON-NLS-1$
			}
		},
		_handleDragEnd: function (e) {
			if (this._ignoreEvent(e)) { return; }
			this._dropTarget = false;
			this._dragOffset = -1;
			if (this.isListening("DragEnd")) { //$NON-NLS-1$
				this.onDragEnd(this._createMouseEvent("DragEnd", e)); //$NON-NLS-1$
			}
			if (util.isFirefox < 13) {
				this._fixCaret();
				/*
				* Bug in Firefox.  For some reason, Firefox stops showing the caret when the 
				* selection is dropped onto itself. The fix is to detected the case and 
				* call fixCaret() a second time.
				*/
				if (e.dataTransfer.dropEffect === "none" && !e.dataTransfer.mozUserCancelled) { //$NON-NLS-1$
					this._fixCaret();
				}
			}
		},
		_handleDragEnter: function (e) {
			if (this._ignoreEvent(e)) { return; }
			var prevent = true;
			this._dropTarget = true;
			if (this.isListening("DragEnter")) { //$NON-NLS-1$
				prevent = false;
				this.onDragEnter(this._createMouseEvent("DragEnter", e)); //$NON-NLS-1$
			}
			/*
			* Webkit will not send drop events if this event is not prevented, as spec in HTML5.
			* Firefox and IE do not follow this spec for contentEditable. Note that preventing this 
			* event will result is loss of functionality (insertion mark, etc).
			*/
			if (util.isWebkit || prevent) {
				if (e.preventDefault) { e.preventDefault(); }
				return false;
			}
		},
		_handleDragOver: function (e) {
			if (this._ignoreEvent(e)) { return; }
			var prevent = true;
			if (this.isListening("DragOver")) { //$NON-NLS-1$
				prevent = false;
				this.onDragOver(this._createMouseEvent("DragOver", e)); //$NON-NLS-1$
			}
			/*
			* Webkit will not send drop events if this event is not prevented, as spec in HTML5.
			* Firefox and IE do not follow this spec for contentEditable. Note that preventing this 
			* event will result is loss of functionality (insertion mark, etc).
			*/
			if (util.isWebkit || prevent) {
				if (prevent) { e.dataTransfer.dropEffect = "none"; } //$NON-NLS-1$
				if (e.preventDefault) { e.preventDefault(); }
				return false;
			}
		},
		_handleDragLeave: function (e) {
			if (this._ignoreEvent(e)) { return; }
			this._dropTarget = false;
			if (this.isListening("DragLeave")) { //$NON-NLS-1$
				this.onDragLeave(this._createMouseEvent("DragLeave", e)); //$NON-NLS-1$
			}
		},
		_handleDrop: function (e) {
			if (this._ignoreEvent(e)) { return; }
			this._dropTarget = false;
			if (this.isListening("Drop")) { //$NON-NLS-1$
				this.onDrop(this._createMouseEvent("Drop", e)); //$NON-NLS-1$
			}
			/*
			* This event must be prevented otherwise the user agent will modify
			* the DOM. Note that preventing the event on some user agents (i.e. IE)
			* indicates that the operation is cancelled. This causes the dropEffect to 
			* be set to none  in the dragend event causing the implementor to not execute
			* the code responsible by the move effect.
			*/
			if (e.preventDefault) { e.preventDefault(); }
			return false;
		},
		_handleFocus: function () {
			this._hasFocus = true;
			if (util.isIOS && this._lastTouchOffset !== undefined) {
				this.setCaretOffset(this._lastTouchOffset, true);
				this._lastTouchOffset = undefined;
			} else {
				this._updateDOMSelection();
			}
			if (this._cursorDiv) {
				this._cursorDiv.style.display = "block"; //$NON-NLS-1$
			}
			if (this._domSelection) {
				this._domSelection.forEach(function(domSelection) { domSelection.update(); });
			}
			if (!this._ignoreFocus) {
				this.onFocus({type: "Focus"}); //$NON-NLS-1$
			}
		},
		_handleKeyDown: function (e) {
			this._cancelCheckSelection();
			if (this._ignoreEvent(e)) {	return;	}
			if (this.isListening("KeyDown")) { //$NON-NLS-1$
				var keyEvent = this._createKeyEvent("KeyDown", e); //$NON-NLS-1$
				this.onKeyDown(keyEvent); //$NON-NLS-1$
				if (keyEvent.defaultPrevented) {
					/*
					* Feature in Firefox. Keypress events still happen even if the keydown event
					* was prevented. The fix is to remember that keydown was prevented and prevent
					* the keypress ourselves.
					*/
					if (util.isFirefox) {
						this._keyDownPrevented = true;
					}
					e.preventDefault();
					return;
				}
			}
			var modifier = false;
			switch (e.keyCode) {
				case 16: /* Shift */
				case 17: /* Control */
				case 18: /* Alt */
				case 91: /* Command */
					modifier = true;
					break;
				default:
					this._setLinksVisible(false);
			}
			if (e.keyCode === 229) {
				if (this._readonly) {
					if (e.preventDefault) { e.preventDefault(); }
					return false;
				}
				var startIME = true;
				
				/*
				* Bug in Safari. Some Control+key combinations send key events
				* with keyCode equals to 229. This is unexpected and causes the
				* view to start an IME composition. The fix is to ignore these
				* events.
				*/
				if (util.isSafari && util.isMac) {
					if (e.ctrlKey) {
						startIME = false;
						e.keyCode = 0x81;
					}
				}
				if (startIME) {
					this._startIME();
				}
			} else {
				if (!modifier) {
					this._commitIME();
				}
			}
			/*
			* Feature in Firefox. When a key is held down the browser sends 
			* right number of keypress events but only one keydown. This is
			* unexpected and causes the view to only execute an action
			* just one time. The fix is to ignore the keydown event and 
			* execute the actions from the keypress handler.
			* Note: This only happens on the Mac and Linux (Firefox 3.6).
			*
			* Feature in Opera < 12.16.  Opera sends keypress events even for non-printable
			* keys.  The fix is to handle actions in keypress instead of keydown.
			*/
			if (((util.isMac || util.isLinux) && util.isFirefox < 4) || util.isOpera < 12.16) {
				this._keyDownEvent = e;
				return true;
			}
			
			if (this._doAction(e)) {
				if (e.preventDefault) {
					e.preventDefault(); 
					e.stopPropagation(); 
				} else {
					e.cancelBubble = true;
					e.returnValue = false;
					e.keyCode = 0;
				}
				return false;
			}
		},
		_handleKeyPress: function (e) {
			if (this._ignoreEvent(e)) { return; }
			/*
			* Feature in Firefox. Keypress events still happen even if the keydown event
			* was prevented. The fix is to remember that keydown was prevented and prevent
			* the keypress ourselves.
			*/
			if (this._keyDownPrevented) { 
				if (e.preventDefault) {
					e.preventDefault(); 
					e.stopPropagation(); 
				} 
				this._keyDownPrevented = undefined;
				return;
			}
			/*
			* Feature in Embedded WebKit.  Embedded WekKit on Mac runs in compatibility mode and
			* generates key press events for these Unicode values (Function keys).  This does not
			* happen in Safari or Chrome.  The fix is to ignore these key events.
			*/
			if (util.isMac && util.isWebkit) {
				if ((0xF700 <= e.keyCode && e.keyCode <= 0xF7FF) || e.keyCode === 13 || e.keyCode === 8) {
					if (e.preventDefault) { e.preventDefault(); }
					return false;
				}
			}
			if (((util.isMac || util.isLinux) && util.isFirefox < 4) || util.isOpera < 12.16) {
				if (this._doAction(this._keyDownEvent)) {
					if (e.preventDefault) { e.preventDefault(); }
					return false;
				}
			}
			var ctrlKey = util.isMac ? e.metaKey : e.ctrlKey;
			if (e.charCode !== undefined) {
				if (ctrlKey) {
					switch (e.charCode) {
						/*
						* In Firefox and Safari if ctrl+v, ctrl+c ctrl+x is canceled
						* the clipboard events are not sent. The fix to allow
						* the browser to handles these key events.
						*/
						case 99://c
						case 118://v
						case 120://x
							return true;
					}
				}
			}
			if (this.isListening("KeyPress")) { //$NON-NLS-1$
				var keyEvent = this._createKeyEvent("KeyPress", e); //$NON-NLS-1$
				this.onKeyPress(keyEvent); //$NON-NLS-1$
				if (keyEvent.defaultPrevented) {
					e.preventDefault();
					return;
				}
			}
			if (this._doAction(e)) {
				if (e.preventDefault) {
					e.preventDefault(); 
					e.stopPropagation(); 
				} else {
					e.cancelBubble = true;
					e.returnValue = false;
					e.keyCode = 0;
				}
				return false;
			}
			var ignore = false;
			if (util.isMac) {
				if (e.ctrlKey || e.metaKey) { ignore = true; }
			} else {
				if (util.isFirefox) {
					//Firefox clears the state mask when ALT GR generates input
					if (e.ctrlKey || e.altKey) { ignore = true; }
				} else {
					//IE and Chrome only send ALT GR when input is generated
					if (e.ctrlKey ^ e.altKey) { ignore = true; }
				}
			}
			if (!ignore) {
				var key = util.isOpera ? e.which : (e.charCode !== undefined ? e.charCode : e.keyCode);
				if (key > 31) {
					this._doContent(String.fromCharCode (key));
					if (e.preventDefault) { e.preventDefault(); }
					return false;
				}
			}
		},
		_handleDocKeyUp: function (e) {
			var ctrlKey = util.isMac ? e.metaKey : e.ctrlKey;
			if (!ctrlKey) {
				this._setLinksVisible(false);
			}
		},
		_handleKeyUp: function (e) {
			if (this._ignoreEvent(e)) { return; }
			if (this.isListening("KeyUp")) { //$NON-NLS-1$
				var keyEvent = this._createKeyEvent("KeyUp", e); //$NON-NLS-1$
				this.onKeyUp(keyEvent); //$NON-NLS-1$
				if (keyEvent.defaultPrevented) {
					e.preventDefault();
					return;
				}
			}
			this._handleDocKeyUp(e);
			// don't commit for space (it happens during JP composition)  
			if (e.keyCode === 13) {
				this._commitIME();
			}
		},
		_handleLinkClick: function (e) {
			var ctrlKey = util.isMac ? e.metaKey : e.ctrlKey;
			if (!ctrlKey) {
				if (e.preventDefault) { e.preventDefault(); }
				return false;
			}
		},
		_handleMouse: function (e) {
			var win = this._getWindow();
			var result = true;
			var target = win;
			if (util.isIE || (util.isFirefox && !this._overlayDiv)) { target = this._clientDiv; }
			if (this._overlayDiv) {
				if (this._hasFocus) {
					this._ignoreFocus = true;
				}
				var that = this;
				win.setTimeout(function () {
					that.focus();
					that._ignoreFocus = false;
				}, 0);
			}
			var extend = e.shiftKey;
			var block = e.altKey;
			var add = util.isMac ? e.metaKey : e.ctrlKey;
			this._blockSelection = this._doubleClickSelection = null;
			if (this._clickCount === 1) {
				var drag = (!util.isOpera || util.isOpera >= 12.16) && this._hasFocus && this.isListening("DragStart"); //$NON-NLS-1$
				result = this._setSelectionTo(e.clientX, e.clientY, true, extend, add, drag);
				if (result) { this._setGrab(target); }
			} else {
				/*
				* Feature in IE8 and older, the sequence of events in the IE8 event model
				* for a doule-click is:
				*
				*	down
				*	up
				*	up
				*	dblclick
				*
				* Given that the mouse down/up events are not balanced, it is not possible to
				* grab on mouse down and ungrab on mouse up.  The fix is to grab on the first
				* mouse down and ungrab on mouse move when the button 1 is not set.
				*/
				if (this._isW3CEvents) { this._setGrab(target); }
				
				this._setSelectionTo(e.clientX, e.clientY, true, extend, add, false);
				this._doubleClickSelection = Selection.editing(this._getSelections());
			}
			if (block) {
				this._blockSelection = Selection.editing(this._getSelections());
			}
			return result;
		},
		_handleMouseDown: function (e) {
			if (this._ignoreEvent(e)) { return; }
			if (this._linksVisible) {
				var target = e.target || e.srcElement;
				if (target.tagName !== "A") { //$NON-NLS-1$
					this._setLinksVisible(false);
				} else {
					return;
				}
			}
			this._commitIME();

			var button = e.which; // 1 - left, 2 - middle, 3 - right
			if (!button) { 
				// if IE 8 or older
				if (e.button === 4) { button = 2; }
				if (e.button === 2) { button = 3; }
				if (e.button === 1) { button = 1; }
			}

			// For middle click we always need getTime(). See _getClipboardText().
			var time = button !== 2 && e.timeStamp ? e.timeStamp : new Date().getTime();
			var timeDiff = time - this._lastMouseTime;
			var deltaX = Math.abs(this._lastMouseX - e.clientX);
			var deltaY = Math.abs(this._lastMouseY - e.clientY);
			var sameButton = this._lastMouseButton === button;
			this._lastMouseX = e.clientX;
			this._lastMouseY = e.clientY;
			this._lastMouseTime = time;
			this._lastMouseButton = button;

			if (button === 1) {
				this._isMouseDown = true;
				if (sameButton && timeDiff <= this._clickTime && deltaX <= this._clickDist && deltaY <= this._clickDist) {
					this._clickCount++;
				} else {
					this._clickCount = 1;
				}
			}
			if (this.isListening("MouseDown")) { //$NON-NLS-1$
				var mouseEvent = this._createMouseEvent("MouseDown", e); //$NON-NLS-1$
				this.onMouseDown(mouseEvent);
				if (mouseEvent.defaultPrevented) {
					e.preventDefault();
					return;
				}
			}
			if (button === 1) {
				if (this._handleMouse(e) && (util.isIE >= 9 || util.isOpera || util.isChrome || util.isSafari || (util.isFirefox && !this._overlayDiv))) {
					if (!this._hasFocus) {
						this.focus();
					}
					e.preventDefault();
				}
			}
			if (util.isFirefox && this._lastMouseButton === 3) {
				// We need to update the DOM selection, because on
				// right-click the caret moves to the mouse location.
				// See bug 366312 and 376508.
				this._updateDOMSelection();
			}
		},
		_handleMouseOver: function (e) {
			if (this._ignoreEvent(e)) { return; }
			if (this._animation) { return; }
			if (this.isListening("MouseOver")) { //$NON-NLS-1$
				this.onMouseOver(this._createMouseEvent("MouseOver", e)); //$NON-NLS-1$
			}
		},
		_handleMouseOut: function (e) {
			if (this._ignoreEvent(e)) { return; }
			if (this._animation) { return; }
			if (this.isListening("MouseOut")) { //$NON-NLS-1$
				this.onMouseOut(this._createMouseEvent("MouseOut", e)); //$NON-NLS-1$
			}
		},
		_handleMouseMove: function (e) {
			if (this._animation) { return; }
			var inClient = this._isClientDiv(e);
			if (this.isListening("MouseMove")) { //$NON-NLS-1$
				if (inClient || this._isMouseDown){
					var mouseEvent = this._createMouseEvent("MouseMove", e); //$NON-NLS-1$
					this.onMouseMove(mouseEvent);
					if (mouseEvent.defaultPrevented) {
						e.preventDefault();
						return;
					}
				}
			}
			if (this._dropTarget) {
				return;
			}
			/*
			* Bug in IE9. IE sends one mouse event when the user changes the text by
			* pasting or undo.  These operations usually happen with the Ctrl key
			* down which causes the view to enter link mode.  Link mode does not end
			* because there are no further events.  The fix is to only enter link
			* mode when the coordinates of the mouse move event have changed.
			*/
			var changed = this._linksVisible || this._lastMouseMoveX !== e.clientX || this._lastMouseMoveY !== e.clientY;
			this._lastMouseMoveX = e.clientX;
			this._lastMouseMoveY = e.clientY;
			this._setLinksVisible(changed && !this._isMouseDown && e.altKey && (util.isMac ? e.metaKey : e.ctrlKey));

			this._checkOverlayScroll();

			/*
			* Feature in IE8 and older, the sequence of events in the IE8 event model
			* for a doule-click is:
			*
			*	down
			*	up
			*	up
			*	dblclick
			*
			* Given that the mouse down/up events are not balanced, it is not possible to
			* grab on mouse down and ungrab on mouse up.  The fix is to grab on the first
			* mouse down and ungrab on mouse move when the button 1 is not set.
			*
			* In order to detect double-click and drag gestures, it is necessary to send
			* a mouse down event from mouse move when the button is still down and isMouseDown
			* flag is not set.
			*/
			if (!this._isW3CEvents) {
				if (e.button === 0) {
					this._setGrab(null);
					return true;
				}
				if (!this._isMouseDown && e.button === 1 && (this._clickCount & 1) !== 0 && inClient) {
					this._clickCount = 2;
					return this._handleMouse(e, this._clickCount);
				}
			}
			if (!this._isMouseDown || this._dragOffset !== -1) {
				return;
			}
			
			var x = e.clientX;
			var y = e.clientY;
			var viewPad = this._getViewPadding();
			var viewRect = this._viewDiv.getBoundingClientRect();
			var width = this._getClientWidth (), height = this._getClientHeight();
			var leftEdge = viewRect.left + viewPad.left;
			var topEdge = viewRect.top + viewPad.top;
			var rightEdge = viewRect.left + viewPad.left + width;
			var bottomEdge = viewRect.top + viewPad.top + height;
			if (y < topEdge) {
				this._doAutoScroll("up", x, y - topEdge); //$NON-NLS-1$
			} else if (y > bottomEdge) {
				this._doAutoScroll("down", x, y - bottomEdge); //$NON-NLS-1$
			} else if (x < leftEdge && !this._wrapMode) {
				this._doAutoScroll("left", x - leftEdge, y); //$NON-NLS-1$
			} else if (x > rightEdge && !this._wrapMode) {
				this._doAutoScroll("right", x - rightEdge, y); //$NON-NLS-1$
			} else {
				this._endAutoScroll();
				this._setSelectionTo(x, y, false, true);
			}
		},
		_isClientDiv: function(e) {
			var topNode = this._overlayDiv || this._clientDiv;
			var temp = e.target ? e.target : e.srcElement;
			while (temp) {
				if (topNode === temp) {
					return true;
				}
				temp = temp.parentNode;
			}
			return false;
		},
		_createKeyEvent: function(type, e) {
			return {
				type: type,
				event: e,
				preventDefault: function() {
					this.defaultPrevented = true;
				}
			};
		},
		_createMouseEvent: function(type, e) {
			var pt = this.convert({x: e.clientX, y: e.clientY}, "page", "document"); //$NON-NLS-1$ //$NON-NLS-2$
			return {
				type: type,
				event: e,
				clickCount: this._clickCount,
				x: pt.x,
				y: pt.y,
				preventDefault: function() {
					this.defaultPrevented = true;
				}
			};
		},
		_createTouchEvent: function(type, e) {
			var pt = e.touches.length ? this.convert({x: e.touches[0].clientX, y: e.touches[0].clientY}, "page", "document") : {}; //$NON-NLS-1$ //$NON-NLS-2$
			return {
				type: type,
				event: e,
				touchCount: e.touches.length,
				x: pt.x,
				y: pt.y,
				preventDefault: function() {
					this.defaultPrevented = true;
				}
			};
		},
		_handleMouseUp: function (e) {
			var left = e.which ? e.button === 0 : e.button === 1;
			if (this.isListening("MouseUp")) { //$NON-NLS-1$
				if (this._isClientDiv(e) || (left && this._isMouseDown)) {
					var mouseEvent = this._createMouseEvent("MouseUp", e); //$NON-NLS-1$
					this.onMouseUp(mouseEvent);
					if (mouseEvent.defaultPrevented) {
						e.preventDefault();
						this._isMouseDown = false;
						return;
					}
				}
			}
			if (this._linksVisible) {
				return;
			}
			if (left && this._isMouseDown) {
				var selections = this._getSelections();
				var selection = Selection.editing(selections);
				selections.forEach(function(sel) {
					sel._editing = false;
				});
				if (this._dragOffset !== -1) {
					selection.extend(this._dragOffset);
					selection.collapse();
					selections = selection;
					this._dragOffset = -1;
				}
				this._setSelection(selections, false);
				this._isMouseDown = false;
				this._endAutoScroll();
				
				/*
				* Feature in IE8 and older, the sequence of events in the IE8 event model
				* for a doule-click is:
				*
				*	down
				*	up
				*	up
				*	dblclick
				*
				* Given that the mouse down/up events are not balanced, it is not possible to
				* grab on mouse down and ungrab on mouse up.  The fix is to grab on the first
				* mouse down and ungrab on mouse move when the button 1 is not set.
				*/
				if (this._isW3CEvents) { this._setGrab(null); }
			}
			/*
			* Note that there cases when Firefox sets the DOM selection in mouse up.
			* This happens for example after a cancelled drag operation.
			*
			* Note that on Chrome and IE, the caret stops blicking if mouse up is
			* prevented.
			*/
			if (left && this._isMouseDown && util.isFirefox) {
				this._updateDOMSelection();
				e.preventDefault();
			}
		},
		_handleMouseWheel: function (e) {
			if (this._noScroll) return;
			var lineHeight = this._getLineHeight();
			var pixelX = 0, pixelY = 0;
			// Note: On the Mac the correct behaviour is to scroll by pixel.
			if (util.isIE || util.isOpera) {
				pixelY = (-e.wheelDelta / 40) * lineHeight;
			} else if (util.isFirefox) {
				var limit = 256;
				if (e.type === "wheel") { //$NON-NLS-1$
					if (e.deltaMode) { // page or line
						pixelX = Math.max(-limit, Math.min(limit, e.deltaX)) * lineHeight;
						pixelY = Math.max(-limit, Math.min(limit, e.deltaY)) * lineHeight;
					} else {
						pixelX = e.deltaX;
						pixelY = e.deltaY;
					}
				} else {
					var pixel;
					if (util.isMac) {
						pixel = e.detail * 3;
					} else {
						pixel = Math.max(-limit, Math.min(limit, e.detail)) * lineHeight;
					}
					if (e.axis === e.HORIZONTAL_AXIS) {
						pixelX = pixel;
					} else {
						pixelY = pixel;
					}
				}
			} else {
				//Webkit
				if (util.isMac) {
					/*
					* In Safari, the wheel delta is a multiple of 120. In order to
					* convert delta to pixel values, it is necessary to divide delta
					* by 40.
					*
					* In Chrome and Safari 5, the wheel delta depends on the type of the
					* mouse. In general, it is the pixel value for Mac mice and track pads,
					* but it is a multiple of 120 for other mice. There is no presise
					* way to determine if it is pixel value or a multiple of 120.
					* 
					* Note that the current approach does not calculate the correct
					* pixel value for Mac mice when the delta is a multiple of 120.
					*
					* For values that are multiples of 120, the denominator varies on
					* the time between events.
					*/
					var denominatorX, denominatorY;
					var deltaTime = e.timeStamp - this._wheelTimeStamp;
					this._wheelTimeStamp = e.timeStamp;
					if (e.wheelDeltaX % 120 !== 0) { 
						denominatorX = 1; 
					} else {
						denominatorX = deltaTime < 40 ? 40/(40-deltaTime) : 40;
					}
					if (e.wheelDeltaY % 120 !== 0) { 
						denominatorY = 1; 
					} else {
						denominatorY = deltaTime < 40 ? 40/(40-deltaTime) : 40; 
					}
					pixelX = Math.ceil(-e.wheelDeltaX / denominatorX);
					if (-1 < pixelX && pixelX < 0) { pixelX = -1; }
					if (0 < pixelX && pixelX < 1) { pixelX = 1; }
					pixelY = Math.ceil(-e.wheelDeltaY / denominatorY);
					if (-1 < pixelY && pixelY < 0) { pixelY = -1; }
					if (0 < pixelY && pixelY < 1) { pixelY = 1; }
				} else {
					pixelX = -e.wheelDeltaX;
					var linesToScroll = 8;
					pixelY = (-e.wheelDeltaY / 120 * linesToScroll) * lineHeight;
				}
			}
			/* 
			* Feature in Safari. If the event target is removed from the DOM 
			* safari stops smooth scrolling. The fix is keep the element target
			* in the DOM and remove it on a later time. 
			*
			* Note: Using a timer is not a solution, because the timeout needs to
			* be at least as long as the gesture (which is too long).
			*/
			if (util.isSafari || (util.isChrome && util.isMac)) {
				var lineDiv = e.target;
				while (lineDiv && lineDiv.lineIndex === undefined) {
					lineDiv = lineDiv.parentNode;
				}
				this._mouseWheelLine = lineDiv;
			}
			var oldScroll = this._getScroll();
			this._scrollView(pixelX, pixelY);
			var newScroll = this._getScroll();
			if (oldScroll.x !== newScroll.x || oldScroll.y !== newScroll.y) {
				if (e.preventDefault) { e.preventDefault(); }
				return false;
			}
		},
		_handlePaste: function (e) {
			this._cancelCheckSelection();
			if (this._ignoreEvent(e)) { return; }
			if (this._ignorePaste) { return; }
			if (this._doPaste(e)) {
				if (util.isIE) {
					/*
					 * Bug in IE,  
					 */
					var that = this;
					this._ignoreFocus = true;
					var win = this._getWindow();
					win.setTimeout(function() {
						that._updateDOMSelection();
						that._ignoreFocus = false;
					}, 0);
				}
				if (e.preventDefault) { e.preventDefault(); }
				return false;
			}
		},
		_handleResize: function () {
			var newWidth = this._rootDiv.clientWidth;
			var newHeight = this._rootDiv.clientHeight;
			if (this._rootWidth !== newWidth || this._rootHeight !== newHeight) {
				if (this._rootWidth !== newWidth && this._wrapMode) {
					this._resetLineHeight();
				}
				this._rootWidth = newWidth;
				this._rootHeight = newHeight;
				/*
				* Feature in IE7. For some reason, sometimes Internet Explorer 7 
				* returns incorrect values for element.getBoundingClientRect() when 
				* inside a resize handler. The fix is to queue the work.
				*/			
				var queue = util.isIE < 9;

				/*
				* The calculated metrics may be out of date when the zoom level changes.
				*/
				var metrics = this._calculateMetrics();
				if (!compare(metrics, this._metrics)) {
					if (this._metrics.invalid && !metrics.invalid) {
						this._updateStyle(false, metrics);
					} else {
						if (this._variableLineHeight) {
							this._variableLineHeight = false;
							this._resetLineHeight();
						}
						this._metrics = metrics;
					}
					queue = true;
				}

				if (queue) {
					this._queueUpdate();
				} else {
					this._update();
				}
				this.dispatchEvent({type: "Resize"}); //$NON-NLS-1$
			}
		},
		_handleRulerEvent: function (e) {
			var target = e.target ? e.target : e.srcElement;
			var lineIndex = target.lineIndex;
			var element = target;
			while (element && !element._ruler) {
				if (lineIndex === undefined && element.lineIndex !== undefined) {
					lineIndex = element.lineIndex;
				}
				element = element.parentNode;
			}
			var ruler = element ? element._ruler : null;
			if (lineIndex === undefined && ruler && ruler.getOverview() === "document") { //$NON-NLS-1$
				var clientHeight = this._getClientHeight ();
				var lineCount = this._model.getLineCount ();
				var viewPad = this._getViewPadding();
				var viewRect = this._viewDiv.getBoundingClientRect();
				var lineHeight = this._getLineHeight();
				var contentHeight = lineHeight * lineCount;
				var trackHeight = clientHeight + viewPad.top + viewPad.bottom - 2 * this._metrics.scrollWidth;
				var divHeight, arrowWidth;
				if (contentHeight < trackHeight) {
					divHeight = lineHeight;
					arrowWidth = viewPad.top;
				} else {
					divHeight = trackHeight / lineCount;
					arrowWidth = this._metrics.scrollWidth;
				}
				lineIndex = Math.floor(((e.clientY - viewRect.top) - arrowWidth) / divHeight);
				if (!(0 <= lineIndex && lineIndex < lineCount)) {
					lineIndex = undefined;
				}
			}
			if (ruler) {
				switch (e.type) {
					case "click": //$NON-NLS-1$
						if (ruler.onClick) { ruler.onClick(lineIndex, e); }
						break;
					case "dblclick": //$NON-NLS-1$
						if (ruler.onDblClick) { ruler.onDblClick(lineIndex, e); }
						break;
					case "mousemove": //$NON-NLS-1$
						if (ruler.onMouseMove) { ruler.onMouseMove(lineIndex, e); }
						break;
					case "mouseover": //$NON-NLS-1$
						if (ruler.onMouseOver) { ruler.onMouseOver(lineIndex, e); }
						break;
					case "mouseout": //$NON-NLS-1$
						if (ruler.onMouseOut) { 
							var tmp = e.relatedTarget;
							while (tmp && tmp !== this._rootDiv) {
								if (tmp === element) {
									return;
								}
								tmp = tmp.parentNode;
							}
							ruler.onMouseOut(lineIndex, e); 
						}
						break;
				}
			}
		},
		_handleScroll: function () {
			this._lastScrollTime = new Date().getTime();
			var _scroll = this._getScroll(false);
			var oldX = this._hScroll;
			var oldY = this._vScroll;
			if (oldX !== _scroll.x || oldY !== _scroll.y) {
				this._hScroll = _scroll.x;
				this._vScroll = _scroll.y;
				this._commitIME();
				this._update(oldY === _scroll.y);
				var e = {
					type: "Scroll", //$NON-NLS-1$
					oldValue: {x: oldX, y: oldY},
					newValue: _scroll
				};
				this.onScroll(e);
			}
		},
		_handleSelectStart: function (e) {
			var menuOpen = this._contextMenuOpen;
			this._contextMenuOpen = false;
			if (menuOpen) {
				this._checkSelectionChange = true;
				return;
			}
			if (this._ignoreSelect) {
				if (e && e.preventDefault) { e.preventDefault(); }
				return false;
			}
		},
		_getModelOffset: function(node, offset) {
			if (!node) { return; }
			var lineNode;
			if (node.tagName === "DIV") { //$NON-NLS-1$
				lineNode = node;
			} else {
				lineNode = node.parentNode.parentNode;
			}
			if (!lineNode._line) {
				return 0;
			}
			return lineNode._line.getModelOffset (node, offset);
		},
		_updateSelectionFromDOM: function() {
			if (!(util.isIOS || util.isAndroid || this._checkSelectionChange)) {
				return false;
			}
			var win = this._getWindow();
			var selection = win.getSelection();
			var start = this._getModelOffset(selection.anchorNode, selection.anchorOffset);
			var end = this._getModelOffset(selection.focusNode, selection.focusOffset);
			var sel = this._getSelections()[0];
			if (start === undefined || end === undefined || (sel.start === start && sel.end === end)) {
				return false;
			}
			
			if (this._checkSelectionChange) {
				var firstLine = this._getLineNext();
				var lastLine = this._getLinePrevious();
				
				// Selection is unchanged and bigger than the visible buffer region
				if (selection.anchorNode === firstLine.firstChild.firstChild && selection.anchorOffset === 0 &&
					selection.focusNode === lastLine.firstChild.firstChild && selection.focusOffset === 0)
				{
					return false;
				}
				
				// Detect select all
				if (
				(selection.anchorNode === firstLine.firstChild.firstChild && selection.anchorOffset === 0 && selection.focusNode === lastLine.lastChild.firstChild)
				|| (selection.anchorNode === this._clientDiv && selection.focusNode === this._clientDiv)
				) {
					start = 0;
					end = this.getModel().getCharCount();
				}
			}
			
			this._setSelection(new Selection(start, end), false, false);
			this._checkSelectionChange = false;
			return true;
		},
		_cancelCheckSelection: function() {
			if (this._checkSelectionChange) {
				this._checkSelectionChange = false;
				this._cancelPollSelectionChange();
			}
		},
		_cancelPollSelectionChange: function() {
			if (this._selPollTimer) {
				var win = this._getWindow();
				win.clearTimeout(this._selPollTimer);
				this._selPollTimer = null; 
			}
		},
		_pollSelectionChange: function(retryPoll) {
			var that = this;
			var win = this._getWindow();
			this._cancelPollSelectionChange();
			this._selPollTimer = win.setTimeout(function() {
				that._selPollTimer = null; 
				if (!that._clientDiv) { return; }
				var changed = that._updateSelectionFromDOM();
				if (!changed && retryPoll) {
					that._pollSelectionChange(retryPoll);
				}
			}, 100);
		},
		_handleSelectionChange: function () {
			if (this._imeOffset !== -1) {
				return;
			}
			/*
			 * Feature in Android. The selection handles are hidden when the DOM changes. Sending
			 * selection events to the application while the user is moving the selection handles
			 * may hide the handles unexpectedly.  The fix is to delay updating the selection and
			 * sending the event to the application.
			 */
			if (util.isAndroid) {
				this._pollSelectionChange();
			} else {
				this._updateSelectionFromDOM();
			}
		},
		_handleTextInput: function (e) {
			if (this._ignoreEvent(e)) { return; }
			if (this._imeOffset !== -1) {
				return;
			}
			var selection = this._getWindow().getSelection();
			if (
				selection.anchorNode !== this._anchorNode || selection.focusNode !== this._focusNode ||
				selection.anchorOffset !== this._anchorOffset || selection.focusOffset !== this._focusOffset
			) {
				var temp = selection.anchorNode;
				while (temp) {
					if (temp.lineIndex !== undefined) {
						break;
					}
					temp = temp.parentNode;
				}
				if (temp) {
					var model = this._model;
					var lineIndex = temp.lineIndex;
					var oldText = model.getLine(lineIndex), text = oldText;
					var offset = 0;
					var lineStart = model.getLineStart(lineIndex);
					if (selection.rangeCount > 0) {
						selection.getRangeAt(0).deleteContents();
						var node = temp.ownerDocument.createTextNode(e.data);
						selection.getRangeAt(0).insertNode(node);
						var nodeText = this._getDOMText(temp, node);
						text = nodeText.text;
						offset = nodeText.offset;
						node.parentNode.removeChild(node);
					}
					temp.lineRemoved = true;
					
					var start = 0;
					while (oldText.charCodeAt(start) === text.charCodeAt(start) && start < offset) {
						start++;
					}
		
					var end = oldText.length - 1, delta = text.length - oldText.length;
					while (oldText.charCodeAt(end) === text.charCodeAt(end + delta) && end + delta >= offset + e.data.length) {
						end--;
					}
					end++;
					
					var deltaText = text.substring(start, end + delta);
					start += lineStart;
					end += lineStart;
					
					var selections = this._getSelections();
					var deltaStart = selections[0].start - start;
					var deltaEnd = selections[0].end - end;
					selections[0].start = start;
					selections[0].end = end;
					for (var i=1; i<selections.length; i++) {
						selections[i].start -= deltaStart;
						selections[i].end -= deltaEnd;
					}
					this._modifyContent({text: deltaText, selection: selections, _ignoreDOMSelection: true}, true);
				}
			} else {
				this._doContent(e.data);
			}
			e.preventDefault();
		},
		_handleTouchStart: function (e) {
			if (this.isListening("TouchStart")) { //$NON-NLS-1$
				var touchEvent = this._createTouchEvent("TouchStart", e); //$NON-NLS-1$
				this.onTouchStart(touchEvent);
				if (touchEvent.defaultPrevented) {
					e.preventDefault();
					return;
				}
				if (this._noScroll) {
					return;
				}
			}
			this._commitIME();
			var win = this._getWindow();
			if (this._touchScrollTimer) {
				this._vScrollDiv.style.display = "none"; //$NON-NLS-1$
				this._hScrollDiv.style.display = "none"; //$NON-NLS-1$
				win.clearInterval(this._touchScrollTimer);
				this._touchScrollTimer = null;
			}
			var touches = e.touches;
			if (touches.length === 1) {
				var touch = touches[0];
				var x = touch.clientX, y = touch.clientY;
				this._touchStartX = x;
				this._touchStartY = y;
				if (util.isAndroid) {
					/*
					* Bug in Android 4.  The clientX/Y coordinates of the touch events
					* include the page scrolling offsets.
					*/
				    if (y < (touch.pageY - win.pageYOffset) || x < (touch.pageX - win.pageXOffset) ) {
						x = touch.pageX - win.pageXOffset;
						y = touch.pageY - win.pageYOffset;
				    }
				}
				var pt = this.convert({x: x, y: y}, "page", "document"); //$NON-NLS-1$ //$NON-NLS-2$
				this._lastTouchOffset = this.getOffsetAtLocation(pt.x, pt.y);
				this._touchStartTime = e.timeStamp;
				this._touching = true;
			}
		},
		_handleTouchMove: function (e) {
			if (this.isListening("TouchMove")) { //$NON-NLS-1$
				var touchEvent = this._createTouchEvent("TouchMove", e); //$NON-NLS-1$
				this.onTouchMove(touchEvent);
				if (touchEvent.defaultPrevented) {
					e.preventDefault();
					return;
				}
				if (this._noScroll) {
					return;
				}
			}
			var touches = e.touches;
			if (touches.length === 1) {
				var touch = touches[0];
				this._touchCurrentX = touch.clientX;
				this._touchCurrentY = touch.clientY;
				var interval = 10;
				if (!this._touchScrollTimer && (e.timeStamp - this._touchStartTime) < (interval*20)) {
					this._vScrollDiv.style.display = "block"; //$NON-NLS-1$
					if (!this._wrapMode) {
						this._hScrollDiv.style.display = "block"; //$NON-NLS-1$
					}
					var that = this;
					var win = this._getWindow();
					this._touchScrollTimer = win.setInterval(function() {
						var deltaX = 0, deltaY = 0;
						if (that._touching) {
							deltaX = that._touchStartX - that._touchCurrentX;
							deltaY = that._touchStartY - that._touchCurrentY;
							that._touchSpeedX = deltaX / interval;
							that._touchSpeedY = deltaY / interval;
							that._touchStartX = that._touchCurrentX;
							that._touchStartY = that._touchCurrentY;
						} else {
							if (Math.abs(that._touchSpeedX) < 0.1 && Math.abs(that._touchSpeedY) < 0.1) {
								that._vScrollDiv.style.display = "none"; //$NON-NLS-1$
								that._hScrollDiv.style.display = "none"; //$NON-NLS-1$
								win.clearInterval(that._touchScrollTimer);
								that._touchScrollTimer = null;
								return;
							} else {
								deltaX = that._touchSpeedX * interval;
								deltaY = that._touchSpeedY * interval;
								that._touchSpeedX *= 0.95;
								that._touchSpeedY *= 0.95;
							}
						}
						that._scrollView(deltaX, deltaY);
					}, interval);
				}
				if (this._touchScrollTimer) {
					e.preventDefault();
				}
			}
		},
		_handleTouchEnd: function (e) {
			if (this.isListening("TouchEnd")) { //$NON-NLS-1$
				var touchEvent = this._createTouchEvent("TouchEnd", e); //$NON-NLS-1$
				this.onTouchEnd(touchEvent);
				if (touchEvent.defaultPrevented) {
					e.preventDefault();
					return;
				}
				if (this._noScroll) {
					return;
				}
			}
			var touches = e.touches;
			if (touches.length === 0) {
				this._touching = false;
			}
		},

		/************************************ Actions ******************************************/
		_doAction: function (e) {
			var mode, i;
			var keyModes = this._keyModes;
			for (i = keyModes.length - 1 ; i >= 0; i--) {
				mode = keyModes[i];
				if (typeof mode.match === "function") { //$NON-NLS-1$
					var actionID = mode.match(e);
					if (actionID !== undefined) {
						return this.invokeAction(actionID);
					}
				}
			}
			return false;
		},
		_doMove: function(args, selection) {
			var model = this._model;
			var caret = selection.getCaret();
			var lineIndex = model.getLineAtOffset(caret);
			if (!args.count) {
				args.count = 1;
			}
			while (args.count !== 0) {
				var lineStart = model.getLineStart(lineIndex);
				if (args.count < 0 && caret === lineStart) {
					if (lineIndex > 0) {
						if (args.unit === "character") { //$NON-NLS-1$
							args.count++;
						}
						lineIndex--;
						selection.extend(model.getLineEnd(lineIndex));
					} else {
						break;
					}
				} else if (args.count > 0 && caret === model.getLineEnd(lineIndex)) {
					if (lineIndex + 1 < model.getLineCount()) {
						if (args.unit === "character") { //$NON-NLS-1$
							args.count--;
						}
						lineIndex++;
						selection.extend(model.getLineStart(lineIndex));
					} else {
						break;
					}
				} else {
					var removeTab = false;
					if (args.expandTab && args.unit === "character" && (caret - lineStart) % this._tabSize === 0) { //$NON-NLS-1$
						var lineText = model.getText(lineStart, caret);
						removeTab = !/[^ ]/.test(lineText); // Only spaces between line start and caret.
					}
					if (removeTab) {
						selection.extend(caret - this._tabSize);
						args.count += args.count < 0 ? 1 : -1;
					} else {
						var line = this._getLine(lineIndex);
						selection.extend(line.getNextOffset(caret, args));
						line.destroy();
					}
				}
				caret = selection.getCaret();
			}
			return selection;
		},
		_doBackspace: function (args) {
			var that = this;
			var selections = this._getSelections();
			selections.forEach(function(selection) {
				if (selection.isEmpty()) {
					if (!args.count) {
						args.count = 1;
					}
					args.count *= -1;
					args.expandTab = that._expandTab;
					that._doMove(args, selection);
				}
			});
			this._modifyContent({text: "", selection: selections}, true);
			return true;
		},
		_doCase: function (args) {
			var that = this;
			var selections = this._getSelections();
			var changes = [];
			selections.forEach(function(selection) {
				that._doMove(args, selection);
				var text = that.getText(selection.start, selection.end);
				switch (args.type) {
					case "lower": text = text.toLowerCase(); break; //$NON-NLS-1$
					case "capitalize": text = text.replace(/(?:^|\s)\S/g, function(a) { return a.toUpperCase(); }); break; //$NON-NLS-1$
					case "reverse":  //$NON-NLS-1$
						var newText = "";
						for (var i=0; i<text.length; i++) {
							var s = text[i];
							var l = s.toLowerCase();
							if (l !== s) {
								s = l;
							} else {
								s = s.toUpperCase();
							}
							newText += s;
						} 
						text = newText;
						break;
					default: text = text.toUpperCase(); break;
				}
				changes.push(text);
			});
			return this._modifyContent({text: changes, selection: selections, _ignoreDOMSelection: true}, true);
		},
		_doContent: function (text) {
			var that = this;
			var selections = this._getSelections();
			selections.forEach(function(selection) {
				if (that._overwriteMode && selection.isEmpty()) {
					var model = that._model;
					var lineIndex = model.getLineAtOffset(selection.end);
					if (selection.end < model.getLineEnd(lineIndex)) {
						var line = that._getLine(lineIndex);
						selection.extend(line.getNextOffset(selection.getCaret(), {unit:"character", count:1})); //$NON-NLS-1$
						line.destroy();
					}
				}
			});
			return this._modifyContent({text: text, selection: selections, _ignoreDOMSelection: true}, true);
		},
		_doCopy: function (e) {
			var text = this.getSelectionText();
			if (text) {
				return this._setClipboardText(text, e);
			}
			return true;
		},
		_doCursorNext: function (args) {
			var that = this;
			var selections = this._getSelections();
			selections.forEach(function(selection) {
				if (!selection.isEmpty() && !args.select) {
					selection.start = selection.end;
				} else {
					that._doMove(args, selection);
				}
				if (!args.select) { selection.collapse(); }
			});
			this._setSelection(selections, true);
			return true;
		},
		_doCursorPrevious: function (args) {
			var that = this;
			var selections = this._getSelections();
			selections.forEach(function(selection) {
				if (!selection.isEmpty() && !args.select) {
					selection.end = selection.start;
				} else {
					if (!args.count) {
						args.count = 1;
					}
					args.count *= -1;
					that._doMove(args, selection);
				}
				if (!args.select) { selection.collapse(); }
			});
			this._setSelection(selections, true);
			return true;
		},
		_doCut: function (e) {
			var text = this.getSelectionText();
			if (text) {
				if (!this._setClipboardText(text, e)) {
					return false;
				}
				this._doContent("");
			}
			return true;
		},
		_doDelete: function (args) {
			var that = this;
			var selections = this._getSelections();
			selections.forEach(function(selection) {
				if (selection.isEmpty()) {
					that._doMove(args, selection);
				}
			});
			this._modifyContent({text: "", selection: selections}, true);
			return true;
		},
		_doEnd: function (args) {
			var model = this._model;
			var that = this;
			var selections = this._getSelections();
			selections.forEach(function(selection) {
				if (args.ctrl) {
					selection.extend(model.getCharCount());
				} else {
					var offset = selection.getCaret();
					var lineIndex = model.getLineAtOffset(offset);
					if (that._wrapMode) {
						var line = that._getLine(lineIndex);
						var visualIndex = line.getLineIndex(offset);
						if (visualIndex === line.getLineCount() - 1) {
							offset = model.getLineEnd(lineIndex);
						} else {
							offset = line.getLineStart(visualIndex + 1) - 1;
						}
						line.destroy();
					} else {
						if (args.count && args.count > 0) {
							lineIndex = Math.min (lineIndex  + args.count - 1, model.getLineCount() - 1);
						}
						offset = model.getLineEnd(lineIndex);
					}
					selection.extend(offset);
				}
				if (!args.select) { selection.collapse(); }
			});
			this._setSelection(selections, true, true, args.ctrl ? function() {} : null);
			return true;
		},
		_doEnter: function (args) {
			if (this._singleMode) return true;
			var model = this._model;
			var selections = this._getSelections();
			this._doContent(model.getLineDelimiter()); 
			if (args && args.noCursor) {
				selections.forEach(function(selection) {
					selection.end = selection.start;
				});
				this._setSelection(selections, true);
			}
			return true;
		},
		_doEscape: function () {
			var selections = this._getSelections();
			if (selections.length > 1) {
				this._setSelection(selections[0], true);
			}
			return true;
		},
		_doHome: function (args) {
			var model = this._model;
			var that = this;
			var selections = this._getSelections();
			selections.forEach(function(selection) {
				if (args.ctrl) {
					selection.extend(0);
				} else {
					var offset = selection.getCaret();
					var lineIndex = model.getLineAtOffset(offset);
					if (that._wrapMode) {
						var line = that._getLine(lineIndex);
						var visualIndex = line.getLineIndex(offset);
						offset = line.getLineStart(visualIndex);
						line.destroy();
					} else {
						offset = model.getLineStart(lineIndex);
					}
					selection.extend(offset); 
				}
				if (!args.select) { selection.collapse(); }
			});
			this._setSelection(selections, true, true, args.ctrl ? function() {} : null);
			return true;
		},
		_doLineDown: function (args) {
			var model = this._model;
			var that = this;
			var selections = this._getSelections();
			selections.forEach(function(selection) {
				var caret = selection.getCaret();
				var lineIndex = model.getLineAtOffset(caret), visualIndex;
				var line = that._getLine(lineIndex);
				var x = selection._columnX, y = 1, lastLine = false;
				if (x === -1 || args.wholeLine || (args.select && util.isIE)) {
					var offset = args.wholeLine ? model.getLineEnd(lineIndex + 1) : caret;
					x = selection._columnX = line.getBoundingClientRect(offset).left;
				}
				if ((visualIndex = line.getLineIndex(caret)) < line.getLineCount() - 1) {
					y = line.getClientRects(visualIndex + 1).top + 1;
				} else {
					var lastLineCount = model.getLineCount() - 1;
					lastLine = lineIndex === lastLineCount;
					if (args.count && args.count > 0) {
						lineIndex = Math.min (lineIndex + args.count, lastLineCount);
					} else {
						lineIndex++;
					}
				}
				var select = false;
				if (lastLine) {
					if (args.select || (util.isMac || util.isLinux)) {
						selection.extend(model.getCharCount());
						select = true;
					}
				} else {
					if (line.lineIndex !== lineIndex) {
						line.destroy();
						line = that._getLine(lineIndex);
					}
					selection.extend(line.getOffset(x, y));
					select = true;
				}
				if (select) {
					if (!args.select) { selection.collapse(); }
				}
				line.destroy();
			});
			that._setSelection(selections, true, true, null, 0, false, true);
			return true;
		},
		_doLineUp: function (args) {
			var model = this._model;
			var that = this;
			var selections = this._getSelections();
			selections.forEach(function(selection) {
				var caret = selection.getCaret();
				var lineIndex = model.getLineAtOffset(caret), visualIndex;
				var line = that._getLine(lineIndex);
				var x = selection._columnX, firstLine = false, y;
				if (x === -1 || args.wholeLine || (args.select && util.isIE)) {
					var offset = args.wholeLine ? model.getLineStart(lineIndex - 1) : caret;
					x = selection._columnX = line.getBoundingClientRect(offset).left;
				}
				if ((visualIndex = line.getLineIndex(caret)) > 0) {
					y = line.getClientRects(visualIndex - 1).top + 1;
				} else {
					firstLine = lineIndex === 0;
					if (!firstLine) {
						if (args.count && args.count > 0) {
							lineIndex = Math.max (lineIndex - args.count, 0);
						} else {
							lineIndex--;
						}
						y = that._getLineHeight(lineIndex) - 1;
					}
				}
				var select = false;
				if (firstLine) {
					if (args.select || (util.isMac || util.isLinux)) {
						selection.extend(0);
						select = true;
					}
				} else {
					if (line.lineIndex !== lineIndex) {
						line.destroy();
						line = that._getLine(lineIndex);
					}
					selection.extend(line.getOffset(x, y));
					select = true;
				}
				if (select) {
					if (!args.select) { selection.collapse(); }
				}
				line.destroy();
			});
			that._setSelection(selections, true, true, null, 0, false, true);
			return true;
		},
		_doNoop: function () {
			return true;
		},
		_doPageDown: function (args) {
			var that = this;
			var model = this._model;
			var selections = this._getSelections();
			var lineCount = model.getLineCount();
			var _scroll = this._getScroll();
			var clientHeight = this._getClientHeight();
			var lineHeight = this._getLineHeight();
			var lines = Math.floor(clientHeight / lineHeight);
			var x, line, pageScroll;
			selections.forEach(function(selection) {
				var caret = selection.getCaret();
				var caretLine = model.getLineAtOffset(caret);
				if (that._lineHeight) {
					x = selection._columnX;
					var caretRect = that._getBoundsAtOffset(caret);
					if (x === -1 || (args.select && util.isIE)) {
						x = selection._columnX = caretRect.left;
					}
					var lineIndex = that._getLineIndex(caretRect.top + clientHeight);
					line = that._getLine(lineIndex);
					var linePixel = that._getLinePixel(lineIndex);
					var y = caretRect.top + clientHeight - linePixel;
					caret = line.getOffset(x, y);
					var rect = line.getBoundingClientRect(caret);
					line.destroy();
					selection.extend(caret);
					if (!args.select) { selection.collapse(); }
					pageScroll = pageScroll !== undefined ? Math.min(pageScroll, rect.top + linePixel - caretRect.top) : rect.top + linePixel - caretRect.top;
				} else {
					if (caretLine < lineCount - 1) {
						var scrollLines = Math.min(lineCount - caretLine - 1, lines);
						scrollLines = Math.max(1, scrollLines);
						x = selection._columnX;
						if (x === -1 || (args.select && util.isIE)) {
							line = that._getLine(caretLine);
							x = selection._columnX = line.getBoundingClientRect(caret).left;
							line.destroy();
						}
						line = that._getLine(caretLine + scrollLines);
						selection.extend(line.getOffset(x, 0));
						line.destroy();
						if (!args.select) { selection.collapse(); }
						var verticalMaximum = lineCount * lineHeight;
						var scrollOffset = _scroll.y + scrollLines * lineHeight;
						if (scrollOffset + clientHeight > verticalMaximum) {
							scrollOffset = verticalMaximum - clientHeight;
						}
						pageScroll = pageScroll !== undefined ? Math.min(pageScroll, scrollOffset - _scroll.y) : scrollOffset - _scroll.y;
					}
				}
			});
			this._setSelection(selections, true, true, function() {}, pageScroll, false, true);
			return true;
		},
		_doPageUp: function (args) {
			var that = this;
			var model = this._model;
			var selections = this._getSelections();
			var _scroll = this._getScroll();
			var clientHeight = this._getClientHeight();
			var lineHeight = this._getLineHeight();
			var lines = Math.floor(clientHeight / lineHeight);
			var x, line, pageScroll;
			selections.forEach(function(selection) {
				var caret = selection.getCaret();
				var caretLine = model.getLineAtOffset(caret);
				if (that._lineHeight) {
					x = selection._columnX;
					var caretRect = that._getBoundsAtOffset(caret);
					if (x === -1 || (args.select && util.isIE)) {
						x = selection._columnX = caretRect.left;
					}
					var lineIndex = that._getLineIndex(caretRect.bottom - clientHeight);
					line = that._getLine(lineIndex);
					var linePixel = that._getLinePixel(lineIndex);
					var y = (caretRect.bottom - clientHeight) - linePixel;
					caret = line.getOffset(x, y);
					var rect = line.getBoundingClientRect(caret);
					line.destroy();
					selection.extend(caret);
					if (!args.select) { selection.collapse(); }
					pageScroll = pageScroll !== undefined ? Math.max(pageScroll, rect.top + linePixel - caretRect.top) : rect.top + linePixel - caretRect.top;
				} else {
					if (caretLine > 0) {
						var scrollLines = Math.max(1, Math.min(caretLine, lines));
						x = selection._columnX;
						if (x === -1 || (args.select && util.isIE)) {
							line = that._getLine(caretLine);
							x = selection._columnX = line.getBoundingClientRect(caret).left;
							line.destroy();
						}
						line = that._getLine(caretLine - scrollLines);
						selection.extend(line.getOffset(x, that._getLineHeight(caretLine - scrollLines) - 1));
						line.destroy();
						if (!args.select) { selection.collapse(); }
						var scrollOffset = Math.max(0, _scroll.y - scrollLines * lineHeight);
						pageScroll = pageScroll !== undefined  ? Math.max(pageScroll, scrollOffset - _scroll.y) : scrollOffset - _scroll.y;
					}
				}
			});
			this._setSelection(selections, true, true, function() {}, pageScroll, false, true);
			return true;
		},
		_doPaste: function(e) {
			var that = this;
			var result = this._getClipboardText(e, function(text) {
				if (text.length) {
					if (util.isLinux && that._lastMouseButton === 2) {
						var timeDiff = new Date().getTime() - that._lastMouseTime;
						if (timeDiff <= that._clickTime) {
							that._setSelectionTo(that._lastMouseX, that._lastMouseY, true);
						}
					}
					var selections = that._getSelections();
					var delimiter = that._singleMode ? "" : that._model.getLineDelimiter();
					that._doContent(selections.length > 1 && selections.length === text.length ? text : text.join(delimiter));
				}
			});
			return result !== null;
		},
		_doScroll: function (args) {
			var type = args.type;
			var model = this._model;
			var lineCount = model.getLineCount();
			var clientHeight = this._getClientHeight();
			var lineHeight = this._getLineHeight();
			var verticalMaximum = this._lineHeight ? this._scrollHeight : lineCount * lineHeight;
			var verticalScrollOffset = this._getScroll().y;
			var pixel;
			switch (type) {
				case "textStart": pixel = 0; break; //$NON-NLS-1$
				case "textEnd": pixel = verticalMaximum - clientHeight; break; //$NON-NLS-1$
				case "pageDown": pixel = verticalScrollOffset + clientHeight; break; //$NON-NLS-1$
				case "pageUp": pixel = verticalScrollOffset - clientHeight; break; //$NON-NLS-1$
				case "lineDown": pixel = verticalScrollOffset + lineHeight; break; //$NON-NLS-1$
				case "lineUp": pixel = verticalScrollOffset - lineHeight; break; //$NON-NLS-1$
				case "centerLine": //$NON-NLS-1$
					var selection = this._getSelections()[0];
					var lineStart = model.getLineAtOffset(selection.start);
					var lineEnd = model.getLineAtOffset(selection.end);
					var selectionHeight = (lineEnd - lineStart + 1) * lineHeight;
					pixel = (lineStart * lineHeight) - (clientHeight / 2) + (selectionHeight / 2);
					break;
			}
			if (pixel !== undefined) {
				pixel = Math.min(Math.max(0, pixel), verticalMaximum - clientHeight);
				this._scrollViewAnimated(0, pixel - verticalScrollOffset, function() {});
			}
			return true;
		},
		_doSelectAll: function () {
			var model = this._model;
			this._setSelection(new Selection(0, model.getCharCount()), false);
			return true;
		},
		_doTab: function () {
			if (!this._tabMode || this._readonly) { return; }
			var text = "\t"; //$NON-NLS-1$
			var selections = this._getSelections();
			if (this._expandTab) {
				text = [];
				var model = this._model;
				var tabSize = this._tabSize;
				selections.forEach(function(selection) {
					var caret = selection.getCaret();
					var lineIndex = model.getLineAtOffset(caret);
					var lineStart = model.getLineStart(lineIndex);
					var spaces = tabSize - ((caret - lineStart) % tabSize);
					text.push((newArray(spaces + 1)).join(" ")); //$NON-NLS-1$
				});
			}
			return this._modifyContent({text: text, selection: selections, _ignoreDOMSelection: true}, true);
		},
		_doShiftTab: function () {
			if (!this._tabMode || this._readonly) { return; }
			return true;
		},
		_doOverwriteMode: function () {
			if (this._readonly) { return; }
			this.setOptions({overwriteMode: !this.getOptions("overwriteMode")}); //$NON-NLS-1$
			return true;
		},
		_doTabMode: function () {
			this._tabMode = !this._tabMode;
			return true;
		},
		_doWrapMode: function () {
			this.setOptions({wrapMode: !this.getOptions("wrapMode")}); //$NON-NLS-1$
			return true;
		},
		
		/************************************ Internals ******************************************/
		_autoScroll: function () {
			var model = this._model;
			var selections = this._getSelections();
			var selection = Selection.editing(selections, this._autoScrollDir === "down"); //$NON-NLS-1$
			var pt = this.convert({x: this._autoScrollX, y: this._autoScrollY}, "page", "document"); //$NON-NLS-1$ //$NON-NLS-2$
			var caret = selection.getCaret();
			var lineCount = model.getLineCount();
			var caretLine = model.getLineAtOffset(caret), lineIndex, line;
			if (this._autoScrollDir === "up" || this._autoScrollDir === "down") { //$NON-NLS-1$ //$NON-NLS-1$
				var _scroll = this._autoScrollY / this._getLineHeight();
				_scroll = _scroll < 0 ? Math.floor(_scroll) : Math.ceil(_scroll);
				lineIndex = caretLine;
				lineIndex = Math.max(0, Math.min(lineCount - 1, lineIndex + _scroll));
			} else if (this._autoScrollDir === "left" || this._autoScrollDir === "right") { //$NON-NLS-1$ //$NON-NLS-1$
				lineIndex = this._getLineIndex(pt.y);
				line = this._getLine(caretLine); 
				pt.x += line.getBoundingClientRect(caret, false).left;
				line.destroy();
			}
			if (this._blockSelection) {
				selections = this._getBlockSelections(selections, lineIndex, pt);
			} else if (lineIndex === 0 && (util.isMac || util.isLinux)) {
				selection.extend(0);
			} else if (lineIndex === lineCount - 1 && (util.isMac || util.isLinux)) {
				selection.extend(model.getCharCount());
			} else {
				line = this._getLine(lineIndex);
				selection.extend(line.getOffset(pt.x, pt.y - this._getLinePixel(lineIndex)));
				line.destroy();
			}
			this._setSelection(selections, true);
		},
		_autoScrollTimer: function () {
			this._autoScroll();
			var that = this;
			var win = this._getWindow();
			this._autoScrollTimerID = win.setTimeout(function () {that._autoScrollTimer();}, this._AUTO_SCROLL_RATE);
		},
		_calculateLineHeightTimer: function(calculate) {
			if (!this._lineHeight) { return; }
			if (this._calculateLHTimer) { return; }
			var lineCount = this._model.getLineCount(), i = 0;
			if (calculate) {
				var c = 0;
				var MAX_TIME = 100;
				var start = new Date().getTime(), firstLine = 0;
				while (i < lineCount) {
					if (!this._lineHeight[i]) {
						c++;
						if (!firstLine) { firstLine = i; }
						this._lineHeight[i] = this._calculateLineHeight(i);
					}
					i++;
					if ((new Date().getTime() - start) > MAX_TIME) {
						break;
					}
				}
				this.redrawRulers(0, lineCount);
				this._queueUpdate();
			}
			var win = this._getWindow();
			if (i !== lineCount) {
				var that = this;
				this._calculateLHTimer = win.setTimeout(function() {
					that._calculateLHTimer = null;
					that._calculateLineHeightTimer(true);
				}, 0);
				return;
			}
			if (this._calculateLHTimer) {
				win.clearTimeout(this._calculateLHTimer);
				this._calculateLHTimer = undefined;
			}
		},
		_calculateLineHeight: function(lineIndex) {
			var line = this._getLine(lineIndex);
			var rect = line.getBoundingClientRect();
			line.destroy();
			return Math.max(1, rect.bottom - rect.top);
		},
		_calculateMetrics: function() {
			var _parent = this._clientDiv;
			var doc = _parent.ownerDocument;
			var c = " "; //$NON-NLS-1$
			var line = util.createElement(doc, "div"); //$NON-NLS-1$
			line.style.lineHeight = "normal"; //$NON-NLS-1$
			var model = this._model;
			var lineText = model.getLine(0);
			var e = {type:"LineStyle", textView: this, 0: 0, lineText: lineText, lineStart: 0}; //$NON-NLS-1$
			this.onLineStyle(e);
			applyStyle(e.style, line);
			line.style.position = "fixed"; //$NON-NLS-1$
			line.style.left = "-1000px"; //$NON-NLS-1$
			var span1 = util.createElement(doc, "span"); //$NON-NLS-1$
			span1.appendChild(doc.createTextNode(c));
			line.appendChild(span1);
			var span2 = util.createElement(doc, "span"); //$NON-NLS-1$
			span2.style.fontStyle = "italic"; //$NON-NLS-1$
			span2.appendChild(doc.createTextNode(c));
			line.appendChild(span2);
			var span3 = util.createElement(doc, "span"); //$NON-NLS-1$
			span3.style.fontWeight = "bold"; //$NON-NLS-1$
			span3.appendChild(doc.createTextNode(c));
			line.appendChild(span3);
			var span4 = util.createElement(doc, "span"); //$NON-NLS-1$
			span4.style.fontWeight = "bold"; //$NON-NLS-1$
			span4.style.fontStyle = "italic"; //$NON-NLS-1$
			span4.appendChild(doc.createTextNode(c));
			line.appendChild(span4);
			_parent.appendChild(line);
			var lineRect = line.getBoundingClientRect();
			var spanRect1 = span1.getBoundingClientRect();
			var spanRect2 = span2.getBoundingClientRect();
			var spanRect3 = span3.getBoundingClientRect();
			var spanRect4 = span4.getBoundingClientRect();
			var h1 = spanRect1.bottom - spanRect1.top;
			var h2 = spanRect2.bottom - spanRect2.top;
			var h3 = spanRect3.bottom - spanRect3.top;
			var h4 = spanRect4.bottom - spanRect4.top;
			var fontStyle = 0;
			var invalid = (lineRect.bottom - lineRect.top) <= 0;
			var lineHeight = Math.max(1, lineRect.bottom - lineRect.top);
			if (h2 > h1) {
				fontStyle = 1;
			}
			if (h3 > h2) {
				fontStyle = 2;
			}
			if (h4 > h3) {
				fontStyle = 3;
			}
			var style;
			if (fontStyle !== 0) {
				style = {style: {}};
				if ((fontStyle & 1) !== 0) {
					style.style.fontStyle = "italic"; //$NON-NLS-1$
				}
				if ((fontStyle & 2) !== 0) {
					style.style.fontWeight = "bold"; //$NON-NLS-1$
				}
			}
			var trim = getLineTrim(line);
			_parent.removeChild(line);
			
			// calculate pad and scroll width
			var pad = getPadding(this._viewDiv);
			var div1 = util.createElement(doc, "div"); //$NON-NLS-1$
			div1.style.position = "fixed"; //$NON-NLS-1$
			div1.style.left = "-1000px"; //$NON-NLS-1$
			div1.style.paddingLeft = pad.left + "px"; //$NON-NLS-1$
			div1.style.paddingTop = pad.top + "px"; //$NON-NLS-1$
			div1.style.paddingRight = pad.right + "px"; //$NON-NLS-1$
			div1.style.paddingBottom = pad.bottom + "px"; //$NON-NLS-1$
			div1.style.width = "100px"; //$NON-NLS-1$
			div1.style.height = "100px"; //$NON-NLS-1$
			var div2 = util.createElement(doc, "div"); //$NON-NLS-1$
			div2.style.width = "100%"; //$NON-NLS-1$
			div2.style.height = "100%"; //$NON-NLS-1$
			div1.appendChild(div2);
			_parent.appendChild(div1);
			var rect1 = div1.getBoundingClientRect();
			var rect2 = div2.getBoundingClientRect();
			var scrollWidth = 0;
			if (!this._singleMode && !this._noScroll) {
				div1.style.overflow = 'hidden'; //$NON-NLS-1$
				div2.style.height = "200px"; //$NON-NLS-1$
				var w1 = div1.clientWidth;
				div1.style.overflow = 'scroll'; //$NON-NLS-1$
				var w2 = div1.clientWidth;
				scrollWidth = w1 - w2;
			}
			_parent.removeChild(div1);
			pad = {
				left: rect2.left - rect1.left,
				top: rect2.top - rect1.top,
				right: rect1.right - rect2.right,
				bottom: rect1.bottom - rect2.bottom
			};
			var wrapWidth = 0, marginWidth = 0, charWidth = 0;
			if (!invalid) {
				div1 = util.createElement(doc, "div"); //$NON-NLS-1$
				div1.style.position = "fixed"; //$NON-NLS-1$
				div1.style.left = "-1000px"; //$NON-NLS-1$
				_parent.appendChild(div1);
				div1.innerHTML = newArray(2).join("a"); //$NON-NLS-1$
				rect1 = div1.getBoundingClientRect();
				charWidth = Math.ceil(rect1.right - rect1.left);
				if (this._wrapOffset || this._marginOffset) {
					div1.innerHTML = newArray(this._wrapOffset + 1 + (util.isWebkit ? 0 : 1)).join(" "); //$NON-NLS-1$
					rect1 = div1.getBoundingClientRect();
					wrapWidth = Math.ceil(rect1.right - rect1.left);
					div1.innerHTML = newArray(this._marginOffset + 1).join(" "); //$NON-NLS-1$
					rect2 = div1.getBoundingClientRect();
					marginWidth = Math.ceil(rect2.right - rect2.left);
				}
				_parent.removeChild(div1);
			}
			return {
				lineHeight: lineHeight,
				largestFontStyle: style,
				lineTrim: trim,
				viewPadding: pad,
				scrollWidth: scrollWidth,
				wrapWidth: wrapWidth,
				marginWidth: marginWidth,
				charWidth: charWidth,
				invalid: invalid
			};
		},
		_cancelAnimation: function() {
			if (this._animation) {
				this._animation.stop();
				this._animation = null;
			}
		},
		_clearSelection: function (direction) {
			var selections = this._getSelections();
			selections.forEach(function(selection) {
				if (direction === "next") { //$NON-NLS-1$
					selection.start = selection.end;
				} else {
					selection.end = selection.start;
				}
			});
			this._setSelection(selections, true);
			return true;
		},
		_commitIME: function (insertText) {
			if (this._imeOffset === -1) { return; }
			var model = this._model;
			var lineIndex = model.getLineAtOffset(this._imeOffset);
			var lineStart = model.getLineStart(lineIndex);
			var line = this._getLineNode(lineIndex);
			if (!insertText) {
				// make the state of the IME match the state the view expects it be in
				// when the view commits the text and IME also need to be committed
				// this can be accomplished by changing the focus around
				this._scrollDiv.focus();
				this._clientDiv.focus();
				
				var newText = this._getDOMText(line).text;
				var oldText = model.getLine(lineIndex);
				var start = this._imeOffset - lineStart;
				var end = start + newText.length - oldText.length;
				if (start !== end) {
					insertText = newText.substring(start, end);
				}
			}
			this._imeOffset = -1;
			if (insertText) {
				if (!this._doContent(insertText) && !util.isWebkit) {
					line.lineRemoved = true;
					this._queueUpdate();
				}
			}
		},
		_createActions: function () {
			this.addKeyMode(new mKeyModes.DefaultKeyMode(this));
			//1 to 1, no duplicates
			var that = this;
			this._actions = {
				"noop": {defaultHandler: function() {return that._doNoop();}}, //$NON-NLS-1$

				"lineUp": {defaultHandler: function(data) {return that._doLineUp(merge(data,{select: false}));}, actionDescription: {name: messages.lineUp}}, //$NON-NLS-1$
				"lineDown": {defaultHandler: function(data) {return that._doLineDown(merge(data,{select: false}));}, actionDescription: {name: messages.lineDown}}, //$NON-NLS-1$
				"lineStart": {defaultHandler: function(data) {return that._doHome(merge(data,{select: false, ctrl:false}));}, actionDescription: {name: messages.lineStart}}, //$NON-NLS-1$
				"lineEnd": {defaultHandler: function(data) {return that._doEnd(merge(data,{select: false, ctrl:false}));}, actionDescription: {name: messages.lineEnd}}, //$NON-NLS-1$
				"charPrevious": {defaultHandler: function(data) {return that._doCursorPrevious(merge(data,{select: false, unit:"character"}));}, actionDescription: {name: messages.charPrevious}}, //$NON-NLS-1$ //$NON-NLS-1$
				"charNext": {defaultHandler: function(data) {return that._doCursorNext(merge(data,{select: false, unit:"character"}));}, actionDescription: {name: messages.charNext}}, //$NON-NLS-1$ //$NON-NLS-1$
				"pageUp": {defaultHandler: function(data) {return that._doPageUp(merge(data,{select: false}));}, actionDescription: {name: messages.pageUp}}, //$NON-NLS-1$
				"pageDown": {defaultHandler: function(data) {return that._doPageDown(merge(data,{select: false}));}, actionDescription: {name: messages.pageDown}}, //$NON-NLS-1$
				"scrollPageUp": {defaultHandler: function(data) {return that._doScroll(merge(data,{type: "pageUp"}));}, actionDescription: {name: messages.scrollPageUp}}, //$NON-NLS-1$ //$NON-NLS-1$
				"scrollPageDown": {defaultHandler: function(data) {return that._doScroll(merge(data,{type: "pageDown"}));}, actionDescription: {name: messages.scrollPageDown}}, //$NON-NLS-1$ //$NON-NLS-1$
				"scrollLineUp": {defaultHandler: function(data) {return that._doScroll(merge(data,{type: "lineUp"}));}, actionDescription: {name: messages.scrollLineUp}}, //$NON-NLS-1$ //$NON-NLS-1$
				"scrollLineDown": {defaultHandler: function(data) {return that._doScroll(merge(data,{type: "lineDown"}));}, actionDescription: {name: messages.scrollLineDown}}, //$NON-NLS-1$ //$NON-NLS-1$
				"wordPrevious": {defaultHandler: function(data) {return that._doCursorPrevious(merge(data,{select: false, unit:"word"}));}, actionDescription: {name: messages.wordPrevious}}, //$NON-NLS-1$ //$NON-NLS-1$
				"wordNext": {defaultHandler: function(data) {return that._doCursorNext(merge(data,{select: false, unit:"word"}));}, actionDescription: {name: messages.wordNext}}, //$NON-NLS-1$ //$NON-NLS-1$
				"textStart": {defaultHandler: function(data) {return that._doHome(merge(data,{select: false, ctrl:true}));}, actionDescription: {name: messages.textStart}}, //$NON-NLS-1$
				"textEnd": {defaultHandler: function(data) {return that._doEnd(merge(data,{select: false, ctrl:true}));}, actionDescription: {name: messages.textEnd}}, //$NON-NLS-1$
				"scrollTextStart": {defaultHandler: function(data) {return that._doScroll(merge(data,{type: "textStart"}));}, actionDescription: {name: messages.scrollTextStart}}, //$NON-NLS-1$ //$NON-NLS-1$
				"scrollTextEnd": {defaultHandler: function(data) {return that._doScroll(merge(data,{type: "textEnd"}));}, actionDescription: {name: messages.scrollTextEnd}}, //$NON-NLS-1$ //$NON-NLS-1$
				"centerLine": {defaultHandler: function(data) {return that._doScroll(merge(data,{type: "centerLine"}));}, actionDescription: {name: messages.centerLine}}, //$NON-NLS-1$ //$NON-NLS-1$
				
				"selectLineUp": {defaultHandler: function(data) {return that._doLineUp(merge(data,{select: true}));}, actionDescription: {name: messages.selectLineUp}}, //$NON-NLS-1$
				"selectLineDown": {defaultHandler: function(data) {return that._doLineDown(merge(data,{select: true}));}, actionDescription: {name: messages.selectLineDown}}, //$NON-NLS-1$
				"selectWholeLineUp": {defaultHandler: function(data) {return that._doLineUp(merge(data,{select: true, wholeLine: true}));}, actionDescription: {name: messages.selectWholeLineUp}}, //$NON-NLS-1$
				"selectWholeLineDown": {defaultHandler: function(data) {return that._doLineDown(merge(data,{select: true, wholeLine: true}));}, actionDescription: {name: messages.selectWholeLineDown}}, //$NON-NLS-1$
				"selectLineStart": {defaultHandler: function(data) {return that._doHome(merge(data,{select: true, ctrl:false}));}, actionDescription: {name: messages.selectLineStart}}, //$NON-NLS-1$
				"selectLineEnd": {defaultHandler: function(data) {return that._doEnd(merge(data,{select: true, ctrl:false}));}, actionDescription: {name: messages.selectLineEnd}}, //$NON-NLS-1$
				"selectCharPrevious": {defaultHandler: function(data) {return that._doCursorPrevious(merge(data,{select: true, unit:"character"}));}, actionDescription: {name: messages.selectCharPrevious}}, //$NON-NLS-1$ //$NON-NLS-1$
				"selectCharNext": {defaultHandler: function(data) {return that._doCursorNext(merge(data,{select: true, unit:"character"}));}, actionDescription: {name: messages.selectCharNext}}, //$NON-NLS-1$ //$NON-NLS-1$
				"selectPageUp": {defaultHandler: function(data) {return that._doPageUp(merge(data,{select: true}));}, actionDescription: {name: messages.selectPageUp}}, //$NON-NLS-1$
				"selectPageDown": {defaultHandler: function(data) {return that._doPageDown(merge(data,{select: true}));}, actionDescription: {name: messages.selectPageDown}}, //$NON-NLS-1$
				"selectWordPrevious": {defaultHandler: function(data) {return that._doCursorPrevious(merge(data,{select: true, unit:"word"}));}, actionDescription: {name: messages.selectWordPrevious}}, //$NON-NLS-1$ //$NON-NLS-1$
				"selectWordNext": {defaultHandler: function(data) {return that._doCursorNext(merge(data,{select: true, unit:"word"}));}, actionDescription: {name: messages.selectWordNext}}, //$NON-NLS-1$ //$NON-NLS-1$
				"selectTextStart": {defaultHandler: function(data) {return that._doHome(merge(data,{select: true, ctrl:true}));}, actionDescription: {name: messages.selectTextStart}}, //$NON-NLS-1$
				"selectTextEnd": {defaultHandler: function(data) {return that._doEnd(merge(data,{select: true, ctrl:true}));}, actionDescription: {name: messages.selectTextEnd}}, //$NON-NLS-1$

				"deletePrevious": {defaultHandler: function(data) {return that._doBackspace(merge(data,{unit:"character"}));}, actionDescription: {name: messages.deletePrevious}}, //$NON-NLS-1$ //$NON-NLS-1$
				"deleteNext": {defaultHandler: function(data) {return that._doDelete(merge(data,{unit:"character"}));}, actionDescription: {name: messages.deleteNext}}, //$NON-NLS-1$ //$NON-NLS-1$
				"deleteWordPrevious": {defaultHandler: function(data) {return that._doBackspace(merge(data,{unit:"word"}));}, actionDescription: {name: messages.deleteWordPrevious}}, //$NON-NLS-1$ //$NON-NLS-1$
				"deleteWordNext": {defaultHandler: function(data) {return that._doDelete(merge(data,{unit:"word"}));}, actionDescription: {name: messages.deleteWordNext}}, //$NON-NLS-1$ //$NON-NLS-1$
				"deleteLineStart": {defaultHandler: function(data) {return that._doBackspace(merge(data,{unit: "line"}));}, actionDescription: {name: messages.deleteLineStart}}, //$NON-NLS-1$ //$NON-NLS-1$
				"deleteLineEnd": {defaultHandler: function(data) {return that._doDelete(merge(data,{unit: "line"}));}, actionDescription: {name: messages.deleteLineEnd}}, //$NON-NLS-1$ //$NON-NLS-1$
				"tab": {defaultHandler: function(data) {return that._doTab(merge(data,{}));}, actionDescription: {name: messages.tab}}, //$NON-NLS-1$
				"shiftTab": {defaultHandler: function(data) {return that._doShiftTab(merge(data,{}));}, actionDescription: {name: messages.shiftTab}}, //$NON-NLS-1$
				"enter": {defaultHandler: function(data) {return that._doEnter(merge(data,{}));}, actionDescription: {name: messages.enter}}, //$NON-NLS-1$
				"enterNoCursor": {defaultHandler: function(data) {return that._doEnter(merge(data,{noCursor:true}));}, actionDescription: {name: messages.enterNoCursor}}, //$NON-NLS-1$
				"escape": {defaultHandler: function(data) {return that._doEscape(merge(data,{}));}, actionDescription: {name: messages.escape}}, //$NON-NLS-1$
				"selectAll": {defaultHandler: function(data) {return that._doSelectAll(merge(data,{}));}, actionDescription: {name: messages.selectAll}}, //$NON-NLS-1$
				"copy": {defaultHandler: function(data) {return that._doCopy(merge(data,{}));}, actionDescription: {name: messages.copy}}, //$NON-NLS-1$
				"cut": {defaultHandler: function(data) {return that._doCut(merge(data,{}));}, actionDescription: {name: messages.cut}}, //$NON-NLS-1$
				"paste": {defaultHandler: function(data) {return that._doPaste(merge(data,{}));}, actionDescription: {name: messages.paste}}, //$NON-NLS-1$
				
				"uppercase": {defaultHandler: function(data) {return that._doCase(merge(data,{type: "upper"}));}, actionDescription: {name: messages.uppercase}}, //$NON-NLS-1$ //$NON-NLS-1$
				"lowercase": {defaultHandler: function(data) {return that._doCase(merge(data,{type: "lower"}));}, actionDescription: {name: messages.lowercase}}, //$NON-NLS-1$ //$NON-NLS-1$
				"capitalize": {defaultHandler: function(data) {return that._doCase(merge(data,{unit: "word", type: "capitalize"}));}, actionDescription: {name: messages.capitalize}}, //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-1$
				"reversecase": {defaultHandler: function(data) {return that._doCase(merge(data,{type: "reverse"}));}, actionDescription: {name: messages.reversecase}}, //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-1$
				
				"toggleOverwriteMode": {defaultHandler: function(data) {return that._doOverwriteMode(merge(data,{}));}, actionDescription: {name: messages.toggleOverwriteMode}}, //$NON-NLS-1$
				"toggleTabMode": {defaultHandler: function(data) {return that._doTabMode(merge(data,{}));}, actionDescription: {name: messages.toggleTabMode}}, //$NON-NLS-1$
				"toggleWrapMode": {defaultHandler: function(data) {return that._doWrapMode(merge(data,{}));}, actionDescription: {name: messages.toggleWrapMode}} //$NON-NLS-1$
			};
		},
		_createRulerParent: function(doc, className) {
			var div = util.createElement(doc, "div"); //$NON-NLS-1$
			div.className = className;
			div.tabIndex = -1;
			div.style.overflow = "hidden"; //$NON-NLS-1$
			div.style.MozUserSelect = "none"; //$NON-NLS-1$
			div.style.WebkitUserSelect = "none"; //$NON-NLS-1$
			div.style.position = "absolute"; //$NON-NLS-1$
			div.style.top = "0px"; //$NON-NLS-1$
			div.style.bottom = "0px"; //$NON-NLS-1$
			div.style.cursor = "default"; //$NON-NLS-1$
			div.style.display = "none"; //$NON-NLS-1$
			div.setAttribute("aria-hidden", "true"); //$NON-NLS-1$ //$NON-NLS-2$
			this._rootDiv.appendChild(div);
			return div;
		},
		_createRuler: function(ruler, index) {
			if (!this._clientDiv) { return; }
			var rulerParent = this._getRulerParent(ruler);
			if (!rulerParent) { return; }
			if (rulerParent !== this._marginDiv || this._marginOffset) {
				rulerParent.style.display = "block"; //$NON-NLS-1$
			}
			rulerParent.rulerWidth = undefined;
			var div = util.createElement(rulerParent.ownerDocument, "div"); //$NON-NLS-1$
			div._ruler = ruler;
			ruler.node = div;
			div.rulerChanged = true;
			div.style.position = "relative"; //$NON-NLS-1$
			div.style.cssFloat = "left"; //$NON-NLS-1$
			div.style.styleFloat = "left"; //$NON-NLS-1$
			div.style.outline = "none"; //$NON-NLS-1$
			if (index === undefined || index < 0 || index >= rulerParent.children.length) {
				rulerParent.appendChild(div);
			} else {
				var sibling = rulerParent.firstChild;
				while (sibling && index-- > 0) {
					sibling = sibling.nextSibling;
				}
				rulerParent.insertBefore(div, sibling);
			}
		},
		_createSelectionDiv: function() {
			var div = util.createElement(this._parent.ownerDocument, "div"); //$NON-NLS-1$
			div.className = "textviewSelection"; //$NON-NLS-1$
			div.style.position = "absolute"; //$NON-NLS-1$
			div.style.borderWidth = "0px"; //$NON-NLS-1$
			div.style.margin = "0px"; //$NON-NLS-1$
			div.style.padding = "0px"; //$NON-NLS-1$
			div.style.outline = "none"; //$NON-NLS-1$
			div.style.width = "0px"; //$NON-NLS-1$
			div.style.height = "0px"; //$NON-NLS-1$
			div.style.zIndex = "0"; //$NON-NLS-1$
			return div;
		},
		_createView: function() {
			if (this._clientDiv) { return; }
			var _parent = this._parent;
			while (_parent.hasChildNodes()) { _parent.removeChild(_parent.lastChild); }

			var doc = _parent.ownerDocument;
			var rootDiv = util.createElement(doc, "div"); //$NON-NLS-1$
			this._rootDiv = rootDiv;
			rootDiv.tabIndex = -1;
			rootDiv.style.position = "relative"; //$NON-NLS-1$
			rootDiv.style.overflow = "hidden"; //$NON-NLS-1$
			rootDiv.style.width = "100%"; //$NON-NLS-1$
			rootDiv.style.height = "100%"; //$NON-NLS-1$
			rootDiv.style.overflow = "hidden"; //$NON-NLS-1$
			rootDiv.style.WebkitTextSizeAdjust = "100%"; //$NON-NLS-1$
			rootDiv.setAttribute("role", "application"); //$NON-NLS-1$ //$NON-NLS-2$
			_parent.appendChild(rootDiv);
			
			var leftDiv = this._createRulerParent(doc, "textviewLeftRuler"); //$NON-NLS-1$
			this._leftDiv = leftDiv;

			var viewDiv = util.createElement(doc, "div"); //$NON-NLS-1$
			viewDiv.className = "textviewScroll"; //$NON-NLS-1$
			this._viewDiv = viewDiv;
			viewDiv.tabIndex = -1;
			viewDiv.style.position = "absolute"; //$NON-NLS-1$
			viewDiv.style.top = "0px"; //$NON-NLS-1$
			viewDiv.style.bottom = "0px"; //$NON-NLS-1$
			viewDiv.style.borderWidth = "0px"; //$NON-NLS-1$
			viewDiv.style.margin = "0px"; //$NON-NLS-1$
			viewDiv.style.outline = "none"; //$NON-NLS-1$
			viewDiv.style.background = "transparent"; //$NON-NLS-1$
			rootDiv.appendChild(viewDiv);
			
			var rightDiv = this._createRulerParent(doc, "textviewRightRuler"); //$NON-NLS-1$
			this._rightDiv = rightDiv;
			if (document.dir == "rtl") { /* ACGC */
				rightDiv.style.left = "0px"; //$NON-NLS-1$
			}else{
				rightDiv.style.right = "0px"; //$NON-NLS-1$	  
			}

			var innerRightDiv = this._createRulerParent(doc, "textviewInnerRightRuler"); //$NON-NLS-1$
			this._innerRightDiv = innerRightDiv;
			innerRightDiv.style.zIndex = "1"; //$NON-NLS-1$

			var scrollDiv = util.createElement(doc, "div"); //$NON-NLS-1$
			this._scrollDiv = scrollDiv;
			scrollDiv.style.margin = "0px"; //$NON-NLS-1$
			scrollDiv.style.borderWidth = "0px"; //$NON-NLS-1$
			scrollDiv.style.padding = "0px"; //$NON-NLS-1$
			viewDiv.appendChild(scrollDiv);
			
			var marginDiv = this._marginDiv = this._createRulerParent(doc, "textviewMarginRuler"); //$NON-NLS-1$
			marginDiv.style.zIndex = "4"; //$NON-NLS-1$
			
			if (!util.isIE && !util.isIOS) {
				var clipDiv = util.createElement(doc, "div"); //$NON-NLS-1$
				this._clipDiv = clipDiv;
				clipDiv.style.position = "absolute"; //$NON-NLS-1$
				clipDiv.style.overflow = "hidden"; //$NON-NLS-1$
				clipDiv.style.margin = "0px"; //$NON-NLS-1$
				clipDiv.style.borderWidth = "0px"; //$NON-NLS-1$
				clipDiv.style.padding = "0px"; //$NON-NLS-1$
				clipDiv.style.background = "transparent"; //$NON-NLS-1$
				rootDiv.appendChild(clipDiv);
				
				var clipScrollDiv = util.createElement(doc, "div"); //$NON-NLS-1$
				this._clipScrollDiv = clipScrollDiv;
				clipScrollDiv.style.position = "absolute"; //$NON-NLS-1$
				clipScrollDiv.style.height = "1px"; //$NON-NLS-1$
				clipScrollDiv.style.top = "-1000px"; //$NON-NLS-1$
				clipScrollDiv.style.background = "transparent"; //$NON-NLS-1$
				clipDiv.appendChild(clipScrollDiv);
			}

			var clientDiv = util.createElement(doc, "div"); //$NON-NLS-1$
			clientDiv.className = "textviewContent"; //$NON-NLS-1$
			this._clientDiv = clientDiv;
			clientDiv.tabIndex = 0;
			clientDiv.style.position = "absolute"; //$NON-NLS-1$
			clientDiv.style.borderWidth = "0px"; //$NON-NLS-1$
			clientDiv.style.margin = "0px"; //$NON-NLS-1$
			clientDiv.style.padding = "0px"; //$NON-NLS-1$
			clientDiv.style.outline = "none"; //$NON-NLS-1$
			clientDiv.style.zIndex = "1"; //$NON-NLS-1$
			clientDiv.style.WebkitUserSelect = "text"; //$NON-NLS-1$
			clientDiv.setAttribute("spellcheck", "false"); //$NON-NLS-1$ //$NON-NLS-2$
			if (util.isIOS || util.isAndroid) {
				clientDiv.style.WebkitTapHighlightColor = "transparent"; //$NON-NLS-1$
			}
			(this._clipDiv || rootDiv).appendChild(clientDiv);
			
			this._setFullSelection(this._fullSelection, true);
			
			if (util.isIOS || util.isAndroid) {
				var vScrollDiv = util.createElement(doc, "div"); //$NON-NLS-1$
				this._vScrollDiv = vScrollDiv;
				vScrollDiv.style.position = "absolute"; //$NON-NLS-1$
				vScrollDiv.style.borderWidth = "1px"; //$NON-NLS-1$
				vScrollDiv.style.borderColor = "white"; //$NON-NLS-1$
				vScrollDiv.style.borderStyle = "solid"; //$NON-NLS-1$
				vScrollDiv.style.borderRadius = "4px"; //$NON-NLS-1$
				vScrollDiv.style.backgroundColor = "black"; //$NON-NLS-1$
				vScrollDiv.style.opacity = "0.5"; //$NON-NLS-1$
				vScrollDiv.style.margin = "0px"; //$NON-NLS-1$
				vScrollDiv.style.padding = "0px"; //$NON-NLS-1$
				vScrollDiv.style.outline = "none"; //$NON-NLS-1$
				vScrollDiv.style.zIndex = "3"; //$NON-NLS-1$
				vScrollDiv.style.width = "8px"; //$NON-NLS-1$
				vScrollDiv.style.display = "none"; //$NON-NLS-1$
				rootDiv.appendChild(vScrollDiv);
				var hScrollDiv = util.createElement(doc, "div"); //$NON-NLS-1$
				this._hScrollDiv = hScrollDiv;
				hScrollDiv.style.position = "absolute"; //$NON-NLS-1$
				hScrollDiv.style.borderWidth = "1px"; //$NON-NLS-1$
				hScrollDiv.style.borderColor = "white"; //$NON-NLS-1$
				hScrollDiv.style.borderStyle = "solid"; //$NON-NLS-1$
				hScrollDiv.style.borderRadius = "4px"; //$NON-NLS-1$
				hScrollDiv.style.backgroundColor = "black"; //$NON-NLS-1$
				hScrollDiv.style.opacity = "0.5"; //$NON-NLS-1$
				hScrollDiv.style.margin = "0px"; //$NON-NLS-1$
				hScrollDiv.style.padding = "0px"; //$NON-NLS-1$
				hScrollDiv.style.outline = "none"; //$NON-NLS-1$
				hScrollDiv.style.zIndex = "3"; //$NON-NLS-1$
				hScrollDiv.style.height = "8px"; //$NON-NLS-1$
				hScrollDiv.style.display = "none"; //$NON-NLS-1$
				rootDiv.appendChild(hScrollDiv);
			}

			if (util.isFirefox && !clientDiv.setCapture) {
				var overlayDiv = util.createElement(doc, "div"); //$NON-NLS-1$
				this._overlayDiv = overlayDiv;
				overlayDiv.style.position = clientDiv.style.position;
				overlayDiv.style.borderWidth = clientDiv.style.borderWidth;
				overlayDiv.style.margin = clientDiv.style.margin;
				overlayDiv.style.padding = clientDiv.style.padding;
				overlayDiv.style.cursor = "text"; //$NON-NLS-1$
				overlayDiv.style.zIndex = "2"; //$NON-NLS-1$
				(this._clipDiv || rootDiv).appendChild(overlayDiv);
			}
			clientDiv.contentEditable = "true"; //$NON-NLS-1$
			clientDiv.setAttribute("role", "textbox"); //$NON-NLS-1$ //$NON-NLS-2$
			clientDiv.setAttribute("aria-multiline", "true"); //$NON-NLS-1$ //$NON-NLS-2$
			this._setWrapMode(this._wrapMode, true);
			this._setReadOnly(this._readonly);
			this._setThemeClass(this._themeClass, true);
			this._setTabSize(this._tabSize, true);
			this._setMarginOffset(this._marginOffset, true);
			this._hookEvents();
			var rulers = this._rulers;
			for (var i=0; i<rulers.length; i++) {
				this._createRuler(rulers[i]);
			}
			this._update();
			// Detect when the parent is attached to the DOM or display
			var that = this;
			function checkDOMReady() {
				if (!that._rootDiv) { return; }
				that.update(true);
				if (that._metrics.invalid) {
					that._getWindow().setTimeout(function() {
						checkDOMReady();
					}, 100);
				}
			}
			DOMReady(doc, rootDiv, "textview", checkDOMReady); //$NON-NLS-1$
		},
		_defaultOptions: function() {
			return {
				parent: {value: undefined, update: null},
				model: {value: undefined, update: this.setModel},
				scrollAnimation: {value: 0, update: null},
				readonly: {value: false, update: this._setReadOnly},
				fullSelection: {value: true, update: this._setFullSelection},
				tabMode: { value: true, update: null },
				tabSize: {value: 8, update: this._setTabSize},
				expandTab: {value: false, update: null},
				singleMode: {value: false, update: this._setSingleMode},
				noScroll: {value: false, update: this._setNoScroll},
				overwriteMode: { value: false, update: this._setOverwriteMode },
				blockCursorVisible: { value: false, update: this._setBlockCursor},
				marginOffset: {value: 0, update: this._setMarginOffset},
				wrapOffset: {value: 0, update: this._setWrapOffset},
				wrapMode: {value: false, update: this._setWrapMode},
				wrappable: {value: false, update: null},
				undoStack: {value: null, update: this._setUndoStack},
				theme: {value: mTextTheme.TextTheme.getTheme(), update: this._setTheme},
				themeClass: {value: undefined, update: this._setThemeClass}
			};
		},
		_destroyRuler: function(ruler) {
			var rulerParent = this._getRulerParent(ruler);
			if (rulerParent) {
				var div = rulerParent.firstChild;
				while (div) {
					if (div._ruler === ruler) {
						div._ruler = undefined;
						rulerParent.removeChild(div);
						if (rulerParent.children.length === 0 && (rulerParent !== this._marginDiv || !this._marginOffset)) {
							rulerParent.style.display = "none"; //$NON-NLS-1$
						}
						rulerParent.rulerWidth = undefined;
						break;
					}
					div = div.nextSibling;
				}
			}
		},
		_destroyView: function() {
			var clientDiv = this._clientDiv;
			if (!clientDiv) { return; }
			this._setGrab(null);
			this._unhookEvents();

			/* Destroy timers */
			var win = this._getWindow();
			if (this._autoScrollTimerID) {
				win.clearTimeout(this._autoScrollTimerID);
				this._autoScrollTimerID = null;
			}
			if (this._updateTimer) {
				win.clearTimeout(this._updateTimer);
				this._updateTimer = null;
			}
			if (this._calculateLHTimer) {
				win.clearTimeout(this._calculateLHTimer);
				this._calculateLHTimer = null;
			}
			if (this._cursorTimer) {
				win.clearInterval(this._cursorTimer);
				this._cursorTimer = null;
			}
			if (this._imeTimeout) {
				win.clearInterval(this._imeTimeout);
				this._imeTimeout = null;
			}
			
			var rootDiv = this._rootDiv;
			rootDiv.parentNode.removeChild(rootDiv);

			/* Destroy DOM */
			this._domSelection = null;
			this._clipboardDiv = null;
			this._rootDiv = null;
			this._scrollDiv = null;
			this._viewDiv = null;
			this._clipDiv = null;
			this._clipScrollDiv = null;
			this._clientDiv = null;
			this._overlayDiv = null;
			this._leftDiv = null;
			this._rightDiv = null;
			this._innerRightDiv = null;
			this._marginDiv = null;
			this._cursorDiv = null;
			this._vScrollDiv = null;
			this._hScrollDiv = null;
		},
		_doAutoScroll: function (direction, x, y) {
			this._autoScrollDir = direction;
			this._autoScrollX = x;
			this._autoScrollY = y;
			if (!this._autoScrollTimerID) {
				this._autoScrollTimer();
			}
		},
		_endAutoScroll: function () {
			if (this._autoScrollTimerID) {
				var win = this._getWindow();
				win.clearTimeout(this._autoScrollTimerID);
			}
			this._autoScrollDir = undefined;
			this._autoScrollTimerID = undefined;
		},
		_fixCaret: function() {
			var clientDiv = this._clientDiv;
			if (clientDiv) {
				var hasFocus = this._hasFocus;
				this._ignoreFocus = true;
				if (hasFocus) { clientDiv.blur(); }
				clientDiv.contentEditable = false;
				clientDiv.contentEditable = true;
				if (hasFocus) { clientDiv.focus(); }
				this._ignoreFocus = false;
			}
		},
		_getBaseText: function(start, end) {
			var model = this._model;
			/* This is the only case the view access the base model, alternatively the view could use a event to application to customize the text */
			if (model.getBaseModel) {
				start = model.mapOffset(start);
				end = model.mapOffset(end);
				model = model.getBaseModel();
			}
			return model.getText(start, end);
		},
		_getBottomIndex: function (fullyVisible) {
			var child = this._bottomChild;
			if (fullyVisible && this._getClientHeight() > this._getLineHeight()) {
				var rect = child.getBoundingClientRect();
				var clientRect = this._clientDiv.getBoundingClientRect();
				if (rect.bottom > clientRect.bottom) {
					child = this._getLinePrevious(child) || child;
				}
			}
			return child.lineIndex;
		},
		_getBlockSelections: function(selections, lineIndex, pt) {
			var model = this._model;
			selections = selections.filter(function(sel) { return !sel._editing; });
			var firstLine = model.getLineAtOffset(this._blockSelection.getAnchor()), lastLine;
			if (lineIndex > firstLine) {
				lastLine = lineIndex;
			} else {
				lastLine = firstLine;
				firstLine = lineIndex;
			}
			for (var l = firstLine; l <= lastLine; l++) {
				var line = this._getLine(l);
				var o1 = line.getOffset(pt.x, 1);
				var o2 = line.getOffset(this._blockSelection._docX, 1);
				line.destroy();
				if (o1 === o2 && o1 === model.getLineEnd(l)) continue;
				var caret = o1 < o2;
				var sel = new Selection(caret ? o1 : o2, caret ? o2 : o1, caret);
				sel._editing = true;
				selections.push(sel);
			}
			return selections;
		},
		_getBoundsAtOffset: function(offset) {
			var model = this._model;
			var line = this._getLine(model.getLineAtOffset(offset));
			var result = line.getBoundingClientRect(offset);
			var linePixel = this._getLinePixel(line.lineIndex);
			result.top += linePixel;
			result.bottom += linePixel;
			line.destroy();
			return result;
		},
		_getClientHeight: function() {
			var viewPad = this._getViewPadding();
			return Math.max(0, this._viewDiv.clientHeight - viewPad.top - viewPad.bottom);
		},
		_getInnerRightWidth: function() {
			var innerRightWidth = this._innerRightDiv.rulerWidth;
			if (innerRightWidth === undefined) {
				var innerRightRect = this._innerRightDiv.getBoundingClientRect();
				this._innerRightDiv.rulerWidth = innerRightWidth = innerRightRect.right - innerRightRect.left;
			}
			return innerRightWidth;
		},
		_getClientWidth: function() {
			var viewPad = this._getViewPadding();
			var innerRightWidth = this._getInnerRightWidth();
			return Math.max(0, this._viewDiv.clientWidth - viewPad.left - viewPad.right - innerRightWidth);
		},
		_getClipboardText: function (evt, handler) {
			// IE
			var win = this._getWindow();
			var clipboardData = win.clipboardData;
			// WebKit and Firefox > 21
			if (!clipboardData && evt) {
				clipboardData = evt.clipboardData;
			}
			function convert(wholeText) {
				var clipboadText = [];
				convertDelimiter(wholeText, function(t) {clipboadText.push(t);}, null);
				if (handler) { handler(clipboadText); }
				return clipboadText;
			}
			if (clipboardData) {
				return convert(clipboardData.getData(util.isIE ? "Text" : "text/plain")); //$NON-NLS-1$"//$NON-NLS-2$
			}
			if (util.isFirefox) {
				this._ignoreFocus = true;
				var clipboardDiv = this._clipboardDiv;
				var doc = this._rootDiv.ownerDocument;
				if (!clipboardDiv) {
					clipboardDiv = util.createElement(doc, "div"); //$NON-NLS-1$
					this._clipboardDiv = clipboardDiv;
					clipboardDiv.style.position = "fixed"; //$NON-NLS-1$
					clipboardDiv.style.whiteSpace = "pre"; //$NON-NLS-1$
					clipboardDiv.style.left = "-1000px"; //$NON-NLS-1$
					this._rootDiv.appendChild(clipboardDiv);
				}
				clipboardDiv.innerHTML = "<pre contenteditable=''></pre>"; //$NON-NLS-1$
				clipboardDiv.firstChild.focus();
				var that = this;
				var _getText = function() {
					var noteText = that._getTextFromElement(clipboardDiv);
					clipboardDiv.innerHTML = "";
					return convert(noteText);
				};
				
				/* Try execCommand first. Works on firefox with clipboard permission. */
				var result = false;
				this._ignorePaste = true;

				/* Do not try execCommand if middle-click is used, because if we do, we get the clipboard text, not the primary selection text. */
				if (!util.isLinux || this._lastMouseButton !== 2) {
					try {
						result = doc.execCommand("paste", false, null); //$NON-NLS-1$
					} catch (ex) {
						/* Firefox can throw even when execCommand() works, see bug 362835. */
						result = clipboardDiv.childNodes.length > 1 || clipboardDiv.firstChild && clipboardDiv.firstChild.childNodes.length > 0;
					}
				}
				this._ignorePaste = false;
				if (!result) {
					/* Try native paste in DOM, works for firefox during the paste event. */
					if (evt) {
						win.setTimeout(function() {
							that.focus();
							_getText();
							that._ignoreFocus = false;
						}, 0);
						return null;
					} else {
						/* no event and no clipboard permission, paste can't be performed */
						this.focus();
						this._ignoreFocus = false;
						return "";
					}
				}
				this.focus();
				this._ignoreFocus = false;
				return _getText();
			}
			return "";
		},
		_getDOMText: function(child, offsetNode) {
			return child._line.getText(offsetNode);
		},
		_getTextFromElement: function(element) {
			var doc = element.ownerDocument;
			var win = doc.defaultView;
			if (!win.getSelection) {
				return element.innerText || element.textContent;
			}

			var newRange = doc.createRange();
			newRange.selectNode(element);

			var selection = win.getSelection();
			var oldRanges = [], i;
			for (i = 0; i < selection.rangeCount; i++) {
				oldRanges.push(selection.getRangeAt(i));
			}

			this._ignoreSelect = true;
			selection.removeAllRanges();
			selection.addRange(newRange);

			var text = selection.toString();

			selection.removeAllRanges();
			for (i = 0; i < oldRanges.length; i++) {
				selection.addRange(oldRanges[i]);
			}

			this._ignoreSelect = false;
			return text;
		},
		_getViewPadding: function() {
			return this._metrics.viewPadding;
		},
		_getLine: function(lineIndex) {
			var child = this._getLineNode(lineIndex);
			if (child && !child.lineChanged && !child.lineRemoved) {
				return child._line;
			}
			return new TextLine(this, lineIndex);
		},
		_getLineHeight: function(lineIndex, calculate) {
			if (lineIndex !== undefined && this._lineHeight) {
				var lineHeight = this._lineHeight[lineIndex];
				if (lineHeight) { return lineHeight; }
				if (calculate || calculate === undefined) {
					var height = this._lineHeight[lineIndex] = this._calculateLineHeight(lineIndex);
					return height;
				}
			}
			return this._metrics.lineHeight;
		},
		_getLineNode: function (lineIndex) {
			var clientDiv = this._clientDiv;
			var child = clientDiv.firstChild;
			while (child) {
				if (lineIndex === child.lineIndex) {
					return child;
				}
				child = child.nextSibling;
			}
			return undefined;
		},
		_getLineNext: function (lineNode) {
			var node = lineNode ? lineNode.nextSibling : this._clientDiv.firstChild;
			while (node && (node.lineIndex === -1 || !node._line)) {
				node = node.nextSibling;
			}
			return node;
		},
		_getLinePrevious: function (lineNode) {
			var node = lineNode ? lineNode.previousSibling : this._clientDiv.lastChild;
			while (node && (node.lineIndex === -1 || !node._line)) {
				node = node.previousSibling;
			}
			return node;
		},
		_getLinePixel: function(lineIndex) {
			lineIndex = Math.min(Math.max(0, lineIndex), this._model.getLineCount());
			if (this._lineHeight) {
				var topIndex = this._getTopIndex();
				var pixel = -this._topIndexY + this._getScroll().y, i;
				if (lineIndex > topIndex) {
					for (i = topIndex; i < lineIndex; i++) {
						pixel += this._getLineHeight(i);
					}
				} else {
					for (i = topIndex - 1; i >= lineIndex; i--) {
						pixel -= this._getLineHeight(i);
					}
				}
				return pixel;
			}
			var lineHeight = this._getLineHeight();
			return lineHeight * lineIndex;
		},
		/**
		 * @name _getLineIndex
		 * @description Returns the line index closest to the given text view relative location.  Will return -1
		 * 				if restrictToValidLines is true and y location is outside of text lines.
		 * @function
		 * @private
		 * @param y location to search
		 * @param restrictToValidLines whether to return -1 if the location is outside a valid line, otherwise return the closest valid line index
		 * @returns returns The line index closest to the location or -1 if restrictToValidLines is true and location is outside text area
		 */
		_getLineIndex: function(y, restrictToValidLines) {
			var lineHeight, lineIndex = 0;
			var lineCount = this._model.getLineCount();
			if (this._lineHeight) {
				lineIndex = this._getTopIndex();
				var pixel = -this._topIndexY + this._getScroll().y;
				if (y !== pixel) {
					if (y < pixel) {
						while (y < pixel && lineIndex > 0) {
							y += this._getLineHeight(--lineIndex);
						}
					} else {
						lineHeight = this._getLineHeight(lineIndex);
						while (y - lineHeight >= pixel && lineIndex < lineCount - 1) {
							y -= lineHeight;
							lineHeight = this._getLineHeight(++lineIndex);
						}
					}
				}
			} else {
				lineHeight = this._getLineHeight();
				lineIndex = Math.floor(y / lineHeight);
			}
			if (restrictToValidLines){
				if (lineCount === 0 || lineIndex < 0 || lineIndex > (lineCount-1)){
					return -1;
				}
			}
			return Math.max(0, Math.min(lineCount - 1, lineIndex));
		},
		_getRulerParent: function(ruler) {
			switch (ruler.getLocation()) {
				case "left": return this._leftDiv; //$NON-NLS-1$
				case "right": return this._rightDiv; //$NON-NLS-1$
				case "innerRight": return this._innerRightDiv; //$NON-NLS-1$
				case "margin": return this._marginDiv; //$NON-NLS-1$
			}
			return null;
		},
		_getScroll: function(cancelAnimation) {
			if (cancelAnimation === undefined || cancelAnimation) {
				this._cancelAnimation();
			}
			var viewDiv = this._viewDiv;
			return {x: viewDiv.scrollLeft, y: viewDiv.scrollTop};
		},
		_getSelection: function () {
			return (Array.isArray(this._selection) ? this._selection[0] : this._selection).clone();
		},
		_getSelections: function () {
			return (Array.isArray(this._selection) ? this._selection : [this._selection]).map(function(s) {
				return s.clone();
			});
		},
		_getTopIndex: function (fullyVisible) {
			var child = this._topChild;
			if (fullyVisible && this._getClientHeight() > this._getLineHeight()) {
				var rect = child.getBoundingClientRect();
				var viewPad = this._getViewPadding();
				var viewRect = this._viewDiv.getBoundingClientRect();
				if (rect.top < viewRect.top + viewPad.top) {
					child = this._getLineNext(child) || child;
				}
			}
			return child.lineIndex;
		},
		_hookEvents: function() {
			var that = this;
			this._modelListener = {
				/** @private */
				onChanging: function(modelChangingEvent) {
					that._onModelChanging(modelChangingEvent);
				},
				/** @private */
				onChanged: function(modelChangedEvent) {
					that._onModelChanged(modelChangedEvent);
				}
			};
			this._model.addEventListener("preChanging", this._modelListener.onChanging); //$NON-NLS-1$
			this._model.addEventListener("postChanged", this._modelListener.onChanged); //$NON-NLS-1$
			
			this._themeListener = {
				onChanged: function() {
					that._setThemeClass(that._themeClass);
				}
			};
			this._theme.addEventListener("ThemeChanged", this._themeListener.onChanged); //$NON-NLS-1$
			
			var handlers = this._handlers = [];
			var clientDiv = this._clientDiv, viewDiv = this._viewDiv, rootDiv = this._rootDiv;
			var topNode = this._overlayDiv || clientDiv;
			var doc = clientDiv.ownerDocument;
			var win = this._getWindow();
			var grabNode = util.isIE ? doc : win;
			handlers.push({target: win, type: "resize", handler: function(e) { return that._handleResize(e ? e : win.event);}}); //$NON-NLS-1$
			handlers.push({target: clientDiv, type: "blur", handler: function(e) { return that._handleBlur(e ? e : win.event);}}); //$NON-NLS-1$
			handlers.push({target: clientDiv, type: "focus", handler: function(e) { return that._handleFocus(e ? e : win.event);}}); //$NON-NLS-1$
			handlers.push({target: viewDiv, type: "focus", handler: function() { clientDiv.focus(); }}); //$NON-NLS-1$
			handlers.push({target: viewDiv, type: "scroll", handler: function(e) { return that._handleScroll(e ? e : win.event);}}); //$NON-NLS-1$
			handlers.push({target: clientDiv, type: "textInput", handler: function(e) { return that._handleTextInput(e ? e : win.event); }}); //$NON-NLS-1$
			handlers.push({target: clientDiv, type: "keydown", handler: function(e) { return that._handleKeyDown(e ? e : win.event);}}); //$NON-NLS-1$
			handlers.push({target: clientDiv, type: "keypress", handler: function(e) { return that._handleKeyPress(e ? e : win.event);}}); //$NON-NLS-1$
			handlers.push({target: clientDiv, type: "keyup", handler: function(e) { return that._handleKeyUp(e ? e : win.event);}}); //$NON-NLS-1$
			if (util.isIE) {
				handlers.push({target: doc, type: "keyup", handler: function(e) { return that._handleDocKeyUp(e ? e : win.event);}}); //$NON-NLS-1$
			}
			handlers.push({target: clientDiv, type: "contextmenu", handler: function(e) { return that._handleContextMenu(e ? e : win.event);}}); //$NON-NLS-1$
			handlers.push({target: clientDiv, type: "copy", handler: function(e) { return that._handleCopy(e ? e : win.event);}}); //$NON-NLS-1$
			handlers.push({target: clientDiv, type: "cut", handler: function(e) { return that._handleCut(e ? e : win.event);}}); //$NON-NLS-1$
			handlers.push({target: clientDiv, type: "paste", handler: function(e) { return that._handlePaste(e ? e : win.event);}}); //$NON-NLS-1$
			handlers.push({target: doc, type: "selectionchange", handler: function(e) { return that._handleSelectionChange(e ? e : win.event); }}); //$NON-NLS-1$
			if (util.isIOS || util.isAndroid) {
				handlers.push({target: clientDiv, type: "touchstart", handler: function(e) { return that._handleTouchStart(e ? e : win.event); }}); //$NON-NLS-1$
				handlers.push({target: clientDiv, type: "touchmove", handler: function(e) { return that._handleTouchMove(e ? e : win.event); }}); //$NON-NLS-1$
				handlers.push({target: clientDiv, type: "touchend", handler: function(e) { return that._handleTouchEnd(e ? e : win.event); }}); //$NON-NLS-1$
			} else {
				handlers.push({target: clientDiv, type: "selectstart", handler: function(e) { return that._handleSelectStart(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: clientDiv, type: "mousedown", handler: function(e) { return that._handleMouseDown(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: clientDiv, type: "mouseover", handler: function(e) { return that._handleMouseOver(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: clientDiv, type: "mouseout", handler: function(e) { return that._handleMouseOut(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: grabNode, type: "mouseup", handler: function(e) { return that._handleMouseUp(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: grabNode, type: "mousemove", handler: function(e) { return that._handleMouseMove(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: rootDiv, type: "mousedown", handler: function(e) { return that._handleRootMouseDown(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: rootDiv, type: "mouseup", handler: function(e) { return that._handleRootMouseUp(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: topNode, type: "dragstart", handler: function(e) { return that._handleDragStart(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: topNode, type: "drag", handler: function(e) { return that._handleDrag(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: topNode, type: "dragend", handler: function(e) { return that._handleDragEnd(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: topNode, type: "dragenter", handler: function(e) { return that._handleDragEnter(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: topNode, type: "dragover", handler: function(e) { return that._handleDragOver(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: topNode, type: "dragleave", handler: function(e) { return that._handleDragLeave(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: topNode, type: "drop", handler: function(e) { return that._handleDrop(e ? e : win.event);}}); //$NON-NLS-1$
				handlers.push({target: this._clientDiv, type: util.isFirefox > 26 ? "wheel" : util.isFirefox ? "DOMMouseScroll" : "mousewheel", handler: function(e) { return that._handleMouseWheel(e ? e : win.event); }}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-3$
				if (this._clipDiv) {
					handlers.push({target: this._clipDiv, type: util.isFirefox > 26 ? "wheel" : util.isFirefox ? "DOMMouseScroll" : "mousewheel", handler: function(e) { return that._handleMouseWheel(e ? e : win.event); }}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-3$
				}
				if (util.isFirefox && (!util.isWindows || util.isFirefox >= 15)) {
					var MO = win.MutationObserver || win.MozMutationObserver;
					if (MO) {
						this._mutationObserver = new MO(function(mutations) { that._handleDataModified(mutations); });
						this._mutationObserver.observe(clientDiv, {subtree: true, characterData: true});
					} else {
						handlers.push({target: this._clientDiv, type: "DOMCharacterDataModified", handler: function (e) { return that._handleDataModified(e ? e : win.event); }}); //$NON-NLS-1$
					}
				}
				if ((util.isFirefox && (!util.isWindows || util.isFirefox >= 15)) || util.isIE || util.isWebkit) {
					handlers.push({target: this._clientDiv, type: "compositionstart", handler: function (e) { return that._handleCompositionStart(e ? e : win.event); }}); //$NON-NLS-1$
					handlers.push({target: this._clientDiv, type: "compositionend", handler: function (e) { return that._handleCompositionEnd(e ? e : win.event); }}); //$NON-NLS-1$
					handlers.push({target: this._clientDiv, type: "compositionupdate", handler: function (e) { return that._handleCompositionUpdate(e ? e : win.event); }}); //$NON-NLS-1$
				}
				if (this._overlayDiv) {
					handlers.push({target: this._overlayDiv, type: "mousedown", handler: function(e) { return that._handleMouseDown(e ? e : win.event);}}); //$NON-NLS-1$
					handlers.push({target: this._overlayDiv, type: "mouseover", handler: function(e) { return that._handleMouseOver(e ? e : win.event);}}); //$NON-NLS-1$
					handlers.push({target: this._overlayDiv, type: "mouseout", handler: function(e) { return that._handleMouseOut(e ? e : win.event);}}); //$NON-NLS-1$
					handlers.push({target: this._overlayDiv, type: "contextmenu", handler: function(e) { return that._handleContextMenu(e ? e : win.event); }}); //$NON-NLS-1$
				}
				if (!this._isW3CEvents) {
					handlers.push({target: this._clientDiv, type: "dblclick", handler: function(e) { return that._handleDblclick(e ? e : win.event); }}); //$NON-NLS-1$
				}
			}

			this._hookRulerEvents(this._leftDiv, handlers);
			this._hookRulerEvents(this._rightDiv, handlers);
			this._hookRulerEvents(this._innerRightDiv, handlers);
			this._hookRulerEvents(this._marginDiv, handlers);
			
			for (var i=0; i<handlers.length; i++) {
				var h = handlers[i];
				addHandler(h.target, h.type, h.handler, h.capture);
			}
		},
		_hookRulerEvents: function(div, handlers) {
			if (!div) { return; }
			var that = this;
			var win = this._getWindow();
			if (util.isIE) {
				handlers.push({target: div, type: "selectstart", handler: function() {return false;}}); //$NON-NLS-1$
			}
			handlers.push({target: div, type: util.isFirefox > 26 ? "wheel" : util.isFirefox ? "DOMMouseScroll" : "mousewheel", handler: function(e) { return that._handleMouseWheel(e ? e : win.event); }}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-3$
			handlers.push({target: div, type: "click", handler: function(e) { that._handleRulerEvent(e ? e : win.event); }}); //$NON-NLS-1$
			handlers.push({target: div, type: "dblclick", handler: function(e) { that._handleRulerEvent(e ? e : win.event); }}); //$NON-NLS-1$
			handlers.push({target: div, type: "mousemove", handler: function(e) { that._handleRulerEvent(e ? e : win.event); }}); //$NON-NLS-1$
			handlers.push({target: div, type: "mouseover", handler: function(e) { that._handleRulerEvent(e ? e : win.event); }}); //$NON-NLS-1$
			handlers.push({target: div, type: "mouseout", handler: function(e) { that._handleRulerEvent(e ? e : win.event); }}); //$NON-NLS-1$
		},
		_getWindow: function() {
			return getWindow(this._parent.ownerDocument);
		},
		_ignoreEvent: function(e) {
			var node = e.target;
			while (node && node !== this._clientDiv) {
				if (node.ignore) { return true; }
				node = node.parentNode;
			}
			return false;
		},
		_init: function(options) {
			var _parent = options.parent;
			if (typeof(_parent) === "string") { //$NON-NLS-1$
				_parent = (options.document || document).getElementById(_parent);
			}
			if (!_parent) { throw new Error("no parent"); } //$NON-NLS-1$
			options.parent = _parent;
			options.model = options.model || new mTextModel.TextModel();
			var defaultOptions = this._defaultOptions();
			for (var option in defaultOptions) {
				if (defaultOptions.hasOwnProperty(option)) {
					var value;
					if (options[option] !== undefined) {
						value = options[option];
					} else {
						value = defaultOptions[option].value;
					}
					this["_" + option] = value; //$NON-NLS-1$
				}
			}
			this._keyModes = [];
			this._rulers = [];
			this._selection = [new Selection(0, 0, false)];
			this._linksVisible = false;
			this._redrawCount = 0;
			this._maxLineWidth = 0;
			this._maxLineIndex = -1;
			this._ignoreSelect = true;
			this._ignoreFocus = false;
			this._hasFocus = false;
			this._dragOffset = -1;
			this._isRangeRects = (!util.isIE || util.isIE >= 9) && typeof _parent.ownerDocument.createRange().getBoundingClientRect === "function"; //$NON-NLS-1$
			this._isW3CEvents = _parent.addEventListener;

			/* Auto scroll */
			this._autoScrollX = null;
			this._autoScrollY = null;
			this._autoScrollTimerID = null;
			this._AUTO_SCROLL_RATE = 50;
			this._grabControl = null;
			this._moseMoveClosure  = null;
			this._mouseUpClosure = null;
			
			/* Double click */
			this._lastMouseX = 0;
			this._lastMouseY = 0;
			this._lastMouseTime = 0;
			this._clickCount = 0;
			this._clickTime = 250;
			this._clickDist = 5;
			this._isMouseDown = false;
			this._doubleClickSelection = null;
			
			/* Scroll */
			this._hScroll = 0;
			this._vScroll = 0;

			/* IME */
			this._imeOffset = -1;
			
			/* Create elements */
			this._createActions();
			this._createView();
		},
		_checkOverlayScroll: function() {
			if (util.isMac && util.isWebkit) {
				if (!this._metrics.invalid && this._metrics.scrollWidth === 0) {
					var viewDiv = this._viewDiv;
					var overlay = this._isOverOverlayScroll();
					if (overlay.vertical || overlay.horizontal) {
						viewDiv.style.pointerEvents = ""; //$NON-NLS-1$
					} else {
						viewDiv.style.pointerEvents = "none"; //$NON-NLS-1$
					}
				}
			}	
		},
		_isOverOverlayScroll: function() {
			var scrollShowing = new Date().getTime() - this._lastScrollTime < 200;
			if (!scrollShowing) {
				return {};
			}
			var rect = this._viewDiv.getBoundingClientRect();
			var x = this._lastMouseMoveX;
			var y = this._lastMouseMoveY;
			var overlayScrollWidth = 15;
			return {
				vertical: rect.top <= y && y < rect.bottom && rect.right - overlayScrollWidth <= x && x < rect.right,
				horizontal: rect.bottom - overlayScrollWidth <= y && y < rect.bottom && rect.left <= x && x < rect.right
			};
		},
		_startUndo: function() {
			if (this._undoStack) {
				var that = this;
				this._compoundChange = this._undoStack.startCompoundChange({
					end: function() {
						that._compoundChange = null;
					}
				});
			}
		},
		_endUndo: function() {
			if (this._undoStack) {
				this._undoStack.endCompoundChange();
			}
		},
		_modifyContent: function(e, caretAtEnd, show, callback) {
			if (this._readonly && !e._code) {
				return false;
			}
			e.type = "Verify"; //$NON-NLS-1$
			var oldStart = e.start = e.selection[0].start;
			var oldEnd = e.end = e.selection[0].end;
			this.onVerify(e);
			if (oldStart !== e.start) e.selection[0].start = e.start;
			if (oldEnd !== e.end) e.selection[0].end = e.end;

			if (e.text === null || e.text === undefined) { return false; }
			
			if (e.selection.length > 1) this.setRedraw(false);
			
			var undo = this._compoundChange;
			if (undo) {
				if (!Selection.compare(this._getSelections(), undo.owner.selection)) {
					this._endUndo();
					if (e.selection.length > 1) this._startUndo();
				}
			} else {
				if (e.selection.length > 1) this._startUndo();
			}
			
			var model = this._model;
			try {
				if (e._ignoreDOMSelection) { this._ignoreDOMSelection = true; }
				var offset = 0, i = 0;
				e.selection.forEach(function(selection) {
					selection.start += offset;
					selection.end += offset;
					var text = Array.isArray(e.text) ? e.text[i] : e.text;
					model.setText(text, selection.start, selection.end);
					offset += (selection.start - selection.end) + text.length;
					selection.setCaret(caretAtEnd ? selection.start + text.length : selection.start);
					i++;
				});
			} finally {
				if (e._ignoreDOMSelection) { this._ignoreDOMSelection = false; }
			}
			this._setSelection(e.selection, show, true, callback);
			
			undo = this._compoundChange;
			if (undo) undo.owner.selection = e.selection;
			
			if (e.selection.length > 1) this.setRedraw(true);

			this.onModify({type: "Modify"}); //$NON-NLS-1$
			return true;
		},
		_onModelChanged: function(modelChangedEvent) {
			modelChangedEvent.type = "ModelChanged"; //$NON-NLS-1$
			this.onModelChanged(modelChangedEvent);
			modelChangedEvent.type = "Changed"; //$NON-NLS-1$
			var start = modelChangedEvent.start;
			var addedCharCount = modelChangedEvent.addedCharCount;
			var removedCharCount = modelChangedEvent.removedCharCount;
			var addedLineCount = modelChangedEvent.addedLineCount;
			var removedLineCount = modelChangedEvent.removedLineCount;
			
			var selections = this._getSelections();
			selections.forEach(function(selection) {
				if (selection.end > start) {
					if (selection.end > start && selection.start < start + removedCharCount) {
						// selection intersects replaced text. set caret behind text change
						selection.setCaret(start + addedCharCount);
					} else {
						// move selection to keep same text selected
						selection.start +=  addedCharCount - removedCharCount;
						selection.end +=  addedCharCount - removedCharCount;
					}
				}
			});
			this._setSelection(selections, false, false);
			
			var model = this._model;
			var startLine = model.getLineAtOffset(start);
			var child = this._getLineNext();
			while (child) {
				var lineIndex = child.lineIndex;
				if (startLine <= lineIndex && lineIndex <= startLine + removedLineCount) {
					if (startLine === lineIndex && !child.modelChangedEvent && !child.lineRemoved) {
						child.modelChangedEvent = modelChangedEvent;
						child.lineChanged = true;
					} else {
						child.lineRemoved = true;
						child.lineChanged = false;
						child.modelChangedEvent = null;
					}
				}
				if (lineIndex > startLine + removedLineCount) {
					child.lineIndex = lineIndex + addedLineCount - removedLineCount;
					child._line.lineIndex = child.lineIndex;
				}
				child = this._getLineNext(child);
			}
			if (this._lineHeight) {
				var args = [startLine, removedLineCount].concat(newArray(addedLineCount));
				Array.prototype.splice.apply(this._lineHeight, args);
			}
			if (!this._wrapMode) {
				if (startLine <= this._maxLineIndex && this._maxLineIndex <= startLine + removedLineCount) {
					this._checkMaxLineIndex = this._maxLineIndex;
					this._maxLineIndex = -1;
					this._maxLineWidth = 0;
				}
			}
			this._update();
		},
		_onModelChanging: function(modelChangingEvent) {
			modelChangingEvent.type = "ModelChanging"; //$NON-NLS-1$
			this.onModelChanging(modelChangingEvent);
			modelChangingEvent.type = "Changing"; //$NON-NLS-1$
		},
		_queueUpdate: function() {
			if (this._updateTimer || this._ignoreQueueUpdate) { return; }
			var that = this;
			var win = this._getWindow();
			this._updateTimer = win.setTimeout(function() { 
				that._updateTimer = null;
				that._update();
			}, 0);
		},
		_rangesToSelections: function(ranges) {
			var selections = [];
			var charCount = this._model.getCharCount();
			ranges.forEach(function(range) {
				var selection;
				if (range instanceof Selection) {
					selection = range.clone();
				} else {
					var start = range.start;
					var end = range.end;
					var caret = start > end;
					if (caret) {
						var tmp = start;
						start = end;
						end = tmp;
					}
					start = Math.max(0, Math.min (start, charCount));
					end = Math.max(0, Math.min (end, charCount));
					selection = new Selection(start, end, caret);
				}
				selections.push(selection);
			});
			return selections;
		},
		_resetLineHeight: function(startLine, endLine) {
			if (this._wrapMode || this._variableLineHeight) {
				if (startLine !== undefined && endLine !== undefined) {
					for (var i = startLine; i < endLine; i++) {
						this._lineHeight[i] = undefined;
					}
				} else {
					this._lineHeight = newArray(this._model.getLineCount());
				}
				this._calculateLineHeightTimer();
			} else {
				this._lineHeight = null;
			}
		},
		_resetLineWidth: function() {
			var clientDiv = this._clientDiv;
			if (clientDiv) {
				var child = clientDiv.firstChild;
				while (child) {
					child.lineWidth = undefined;
					child = child.nextSibling;
				}
			}
		},
		_reset: function() {
			this._maxLineIndex = -1;
			this._maxLineWidth = 0;
			this._topChild = null;
			this._bottomChild = null;
			this._topIndexY = 0;
			this._variableLineHeight = false;
			this._resetLineHeight();
			this._setSelection(new Selection(0, 0, false), false, false);
			if (this._viewDiv) {
				this._viewDiv.scrollLeft = 0;
				this._viewDiv.scrollTop = 0;
			}
			var clientDiv = this._clientDiv;
			if (clientDiv) {
				var child = clientDiv.firstChild;
				while (child) {
					child.lineRemoved = true;
					child = child.nextSibling;
				}
				/*
				* Bug in Firefox.  For some reason, the caret does not show after the
				* view is refreshed.  The fix is to toggle the contentEditable state and
				* force the clientDiv to loose and receive focus if it is focused.
				*/
				if (util.isFirefox < 13) {
					this._fixCaret ();
				}
			}
		},
		_scrollViewAnimated: function (pixelX, pixelY, callback) {
			var win = this._getWindow();
			if (callback && this._scrollAnimation) {
				var that = this;
				this._animation = new Animation({
					window: win,
					duration: this._scrollAnimation,
					curve: [pixelY, 0],
					onAnimate: function(x) {
						var deltaY = pixelY - Math.floor(x);
						that._scrollView (0, deltaY);
						pixelY -= deltaY;
					},
					onEnd: function() {
						that._animation = null;
						that._scrollView (pixelX, pixelY);
						if (callback) {
							win.setTimeout(callback, 0);
						}
					}
				});
				this._animation.play();
			} else {
				this._scrollView (pixelX, pixelY);
				if (callback) {
					win.setTimeout(callback, 0);
				}
			}
		}, 
		_scrollView: function (pixelX, pixelY) {
			/*
			* Always set _ensureCaretVisible to false so that the view does not scroll
			* to show the caret when scrollView is not called from showCaret().
			*/
			this._ensureCaretVisible = false;
			
			/*
			* Scrolling is done only by setting the scrollLeft and scrollTop fields in the
			* view div. This causes an update from the scroll event. In some browsers 
			* this event is asynchronous and forcing update page to run synchronously
			* leads to redraw problems. 
			* On Chrome 11, the view redrawing at times when holding PageDown/PageUp key.
			* On Firefox 4 for Linux, the view redraws the first page when holding 
			* PageDown/PageUp key, but it will not redraw again until the key is released.
			*/
			var viewDiv = this._viewDiv;
			if (pixelX) { viewDiv.scrollLeft += pixelX; }
			if (pixelY) { viewDiv.scrollTop += pixelY; }
		},
		_setClipboardText: function (text, evt) {
			var clipboardText;
			// IE
			var win = this._getWindow();
			var clipboardData = win.clipboardData;
			// WebKit and Firefox > 21
			if (!clipboardData && evt) {
				clipboardData = evt.clipboardData;
			}
			if (clipboardData) {
				clipboardText = [];
				convertDelimiter(text, function(t) {clipboardText.push(t);}, function() {clipboardText.push(util.platformDelimiter);});
				/*
				* Note that setData() succeeds on Firefox > 21 and WebKit, but the return value is not a boolean like IE.
				*/
				var success = clipboardData.setData(util.isIE ? "Text" : "text/plain", clipboardText.join("")); //$NON-NLS-1$ //$NON-NLS-2$
				if (success || (evt && (util.isFirefox > 21 || util.isWebkit))) {
					return true;
				}
				if (!evt) return false;
			}
			var doc = this._parent.ownerDocument;
			var child = util.createElement(doc, "pre"); //$NON-NLS-1$
			child.style.position = "fixed"; //$NON-NLS-1$
			child.style.left = "-1000px"; //$NON-NLS-1$
			convertDelimiter(text, 
				function(t) {
					child.appendChild(doc.createTextNode(t));
				}, 
				function() {
					child.appendChild(util.createElement(doc, "br")); //$NON-NLS-1$
				}
			);
			child.appendChild(doc.createTextNode(" ")); //$NON-NLS-1$
			this._clientDiv.appendChild(child);
			var range = doc.createRange();
			range.setStart(child.firstChild, 0);
			range.setEndBefore(child.lastChild);
			var sel = win.getSelection();
			if (sel.rangeCount > 0) { sel.removeAllRanges(); }
			sel.addRange(range);
			var that = this;
			/** @ignore */
			var cleanup = function() {
				if (child && child.parentNode === that._clientDiv) {
					that._clientDiv.removeChild(child);
				}
				that._updateDOMSelection();
			};
			var result = false;
			/* 
			* Try execCommand first, it works on firefox with clipboard permission,
			* chrome 5, safari 4.
			*/
			this._ignoreCopy = true;
			try {
				result = doc.execCommand("copy", false, null); //$NON-NLS-1$
			} catch (e) {}
			this._ignoreCopy = false;
			if (!result) {
				if (evt) {
					win.setTimeout(cleanup, 0);
					return false;
				}
			}
			/* no event and no permission, copy can not be done */
			cleanup();
			return true;
		},
		_setGrab: function (target) {
			if (target === this._grabControl) { return; }
			if (target) {
				if (target.setCapture) { target.setCapture(); }
				this._grabControl = target;
			} else {
				if (this._grabControl.releaseCapture) { this._grabControl.releaseCapture(); }
				this._grabControl = null;
			}
		},
		_setLinksVisible: function(visible) {
			if (this._linksVisible === visible) { return; }
			this._linksVisible = visible;
			/*
			* Feature in IE.  The client div looses focus and does not regain it back
			* when the content editable flag is reset. The fix is to remember that it
			* had focus when the flag is cleared and give focus back to the div when
			* the flag is set.
			*/
			if (util.isIE && visible) {
				this._hadFocus = this._hasFocus;
			}
			var clientDiv = this._clientDiv;
			clientDiv.contentEditable = !visible;
			if (this._hadFocus && !visible) {
				clientDiv.focus();
			}
			if (this._overlayDiv) {
				this._overlayDiv.style.zIndex = visible ? "-1" : "1"; //$NON-NLS-1$ //$NON-NLS-2$
			}
			var line = this._getLineNext();
			while (line) {
				line._line.updateLinks();
				line = this._getLineNext(line);
			}
			this._updateDOMSelection();
		},
		_setSelection: function (selection, _scroll, update, callback, pageScroll, add, preserveCursorX) {
			if (selection) {
				if (update === undefined) { update = true; }
				var oldSelection = this._getSelections(), newSelection;
				if (Array.isArray(selection)) {
					newSelection = selection;
				} else if (add) {
					newSelection = oldSelection.concat([selection]);
				} else {
					newSelection = [selection];
				}
				this._selection = Selection.merge(newSelection);
				
				if (!preserveCursorX) {
					newSelection.forEach(function(sel) {
						sel._columnX = -1;
					});
				}

				/* 
				* Always showCaret(), even when the selection is not changing, to ensure the
				* caret is visible. Note that some views do not scroll to show the caret during
				* keyboard navigation when the selection does not chanage. For example, line down
				* when the caret is already at the last line.
				*/
				if (_scroll !== false) { /*update = !*/this._showCaret(false, callback, _scroll, pageScroll); }
				
				/* 
				* Sometimes the browser changes the selection 
				* as result of method calls or "leaked" events. 
				* The fix is to set the visual selection even
				* when the logical selection is not changed.
				*/
				if (update) { this._updateDOMSelection(); }
				
				if (!Selection.compare(oldSelection, newSelection)) {
					var e = {
						type: "Selection", //$NON-NLS-1$
						oldValue: Selection.convert(oldSelection),
						newValue: Selection.convert(newSelection)
					};
					this.onSelection(e);
				}
			}
		},
		_setSelectionTo: function (x, y, down, extent, add, drag) {
			var model = this._model;
			var selections = this._getSelections();
			var pt = this.convert({x: x, y: y}, "page", "document"); //$NON-NLS-1$ //$NON-NLS-2$
			var lineIndex = this._getLineIndex(pt.y);
			var line = this._getLine(lineIndex);
			var offset = line.getOffset(pt.x, pt.y - this._getLinePixel(lineIndex));
			if (drag && !extent) {
				if (Selection.contains(selections, offset)) {
					this._dragOffset = offset;
					line.destroy();
					return false;
				}
			}
			if (this._blockSelection) {
				selections = this._getBlockSelections(selections, lineIndex, pt);
			} else {
				var selection;
				if (!down) {
					selection = Selection.editing(selections);
				} else if (extent) {
					selection = selections[selections.length - 1];
					selection._editing = true;
				} else {
					selection = new Selection(0, 0);
					selection._editing = true;
					if (add) {
						selections.push(selection);
					} else {
						selections = [selection];
					}
					selection._docX = pt.x;
				}
				if (this._clickCount === 1) {
					selection.extend(offset);
					if (!extent) { selection.collapse(); }
				} else {
					var word = (this._clickCount & 1) === 0;
					var start, end;
					if (word) {
						if (this._doubleClickSelection) {
							if (offset >= this._doubleClickSelection.start) {
								start = this._doubleClickSelection.start;
								end = line.getNextOffset(offset, {unit:"wordend", count:1}); //$NON-NLS-1$
							} else {
								start = line.getNextOffset(offset, {unit:"word", count:-1}); //$NON-NLS-1$
								end = this._doubleClickSelection.end;
							}
						} else {
							start = line.getNextOffset(offset, {unit:"word", count:-1}); //$NON-NLS-1$
							end = line.getNextOffset(start, {unit:"wordend", count:1}); //$NON-NLS-1$
						}
					} else {
						if (this._doubleClickSelection) {
							var doubleClickLine = model.getLineAtOffset(this._doubleClickSelection.start);
							if (lineIndex >= doubleClickLine) {
								start = model.getLineStart(doubleClickLine);
								end = model.getLineEnd(lineIndex);
							} else {
								start = model.getLineStart(lineIndex);
								end = model.getLineEnd(doubleClickLine);
							}
						} else {
							start = model.getLineStart(lineIndex);
							end = model.getLineEnd(lineIndex);
						}
					}
					selection.setCaret(start);
					selection.extend(end);
				}
			}
			this._setSelection(selections, true, true, null, false);
			line.destroy();
			return true;
		},
		_setFullSelection: function(fullSelection, init) {
			this._fullSelection = fullSelection;
			if (util.isWebkit) {
				this._fullSelection = fullSelection = true;
			}
			if (!this._domSelection) {
				this._domSelection = [];
				this._cursorVisible = true;
			}
			if (!init) {
				this._updateDOMSelection();
			}
		},
		_setBlockCursor: function (visible) {
			this._blockCursorVisible = visible;
			this._updateBlockCursorVisible();
		},
		_setOverwriteMode: function (overwrite) {
			this._overwriteMode = overwrite;
			this._updateBlockCursorVisible();
		},
		_updateBlockCursorVisible: function () {
			if (this._blockCursorVisible || this._overwriteMode) {
				if (!this._cursorDiv) {
					var viewDiv = this._viewDiv;
					var cursorDiv = util.createElement(viewDiv.ownerDocument, "div"); //$NON-NLS-1$
					cursorDiv.className = "textviewBlockCursor"; //$NON-NLS-1$
					this._cursorDiv = cursorDiv;
					cursorDiv.tabIndex = -1;
					cursorDiv.style.zIndex = "2"; //$NON-NLS-1$
					cursorDiv.style.color = "transparent"; //$NON-NLS-1$
					cursorDiv.style.position = "absolute"; //$NON-NLS-1$
					cursorDiv.style.pointerEvents = "none"; //$NON-NLS-1$
					cursorDiv.innerHTML = "&nbsp;"; //$NON-NLS-1$
					viewDiv.appendChild(cursorDiv);
					this._updateDOMSelection();
				}
			} else {
				if (this._cursorDiv) {
					this._cursorDiv.parentNode.removeChild(this._cursorDiv);
					this._cursorDiv = null;
				}
			}
		},
		_setMarginOffset: function(marginOffset, init) {
			this._marginOffset = marginOffset;
			this._marginDiv.style.display = marginOffset ? "block" : "none"; //$NON-NLS-1$ //$NON-NLS-2$
			if (!init) {
				this._metrics = this._calculateMetrics();
				this._queueUpdate();
			}
		},
		_setWrapOffset: function(wrapOffset, init) {
			this._wrapOffset = wrapOffset;
			if (!init) {
				this._metrics = this._calculateMetrics();
				this._queueUpdate();
			}
		},
		_setReadOnly: function (readOnly) {
			this._readonly = readOnly;
			this._clientDiv.setAttribute("aria-readonly", readOnly ? "true" : "false"); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-3$
		},
		_setSingleMode: function (singleMode, init) {
			this._singleMode = singleMode;
			this._updateOverflow();
			this._updateStyle(init);
		},
		_setNoScroll: function (noScroll, init) {
			this._noScroll = noScroll;
			this._updateOverflow();
			this._updateStyle(init);
		},
		_setTabSize: function (tabSize, init) {
			this._tabSize = tabSize;
			this._customTabSize = undefined;
			var clientDiv = this._clientDiv;
			if (util.isOpera) {
				if (clientDiv) { clientDiv.style.OTabSize = this._tabSize+""; }
			} else if (util.isWebkit >= 537.1) {
				if (clientDiv) { clientDiv.style.tabSize = this._tabSize+""; }
			} else if (util.isFirefox >= 4) {
				if (clientDiv) {  clientDiv.style.MozTabSize = this._tabSize+""; }
			} else if (this._tabSize !== 8) {
				this._customTabSize = this._tabSize;
			}
			if (!init) {
				this.redrawLines();
				this._resetLineWidth();
			}
		},
		_setTheme: function(theme) {
			if (this._theme) {
				this._theme.removeEventListener("ThemeChanged", this._themeListener.onChanged); //$NON-NLS-1$
			}
			this._theme = theme;
			if (this._theme) {
				this._theme.addEventListener("ThemeChanged", this._themeListener.onChanged); //$NON-NLS-1$
			}
			this._setThemeClass(this._themeClass);
		},
		_setThemeClass: function (themeClass, init) {
			this._themeClass = themeClass;
			var viewContainerClass = "textview"; //$NON-NLS-1$
			var globalThemeClass = this._theme.getThemeClass();
			if (globalThemeClass) { viewContainerClass += " " + globalThemeClass; } //$NON-NLS-1$
			if (this._themeClass && globalThemeClass !== this._themeClass) { viewContainerClass += " " + this._themeClass; } //$NON-NLS-1$
			this._rootDiv.className = viewContainerClass;
			this._updateStyle(init);
		},
		_setUndoStack: function (undoStack) {
			this._undoStack = undoStack;
		},
		_setWrapMode: function (wrapMode, init) {
			this._wrapMode = wrapMode && this._wrappable;
			var clientDiv = this._clientDiv;
			if (this._wrapMode) {
				clientDiv.style.whiteSpace = "pre-wrap"; //$NON-NLS-1$
				clientDiv.style.wordWrap = "break-word"; //$NON-NLS-1$
			} else {
				clientDiv.style.whiteSpace = "pre"; //$NON-NLS-1$
				clientDiv.style.wordWrap = "normal"; //$NON-NLS-1$
			}
			this._updateOverflow();
			if (!init) {
				this.redraw();
				this._resetLineWidth();
			}
			this._resetLineHeight();
		},
		_showCaret: function (allSelection, callback, showOptions, pageScroll) {
			if (!this._clientDiv) { return; }
			if (this._redrawCount > 0) { return; }
			if (this._ignoreDOMSelection) { return; }
			if (this._imeOffset !== -1) return;
			var model = this._model;
			var selections = this._getSelections();
			var selection = Selection.editing(selections, this._autoScrollDir === "down"); //$NON-NLS-1$
			var _scroll = this._getScroll();
			var caret = selection.getCaret();
			var start = selection.start;
			var end = selection.end;
			var startLine = model.getLineAtOffset(start);
			var endLine = model.getLineAtOffset(end);
			var endInclusive = Math.max(Math.max(start, model.getLineStart(endLine)), end - 1);
			var clientWidth = this._getClientWidth();
			var clientHeight = this._getClientHeight();
			var minScroll = clientWidth / 4;
			var bounds = this._getBoundsAtOffset(caret === start ? start : endInclusive);
			var left = bounds.left;
			var right = bounds.right;
			var _top = bounds.top;
			var bottom = bounds.bottom;
			var selectionHeight = 0;
			var hasShowOptions = typeof showOptions === "object"; //$NON-NLS-1$
			if ((allSelection || hasShowOptions) && !selection.isEmpty()) {
				bounds = this._getBoundsAtOffset(caret === end ? start : endInclusive);
				selectionHeight = (bounds.bottom > bottom ? bounds.bottom : bottom) - (bounds.top < _top ? bounds.top : _top);
				if (allSelection) {
					if (bounds.top === _top) {
						if (caret === start) {
							right = left + Math.min(bounds.right - left, clientWidth);
						} else {
							left = right - Math.min(right - bounds.left, clientWidth);
						}
					} else {
						if (caret === start) {
							bottom = _top + Math.min(bounds.bottom - _top, clientHeight);
						} else {
							_top = bottom - Math.min(bottom - bounds.top, clientHeight);
						}
					}
				}
			}
			var pixelX = 0;
			if (left < _scroll.x) {
				pixelX = Math.min(left - _scroll.x, -minScroll);
			}
			if (right > _scroll.x + clientWidth) {
				pixelX = Math.max(right - _scroll.x - clientWidth, minScroll);
			}
			var pixelY = 0;
			if (_top < _scroll.y) {
				pixelY = _top - _scroll.y;
			} else if (bottom > _scroll.y + clientHeight) {
				pixelY = bottom - _scroll.y - clientHeight;
			}
			if (pageScroll) {
				if (pageScroll > 0) {
					if (pixelY > 0) {
						pixelY = Math.max(pixelY, pageScroll);
					}
				} else {
					if (pixelY < 0) {
						pixelY = Math.min(pixelY, pageScroll);
					}
				}
			}
			var alwaysScroll = hasShowOptions && showOptions.scrollPolicy === "always"; //$NON-NLS-1$
			if (pixelX !== 0 || pixelY !== 0 || alwaysScroll) {
				if (hasShowOptions) {
					var flag = pixelY > 0;
					if (pixelY === 0) {
						pixelY = _top - _scroll.y;
					}
					var viewAnchor = showOptions.viewAnchor;
					var selectionAnchor = showOptions.selectionAnchor;
					var viewAnchorOffset = Math.min(Math.max(0, showOptions.viewAnchorOffset || 0));
//					var selectionAnchorOffset = Math.min(Math.max(0, showOptions.selectionAnchorOffset || 0));
					if (viewAnchor === "top") { //$NON-NLS-1$
						pixelY += Math.floor(flag ? (1 - viewAnchorOffset) * clientHeight : -viewAnchorOffset * clientHeight);
					} else if (viewAnchor === "bottom") { //$NON-NLS-1$
						pixelY += Math.floor(flag ? viewAnchorOffset * clientHeight : -(1 - viewAnchorOffset) * clientHeight);
					} else if (viewAnchor === "center") { //$NON-NLS-1$
						pixelY += Math.floor(flag ? clientHeight / 2 + viewAnchorOffset * clientHeight : clientHeight / 2  - (1 - viewAnchorOffset) * clientHeight);
					} else { // caret is the default
						pixelY += Math.floor(flag ? viewAnchorOffset * clientHeight : -viewAnchorOffset * clientHeight);
					}
					if (startLine !== endLine) {
						if (selectionAnchor === "top" && caret !== start) { //$NON-NLS-1$
							pixelY += Math.floor(-selectionHeight);
						} else if (selectionAnchor === "bottom" && caret !== end) { //$NON-NLS-1$
							pixelY += Math.floor(selectionHeight);
						} else if (selectionAnchor === "center") { //$NON-NLS-1$
							pixelY += Math.floor(selectionHeight / 2);
						} else {
							// caret is the default
						}
					}
				} else if (pixelY !== 0 && typeof showOptions === "number") { //$NON-NLS-1$
					if (showOptions < 0) { showOptions = 0; }
					if (showOptions > 1) { showOptions = 1; }
					pixelY += Math.floor(pixelY > 0 ? showOptions * clientHeight : -showOptions * clientHeight);
				}
				this._scrollViewAnimated(pixelX, pixelY, callback);
				/*
				* When the view scrolls it is possible that one of the scrollbars can show over the caret.
				* Depending on the browser scrolling can be synchronous (Safari), in which case the change 
				* can be detected before showCaret() returns. When scrolling is asynchronous (most browsers), 
				* the detection is done during the next update page.
				*/
				if (clientHeight !== this._getClientHeight() || clientWidth !== this._getClientWidth()) {
					this._showCaret();
				} else {
					this._ensureCaretVisible = true;
				}
				return true;
			} else {
				if (callback) {
					callback();
				}
			}
			return false;
		},
		_startIME: function () {
			if (this._imeOffset !== -1) { return; }
			var selected = false;
			var selections = this._getSelections();
			for (var i=0; i<selections.length && !selected; i++) {
				selected = !selections[i].isEmpty();
			}
			if (selected) {
				this._modifyContent({text: "", selection: selections}, true);
			}
			this._imeOffset = selections[0].start;
		},
		_unhookEvents: function() {
			this._model.removeEventListener("preChanging", this._modelListener.onChanging); //$NON-NLS-1$
			this._model.removeEventListener("postChanged", this._modelListener.onChanged); //$NON-NLS-1$
			this._theme.removeEventListener("ThemeChanged", this._themeListener.onChanged); //$NON-NLS-1$
			this._modelListener = null;
			for (var i=0; i<this._handlers.length; i++) {
				var h = this._handlers[i];
				removeHandler(h.target, h.type, h.handler);
			}
			this._handlers = null;
			if (this._mutationObserver) {
				this._mutationObserver.disconnect();
			}
		},
		_updateDOMSelection: function () {
			if (this._redrawCount > 0) { return; }
			if (this._ignoreDOMSelection) { return; }
			if (this._imeOffset !== -1) return;
			if (!this._clientDiv) { return; }
			var selection = this._getSelections();
			var domSelection = this._domSelection, i;
			if (domSelection.length < selection.length) {
				for (i=domSelection.length; i<selection.length; i++) {
					domSelection.push(new DOMSelection(this));
				}
			} else if (domSelection.length > selection.length) {
				domSelection.splice(selection.length).forEach(function(s) {
					s.destroy();
				});
			}
			for (i=0; i<domSelection.length; i++) {
				domSelection[i].setPrimary(i === 0);
				domSelection[i].setSelection(selection[i]);
			}
			var win = this._getWindow();
			var that = this;
			if (domSelection.length > 1) {
				if (!this._cursorTimer) {
					this._cursorTimer = win.setInterval(function() {
						that._cursorVisible = !that._cursorVisible;
						that._domSelection.forEach(function(domSelection) { domSelection.update(); });
					}, 500);
				}
			} else {
				if (this._cursorTimer) {
					win.clearInterval(this._cursorTimer);
					this._cursorTimer = null;
				}
			}
		},
		_update: function(hScrollOnly) {
			if (this._redrawCount > 0) { return; }
			if (this._updateTimer) {
				var win = this._getWindow();
				win.clearTimeout(this._updateTimer);
				this._updateTimer = null;
				hScrollOnly = false;
			}
			var clientDiv = this._clientDiv;
			var viewDiv = this._viewDiv;
			if (!clientDiv) { return; }
			if (this._metrics.invalid) {
				this._ignoreQueueUpdate = true;
				this._updateStyle();
				this._ignoreQueueUpdate = false;
			}
			var model = this._model;
			var _scroll = this._getScroll(false);
			var viewPad = this._getViewPadding();
			var lineCount = model.getLineCount();
			var lineHeight = this._getLineHeight();
			var needUpdate = false;
			var hScroll = false, vScroll = false;
			var scrollbarWidth = this._metrics.scrollWidth;
			
			if (this._wrapMode) {
				clientDiv.style.width = (this._metrics.wrapWidth || this._getClientWidth()) + "px"; //$NON-NLS-1$
			}
			
			/*
			* topIndex - top line index of the view (maybe be particialy visible)
			* lineStart - top line minus one line (if any)
			* topIndexY - portion of the top line that is NOT visible.
			* top - topIndexY plus height of the line before top line (if any)
			*/
			var topIndex, lineStart, _top, topIndexY,
				leftWidth, leftRect,
				clientWidth, clientHeight, scrollWidth, scrollHeight,
				totalHeight = 0, totalLineIndex = 0, tempLineHeight;
			if (this._lineHeight) {
				while (totalLineIndex < lineCount) {
					tempLineHeight = this._getLineHeight(totalLineIndex);
					if (totalHeight + tempLineHeight > _scroll.y) {
						break;
					}
					totalHeight += tempLineHeight;
					totalLineIndex++;
				}
				topIndex = totalLineIndex;
				lineStart = Math.max(0, topIndex - 1);
				topIndexY = _top = _scroll.y - totalHeight;
				if (topIndex > 0) {
					_top += this._getLineHeight(topIndex - 1);
				}
			} else {
				var firstLine = Math.max(0, _scroll.y) / lineHeight;
				topIndex = Math.floor(firstLine);
				lineStart = Math.max(0, topIndex - 1);
				_top = Math.round((firstLine - lineStart) * lineHeight);
				topIndexY = Math.round((firstLine - topIndex) * lineHeight);
				scrollHeight = lineCount * lineHeight;
			}
			this._topIndexY = topIndexY;
			var rootDiv = this._rootDiv;
			var rootWidth = rootDiv.clientWidth;
			var rootHeight = rootDiv.clientHeight;
			if (hScrollOnly) {
				leftWidth = 0;
				if (this._leftDiv) {
					leftRect = this._leftDiv.getBoundingClientRect();
					leftWidth = leftRect.right - leftRect.left;
				}
				clientWidth = this._getClientWidth();
				clientHeight = this._getClientHeight();
				scrollWidth = clientWidth;
				if (this._wrapMode) {
					if (this._metrics.wrapWidth) {
						scrollWidth = this._metrics.wrapWidth;
					}
				} else {
					scrollWidth = Math.max(this._maxLineWidth, scrollWidth);
				}
				while (totalLineIndex < lineCount) {
					tempLineHeight = this._getLineHeight(totalLineIndex, false);
					totalHeight += tempLineHeight;
					totalLineIndex++;
				}
				scrollHeight = totalHeight;
			} else {
				clientHeight = this._getClientHeight();

				var linesPerPage = Math.floor((clientHeight + topIndexY) / lineHeight);
				var bottomIndex = Math.min(topIndex + linesPerPage, lineCount - 1);
				var lineEnd = Math.min(bottomIndex + 1, lineCount - 1);
				
				var lineIndex, lineWidth;
				var child = clientDiv.firstChild;
				while (child) {
					lineIndex = child.lineIndex;
					var nextChild = child.nextSibling;
					if (!(lineStart <= lineIndex && lineIndex <= lineEnd) || child.lineRemoved || child.lineIndex === -1) {
						if (this._mouseWheelLine === child) {
							child.style.display = "none"; //$NON-NLS-1$
							child.lineIndex = -1;
						} else {
							clientDiv.removeChild(child);
						}
					}
					child = nextChild;
				}
	
				child = this._getLineNext();
				var doc = viewDiv.ownerDocument;
				var frag = doc.createDocumentFragment();
				for (lineIndex=lineStart; lineIndex<=lineEnd; lineIndex++) {
					if (!child || child.lineIndex > lineIndex) {
						new TextLine(this, lineIndex).create(frag, null);
					} else {
						if (frag.firstChild) {
							clientDiv.insertBefore(frag, child);
							frag = doc.createDocumentFragment();
						}
						if (child && child.lineChanged) {
							child = new TextLine(this, lineIndex).create(frag, child);
							child.lineChanged = false;
						}
						child = this._getLineNext(child);
					}
				}
				if (frag.firstChild) { clientDiv.insertBefore(frag, child); }
	
				/*
				* Feature in WekKit. Webkit limits the width of the lines
				* computed below to the width of the client div.  This causes
				* the lines to be wrapped even though "pre" is set.  The fix
				* is to set the width of the client div to "0x7fffffffpx"
				* before computing the lines width.  Note that this value is
				* reset to the appropriate value further down.
				*/ 
				if (util.isWebkit && !this._wrapMode) {
					clientDiv.style.width = "0x7fffffffpx"; //$NON-NLS-1$
				}
	
				var rect;
				child = this._getLineNext();
				var bottomHeight = clientHeight + _top;
				var foundBottomIndex = false;
				while (child) {
					lineWidth = child.lineWidth;
					if (lineWidth === undefined) {
						rect = child._line.getBoundingClientRect();
						lineWidth = child.lineWidth = Math.ceil(rect.right - rect.left);
						var lh = rect.bottom - rect.top;
						if (this._lineHeight) {
							this._lineHeight[child.lineIndex] = lh;
						} else if (lineHeight !== 0 && lh !== 0 && Math.ceil(lineHeight) !== Math.ceil(lh)) {
							this._variableLineHeight = true;
							this._lineHeight = [];
							this._lineHeight[child.lineIndex] = lh;
						}
					}
					if (this._lineHeight && !foundBottomIndex) {
						bottomHeight -= this._lineHeight[child.lineIndex];
						if (bottomHeight < 0) {
							bottomIndex = child.lineIndex;
							foundBottomIndex = true;
						}
					}
					if (!this._wrapMode) {
						if (lineWidth >= this._maxLineWidth) {
							this._maxLineWidth = lineWidth;
							this._maxLineIndex = child.lineIndex;
						}
						if (this._checkMaxLineIndex === child.lineIndex) { this._checkMaxLineIndex = -1; }
					}
					if (child.lineIndex === topIndex) { this._topChild = child; }
					if (child.lineIndex === bottomIndex) { this._bottomChild = child; }
					child = this._getLineNext(child);
				}
				if (this._checkMaxLineIndex !== -1) {
					lineIndex = this._checkMaxLineIndex;
					this._checkMaxLineIndex = -1;
					if (0 <= lineIndex && lineIndex < lineCount) {
						var line = new TextLine(this, lineIndex);
						rect = line.getBoundingClientRect();
						lineWidth = rect.right - rect.left;
						if (lineWidth >= this._maxLineWidth) {
							this._maxLineWidth = lineWidth;
							this._maxLineIndex = lineIndex;
						}
						line.destroy();
					}
				}
				
				while (totalLineIndex < lineCount) {
					tempLineHeight = this._getLineHeight(totalLineIndex, totalLineIndex <= bottomIndex);
					totalHeight += tempLineHeight;
					totalLineIndex++;
				}
				scrollHeight = totalHeight;
	
				// Update rulers
				this._updateRuler(this._leftDiv, topIndex, lineEnd, rootHeight);
				this._updateRuler(this._rightDiv, topIndex, lineEnd, rootHeight);
				this._updateRuler(this._innerRightDiv, topIndex, lineEnd, rootHeight);
				this._updateRuler(this._marginDiv, topIndex, lineEnd, rootHeight);
				
				leftWidth = 0;
				if (this._leftDiv) {
					leftRect = this._leftDiv.getBoundingClientRect();
					leftWidth = leftRect.right - leftRect.left;
				}
				var rightWidth = 0;
				if (this._rightDiv) {
					var rightRect = this._rightDiv.getBoundingClientRect();
					rightWidth = rightRect.right - rightRect.left;
				}
				viewDiv.style.left = leftWidth + "px"; //$NON-NLS-1$
				viewDiv.style.right = rightWidth + "px"; //$NON-NLS-1$

				/* Need to set the height first in order for the width to consider the vertical scrollbar */
				var scrollDiv = this._scrollDiv;
				scrollDiv.style.height = (scrollHeight + (util.isWebkit ? 0 : viewPad.bottom)) + "px"; //$NON-NLS-1$
				
				clientWidth = this._getClientWidth();
				if (!this._singleMode && !this._wrapMode && !this._noScroll) {
					var clientHeightNoScroll = clientHeight, clientHeightScroll = clientHeight;
					var oldHScroll = viewDiv.style.overflowX === "scroll"; //$NON-NLS-1$
					if (oldHScroll) {
						clientHeightNoScroll += scrollbarWidth;
					} else {
						clientHeightScroll -= scrollbarWidth;
					}
					var clientWidthNoScroll = clientWidth, clientWidthScroll = clientWidth;
					var oldVScroll = viewDiv.style.overflowY === "scroll"; //$NON-NLS-1$
					if (oldVScroll) {
						clientWidthNoScroll += scrollbarWidth;
					} else {
						clientWidthScroll -= scrollbarWidth;
					}
					clientHeight = clientHeightNoScroll;
					clientWidth = clientWidthNoScroll;
					if (scrollHeight > clientHeight) {
						vScroll = true;
						clientWidth = clientWidthScroll;
					}
					if (this._maxLineWidth > clientWidth) {
						hScroll = true;
						clientHeight = clientHeightScroll;
						if (scrollHeight > clientHeight) {
							vScroll = true;
							clientWidth = clientWidthScroll;
						}
					}
					if (oldHScroll !== hScroll) {
						viewDiv.style.overflowX = hScroll ? "scroll" : "hidden"; //$NON-NLS-1$ //$NON-NLS-2$
					}
					if (oldVScroll !== vScroll) {
						viewDiv.style.overflowY = vScroll ? "scroll" : "hidden"; //$NON-NLS-1$ //$NON-NLS-2$
					}
					needUpdate = oldHScroll !== hScroll || oldVScroll !== vScroll;
				}
				
				var width = clientWidth;
				if (this._wrapMode) {
					if (this._metrics.wrapWidth) {
						width = this._metrics.wrapWidth;
					}
				} else {
					width = Math.max(this._maxLineWidth + this._getInnerRightWidth(), width);
				}
				/*
				* Except by IE 8 and earlier, all other browsers are not allocating enough space for the right padding 
				* in the scrollbar. It is possible this a bug since all other paddings are considered.
				*/
				scrollWidth = width;
				if ((!util.isIE || util.isIE >= 9) && this._maxLineWidth > clientWidth) { width += viewPad.right + viewPad.left; }
				scrollDiv.style.width = width + "px"; //$NON-NLS-1$
				if (this._clipScrollDiv) {
					this._clipScrollDiv.style.width = width + "px"; //$NON-NLS-1$
				}
				/* Get the left scroll after setting the width of the scrollDiv as this can change the horizontal scroll offset. */
				_scroll = this._getScroll(false);

				var innerRightDiv = this._innerRightDiv;
				if (innerRightDiv) {
					innerRightDiv.style.right = rightWidth + (viewDiv.style.overflowY === "scroll" ? this._metrics.scrollWidth : 0) + "px"; //$NON-NLS-1$ //$NON-NLS-1$
					innerRightDiv.style.bottom = (viewDiv.style.overflowX === "scroll" ? scrollbarWidth : 0) + "px"; //$NON-NLS-1$ //$NON-NLS-1$
				}
			}
			this._scrollHeight = scrollHeight;
			if (this._vScrollDiv) {
				var trackHeight = clientHeight - 8;
				var thumbHeight = Math.max(15, Math.ceil(Math.min(1, trackHeight / (scrollHeight + viewPad.top + viewPad.bottom)) * trackHeight));
				this._vScrollDiv.style.left = (leftWidth + clientWidth - 8) + "px"; //$NON-NLS-1$
				this._vScrollDiv.style.top = Math.floor(Math.max(0, (_scroll.y * trackHeight / scrollHeight))) + "px"; //$NON-NLS-1$
				this._vScrollDiv.style.height = thumbHeight + "px"; //$NON-NLS-1$
			}
			if (!this._wrapMode && this._hScrollDiv) {
				var trackWidth = clientWidth - 8;
				var thumbWidth = Math.max(15, Math.ceil(Math.min(1, trackWidth / (this._maxLineWidth + viewPad.left + viewPad.right)) * trackWidth));
				this._hScrollDiv.style.left = leftWidth + Math.floor(Math.max(0, Math.floor(_scroll.x * trackWidth / this._maxLineWidth))) + "px"; //$NON-NLS-1$
				this._hScrollDiv.style.top = (clientHeight - 9) + "px"; //$NON-NLS-1$
				this._hScrollDiv.style.width = thumbWidth + "px"; //$NON-NLS-1$
			}
			var left = _scroll.x;	
			var clipDiv = this._clipDiv;
			var overlayDiv = this._overlayDiv;
			var marginDiv = this._marginDiv;
			var clipLeft, clipTop;
			if (marginDiv) {
				marginDiv.style.left = (-left + leftWidth + this._metrics.marginWidth + viewPad.left) + "px"; //$NON-NLS-1$
				marginDiv.style.bottom = (viewDiv.style.overflowX === "scroll" ? scrollbarWidth : 0) + "px"; //$NON-NLS-1$ //$NON-NLS-1$
			}
			if (clipDiv) {
				clipDiv.scrollLeft = left;
				clipDiv.scrollTop = 0;
				clipLeft = leftWidth + viewPad.left;
				clipTop = viewPad.top;
				var clipWidth = clientWidth;
				var clipHeight = clientHeight;
				var clientLeft = 0, clientTop = -_top;
				if (_scroll.x === 0) {
					clipLeft -= viewPad.left;
					clipWidth += viewPad.left;
					clientLeft = viewPad.left;
				} 
				if (_scroll.x + clientWidth === scrollWidth) {
					clipWidth += viewPad.right;
				}
				if (_scroll.y === 0) {
					clipTop -= viewPad.top;
					clipHeight += viewPad.top;
					clientTop += viewPad.top;
				}
				if (_scroll.y + clientHeight === scrollHeight) { 
					clipHeight += viewPad.bottom; 
				}
				clipDiv.style.left = clipLeft + "px"; //$NON-NLS-1$
				clipDiv.style.top = clipTop + "px"; //$NON-NLS-1$
				clipDiv.style.right = (rootWidth - clipWidth - clipLeft) + "px"; //$NON-NLS-1$
				clipDiv.style.bottom = (rootHeight - clipHeight - clipTop) + "px"; //$NON-NLS-1$
				clientDiv.style.left = clientLeft + "px"; //$NON-NLS-1$
				clientDiv.style.top = clientTop + "px"; //$NON-NLS-1$
				clientDiv.style.width = scrollWidth + "px"; //$NON-NLS-1$
				clientDiv.style.height = (clientHeight + _top) + "px"; //$NON-NLS-1$
				if (overlayDiv) {
					overlayDiv.style.left = clientDiv.style.left;
					overlayDiv.style.top = clientDiv.style.top;
					overlayDiv.style.width = clientDiv.style.width;
					overlayDiv.style.height = clientDiv.style.height;
				}
			} else {
				clipLeft = left;
				clipTop = _top;
				var clipRight = left + clientWidth;
				var clipBottom = _top + clientHeight;
				if (clipLeft === 0) { clipLeft -= viewPad.left; }
				if (clipTop === 0) { clipTop -= viewPad.top; }
				if (clipRight === scrollWidth) { clipRight += viewPad.right; }
				if (_scroll.y + clientHeight === scrollHeight) { clipBottom += viewPad.bottom; }
				clientDiv.style.clip = "rect(" + clipTop + "px," + clipRight + "px," + clipBottom + "px," + clipLeft + "px)"; //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-5$
				if (document.dir == "rtl") { /* ACGC */
					clientDiv.style.right = (-left + leftWidth + viewPad.left) + "px"; //$NON-NLS-1$
				}else{
					clientDiv.style.left = (-left + leftWidth + viewPad.left) + "px"; //$NON-NLS-1$	  
				}
				clientDiv.style.width = (this._wrapMode || util.isWebkit ? scrollWidth : clientWidth + left) + "px"; //$NON-NLS-1$
				if (!hScrollOnly) {
					clientDiv.style.top = (-_top + viewPad.top) + "px"; //$NON-NLS-1$
					clientDiv.style.height = (clientHeight + _top) + "px"; //$NON-NLS-1$
				}
				if (overlayDiv) {
					overlayDiv.style.clip = clientDiv.style.clip;
					overlayDiv.style.left = clientDiv.style.left;
					overlayDiv.style.width = clientDiv.style.width;
					if (!hScrollOnly) {
						overlayDiv.style.top = clientDiv.style.top;
						overlayDiv.style.height = clientDiv.style.height;
					}
				}
			}
			this._updateDOMSelection();

			if (needUpdate) {
				var ensureCaretVisible = this._ensureCaretVisible;
				this._ensureCaretVisible = false;
				if (ensureCaretVisible) {
					this._showCaret();
				}
				this._queueUpdate();
			}
		},
		_updateOverflow: function() {
			var viewDiv = this._viewDiv;
			if (this._noScroll) {
				viewDiv.style.overflow = "hidden"; //$NON-NLS-1$
			} else if (this._wrapMode) {
				viewDiv.style.overflowX = "hidden"; //$NON-NLS-1$
				viewDiv.style.overflowY = "scroll"; //$NON-NLS-1$
			} else {
				viewDiv.style.overflow = "hidden"; //$NON-NLS-1$
			}
		},
		_updateRuler: function (divRuler, topIndex, bottomIndex, rootHeight) {
			if (!divRuler) { return; }
			var doc = this._parent.ownerDocument;
			var lineHeight = this._getLineHeight();
			var viewPad = this._getViewPadding();
			var div = divRuler.firstChild;
			while (div) {
				var ruler = div._ruler;
				var overview = ruler.getOverview();
				if (div.rulerChanged) {
					applyStyle(ruler.getRulerStyle(), div);
					divRuler.rulerWidth = undefined;
				}
				if (overview === "fixed") { //$NON-NLS-1$
					div.rulerChanged = false;
					div = div.nextSibling;
					continue;
				}
				var offset = lineHeight;
				if (overview === "page") { offset += this._topIndexY; } //$NON-NLS-1$
				div.style.top = -offset + "px"; //$NON-NLS-1$
				div.style.height = (rootHeight + offset) + "px"; //$NON-NLS-1$
				
				
				var widthDiv;
				var child = div.firstChild;
				if (child) {
					widthDiv = child;
					child = child.nextSibling;
				} else {
					widthDiv = util.createElement(doc, "div"); //$NON-NLS-1$
					widthDiv.style.visibility = "hidden"; //$NON-NLS-1$
					div.appendChild(widthDiv);
				}
				var lineIndex, annotation;
				if (div.rulerChanged) {
					if (widthDiv) {
						lineIndex = -1;
						annotation = ruler.getWidestAnnotation();
						if (annotation) {
							applyStyle(annotation.style, widthDiv);
							if (annotation.html) {
								widthDiv.innerHTML = annotation.html;
							}
						}
						widthDiv.lineIndex = lineIndex;
						widthDiv.style.height = (lineHeight + viewPad.top) + "px"; //$NON-NLS-1$
					}
				}

				var lineDiv, frag, annotations;
				if (overview === "page") { //$NON-NLS-1$
					annotations = ruler.getAnnotations(topIndex, bottomIndex + 1);
					while (child) {
						lineIndex = child.lineIndex;
						var nextChild = child.nextSibling;
						if (!(topIndex <= lineIndex && lineIndex <= bottomIndex) || child.lineChanged) {
							div.removeChild(child);
						}
						child = nextChild;
					}
					child = div.firstChild.nextSibling;
					frag = doc.createDocumentFragment();
					for (lineIndex=topIndex; lineIndex<=bottomIndex; lineIndex++) {
						if (!child || child.lineIndex > lineIndex) {
							lineDiv = util.createElement(doc, "div"); //$NON-NLS-1$
							annotation = annotations[lineIndex];
							if (annotation) {
								applyStyle(annotation.style, lineDiv);
								if (annotation.html) {
									lineDiv.innerHTML = annotation.html;
								}
								lineDiv.annotation = annotation;
							}
							lineDiv.lineIndex = lineIndex;
							lineDiv.style.height = this._getLineHeight(lineIndex) + "px"; //$NON-NLS-1$
							frag.appendChild(lineDiv);
						} else {
							if (frag.firstChild) {
								div.insertBefore(frag, child);
								frag = doc.createDocumentFragment();
							}
							if (child) {
								child = child.nextSibling;
							}
						}
					}
					if (frag.firstChild) { div.insertBefore(frag, child); }
				} else {
					var clientHeight = this._getClientHeight ();
					var lineCount = this._model.getLineCount ();
					var contentHeight = lineHeight * lineCount;
					var trackHeight = clientHeight + viewPad.top + viewPad.bottom - 2 * this._metrics.scrollWidth;
					var divHeight, arrowWidth;
					if (contentHeight < trackHeight) {
						divHeight = lineHeight;
						arrowWidth = viewPad.top;
					} else {
						divHeight = trackHeight / lineCount;
						arrowWidth = this._metrics.scrollWidth;
					}
					if (div.rulerChanged) {
						var count = div.childNodes.length;
						while (count > 1) {
							div.removeChild(div.lastChild);
							count--;
						}
						annotations = ruler.getAnnotations(0, lineCount);
						frag = doc.createDocumentFragment();
						for (var prop in annotations) {
							lineIndex = prop >>> 0;
							if (lineIndex < 0) { continue; }
							lineDiv = util.createElement(doc, "div"); //$NON-NLS-1$
							annotation = annotations[prop];
							applyStyle(annotation.style, lineDiv);
							lineDiv.style.position = "absolute"; //$NON-NLS-1$
							lineDiv.style.top = arrowWidth + lineHeight + Math.floor(lineIndex * divHeight) + "px"; //$NON-NLS-1$
							if (annotation.html) {
								lineDiv.innerHTML = annotation.html;
							}
							lineDiv.annotation = annotation;
							lineDiv.lineIndex = lineIndex;
							frag.appendChild(lineDiv);
						}
						div.appendChild(frag);
					} else if (div._oldTrackHeight !== trackHeight) {
						lineDiv = div.firstChild ? div.firstChild.nextSibling : null;
						while (lineDiv) {
							lineDiv.style.top = this._metrics.scrollWidth + lineHeight + Math.floor(lineDiv.lineIndex * divHeight) + "px"; //$NON-NLS-1$
							lineDiv = lineDiv.nextSibling;
						}
					}
					div._oldTrackHeight = trackHeight;
				}
				div.rulerChanged = false;
				div = div.nextSibling;
			}
		},
		_updateStyleSheet: function() {
			var styleText = "";
			if (util.isWebkit && this._metrics.scrollWidth > 0) {
				styleText += "\n.textview ::-webkit-scrollbar-corner {background: #eeeeee;}"; //$NON-NLS-1$
			}
			if (styleText) {
				var doc = this._clientDiv.ownerDocument;
				var node = doc.getElementById("_textviewStyle"); //$NON-NLS-1$
				if (!node) {
					node = util.createElement(doc, "style"); //$NON-NLS-1$
					node.id = "_textviewStyle"; //$NON-NLS-1$
					var head = doc.getElementsByTagName("head")[0] || doc.documentElement; //$NON-NLS-1$
					node.appendChild(doc.createTextNode(styleText));
					head.insertBefore(node, head.firstChild);
				} else {
					node.removeChild(node.firstChild);
					node.appendChild(doc.createTextNode(styleText));
				}
			}
		},
		_updateStyle: function (init, metrics) {
			if (!init && util.isIE) {
				this._rootDiv.style.lineHeight = "normal"; //$NON-NLS-1$
			}
			metrics = this._metrics = metrics || this._calculateMetrics();
			if (this._variableLineHeight) {
				this._variableLineHeight = false;
				this._resetLineHeight();
			}
			if (util.isIE) {
				this._rootDiv.style.lineHeight = (metrics.lineHeight - (metrics.lineTrim.top + metrics.lineTrim.bottom)) + "px"; //$NON-NLS-1$
			} else {
				this._rootDiv.style.lineHeight = "normal"; //$NON-NLS-1$
			}
			this._updateStyleSheet();
			if (util.isMac && util.isWebkit) {
				var viewDiv = this._viewDiv;
				if (!metrics.invalid && metrics.scrollWidth === 0) {
					viewDiv.style.pointerEvents = "none"; //$NON-NLS-1$
					viewDiv.style.zIndex = "2"; //$NON-NLS-1$
				} else {
					viewDiv.style.pointerEvents = ""; //$NON-NLS-1$
					viewDiv.style.zIndex = ""; //$NON-NLS-1$
				}
			}
			if (!init) {
				this.redraw();
				this._resetLineWidth();
			}
		}
	};//end prototype
	mEventTarget.EventTarget.addMixin(TextView.prototype);
	
	return {TextView: TextView};
});


/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: 
 *		Felipe Heidrich (IBM Corporation) - initial API and implementation
 *		Silenio Quarti (IBM Corporation) - initial API and implementation
 ******************************************************************************/

/*eslint-env browser, amd*/
define("orion/editor/projectionTextModel", ['orion/editor/textModel', 'orion/editor/eventTarget'], function(mTextModel, mEventTarget) { //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$

	/**
	 * @class This object represents a projection range. A projection specifies a
	 * range of text and the replacement text. The range of text is relative to the
	 * base text model associated to a projection model.
	 * <p>
	 * <b>See:</b><br/>
	 * {@link orion.editor.ProjectionTextModel}<br/>
	 * {@link orion.editor.ProjectionTextModel#addProjection}<br/>
	 * </p>		 
	 * @name orion.editor.Projection
	 * 
	 * @property {Number} start The start offset of the projection range. 
	 * @property {Number} end The end offset of the projection range. This offset is exclusive.
	 * @property {String|orion.editor.TextModel} [text=""] The projection text to be inserted
	 */
	/**
	 * Constructs a new <code>ProjectionTextModel</code> based on the specified <code>TextModel</code>.
	 *
	 * @param {orion.editor.TextModel} baseModel The base text model.
	 *
	 * @name orion.editor.ProjectionTextModel
	 * @class The <code>ProjectionTextModel</code> represents a projection of its base text
	 * model. Projection ranges can be added to the projection text model to hide and/or insert
	 * ranges to the base text model.
	 * <p>
	 * The contents of the projection text model is modified when changes occur in the base model,
	 * projection model or by calls to {@link #addProjection} and {@link #removeProjection}.
	 * </p>
	 * <p>
	 * <b>See:</b><br/>
	 * {@link orion.editor.TextView}<br/>
	 * {@link orion.editor.TextModel}
	 * {@link orion.editor.TextView#setModel}
	 * </p>
	 * @borrows orion.editor.EventTarget#addEventListener as #addEventListener
	 * @borrows orion.editor.EventTarget#removeEventListener as #removeEventListener
	 * @borrows orion.editor.EventTarget#dispatchEvent as #dispatchEvent
	 */
	function ProjectionTextModel(baseModel) {
		this._model = baseModel;
		this._projections = [];
		var self = this;
		this._listener = {
			onChanged: function(e) {
				self._onChanged(e);
			},
			onChanging: function(e) {
				self._onChanging(e);
			}
		};
		baseModel.addEventListener("postChanged", this._listener.onChanged); //$NON-NLS-0$
		baseModel.addEventListener("preChanging", this._listener.onChanging); //$NON-NLS-0$
	}

	ProjectionTextModel.prototype = /** @lends orion.editor.ProjectionTextModel.prototype */ {
		/**
		 * Destroys this projection text model.
		 */
		destroy: function() {
			if (this._model) {
				this._model.removeEventListener("postChanged", this._listener.onChanged); //$NON-NLS-0$
				this._model.removeEventListener("preChanging", this._listener.onChanging); //$NON-NLS-0$
				this._model = null;
			}
		},
		/**
		 * Adds a projection range to the model.
		 * <p>
		 * The model must notify the listeners before and after the the text is
		 * changed by calling {@link #onChanging} and {@link #onChanged} respectively. 
		 * </p>
		 * @param {orion.editor.Projection} projection The projection range to be added.
		 * 
		 * @see orion.editor.ProjectionTextModel#removeProjection
		 */
		addProjection: function(projection) {
			if (!projection) {return;}
			//start and end can't overlap any exist projection
			var model = this._model, projections = this._projections;
			projection._lineIndex = model.getLineAtOffset(projection.start);
			projection._lineCount = model.getLineAtOffset(projection.end) - projection._lineIndex;
			var text = projection.text;
			if (!text) { text = ""; }
			if (typeof text === "string") { //$NON-NLS-0$
				projection._model = new mTextModel.TextModel(text, model.getLineDelimiter());
			} else {
				projection._model = text;
			}
			var eventStart = this.mapOffset(projection.start, true);
			var removedCharCount = projection.end - projection.start;
			var removedLineCount = projection._lineCount;
			var addedCharCount = projection._model.getCharCount();
			var addedLineCount = projection._model.getLineCount() - 1;
			var modelChangingEvent = {
				type: "Changing", //$NON-NLS-0$
				text: projection._model.getText(),
				start: eventStart,
				removedCharCount: removedCharCount,
				addedCharCount: addedCharCount,
				removedLineCount: removedLineCount,
				addedLineCount: addedLineCount
			};
			this.onChanging(modelChangingEvent);
			var index = this._binarySearch(projections, projection.start);
			projections.splice(index, 0, projection);
			var modelChangedEvent = {
				type: "Changed", //$NON-NLS-0$
				start: eventStart,
				removedCharCount: removedCharCount,
				addedCharCount: addedCharCount,
				removedLineCount: removedLineCount,
				addedLineCount: addedLineCount
			};
			this.onChanged(modelChangedEvent);
		},
		/**
		 * Returns all projection ranges of this model.
		 * 
		 * @return {orion.editor.Projection[]} The projection ranges.
		 * 
		 * @see orion.editor.ProjectionTextModel#addProjection
		 */
		getProjections: function() {
			return this._projections.slice(0);
		},
		/**
		 * Gets the base text model.
		 *
		 * @return {orion.editor.TextModel} The base text model.
		 */
		getBaseModel: function() {
			return this._model;
		},
		/**
		 * Maps offsets between the projection model and its base model.
		 *
		 * @param {Number} offset The offset to be mapped.
		 * @param {Boolean} [baseOffset=false] <code>true</code> if <code>offset</code> is in base model and
		 *	should be mapped to the projection model.
		 * @return {Number} The mapped offset
		 */
		mapOffset: function(offset, baseOffset) {
			var projections = this._projections, delta = 0, i, projection;
			if (baseOffset) {
				for (i = 0; i < projections.length; i++) {
					projection = projections[i];
					if (projection.start > offset) { break; }
					if (projection.end > offset) { return -1; }
					delta += projection._model.getCharCount() - (projection.end - projection.start);
				}
				return offset + delta;
			}
			for (i = 0; i < projections.length; i++) {
				projection = projections[i];
				if (projection.start > offset - delta) { break; }
				var charCount = projection._model.getCharCount();
				if (projection.start + charCount > offset - delta) {
					return -1;
				}
				delta += charCount - (projection.end - projection.start);
			}
			return offset - delta;
		},
		/**
		 * Removes a projection range from the model.
		 * <p>
		 * The model must notify the listeners before and after the the text is
		 * changed by calling {@link #onChanging} and {@link #onChanged} respectively. 
		 * </p>
		 * 
		 * @param {orion.editor.Projection} projection The projection range to be removed.
		 * 
		 * @see orion.editor.ProjectionTextModel#addProjection
		 */
		removeProjection: function(projection) {
			this._removeProjection(projection);
		},
		_removeProjection: function(projection, noEvents) {
			var i, delta = 0;
			for (i = 0; i < this._projections.length; i++) {
				var p = this._projections[i];
				if (p === projection) {
					projection = p;
					break;
				}
				delta += p._model.getCharCount() - (p.end - p.start);
			}
			if (i < this._projections.length) {
				var model = this._model;
				var eventStart = projection.start + delta;
				var addedCharCount = projection.end - projection.start;
				var addedLineCount = projection._lineCount;
				var removedCharCount = projection._model.getCharCount();
				var removedLineCount = projection._model.getLineCount() - 1;
				if (!noEvents) {
					var modelChangingEvent = {
						type: "Changing", //$NON-NLS-0$
						text: model.getText(projection.start, projection.end),
						start: eventStart,
						removedCharCount: removedCharCount,
						addedCharCount: addedCharCount,
						removedLineCount: removedLineCount,
						addedLineCount: addedLineCount
					};
					this.onChanging(modelChangingEvent);
				}
				this._projections.splice(i, 1);
				if (!noEvents) {
					var modelChangedEvent = {
						type: "Changed", //$NON-NLS-0$
						start: eventStart,
						removedCharCount: removedCharCount,
						addedCharCount: addedCharCount,
						removedLineCount: removedLineCount,
						addedLineCount: addedLineCount
					};
					this.onChanged(modelChangedEvent);
				}
			}
		},
		/** @ignore */
		_binarySearch: function (array, offset) {
			var high = array.length, low = -1, index;
			while (high - low > 1) {
				index = Math.floor((high + low) / 2);
				if (offset <= array[index].start) {
					high = index;
				} else {
					low = index;
				}
			}
			return high;
		},
		/**
		 * @see orion.editor.TextModel#getCharCount
		 */
		getCharCount: function() {
			var count = this._model.getCharCount(), projections = this._projections;
			for (var i = 0; i < projections.length; i++) {
				var projection = projections[i];
				count += projection._model.getCharCount() - (projection.end - projection.start);
			}
			return count;
		},
		/**
		 * @see orion.editor.TextModel#getLine
		 */
		getLine: function(lineIndex, includeDelimiter) {
			if (lineIndex < 0) { return null; }
			var model = this._model, projections = this._projections;
			var delta = 0, result = [], offset = 0, i, lineCount, projection;
			for (i = 0; i < projections.length; i++) {
				projection = projections[i];
				if (projection._lineIndex >= lineIndex - delta) { break; }
				lineCount = projection._model.getLineCount() - 1;
				if (projection._lineIndex + lineCount >= lineIndex - delta) {
					var projectionLineIndex = lineIndex - (projection._lineIndex + delta);
					if (projectionLineIndex < lineCount) {
						return projection._model.getLine(projectionLineIndex, includeDelimiter);
					} else {
						result.push(projection._model.getLine(lineCount));
					}
				}
				offset = projection.end;
				delta += lineCount - projection._lineCount;
			}
			offset = Math.max(offset, model.getLineStart(lineIndex - delta));
			for (; i < projections.length; i++) {
				projection = projections[i];
				if (projection._lineIndex > lineIndex - delta) { break; }
				result.push(model.getText(offset, projection.start));
				lineCount = projection._model.getLineCount() - 1;
				if (projection._lineIndex + lineCount > lineIndex - delta) {
					result.push(projection._model.getLine(0, includeDelimiter));
					return result.join("");
				}
				result.push(projection._model.getText());
				offset = projection.end;
				delta += lineCount - projection._lineCount;
			}
			var end = model.getLineEnd(lineIndex - delta, includeDelimiter);
			if (offset < end) {
				result.push(model.getText(offset, end));
			}
			return result.join("");
		},
		/**
		 * @see orion.editor.TextModel#getLineAtOffset
		 */
		getLineAtOffset: function(offset) {
			var model = this._model, projections = this._projections;
			var delta = 0, lineDelta = 0;
			for (var i = 0; i < projections.length; i++) {
				var projection = projections[i];
				if (projection.start > offset - delta) { break; }
				var charCount = projection._model.getCharCount();
				if (projection.start + charCount > offset - delta) {
					var projectionOffset = offset - (projection.start + delta);
					lineDelta += projection._model.getLineAtOffset(projectionOffset);
					delta += projectionOffset;
					break;
				}
				lineDelta += projection._model.getLineCount() - 1 - projection._lineCount;
				delta += charCount - (projection.end - projection.start);
			}
			return model.getLineAtOffset(offset - delta) + lineDelta;
		},
		/**
		 * @see orion.editor.TextModel#getLineCount
		 */
		getLineCount: function() {
			var model = this._model, projections = this._projections;
			var count = model.getLineCount();
			for (var i = 0; i < projections.length; i++) {
				var projection = projections[i];
				count += projection._model.getLineCount() - 1 - projection._lineCount;
			}
			return count;
		},
		/**
		 * @see orion.editor.TextModel#getLineDelimiter
		 */
		getLineDelimiter: function() {
			return this._model.getLineDelimiter();
		},
		/**
		 * @see orion.editor.TextModel#getLineEnd
		 */
		getLineEnd: function(lineIndex, includeDelimiter) {
			if (lineIndex < 0) { return -1; }
			var model = this._model, projections = this._projections;
			var delta = 0, offsetDelta = 0;
			for (var i = 0; i < projections.length; i++) {
				var projection = projections[i];
				if (projection._lineIndex > lineIndex - delta) { break; }
				var lineCount = projection._model.getLineCount() - 1;
				if (projection._lineIndex + lineCount > lineIndex - delta) {
					var projectionLineIndex = lineIndex - (projection._lineIndex + delta);
					return projection._model.getLineEnd (projectionLineIndex, includeDelimiter) + projection.start + offsetDelta;
				}
				offsetDelta += projection._model.getCharCount() - (projection.end - projection.start);
				delta += lineCount - projection._lineCount;
			}
			return model.getLineEnd(lineIndex - delta, includeDelimiter) + offsetDelta;
		},
		/**
		 * @see orion.editor.TextModel#getLineStart
		 */
		getLineStart: function(lineIndex) {
			if (lineIndex < 0) { return -1; }
			var model = this._model, projections = this._projections;
			var delta = 0, offsetDelta = 0;
			for (var i = 0; i < projections.length; i++) {
				var projection = projections[i];
				if (projection._lineIndex >= lineIndex - delta) { break; }
				var lineCount = projection._model.getLineCount() - 1;
				if (projection._lineIndex + lineCount >= lineIndex - delta) {
					var projectionLineIndex = lineIndex - (projection._lineIndex + delta);
					return projection._model.getLineStart (projectionLineIndex) + projection.start + offsetDelta;
				}
				offsetDelta += projection._model.getCharCount() - (projection.end - projection.start);
				delta += lineCount - projection._lineCount;
			}
			return model.getLineStart(lineIndex - delta) + offsetDelta;
		},
		/**
		 * @see orion.editor.TextModel#getText
		 */
		getText: function(start, end) {
			if (start === undefined) { start = 0; }
			var model = this._model, projections = this._projections;
			var delta = 0, result = [], i, projection, charCount;
			for (i = 0; i < projections.length; i++) {
				projection = projections[i];
				if (projection.start > start - delta) { break; }
				charCount = projection._model.getCharCount();
				if (projection.start + charCount > start - delta) {
					if (end !== undefined && projection.start + charCount > end - delta) {
						return projection._model.getText(start - (projection.start + delta), end - (projection.start + delta));
					} else {
						result.push(projection._model.getText(start - (projection.start + delta)));
						start = projection.end + delta + charCount - (projection.end - projection.start);
					}
				}
				delta += charCount - (projection.end - projection.start);
			}
			var offset = start - delta;
			if (end !== undefined) {
				for (; i < projections.length; i++) {
					projection = projections[i];
					if (projection.start > end - delta) { break; }
					result.push(model.getText(offset, projection.start));
					charCount = projection._model.getCharCount();
					if (projection.start + charCount > end - delta) {
						result.push(projection._model.getText(0, end - (projection.start + delta)));
						return result.join("");
					}
					result.push(projection._model.getText());
					offset = projection.end;
					delta += charCount - (projection.end - projection.start);
				}
				result.push(model.getText(offset, end - delta));
			} else {
				for (; i < projections.length; i++) {
					projection = projections[i];
					result.push(model.getText(offset, projection.start));
					result.push(projection._model.getText());
					offset = projection.end;
				}
				result.push(model.getText(offset));
			}
			return result.join("");
		},
		/** @ignore */
		_onChanged: function(modelChangedEvent) {
			var change = this._change;
			var start = change.baseStart, end = change.baseEnd, i;
			var projection, projections = this._projections;
			for (i = 0; i < projections.length; i++) {
				projection = projections[i];
				if (projection.end > start) { break; }
			}
			var rangeStart = i;
			for (i = 0; i < projections.length; i++) {
				projection = projections[i];
				if (projection.start >= end) { break; }
			}
			var rangeEnd = i;
			var model = this._model;
			var changeCount = change.baseText.length - (end - start);
			for (i = rangeEnd; i < projections.length; i++) {
				projection = projections[i];
				projection.start += changeCount;
				projection.end += changeCount;
				projection._lineIndex = model.getLineAtOffset(projection.start);
			}
			var removed = projections.splice(rangeStart, rangeEnd - rangeStart);
			for (i = 0; i < removed.length; i++) {
				if (removed[i].annotation) {
					removed[i].annotation._expand();
				}
			}
			var modelChangedEvent1 = {
				type: "Changed", //$NON-NLS-0$
				start: change.start,
				removedCharCount: change.removedCharCount,
				addedCharCount: change.addedCharCount,
				removedLineCount: change.removedLineCount,
				addedLineCount: change.addedLineCount
			};
			this.onChanged(modelChangedEvent1);
			this._change = undefined;
		},
		_onChanging: function(modelChangingEvent) {
			var hasChange = !!this._change;
			var change = this._change || {};
			var start = modelChangingEvent.start, end = start + modelChangingEvent.removedCharCount;
			change.baseStart = start;
			change.baseEnd = end;
			change.baseText = modelChangingEvent.text;
			change.addedLineCount = modelChangingEvent.addedLineCount;
			if (!hasChange) {
				this._change = change;
				change.text = modelChangingEvent.text;
				var projections = this._projections, delta, i, projection;
				function mapOffset(offset) {
					for (i = 0, delta = 0; i < projections.length; i++) {
						projection = projections[i];
						if (projection.start > offset) { break; }
						if (projection.end > offset) { return -1; }
						delta += projection._model.getCharCount() - (projection.end - projection.start);
					}
					return offset + delta;
				}
				change.start = mapOffset(start);
				if (change.start === -1) {
					change.text = this._model.getText(projection.start, start) + change.text;
					change.addedLineCount += this._model.getLineAtOffset(start) - this._model.getLineAtOffset(projection.start);
					change.start = projection.start + delta;
				}
				change.end = mapOffset(end);
				if (change.end === -1) {
					change.text += this._model.getText(end, projection.end);
					change.addedLineCount += this._model.getLineAtOffset(projection.end) - this._model.getLineAtOffset(end);
					change.end = projection.start + delta;
				}
			}
			change.addedCharCount = change.text.length;
			change.removedCharCount = change.end - change.start;
			change.removedLineCount = this.getLineAtOffset(change.end) - this.getLineAtOffset(change.start);
			var modelChangingEvent1 = {
				type: "Changing", //$NON-NLS-0$
				text: change.text,
				start: change.start,
				removedCharCount: change.removedCharCount,
				addedCharCount: change.addedCharCount,
				removedLineCount: change.removedLineCount,
				addedLineCount: change.addedLineCount
			};
			this.onChanging(modelChangingEvent1);
		},
		/**
		 * @see orion.editor.TextModel#onChanging
		 */
		onChanging: function(modelChangingEvent) {
			return this.dispatchEvent(modelChangingEvent);
		},
		/**
		 * @see orion.editor.TextModel#onChanged
		 */
		onChanged: function(modelChangedEvent) {
			return this.dispatchEvent(modelChangedEvent);
		},
		/**
		 * @see orion.editor.TextModel#setLineDelimiter
		 */
		setLineDelimiter: function(lineDelimiter) {
			this._model.setLineDelimiter(lineDelimiter);
		},
		/**
		 * @see orion.editor.TextModel#setText
		 */
		setText: function(text, start, end) {
			this._change = {
				text: text || "",
				start: start || 0,
				end: end === undefined ? this.getCharCount() : end
			};
			var projections = this._projections, delta, i, projection;
			function mapOffset(offset) {
				for (i = 0, delta = 0; i < projections.length; i++) {
					projection = projections[i];
					if (projection.start > offset - delta) { break; }
					var charCount = projection._model.getCharCount();
					if (projection.start + charCount > offset - delta) {
						return -1;
					}
					delta += charCount - (projection.end - projection.start);
				}
				return offset - delta;
			}
			var startProjection, endProjection;
			var mapStart = mapOffset(this._change.start);
			if (mapStart === -1) {
				startProjection = {
					projection: projection,
					start: this._change.start - (projection.start + delta)
				};
				mapStart = projection.end;
			}
			var mapEnd = mapOffset(this._change.end);
			if (mapEnd === -1) {
				endProjection = {
					projection: projection,
					end: this._change.end - (projection.start + delta)
				};
				mapEnd = projection.start;
			}
			if (startProjection && endProjection && startProjection.projection === endProjection.projection) {
				//TODO events - special case - change is completely inside of a projection
				projection._model.setText(this._change.text, startProjection.start, endProjection.end);
			} else {
				this._model.setText(this._change.text, mapStart, mapEnd);
				if (startProjection) {
					projection = startProjection.projection;
					projection._model.setText("", startProjection.start);
				}		
				if (endProjection) {
					projection = endProjection.projection;
					projection._model.setText("", 0, endProjection.end);
					projection.start = projection.end;
					projection._lineCount = 0;
				}
			}
			this._change = undefined;
		}
	};
	mEventTarget.EventTarget.addMixin(ProjectionTextModel.prototype);

	return {ProjectionTextModel: ProjectionTextModel};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2015 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/

/*eslint-env browser, amd, node*/
define("orion/editor/tooltip", [ //$NON-NLS-0$
	'i18n!orion/editor/nls/messages', //$NON-NLS-0$
	'orion/editor/textView', //$NON-NLS-0$
	'orion/editor/projectionTextModel', //$NON-NLS-0$
	'orion/Deferred', //$NON-NLS-0$
	'orion/editor/util', //$NON-NLS-0$
	'orion/webui/littlelib', //$NON-NLS-0$
	'orion/util' //$NON-NLS-0$
], function(messages, mTextView, mProjectionTextModel, Deferred, textUtil, lib, util) {

/**
 * @name Tooltip
 * @description Tooltip support for text views
 * 
 * show() - Opens the tooltip
 * update() - Updates the content of the open tooltip
 * onHover() - Checks if the x,y coordinates are outside the current tooltip area and updates the tooltip if necessary
 * hide() - Closes the tooltip
 * isVisible() - Returns whether the tooltip is currently showing
 * 
 * The methods above ask for a tooltipInfo object, which must contain a callback function getTooltipInfo()
 * This function must return an info object with the following contents:
 * 
 * Info {
 * 	Contents {string, html element, annotation array, etc.} Contents to display, if undefined, the hover service will be asked for content
 * 	Context {source, offset, offsetStart, offsetEnd}} Information used to modify the tooltip position or contents
 * 	Position {String} One of left, right, top, bottom used to position the tooltip relative to the anchor area
 * 	AnchorArea {x, y, width, height} rectangle defining the source of the tooltip and where to position relative to
 * 	TooltipArea {x, y, width, height} rectangle to define the tooltip's exact placement rather than a relative position to the anchor
 * 	TooltipOffsetX {Number} Hint to move the tooltip position horizontally
 * 	TooltipOffsetY {Number} Hint to move the tooltip position vertically
 * 
 * @param view
 */
function Tooltip (view) {
		this._view = view;
		var parent = view.getOptions("parent"); //$NON-NLS-0$
		this._create(parent ? parent.ownerDocument : document);
	}
	Tooltip.getTooltip = function(view) {
		if (!view._tooltip) {
			 view._tooltip = new Tooltip(view);
		}
		return view._tooltip;
	};
	Tooltip.prototype = /** @lends orion.editor.Tooltip.prototype */ {
		_create: function(document) {
			if (this._tooltipDiv) { return; }
			var tooltipDiv = this._tooltipDiv = util.createElement(document, "div"); //$NON-NLS-0$
			tooltipDiv.tabIndex = 0;
			tooltipDiv.className = "textviewTooltip"; //$NON-NLS-0$
			tooltipDiv.setAttribute("aria-live", "assertive"); //$NON-NLS-1$ //$NON-NLS-0$
			tooltipDiv.setAttribute("aria-atomic", "true"); //$NON-NLS-1$ //$NON-NLS-0$
			this._tooltipDiv.style.visibility = "hidden"; //$NON-NLS-0$
			this._tipShowing = false;
			document.body.appendChild(tooltipDiv);
			var self = this;
			textUtil.addEventListener(document, "mousedown", this._mouseDownHandler = function(event) { //$NON-NLS-0$
				if (!self.isVisible()) { return; }
				if (textUtil.contains(tooltipDiv, event.target || event.srcElement)) { return; }
				if (!self._locked){
					self.hide();
				}
			}, true);
			textUtil.addEventListener(document, "scroll", this._scrollHandler = function(event) { //$NON-NLS-0$
				if (!self.isVisible()) return;
				if (self._topPixel !== self._view.getTopPixel() || self._leftPixel !== self._view.getHorizontalPixel()) {
					self.hide();
				}
			}, true);
			textUtil.addEventListener(document, "mousemove", this._mouseMoveHandler = function(event) { //$NON-NLS-0$
				// Ignore spurious mousemove events
				if (self._prevX && self._prevX === event.clientX && self._prevY && self._prevY === event.clientY) {
					return;
				}
				self._prevX = event.clientX;
				self._prevY = event.clientY;
				
				if (!self.isVisible() || self._locked || self._hasFocus()) { return; }
				if (self._isInRect(self._outerArea, event.clientX, event.clientY)){ return; }
				self.hide();
			}, true);
			textUtil.addEventListener(tooltipDiv, "focus", /* @callback */ function(event) { //$NON-NLS-0$
				if (!self._locked){
					self._tooltipDiv.classList.add('textViewTooltipOnFocus'); //$NON-NLS-0$
				}
			}, false);
			textUtil.addEventListener(tooltipDiv, "blur", /* @callback */ function(event) { //$NON-NLS-0$
				self._tooltipDiv.classList.remove('textViewTooltipOnFocus'); //$NON-NLS-0$
			}, false);
			textUtil.addEventListener(tooltipDiv, "mouseenter", /* @callback */ function(event) { //$NON-NLS-0$
				if (!self._locked){
					self._tooltipDiv.classList.add('textViewTooltipOnHover'); //$NON-NLS-0$
				}
			}, false);
			textUtil.addEventListener(tooltipDiv, "mouseleave", /* @callback */ function(event) { //$NON-NLS-0$
				if (!self._hasFocus()){
					self._tooltipDiv.classList.remove('textViewTooltipOnHover'); //$NON-NLS-0$
				}
			}, false);
			textUtil.addEventListener(tooltipDiv, "keydown", function(event) { //$NON-NLS-0$
				if (event.keyCode === 27) {
					if (!self._locked){
						self.hide();
					}
				}
			}, false);
			this._view.addEventListener("Destroy", function() { //$NON-NLS-0$
				self.destroy();
			});
		},
		destroy: function() {
			if (!this._tooltipDiv) { return; }
			this.hide();
			var parent = this._tooltipDiv.parentNode;
			if (parent) { parent.removeChild(this._tooltipDiv); }
			var doc = this._tooltipDiv.ownerDocument;
			textUtil.removeEventListener(doc, "mousedown", this._mouseDownHandler, true); //$NON-NLS-0$
			textUtil.removeEventListener(doc, "scroll", this._scrollHandler, true); //$NON-NLS-0$
			textUtil.removeEventListener(doc, "mousemove", this._mouseMoveHandler, true); //$NON-NLS-0$
			this._tooltipDiv = null;
		},
		
		/**
		 * @name show
		 * @description Show the tooltip using the given target information
		 * @function
		 * @param target The target through which the info is obtained
		 * @param locked If true locks the tooltip (never hides unless 'hide' is called)
		 * @param giveFocus If true forces the focus onto the tooltip (used for F2 processing)
		 */
		show: function(tooltipInfo, locked, giveFocus) {
			this._locked = locked;
			this._giveFocus = giveFocus;
			this._topPixel = this._view.getTopPixel();
			this._leftPixel = this._view.getHorizontalPixel();
			this._processInfo(tooltipInfo.getTooltipInfo());
		},
		
		/**
		 * @name update
		 * @description Updates the information in an already visible tooltip
		 * @function
		 * @param tooltipInfo a function that will return the parameters need to update the information
		 * @param noContent If true makes no attempt to gather new info and just updates the tooltip's position
		 */
		update: function(tooltipInfo, noContent) {
			if (!tooltipInfo){
				return;
			}
			if (noContent) {
				this._showContents(null, tooltipInfo.getTooltipInfo(), true);
			} else {
				this._processInfo(tooltipInfo.getTooltipInfo(), true);
			}
		},
		
		/**
		 * @name onHover
		 * @description Show the tooltip using the given target information. Only called for hover events.
		 * @function
		 * @param target
		 * @param giveFocus
		 */
		onHover: function(tooltipInfo, x, y) {
			if (!tooltipInfo) {
				return;
			}
			
			// Don't process if we're in the anchor or tooltip
			if (this._isInRect(this._anchorArea, x, y)
					|| this._isInRect(this._tooltipArea, x, y)
					|| this._locked
					|| this._hasFocus()) {
				return;
			}
			
			this._processInfo(tooltipInfo.getTooltipInfo());
		},		
	
		/**
		 * @name hide
		 * @description Hides the current tooltip (if it's showing)
		 * @function
		 * @public
		*/
		hide: function(clearLock) {
			if (clearLock) {
				this._locked = undefined;
			}
			
			if (this._locked || !this.isVisible()){
				return;
			}
				
			if (this.hover) {
				this.hover.clearQuickFixes();
			}

			if (this._hasFocus()) {
				this._view.focus();
			}
			if (this._tooltipContents) {
				this._tooltipDiv.removeChild(this._tooltipContents);
				this._tooltipContents = null;
			}
			
			// Code projections are displayed using the editor theme colors
			this._tooltipDiv.classList.remove("textviewTooltipCodeProjection"); //$NON-NLS-0$
			this._tooltipDiv.classList.remove("textviewTooltipOnHover"); //$NON-NLS-0$
			this._tooltipDiv.classList.remove("textviewTooltipOnFocus"); //$NON-NLS-0$
			
			this._tooltipDiv.style.visibility = "hidden"; //$NON-NLS-0$
			this._tipShowing = false;
			this._tooltipDiv.style.left = "";
			this._tooltipDiv.style.right = "";
			this._tooltipDiv.style.top = "";
			this._tooltipDiv.style.bottom = "";
			this._tooltipDiv.style.width = "auto";		 //$NON-NLS-0$
			this._tooltipDiv.style.maxWidth = "";
			this._tooltipDiv.style.height = "auto";		 //$NON-NLS-0$	
			this._tooltipDiv.style.maxHeight = "";		 //$NON-NLS-0$
			this._tooltipDiv.style.overflowX = "";		 //$NON-NLS-0$	
			this._tooltipDiv.style.overflowY = "";		 //$NON-NLS-0$	
			
			this._giveFocus = undefined;
			
			this._anchorArea = undefined;  // Area of text/ruler/etc. we are showing a tooltip for
			this._tooltipArea = undefined;  // The area the tooltip covers
			this._outerArea = undefined; // The rectangle encapsulating both anchor and tooltip areas where we want to keep the tooltip open
			
			// cancel any outstanding defers
			if (this._hoverPromises) {
				this._hoverPromises.forEach(function(info) {
					if (!info.resolved) {
						info.cancel();
					}
				});
			}
			this._hoverPromises = undefined;
			
			// values that are calculated
			this._tipRect = undefined;
		},
		
		/**
		 * @name isVisible
		 * @description Returns a boolean indicating whether the tooltip is currently visible
		 * @function
		 * @public
		 * @returns {boolean} 'true' iff the tooltip is currently visible
		*/
		isVisible: function() {
			return this._tipShowing;
		},
		
		/**
		 * @name _processInfo
		 * @description Renders the content, positions the tooltip then makes the tooltip visible
		 * @function
		 * @private
		 * @param info {Object} The info object passed by the caller
		 * 
		 * Info:
		 * 1) Contents
		 * 2) Context
		 * 3) Position string left, right, top, bottom
		 * 4) AnchorArea x, y, width, height
		 * 5) TooltipArea x, y, width, height
		 * 6) TooltipOffsetX
		 * 7) TooltipOffsetY
		 * 8) AllowFullWidth
		 * 
		 * @param update Whether to update the existing tooltip contents or open a new tooltip
		 */
		_processInfo: function(info, update) {
			if (!this._tooltipDiv){
				return;
			}
			
			var newTooltipContents;
			if (update && this._tooltipContents) {
				this._tooltipContents.innerHTML = "";
				newTooltipContents = this._tooltipContents;
			} else {
 				newTooltipContents = util.createElement(this._tooltipDiv.ownerDocument, "div"); //$NON-NLS-0$
 			}
 			
			if (info) {
				// Render provided content
				if (info.contents) {
					if (this._renderImmediateInfo(newTooltipContents, info.contents, info.context)) {
						this._showContents(newTooltipContents, info, update);
						return true;
					}
				}
				
				// If no immediate content, check if hover service plug-ins have content
				if (this.hover && info.context){
					this._hoverPromises = this.hover.computeHoverInfo(info.context);
					if (this._hoverPromises) {
						var self = this;
						var promises = this._hoverPromises.slice(0);
						promises.forEach(function(hoverPromise) {
							Deferred.when(hoverPromise, function (data) {
								// Remove this promise from the list so we don't try cancelling it while processing (see hide())
								if (self._hoverPromises){
									var index = self._hoverPromises.indexOf(hoverPromise);
									if (index >= 0){
										self._hoverPromises.splice(index, 1);
									}
								}
								if (data) {
									if (self._renderPluginContent(newTooltipContents, data)) {
										if (data.offsetStart){
											info.context.offsetStart = data.offsetStart;
										}
										if (data.offsetEnd){
											info.context.offsetEnd = data.offsetEnd;
										}
										if (data.allowFullWidth){
											info.allowFullWidth = data.allowFullWidth;
										}
										self._showContents(newTooltipContents, info, update);
									}
								}
							}, function(error) {
								if (console && error && error.name !== 'Cancel') { //$NON-NLS-0$ //$NON-NLS-1$
									console.log("Error computing hover tooltip"); //$NON-NLS-0$
									console.log(error && error.stack);
								}
							});
						});
						return true;
					}
				}
			}
		},
		/**
		 * @name _showContents
		 * @description Takes the rendered contents, positions the tooltip, opens the tooltip
		 * @function
		 * @private
		 * @param newContentsDiv
		 * @param info
		 * @param update
		 */
		_showContents: function _showContents(newContentsDiv, info, update) {
			if (!update){
				this.hide();
			} else {
				// If update is called but the set tooltip area is different, recreate the tooltip with the new sizing (ex: content assist resizing)
				// We could also check if the set anchor area is different, but no one would be using it
				if (this._tooltipArea && info.tooltipArea){
					if (this._tooltipArea.left !== info.tooltipArea.left || this._tooltipArea.top !== info.tooltipArea.top || this._tooltipArea.width !== info.tooltipArea.width || this._tooltipArea.height !== info.tooltipArea.height){
						this._anchorArea = null;
						this._tooltipArea = null;
						this._outerArea = null;
					}
				}
			}
			
			if (newContentsDiv) {
				if (this._tooltipContents) {
					this._tooltipDiv.removeChild(this._tooltipContents);
				}
				this._tooltipContents = newContentsDiv;
				this._tooltipDiv.appendChild(newContentsDiv);				
			}
			
			if (!this._anchorArea){
				this._anchorArea = this._computeAnchorArea(info);
			}
			if (!this._tooltipArea){
				// This function performs two tasks, computing the rectangle and setting all the style properties
				this._tooltipArea = this._computeTooltipArea(info, this._anchorArea, this._tooltipDiv);
			}
			if (!this._outerArea){
				this._outerArea = this._computeOuterArea(this._anchorArea, this._tooltipArea);
			}

			this._tooltipDiv.style.visibility = "visible"; //$NON-NLS-0$
			this._tipShowing = true;
			
			if (this._giveFocus) {
				this._setInitialFocus(this._tooltipDiv);
				this._giveFocus = undefined;
			}
		},
		
		/**
		 * @name _computeAnchorArea
		 * @description Computes and returns the rectangle that the tooltip is anchored to.  For example the anchor for an annotation 
		 * 				may be its text range.
		 * @function
		 * @private
		 * @param info {Object} the info object passed by the caller.
		 * @returns returns the computed rectangle anchor area
		 */
		_computeAnchorArea: function _computeAnchorArea(info){
			if (info.anchorArea){
				if (info.anchorArea.top && info.anchorArea.left && info.anchorArea.height && info.anchorArea.width){
					return info.anchorArea;
				}
			}
			
			if (info.context){
				if (info.context.offsetStart && info.context.offsetEnd){
					// The full text content of the editor is given to the plug-in hover service, so we must adjust the offsets
					// for the projection model as folded comments will change the offsets/coordinates in the displayed editor
					var mappedStart = this.mapOffset(info.context.offsetStart, false);
					var mappedEnd = this.mapOffset(info.context.offsetEnd, false);
					return this._computeRectangleFromOffset(mappedStart, mappedEnd);
				}
				
				if (info.context.offset >= 0){
					// The provided offset is based on the full text content, not the projection model
					// Adjust the offset before finding the closest enclosing word
					var mappedOffset = this.mapOffset(info.context.offset, false);
					var end = this._view.getNextOffset(mappedOffset, { unit: "wordend", count: 0}); //$NON-NLS-0$
					var start = this._view.getNextOffset(end, { unit: "word", count: -1}); //$NON-NLS-0$
					return this._computeRectangleFromOffset(start, end);
				}
			}
			
			return {top: 0, left: 0, height: 0, width: 0};
		},
		
		/**
		 * @name _computeTooltipPosition
		 * @description Defines the tooltip area and placement.  Returns the rectangle defining the tooltip area and sets the tooltipDiv
		 * 				style to match the rectangles values.
		 * @param info the tooltip info object passed by the caller
		 * @param anchorArea the computed tooltip anchor area to which the tooltip will be positioned
		 * @param tooltipDiv the div for the tooltip, this function will modify its style to match the computed position/size
		 * @returns returns the computed tooltip area rectangle
		 * @function
		 * @private
		 */
		_computeTooltipArea: function _computeTooltipArea(info, anchorArea, tooltipDiv){
			var documentElement = tooltipDiv.ownerDocument.documentElement;
			
			// TODO This padding must match what is in tooltip.css
			var padding = 16;
			
			// Callers can specify the exact placement of the tooltip
			if (info.tooltipArea && info.tooltipArea.top && info.tooltipArea.left && info.tooltipArea.height && info.tooltipArea.width){										 
					tooltipDiv.style.overflowY = "auto"; //$NON-NLS-0$ // If caller specifies a height, allow scrolling
					tooltipDiv.style.resize = "none"; //$NON-NLS-0$
					tooltipDiv.style.top = (info.tooltipArea.top) + "px"; //$NON-NLS-0$
					tooltipDiv.style.left = (info.tooltipArea.left) + "px"; //$NON-NLS-0$
					tooltipDiv.style.height = (info.tooltipArea.height - padding) + "px"; //$NON-NLS-0$
					tooltipDiv.style.width = (info.tooltipArea.width - padding) + "px"; //$NON-NLS-0$
					return info.tooltipArea;
			}
			
			var divBounds = tooltipDiv.getBoundingClientRect();
			var tipRect = {
				width: divBounds.width,
				height: divBounds.height
			};
			
			// If there is an unloaded image its width will not be included in the calculated size, add some buffer space so tooltip doesn't wrap (See bug 478357)
			var image = tooltipDiv.getElementsByTagName('img')[0]; //$NON-NLS-1$
			if (image && !image.complete && image.width === 0){
				tipRect.width += 30;
			}
			
			var position = info.position ? info.position : "below"; //$NON-NLS-0$
			
			var viewBounds = (this._view._rootDiv ? this._view._rootDiv : documentElement).getBoundingClientRect();
			var viewportLeft = viewBounds.left;
			var viewportTop = viewBounds.top;
			var viewportWidth = viewBounds.width;
			var viewportHeight = viewBounds.height;
			
			// Set a default size for the tooltip
			var defWidth = viewportWidth;
			var defHeight = viewportHeight;
			if (!info.allowFullWidth){
				defWidth = Math.min(viewportWidth/2, 600);
				defHeight = Math.min(viewportHeight/2, 400);
				tipRect.width = Math.min(tipRect.width, defWidth);
				tipRect.height = Math.min(tipRect.height, defHeight);
			}

			// Now that we have our width recalculate the desired height...
			tooltipDiv.style.width = (tipRect.width - padding) + "px"; //$NON-NLS-1$
			tipRect.height = Math.min(tooltipDiv.getBoundingClientRect().height, defHeight);
			
			// Hack for single line tooltips that wrap, set a minimum height to make them show 2 lines without scrolling
			// The largest line height was MacOS Chrome with 20px+padding.  So 25 is the minimum height we are sure we are one two lines
			// Similarly, the minimum height for the tooltip is two lines (20px x 2 + padding) (this will actually show three lines on Win7 Chrome)
			if ((25+padding) > tipRect.height && tipRect.width > (defWidth-padding)){
				tipRect.height = 40+padding;
			}

			var spaceBelow = viewportHeight - (anchorArea.top + anchorArea.height - viewportTop);
			var spaceAbove = anchorArea.top - viewportTop;
			var spaceRight = viewportWidth - (anchorArea.left + anchorArea.width - viewportLeft);
			
			// If there is not enough space above or below, swap the position.  Can't do the same for right/left because rulers are at client bounds
			if (position === "above" && tipRect.height > spaceAbove && tipRect.height <= spaceBelow){ //$NON-NLS-0$
				position = "below"; //$NON-NLS-0$
			} else if (position === "below" && tipRect.height > spaceBelow && tipRect.height <= spaceAbove){ //$NON-NLS-0$
				position = "above"; //$NON-NLS-0$
			}
			
			var offsetX = info.tooltipOffsetX ? info.tooltipOffsetX : 0;
			var offsetY = info.tooltipOffsetY ? info.tooltipOffsetY : 0;
			
			// Attempt to line up tooltip with the anchor area
			// If not enough space, shift the tooltip horiz (above/below) or vert (left/right) until it fits
			// Force the tooltip to start within the viewport area
			// Set maximum sizes for remaining area in the viewport area
			switch (position){
				case "left": //$NON-NLS-0$
					if ((tipRect.height + offsetY) > (spaceBelow + anchorArea.height)){
						// Shift the top of the tooltip upwards to fit, ignore the offset value
						tipRect.top = viewportHeight + viewportTop - tipRect.height;
					} else {
						tipRect.top = anchorArea.top + offsetY;
					}
					tipRect.top = Math.max(tipRect.top, viewportTop);
					tipRect.left = Math.max(anchorArea.left - tipRect.width + offsetX, viewportLeft);
				break;
				case "right": //$NON-NLS-0$
					if ((tipRect.height + offsetY) > (spaceBelow + anchorArea.height)){
						// Shift the top of the tooltip upwards to fit, ignore the offset value
						tipRect.top = viewportHeight + viewportTop - tipRect.height;
					} else {
						tipRect.top = anchorArea.top + offsetY;
					}
					tipRect.top = Math.max(tipRect.top, viewportTop);
					tipRect.left = Math.max(anchorArea.left + anchorArea.width + offsetX, viewportLeft);
				break;
				case "above": //$NON-NLS-0$
					if ((tipRect.width + offsetX) > (spaceRight + anchorArea.width)){
						// Shift the left side of the tooltip to the left, ignore the offset value
						tipRect.left = viewportWidth + viewportLeft - tipRect.width;
					} else {
						tipRect.left = anchorArea.left + offsetX;
					}
					tipRect.left = Math.max(tipRect.left, viewportLeft);
					tipRect.top = Math.max(anchorArea.top - tipRect.height + offsetY, viewportTop);
				break;
				case "below": //$NON-NLS-0$
					if ((tipRect.width + offsetX) > (spaceRight + anchorArea.width)){
						// Shift the left side of the tooltip to the left, ignore the offset value
						tipRect.left = viewportWidth + viewportLeft - tipRect.width;
					} else {
						tipRect.left = anchorArea.left + offsetX;
					}
					tipRect.left = Math.max(tipRect.left, viewportLeft);
					tipRect.top = Math.max(anchorArea.top + anchorArea.height + offsetY, viewportTop);
				break;
			}
			
			tipRect.maxWidth = Math.min(viewportWidth + viewportLeft - tipRect.left, viewportWidth);
			tipRect.maxHeight = Math.min(viewportHeight + viewportTop - tipRect.top, viewportHeight);
			
			// Adjust sizes for div padding, but not the actual tooltip box.
			tooltipDiv.style.maxWidth = (tipRect.maxWidth - padding) + "px"; //$NON-NLS-0$
			tooltipDiv.style.maxHeight = (tipRect.maxHeight - padding) + "px"; //$NON-NLS-0$
			tooltipDiv.style.width = (tipRect.width - padding) + "px"; //$NON-NLS-1$
			tooltipDiv.style.height = (tipRect.height - padding) + "px"; //$NON-NLS-1$
			tooltipDiv.style.left = tipRect.left + "px"; //$NON-NLS-0$
			tooltipDiv.style.top = tipRect.top + "px"; //$NON-NLS-0$
			return tipRect;
		},
		
		_computeOuterArea: function _computeOuterArea(anchorArea, tooltipArea){
			var left = Math.min(anchorArea.left, tooltipArea.left);
			var top = Math.min(anchorArea.top, tooltipArea.top);
			var positionRight = anchorArea.left + anchorArea.width;
			var tipRight = tooltipArea.left + tooltipArea.width;
			var right = Math.max(positionRight, tipRight);
			var positionBottom = anchorArea.top + anchorArea.height;
			var tipBottom = tooltipArea.top + tooltipArea.height;
			var bottom = Math.max(positionBottom, tipBottom);
				
			return {
					left: left,
					top : top,
					width: right - left,
					height: bottom - top
				};
		},
		
		/*
		 ******************************************************************************************************************* 
		 * Utility: This section contains various utility methods
		 ******************************************************************************************************************* 
		 */
		_hasFocus: function() {
			var tooltipDiv = this._tooltipDiv;
			if (!tooltipDiv) { return false; }
			return textUtil.contains(tooltipDiv, tooltipDiv.ownerDocument.activeElement);
		},
		_isNode: function (obj) {
			return typeof Node === "object" ? obj instanceof Node : //$NON-NLS-0$
				obj && typeof obj === "object" && typeof obj.nodeType === "number" && typeof obj.nodeName === "string"; //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		},
		_setInitialFocus: function(tooltipDiv) {
			// Any buttons ?
			var button = lib.$("button", tooltipDiv); //$NON-NLS-0$
			if (button) {
				button.focus();
				return;
			}
			// Any links ?
			var link = lib.$("a", tooltipDiv); //$NON-NLS-0$
			if (link) {
				link.focus();
				var self = this;
				link.addEventListener("click", function() { //$NON-NLS-0$
					self.hide();
				});
				return;
			}
			// Give up and focus on the first tabbable
			var toFocus = lib.firstTabbable(tooltipDiv);
			if (toFocus) {
				toFocus.focus();
			}
		},
		_isInRect: function(rect, x, y) {
			if (!rect){
				return false;
			}
			var xOK = x >= rect.left && x <= (rect.left + rect.width);
			var yOK = y >= rect.top && y <= (rect.top + rect.height);
			return xOK && yOK;
		},
		mapOffset: function(offset) {
			var textView = this._view;
			var model = textView.getModel();
			if (model.getBaseModel) {
				offset = model.mapOffset(offset, true);
			}
			return offset;
		},
		/**
		 * Note that the offsets passed here must already be mapped to the base model being displayed (i.e. reduced by collapsed comments)
		 */
		_computeRectangleFromOffset: function(start, end) {
			var tv = this._view;
			var curLine = tv.getLineAtOffset(start);
			var endLine = tv.getLineAtOffset(end);
			
			// Adjust start / end to be on the current line if necessary
			if (curLine !== endLine) {
				// 'getLineEnd' isn't API in textView but is in textModel...
				end = tv.getModel().getLineEnd(curLine);
			}
			
			var height = tv.getLineHeight(curLine);
			var startPos = tv.getLocationAtOffset(start);
			var endPos = tv.getLocationAtOffset(end);
			
			var viewRect = { x: startPos.x, y: startPos.y, 
								width: endPos.x - startPos.x, height: height};
								
			viewRect = this._view.convert(viewRect, "document", "page"); //$NON-NLS-0$ //$NON-NLS-1$
			return {left: viewRect.x, top: viewRect.y, width: viewRect.width, height: viewRect.height};
		},
		/*
		 ******************************************************************************************************************* 
		 * Rendering: This section deals with rendering supplied data into HTML for inclusion into the Info Popup.
		 ******************************************************************************************************************* 
		 */
		_renderPluginContent: function(contentsDiv, data) {
			var document = this._tooltipDiv.ownerDocument;
			// data object should be an object containing the type and content.  If no type or unknown type, default to string.
			if (typeof data !== 'string' && typeof data.content === 'undefined') { //$NON-NLS-0$ //$NON-NLS-1$
			    return false;
			}
			
			var sectionDiv = util.createElement(document, "div"); //$NON-NLS-0$;
			// render the title, if any
			if (data.title) {
				var titleDiv = util.createElement(document, "div"); //$NON-NLS-0$;
				titleDiv.innerHTML = this.hover.renderMarkDown ? this.hover.renderMarkDown(data.title) : data.title;
				titleDiv.classList.add("hoverTooltipTitle"); //$NON-NLS-0$
				sectionDiv.appendChild(titleDiv);
			}
			var contentDiv = util.createElement(document, "div"); //$NON-NLS-0$
			
			if (typeof data === 'string'){ //$NON-NLS-0$
				contentDiv.appendChild(document.createTextNode(data));
			} else {
				switch(data.type) { //$NON-NLS-0$
					case 'delegatedUI': { //$NON-NLS-0$
						// The delegated UI is not included in the 8.0 release, see Bug 449240.
					}
					case 'html': { //$NON-NLS-0$
						if (data.content){
							var iframe = document.createElement("iframe"); //$NON-NLS-0$
							iframe.id = 'HtmlHover'; //$NON-NLS-0$
							iframe.name = 'HTML Hover'; //$NON-NLS-0$
							iframe.type = "text/html"; //$NON-NLS-0$
							iframe.sandbox = "allow-scripts allow-same-origin allow-forms"; //$NON-NLS-0$
							iframe.style.border = "none"; //$NON-NLS-0$
							iframe.style.width = "100%"; //$NON-NLS-0$
							iframe.style.height = "100%"; //$NON-NLS-0$
							// TODO The iframe computed height is always 3px smaller than the tooltip, giving the impression of inconsistent padding
							this._tooltipDiv.style.paddingBottom = "5px";  //$NON-NLS-0$
							iframe.srcdoc = data.content;
							if (data.width) {
								iframe.style.width = data.width;
							}
							if (data.height) {
								iframe.style.height = data.height;
							}
							sectionDiv.style.height = '100%';  //$NON-NLS-0$ // Height should grow on resize
							contentsDiv.style.height = '100%';  //$NON-NLS-0$
							sectionDiv.appendChild(iframe);
						}
						break;
					}
					case 'markdown': { //$NON-NLS-0$
						if (this.hover.renderMarkDown) {
							contentDiv.innerHTML = this.hover.renderMarkDown(data.content);
						}
						break;
					}
					default: {
						contentDiv.appendChild(document.createTextNode(data.content));
					}
				}
			}
			sectionDiv.appendChild(contentDiv);
			contentsDiv.appendChild(sectionDiv);
			return true;
		},
		/*
		 * Empty or empty array = call hover service for dynamic content
		 * Array = annotations
		 * String = HTML text
		 * Node = HTML node
		 * ProjectionTextModel = code projection
		 */
		_renderImmediateInfo: function _renderImmediateInfo(contentsDiv, contents, context) {						
			// If it's an annotation then process the annotation(s) to get the actual data
			if (contents instanceof Array) {
				contents = this._getAnnotationContents(contents, context);			
				if (!contents) {
					return false;
				}
			}
			
			if (typeof contents === "string") { //$NON-NLS-0$
				contentsDiv.innerHTML = contents;
				return true;
			} else if (this._isNode(contents)) {
				contentsDiv.appendChild(contents);
				return true;
			} else if (contents instanceof mProjectionTextModel.ProjectionTextModel) {
				var view = this._view;
				var options = view.getOptions();
				options.wrapMode = false;
				options.parent = contentsDiv;
				var tooltipTheme = "tooltipTheme"; //$NON-NLS-0$
				var theme = options.themeClass;
				if (theme) {
					theme = theme.replace(tooltipTheme, "");
					if (theme) { theme = " " + theme; } //$NON-NLS-0$
					theme = tooltipTheme + theme;
				} else {
					theme = tooltipTheme;
				}
				options.themeClass = theme;
				var contentsView = this._contentsView = new mTextView.TextView(options);
				//TODO need to find a better way of sharing the styler for multiple views
				var listener = {
					onLineStyle: function(e) {
						view.onLineStyle(e);
					}
				};
				contentsView.addEventListener("LineStyle", listener.onLineStyle); //$NON-NLS-0$
				contentsView.setModel(contents);
				
				// TODO This is a hack to compute the projection size we will have in the tooltip, we remove the child after computing
				this._tooltipDiv.appendChild(contentsDiv);
				// Use the editor colors with a simple border
				this._tooltipDiv.classList.add("textviewTooltipCodeProjection"); //$NON-NLS-0$
				var size = contentsView.computeSize();
				// Adjust the size for the padding
				contentsDiv.style.width = (size.width+8) + "px"; //$NON-NLS-0$
				contentsDiv.style.height = (size.height+8) + "px"; //$NON-NLS-0$
				contentsView.resize();
				this._tooltipDiv.removeChild(contentsDiv);
				return true;
			}
			return false;
		},
		
		/**
		 * @name _getAnnotationContents
		 * @description Takes a list of annotation and renders them in the tooltip
		 * @function
		 * @private
		 * @param annotations the list of annotations to render
		 * @param context optional object containing where the annotations are displayed (ruler, editor, etc.)
		 * @returns returns document node containing rendered tooltip content
		 */
		_getAnnotationContents: function(annotations, context) {
			var self = this;
			var inEditor = self.hover ? true : false;
			if (inEditor && context && context.source && context.source.indexOf('ruler') >= 0 && annotations.length > 1){ //$NON-NLS-0$
				inEditor = false;
			}
			
			var annotation;
			var newAnnotations = [];
			for (var j = 0; j < annotations.length; j++) {
				annotation = annotations[j];
				if (annotation.title !== "" && !annotation.groupAnnotation) {
					// Don't display untitled annotations in the editor such as occurrences as the code is already visible
					if (!inEditor || annotation.title || annotation.type === "orion.annotation.folding"){
						newAnnotations.push(annotation); 
					}
				}
			}
			annotations = newAnnotations;
			if (annotations.length === 0) {
				return null;
			}
			
			
			var html;
			var document = this._tooltipDiv.ownerDocument;
			var view = this._view;
			var model = view.getModel();
			var baseModel = model.getBaseModel ? model.getBaseModel() : model;
			
			// If this is a code folding annotation, display code projection
			if (annotations.length === 1 && annotations[0].type === "orion.annotation.folding") {
				var newModel = new mProjectionTextModel.ProjectionTextModel(baseModel);
				var lineStart = baseModel.getLineStart(baseModel.getLineAtOffset(annotations[0].start));
				var charCount = baseModel.getCharCount();
				if (annotations[0].end !== charCount) {
					newModel.addProjection({start: annotations[0].end, end: charCount});
				}
				if (lineStart > 0) {
					newModel.addProjection({start: 0, end: lineStart});
				}
				return newModel;
			}
			
			if (annotations.length === 1) {
				html = getAnnotationHTML(annotations[0], inEditor);
				if (html && html.firstChild) {
					var className = html.firstChild.className;
					if (className) { className += " "; } //$NON-NLS-0$
					className += "single"; //$NON-NLS-0$
					html.firstChild.className = className;
				}
				return html;
			} else {
				var tooltipHTML = util.createElement(document, "div"); //$NON-NLS-0$
				var em = util.createElement(document, "multi_anno"); //$NON-NLS-0$
				em.appendChild(document.createTextNode(messages.multipleAnnotations));
				tooltipHTML.appendChild(em);
				for (var i = 0; i < annotations.length; i++) {
					html = getAnnotationHTML(annotations[i], inEditor);
					if (html) {
						tooltipHTML.appendChild(html);
					}
				}
				return tooltipHTML;
			}
			
			function getAnnotationHTML(annotation, inEditor) {
				var title = annotation.title;
				var result = util.createElement(document, "div"); //$NON-NLS-0$
				result.className = "tooltipRow"; //$NON-NLS-0$
				if (annotation.html) {
					var htmlHolder = util.createElement(document, "div"); //$NON-NLS-0$
					htmlHolder.className = "tooltipImage"; //$NON-NLS-0$
					htmlHolder.innerHTML = annotation.html;
					if (htmlHolder.lastChild) {
						textUtil.addEventListener(htmlHolder.lastChild, "click", function() { //$NON-NLS-0$
							var start = annotation.start, end = annotation.end;
							if (model.getBaseModel) {
								start = model.mapOffset(start, true);
								end = model.mapOffset(end, true);
							}
							view.setSelection(start, end, 1 / 3, function() { self.hide(); });
						}, false);
					}
					result.appendChild(htmlHolder); //$NON-NLS-0$
				}
				if (!title) {
					var textStart = baseModel.getLineStart(baseModel.getLineAtOffset(annotation.start));
					var textEnd = baseModel.getLineEnd(baseModel.getLineAtOffset(annotation.end), true);
					title = baseModel.getText(textStart, textEnd);
				}
				if (typeof title === "function") { //$NON-NLS-0$
					title = annotation.title();
				}
				if (typeof title === "string") { //$NON-NLS-0$
					var span = util.createElement(document, "span"); //$NON-NLS-0$
					span.className = "tooltipTitle"; //$NON-NLS-0$
					span.appendChild(document.createTextNode(title));
					title = span;
				}
				result.appendChild(title);
				
				// Handle quick fixes
				if (inEditor) {
					self.hover.renderQuickFixes(annotation, result);
				}
				if (context){	
					// Set the hover area to the annotation if it's not already set
					context.offsetStart = annotation.start;
					context.offsetEnd = annotation.end;
				}
				return result;
			}
		}
		
	};
	return {Tooltip: Tooltip};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors:
 *		Felipe Heidrich (IBM Corporation) - initial API and implementation
 *		Silenio Quarti (IBM Corporation) - initial API and implementation
 ******************************************************************************/

/*eslint-env browser, amd*/
define("orion/editor/annotations", ['i18n!orion/editor/nls/messages', 'orion/editor/eventTarget'], function(messages, mEventTarget) { //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
	
	/**
	 * @class This object represents a regitry of annotation types.
	 * @name orion.editor.AnnotationType
	 */
	function AnnotationType() {
	}
	
	/**
	 * @class This object represents a decoration attached to a range of text. Annotations are added to a
	 * <code>AnnotationModel</code> which is attached to a <code>TextModel</code>.
	 * <p>
	 * <b>See:</b><br/>
	 * {@link orion.editor.AnnotationModel}<br/>
	 * {@link orion.editor.Ruler}<br/>
	 * </p>
	 * @name orion.editor.Annotation
	 *
	 * @property {String} type The annotation type (for example, orion.annotation.error).
	 * @property {Number} start The start offset of the annotation in the text model.
	 * @property {Number} end The end offset of the annotation in the text model.
	 * @property {String} html The HTML displayed for the annotation.
	 * @property {String} title The text description for the annotation.
	 * @property {orion.editor.Style} style The style information for the annotation used in the annotations ruler and tooltips.
	 * @property {orion.editor.Style} overviewStyle The style information for the annotation used in the overview ruler.
	 * @property {orion.editor.Style} rangeStyle The style information for the annotation used in the text view to decorate a range of text.
	 * @property {orion.editor.Style} lineStyle The style information for the annotation used in the text view to decorate a line of text.
	 */
	/**
	 * Constructs a new folding annotation.
	 *
	 * @param {Number} start The start offset of the annotation in the text model.
	 * @param {Number} end The end offset of the annotation in the text model.
	 * @param {orion.editor.ProjectionTextModel} projectionModel The projection text model.
	 *
	 * @class This object represents a folding annotation.
	 * @name orion.editor.FoldingAnnotation
	 */
	function FoldingAnnotation (start, end, projectionModel) {
		this.start = start;
		this.end = end;
		this._projectionModel = projectionModel;
		this.html = this._expandedHTML;
		this.style = this._expandedStyle;
		this.expanded = true;
	}

	FoldingAnnotation.prototype = /** @lends orion.editor.FoldingAnnotation.prototype */ {
		_expandedHTML: "<div class='annotationHTML expanded'></div>", //$NON-NLS-0$
		_expandedStyle: {styleClass: "annotation expanded"}, //$NON-NLS-0$
		_collapsedHTML: "<div class='annotationHTML collapsed'></div>", //$NON-NLS-0$
		_collapsedStyle: {styleClass: "annotation collapsed"}, //$NON-NLS-0$
		_collapse: function() {
			if (!this.expanded) { return false; }
			this.expanded = false;
			this.html = this._collapsedHTML;
			this.style = this._collapsedStyle;
			if (this._annotationModel) {
				this._annotationModel.modifyAnnotation(this);
			}
			return true;
		},
		_expand: function() {
			if (this.expanded) { return false; }
			this.expanded = true;
			this.html = this._expandedHTML;
			this.style = this._expandedStyle;
			if (this._annotationModel) {
				this._annotationModel.modifyAnnotation(this);
			}
			return true;
		},
		_collapseImpl: function (checkOverlaping) {
			if (this._collapse()) {
				if (checkOverlaping) {
					this._forEachOverlaping(function(annotation) {
						if (!annotation.expanded) {
							annotation._expandImpl(false);
							annotation._recollapse = true;
						}
					});
				}
				var projectionModel = this._projectionModel;
				var baseModel = projectionModel.getBaseModel();
				this._projection = {
					annotation: this,
					start: baseModel.getLineStart(baseModel.getLineAtOffset(this.start) + 1),
					end: baseModel.getLineEnd(baseModel.getLineAtOffset(this.end), true)
				};
				projectionModel.addProjection(this._projection);
			}
		},
		_expandImpl: function(checkOverlaping) {
			if (this._expand()) {
				this._projectionModel._removeProjection(this._projection, !this._annotationModel);
				if (checkOverlaping) {
					this._forEachOverlaping(function(annotation) {
						if (annotation._recollapse) {
							annotation._collapseImpl(false);
							annotation._recollapse = false;
						}
					});
				}
			}
		},
		_forEachOverlaping: function(callback) {
			if (!this._annotationModel) { return; }
			var annotations = this._annotationModel.getAnnotations(this.start, this.end);
			while (annotations.hasNext()) {
				var annotation = annotations.next();
				if (annotation !== this && annotation.type === AnnotationType.ANNOTATION_FOLDING) {
					callback.call(this, annotation);
				}
			}
		},
		/**
		 * Collapses the annotation.
		 */
		collapse: function () {
			this._recollapse = false;
			this._collapseImpl(true);
		},
		/**
		 * Expands the annotation.
		 */
		expand: function () {
			this._recollapse = false;
			this._expandImpl(true);
		}
	};
	 
	/**
	 * Error annotation type.
	 */
	AnnotationType.ANNOTATION_ERROR = "orion.annotation.error"; //$NON-NLS-0$
	/**
	 * Warning annotation type.
	 */
	AnnotationType.ANNOTATION_WARNING = "orion.annotation.warning"; //$NON-NLS-0$
	/**
	 * Task annotation type.
	 */
	AnnotationType.ANNOTATION_TASK = "orion.annotation.task"; //$NON-NLS-0$
	/**
	 * Breakpoint annotation type.
	 */
	AnnotationType.ANNOTATION_BREAKPOINT = "orion.annotation.breakpoint"; //$NON-NLS-0$
	/**
	 * Bookmark annotation type.
	 */
	AnnotationType.ANNOTATION_BOOKMARK = "orion.annotation.bookmark"; //$NON-NLS-0$
	/**
	 * Folding annotation type.
	 */
	AnnotationType.ANNOTATION_FOLDING = "orion.annotation.folding"; //$NON-NLS-0$
	/**
	 * Curent bracket annotation type.
	 */
	AnnotationType.ANNOTATION_CURRENT_BRACKET = "orion.annotation.currentBracket"; //$NON-NLS-0$
	/**
	 * Matching bracket annotation type.
	 */
	AnnotationType.ANNOTATION_MATCHING_BRACKET = "orion.annotation.matchingBracket"; //$NON-NLS-0$
	/**
	 * Current line annotation type.
	 */
	AnnotationType.ANNOTATION_CURRENT_LINE = "orion.annotation.currentLine"; //$NON-NLS-0$
	/**
	 * Current search annotation type.
	 */
	AnnotationType.ANNOTATION_CURRENT_SEARCH = "orion.annotation.currentSearch"; //$NON-NLS-0$
	/**
	 * Matching search annotation type.
	 */
	AnnotationType.ANNOTATION_MATCHING_SEARCH = "orion.annotation.matchingSearch"; //$NON-NLS-0$
	/**
	 * Read Occurrence annotation type.
	 */
	AnnotationType.ANNOTATION_READ_OCCURRENCE = "orion.annotation.readOccurrence"; //$NON-NLS-0$
	/**
	 * Write Occurrence annotation type.
	 */
	AnnotationType.ANNOTATION_WRITE_OCCURRENCE = "orion.annotation.writeOccurrence"; //$NON-NLS-0$
	/**
	 * Selected linked group annotation type.
	 */
	AnnotationType.ANNOTATION_SELECTED_LINKED_GROUP = "orion.annotation.selectedLinkedGroup"; //$NON-NLS-0$
	/**
	 * Current linked group annotation type.
	 */
	AnnotationType.ANNOTATION_CURRENT_LINKED_GROUP = "orion.annotation.currentLinkedGroup"; //$NON-NLS-0$
	/**
	 * Linked group annotation type.
	 */
	AnnotationType.ANNOTATION_LINKED_GROUP = "orion.annotation.linkedGroup"; //$NON-NLS-0$
	/**
	* Blame annotation type.
	*/
	AnnotationType.ANNOTATION_BLAME = "orion.annotation.blame"; //$NON-NLS-0$
	/**
	* Current Blame annotation type.
	*/
	AnnotationType.ANNOTATION_CURRENT_BLAME = "orion.annotation.currentBlame"; //$NON-NLS-0$
	/**
	 * Diff Added annotation type.
	 */
	AnnotationType.ANNOTATION_DIFF_ADDED = "orion.annotation.diffAdded"; //$NON-NLS-0$
	/**
	 * Diff Deleted annotation type.
	 */
	AnnotationType.ANNOTATION_DIFF_DELETED = "orion.annotation.diffDeleted"; //$NON-NLS-0$
	/**
	 * Diff Modification annotation type.
	 */
	AnnotationType.ANNOTATION_DIFF_MODIFIED = "orion.annotation.diffModified"; //$NON-NLS-0$

	/** @private */
	var annotationTypes = {};

	/**
	 * Register an annotation type.
	 *
	 * @param {String} type The annotation type (for example, orion.annotation.error).
	 * @param {Object|Function} properties The common annotation properties of the registered
	 *		annotation type. All annotations create with this annotation type will expose these
	 *		properties.
	 */
	AnnotationType.registerType = function(type, properties) {
		var constructor = properties;
		if (typeof constructor !== "function") { //$NON-NLS-0$
			constructor = function(start, end, title) {
				this.start = start;
				this.end = end;
				if (title !== undefined) { this.title = title; }
			};
			constructor.prototype = properties;
		}
		constructor.prototype.type = type;
		annotationTypes[type] = constructor;
		return type;
	};

	/**
	 * Creates an annotation of a given type with the specified start end end offsets.
	 *
	 * @param {String} type The annotation type (for example, orion.annotation.error).
	 * @param {Number} start The start offset of the annotation in the text model.
	 * @param {Number} end The end offset of the annotation in the text model.
	 * @param {String} [title] The text description for the annotation if different then the type description.
	 * @return {orion.editor.Annotation} the new annotation
	 */
	AnnotationType.createAnnotation = function(type, start, end, title) {
		return new (this.getType(type))(start, end, title);
	};

	/**
	 * Gets the registered annotation type with specified type. The returned
	 * value is a constructor that can be used to create annotations of the
	 * speficied type.  The constructor takes the start and end offsets of
	 * the annotation.
	 *
	 * @param {String} type The annotation type (for example, orion.annotation.error).
	 * @return {Function} The annotation type constructor ( i.e function(start, end, title) ).
	 */
	AnnotationType.getType = function(type) {
		return annotationTypes[type];
	};

	/** @private */
	function registerType(type, lineStyling) {
		var index = type.lastIndexOf('.'); //$NON-NLS-0$
		var suffix = type.substring(index + 1);
		var properties = {
			title: messages[suffix],
			style: {styleClass: "annotation " + suffix}, //$NON-NLS-0$
			html: "<div class='annotationHTML " + suffix + "'></div>", //$NON-NLS-1$ //$NON-NLS-0$
			overviewStyle: {styleClass: "annotationOverview " + suffix} //$NON-NLS-0$
		};
		if (lineStyling) {
			properties.lineStyle = {styleClass: "annotationLine " + suffix}; //$NON-NLS-0$
		} else {
			properties.rangeStyle = {styleClass: "annotationRange " + suffix}; //$NON-NLS-0$
		}
		AnnotationType.registerType(type, properties);
	}
	registerType(AnnotationType.ANNOTATION_ERROR);
	registerType(AnnotationType.ANNOTATION_WARNING);
	registerType(AnnotationType.ANNOTATION_TASK);
	registerType(AnnotationType.ANNOTATION_BREAKPOINT);
	registerType(AnnotationType.ANNOTATION_BOOKMARK);
	registerType(AnnotationType.ANNOTATION_CURRENT_BRACKET);
	registerType(AnnotationType.ANNOTATION_MATCHING_BRACKET);
	registerType(AnnotationType.ANNOTATION_CURRENT_SEARCH);
	registerType(AnnotationType.ANNOTATION_MATCHING_SEARCH);
	registerType(AnnotationType.ANNOTATION_READ_OCCURRENCE);
	registerType(AnnotationType.ANNOTATION_WRITE_OCCURRENCE);
	registerType(AnnotationType.ANNOTATION_SELECTED_LINKED_GROUP);
	registerType(AnnotationType.ANNOTATION_CURRENT_LINKED_GROUP);
	registerType(AnnotationType.ANNOTATION_LINKED_GROUP);
	registerType(AnnotationType.ANNOTATION_CURRENT_LINE, true);
	registerType(AnnotationType.ANNOTATION_BLAME, true);
	registerType(AnnotationType.ANNOTATION_CURRENT_BLAME, true);
	registerType(AnnotationType.ANNOTATION_DIFF_ADDED);
	registerType(AnnotationType.ANNOTATION_DIFF_DELETED);
	registerType(AnnotationType.ANNOTATION_DIFF_MODIFIED);

	AnnotationType.registerType(AnnotationType.ANNOTATION_FOLDING, FoldingAnnotation);

	/**
	 * Constructs a new AnnotationTypeList object.
	 *
	 * @class This represents an interface of prioritized annotation types.
	 * @name orion.editor.AnnotationTypeList
	 */
	function AnnotationTypeList () {
	}
	/**
	 * Adds in the annotation type interface into the specified object.
	 *
	 * @param {Object} object The object to add in the annotation type interface.
	 */
	AnnotationTypeList.addMixin = function(object) {
		var proto = AnnotationTypeList.prototype;
		for (var p in proto) {
			if (proto.hasOwnProperty(p)) {
				object[p] = proto[p];
			}
		}
	};
	AnnotationTypeList.prototype = /** @lends orion.editor.AnnotationTypeList.prototype */ {
		/**
		 * Adds an annotation type to the receiver.
		 * <p>
		 * Only annotations of the specified types will be shown by
		 * the receiver.
		 * </p>
		 * <p>
		 * If the priority is not specified, the annotation type will be added
		 * to the end of the receiver's list (lowest pririoty).
		 * </p>
		 *
		 * @param {Object} type the annotation type to be shown
		 * @param {Number} priority the priority for the annotation type
		 * 
		 * @see orion.editor.AnnotationTypeList#removeAnnotationType
		 * @see orion.editor.AnnotationTypeList#isAnnotationTypeVisible
		 * @see orion.editor.AnnotationTypeList#getAnnotationTypePriority
		 */
		addAnnotationType: function(type, priority) {
			if (!this._annotationTypes) { this._annotationTypes = []; }
			var index = priority - 1;
			if (priority == undefined || !(0 <= index && index < this._annotationTypes.length)) {
				this._annotationTypes.push(type);
			} else {
				this._annotationTypes.splice(index, 0, type);
			}
		},
		/**
		 * Gets the annotation type priority.  The priority is determined by the
		 * order the annotation type is added to the receiver.  Annotation types
		 * added first have higher priority.
		 * <p>
		 * Returns <code>0</code> if the annotation type is not added.
		 * </p>
		 *
		 * @param {Object} type the annotation type
		 *
		 * @see orion.editor.AnnotationTypeList#addAnnotationType
		 * @see orion.editor.AnnotationTypeList#removeAnnotationType
		 * @see orion.editor.AnnotationTypeList#isAnnotationTypeVisible
		 */
		getAnnotationTypePriority: function(type) {
			if (this._annotationTypes) {
				for (var i = 0; i < this._annotationTypes.length; i++) {
					if (this._annotationTypes[i] === type) {
						return i + 1;
					}
				}
			}
			return 0;
		},
		/**
		 * Returns an array of annotations in the specified annotation model for the given range of text sorted by type.
		 *
		 * @param {orion.editor.AnnotationModel} annotationModel the annotation model.
		 * @param {Number} start the start offset of the range.
		 * @param {Number} end the end offset of the range.
		 * @return {orion.editor.Annotation[]} an annotation array.
		 */
		getAnnotationsByType: function(annotationModel, start, end) {
			var iter = annotationModel.getAnnotations(start, end);
			var annotation, annotations = [];
			while (iter.hasNext()) {
				annotation = iter.next();
				var priority = this.getAnnotationTypePriority(annotation.type);
				if (priority === 0) { continue; }
				annotations.push(annotation);
			}
			var self = this;
			annotations.sort(function(a, b) {
				return self.getAnnotationTypePriority(a.type) - self.getAnnotationTypePriority(b.type);
			});
			return annotations;
		},
		/**
		 * Returns whether the receiver shows annotations of the specified type.
		 *
		 * @param {Object} type the annotation type
		 * @returns {Boolean} whether the specified annotation type is shown
		 *
		 * @see orion.editor.AnnotationTypeList#addAnnotationType
		 * @see orion.editor.AnnotationTypeList#removeAnnotationType
		 */
		isAnnotationTypeVisible: function(type) {
			return this.getAnnotationTypePriority(type) !== 0;
		},
		/**
		 * Removes an annotation type from the receiver.
		 *
		 * @param {Object} type the annotation type to be removed
		 *
		 * @see orion.editor.AnnotationTypeList#addAnnotationType
		 * @see orion.editor.AnnotationTypeList#isAnnotationTypeVisible
		 */
		removeAnnotationType: function(type) {
			if (!this._annotationTypes) { return; }
			for (var i = 0; i < this._annotationTypes.length; i++) {
				if (this._annotationTypes[i] === type) {
					this._annotationTypes.splice(i, 1);
					break;
				}
			}
		}
	};
	
	/** @private */
	function binarySearch(array, offset, inclusive, low, high) {
		var index;
		if (low === undefined) { low = -1; }
		if (high === undefined) { high = array.length; }
		while (high - low > 1) {
			index = Math.floor((high + low) / 2);
			if (offset <= array[index].start) {
				high = index;
			} else if (inclusive && offset < array[index].end) {
				high = index;
				break;
			} else {
				low = index;
			}
		}
		return high;
	}
	
	/**
	 * Constructs an annotation model.
	 * 
	 * @param {orion.editor.TextModel} textModel The text model.
	 * 
	 * @class This object manages annotations for a <code>TextModel</code>.
	 * <p>
	 * <b>See:</b><br/>
	 * {@link orion.editor.Annotation}<br/>
	 * {@link orion.editor.TextModel}<br/> 
	 * </p>	
	 * @name orion.editor.AnnotationModel
	 * @borrows orion.editor.EventTarget#addEventListener as #addEventListener
	 * @borrows orion.editor.EventTarget#removeEventListener as #removeEventListener
	 * @borrows orion.editor.EventTarget#dispatchEvent as #dispatchEvent
	 */
	function AnnotationModel(textModel) {
		this._annotations = [];
		var self = this;
		this._listener = {
			onChanged: function(modelChangedEvent) {
				self._onChanged(modelChangedEvent);
			}
		};
		this.setTextModel(textModel);
	}

	AnnotationModel.prototype = /** @lends orion.editor.AnnotationModel.prototype */ {
		/**
		 * Adds an annotation to the annotation model.
		 * <p>The annotation model listeners are notified of this change.</p>
		 *
		 * @param {orion.editor.Annotation} annotation the annotation to be added.
		 *
		 * @see orion.editor.AnnotationModel#removeAnnotation
		 */
		addAnnotation: function(annotation) {
			if (!annotation) { return; }
			var annotations = this._annotations;
			var index = binarySearch(annotations, annotation.start);
			annotations.splice(index, 0, annotation);
			annotation._annotationModel = this;
			var e = {
				type: "Changed", //$NON-NLS-0$
				added: [annotation],
				removed: [],
				changed: []
			};
			this.onChanged(e);
		},
		/**
		 * Returns the text model.
		 *
		 * @return {orion.editor.TextModel} The text model.
		 *
		 * @see orion.editor.AnnotationModel#setTextModel
		 */
		getTextModel: function() {
			return this._model;
		},
		/**
		 * @class This object represents an annotation iterator.
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.AnnotationModel#getAnnotations}<br/>
		 * </p>
		 * @name orion.editor.AnnotationIterator
		 *
		 * @property {Function} hasNext Determines whether there are more annotations in the iterator.
		 * @property {Function} next Returns the next annotation in the iterator.
		 */
		/**
		 * Returns an iterator of annotations for the given range of text. If called with no parameters,
		 * returns all annotations in the model.
		 *
		 * @param {Number} start the start offset of the range.
		 * @param {Number} end the end offset of the range.
		 * @return {orion.editor.AnnotationIterator} an annotation iterartor.
		 */
		getAnnotations: function(start, end) {
			var annotations = this._annotations, current;
			var i = 0, skip;
			if (start === undefined && end === undefined) {
				skip = function() {
					return (i < annotations.length) ? annotations[i++] : null;
				};
			} else {
				//TODO binary search does not work for range intersection when there are overlaping ranges, need interval search tree for this
				skip = function() {
					while (i < annotations.length) {
						var a =  annotations[i++];
						if ((start === a.start) || (start > a.start ? start < a.end : a.start < end)) {
							return a;
						}
						if (a.start >= end) {
							break;
						}
					}
					return null;
				};
			}
			current = skip();
			return {
				next: function() {
					var result = current;
					if (result) { current = skip(); }
					return result;
				},
				hasNext: function() {
					return current !== null;
				}
			};
		},
		/**
		 * Notifies the annotation model that the given annotation has been modified.
		 * <p>The annotation model listeners are notified of this change.</p>
		 *
		 * @param {orion.editor.Annotation} annotation the modified annotation.
		 *
		 * @see orion.editor.AnnotationModel#addAnnotation
		 */
		modifyAnnotation: function(annotation) {
			if (!annotation) { return; }
			var index = this._getAnnotationIndex(annotation);
			if (index < 0) { return; }
			var e = {
				type: "Changed", //$NON-NLS-0$
				added: [],
				removed: [],
				changed: [annotation]
			};
			this.onChanged(e);
		},
		/**
		 * Notifies all listeners that the annotation model has changed.
		 *
		 * @param {orion.editor.Annotation[]} added The list of annotation being added to the model.
		 * @param {orion.editor.Annotation[]} changed The list of annotation modified in the model.
		 * @param {orion.editor.Annotation[]} removed The list of annotation being removed from the model.
		 * @param {ModelChangedEvent} textModelChangedEvent the text model changed event that trigger this change, can be null if the change was trigger by a method call (for example, {@link #addAnnotation}).
		 */
		onChanged: function(e) {
			return this.dispatchEvent(e);
		},
		/**
		 * Removes all annotations of the given <code>type</code>. All annotations
		 * are removed if the type is not specified.
		 * <p>The annotation model listeners are notified of this change.  Only one changed event is generated.</p>
		 *
		 * @param {Object} type the type of annotations to be removed.
		 *
		 * @see orion.editor.AnnotationModel#removeAnnotation
		 */
		removeAnnotations: function(type) {
			var annotations = this._annotations;
			var removed, i;
			if (type) {
				removed = [];
				for (i = annotations.length - 1; i >= 0; i--) {
					var annotation = annotations[i];
					if (annotation.type === type) {
						annotations.splice(i, 1);
						removed.splice(0, 0, annotation);
						annotation._annotationModel = null;
					}
				}
			} else {
				removed = annotations;
				annotations = [];
			}
			var e = {
				type: "Changed", //$NON-NLS-0$
				removed: removed,
				added: [],
				changed: []
			};
			this.onChanged(e);
		},
		/**
		 * Removes an annotation from the annotation model.
		 * <p>The annotation model listeners are notified of this change.</p>
		 *
		 * @param {orion.editor.Annotation} annotation the annotation to be removed.
		 *
		 * @see orion.editor.AnnotationModel#addAnnotation
		 */
		removeAnnotation: function(annotation) {
			if (!annotation) { return; }
			var index = this._getAnnotationIndex(annotation);
			if (index < 0) { return; }
			annotation._annotationModel = null;
			var e = {
				type: "Changed", //$NON-NLS-0$
				removed: this._annotations.splice(index, 1),
				added: [],
				changed: []
			};
			this.onChanged(e);
		},
		/**
		 * Removes and adds the specifed annotations to the annotation model.
		 * <p>The annotation model listeners are notified of this change.  Only one changed event is generated.</p>
		 *
		 * @param {orion.editor.Annotation} remove the annotations to be removed.
		 * @param {orion.editor.Annotation} add the annotations to be added.
		 *
		 * @see orion.editor.AnnotationModel#addAnnotation
		 * @see orion.editor.AnnotationModel#removeAnnotation
		 */
		replaceAnnotations: function(remove, add) {
			var annotations = this._annotations, i, index, annotation, removed = [];
			if (remove) {
				for (i = remove.length - 1; i >= 0; i--) {
					annotation = remove[i];
					index = this._getAnnotationIndex(annotation);
					if (index < 0) { continue; }
					annotation._annotationModel = null;
					annotations.splice(index, 1);
					removed.splice(0, 0, annotation);
				}
			}
			if (!add) { add = []; }
			for (i = 0; i < add.length; i++) {
				annotation = add[i];
				index = binarySearch(annotations, annotation.start);
				annotation._annotationModel = this;
				annotations.splice(index, 0, annotation);
			}
			var e = {
				type: "Changed", //$NON-NLS-0$
				removed: removed,
				added: add,
				changed: []
			};
			
			this.onChanged(e);
		},
		/**
		 * Sets the text model of the annotation model.  The annotation
		 * model listens for changes in the text model to update and remove
		 * annotations that are affected by the change.
		 *
		 * @param {orion.editor.TextModel} textModel the text model.
		 *
		 * @see orion.editor.AnnotationModel#getTextModel
		 */
		setTextModel: function(textModel) {
			if (this._model) {
				this._model.removeEventListener("Changed", this._listener.onChanged); //$NON-NLS-0$
			}
			this._model = textModel;
			if (this._model) {
				this._model.addEventListener("Changed", this._listener.onChanged); //$NON-NLS-0$
			}
		},
		/** @ignore */
		_getAnnotationIndex: function(annotation) {
			var annotations = this._annotations;
			var index = binarySearch(annotations, annotation.start);
			while (index < annotations.length && annotations[index].start === annotation.start) {
				if (annotations[index] === annotation) {
					return index;
				}
				index++;
			}
			return -1;
		},
		/** @ignore */
		_onChanged: function(modelChangedEvent) {
			var start = modelChangedEvent.start;
			var addedCharCount = modelChangedEvent.addedCharCount;
			var removedCharCount = modelChangedEvent.removedCharCount;
			var annotations = this._annotations, end = start + removedCharCount;
			//TODO binary search does not work for range intersection when there are overlaping ranges, need interval search tree for this
			var startIndex = 0;
			if (!(0 <= startIndex && startIndex < annotations.length)) { return; }
			var e = {
				type: "Changed", //$NON-NLS-0$
				added: [],
				removed: [],
				changed: [],
				textModelChangedEvent: modelChangedEvent
			};
			var changeCount = addedCharCount - removedCharCount, i;
			for (i = startIndex; i < annotations.length; i++) {
				var annotation = annotations[i];
				if (annotation.start >= end) {
					annotation._oldStart = annotation.start;
					annotation._oldEnd = annotation.end;
					annotation.start += changeCount;
					annotation.end += changeCount;
					e.changed.push(annotation);
				} else if (annotation.end <= start) {
					//nothing
				} else if (annotation.start < start && end < annotation.end) {
					annotation._oldStart = annotation.start;
					annotation._oldEnd = annotation.end;
					annotation.end += changeCount;
					e.changed.push(annotation);
				} else {
					annotations.splice(i, 1);
					e.removed.push(annotation);
					annotation._annotationModel = null;
					if (annotation.expand) {
						annotation.expand();
					}
					i--;
				}
			}
			if (e.added.length > 0 || e.removed.length > 0 || e.changed.length > 0) {
				this.onChanged(e);
			}
		}
	};
	mEventTarget.EventTarget.addMixin(AnnotationModel.prototype);

	/**
	 * Constructs a new styler for annotations.
	 *
	 * @param {orion.editor.TextView} view The styler view.
	 * @param {orion.editor.AnnotationModel} view The styler annotation model.
	 *
	 * @class This object represents a styler for annotation attached to a text view.
	 * @name orion.editor.AnnotationStyler
	 * @borrows orion.editor.AnnotationTypeList#addAnnotationType as #addAnnotationType
	 * @borrows orion.editor.AnnotationTypeList#getAnnotationTypePriority as #getAnnotationTypePriority
	 * @borrows orion.editor.AnnotationTypeList#getAnnotationsByType as #getAnnotationsByType
	 * @borrows orion.editor.AnnotationTypeList#isAnnotationTypeVisible as #isAnnotationTypeVisible
	 * @borrows orion.editor.AnnotationTypeList#removeAnnotationType as #removeAnnotationType
	 */
	function AnnotationStyler (view, annotationModel) {
		this._view = view;
		this._annotationModel = annotationModel;
		var self = this;
		this._listener = {
			onDestroy: function(e) {
				self._onDestroy(e);
			},
			onLineStyle: function(e) {
				self._onLineStyle(e);
			},
			onChanged: function(e) {
				self._onAnnotationModelChanged(e);
			}
		};
		view.addEventListener("Destroy", this._listener.onDestroy); //$NON-NLS-0$
		view.addEventListener("postLineStyle", this._listener.onLineStyle); //$NON-NLS-0$
		annotationModel.addEventListener("Changed", this._listener.onChanged); //$NON-NLS-0$
	}
	AnnotationStyler.prototype = /** @lends orion.editor.AnnotationStyler.prototype */ {
		/**
		 * Destroys the styler.
		 * <p>
		 * Removes all listeners added by this styler.
		 * </p>
		 */
		destroy: function() {
			var view = this._view;
			if (view) {
				view.removeEventListener("Destroy", this._listener.onDestroy); //$NON-NLS-0$
				view.removeEventListener("LineStyle", this._listener.onLineStyle); //$NON-NLS-0$
				this.view = null;
			}
			var annotationModel = this._annotationModel;
			if (annotationModel) {
				annotationModel.removeEventListener("Changed", this._listener.onChanged); //$NON-NLS-0$
				annotationModel = null;
			}
		},
		_mergeStyle: function(result, style) {
			if (style) {
				if (!result) { result = {}; }
				if (result.styleClass && style.styleClass && result.styleClass !== style.styleClass) {
					result.styleClass += " " + style.styleClass; //$NON-NLS-0$
				} else {
					result.styleClass = style.styleClass;
				}
				var prop;
				if (style.tagName) {
					if (!result.tagName) {
						result.tagName = style.tagName;
					}
				}
				if (style.style) {
					if (!result.style) { result.style  = {}; }
					for (prop in style.style) {
						if (!result.style[prop]) {
							result.style[prop] = style.style[prop];
						}
					}
				}
				if (style.attributes) {
					if (!result.attributes) { result.attributes  = {}; }
					for (prop in style.attributes) {
						if (!result.attributes[prop]) {
							result.attributes[prop] = style.attributes[prop];
						}
					}
				}
			}
			return result;
		},
		_mergeStyleRanges: function(ranges, styleRange) {
			if (!ranges) {
				ranges = [];
			}
			var mergedStyle, i = binarySearch(ranges, styleRange.start, true);
			for (; i<ranges.length && styleRange; i++) {
				var range = ranges[i];
				if (styleRange.end <= range.start) { break; }
				if (styleRange.start >= range.end) { continue; }
				mergedStyle = this._mergeStyle({}, range.style);
				mergedStyle = this._mergeStyle(mergedStyle, styleRange.style);
				var args = [];
				args.push(i, 1);
				if (styleRange.start < range.start) {
					args.push({start: styleRange.start, end: range.start, style: styleRange.style});
				}
				if (styleRange.start > range.start) {
					args.push({start: range.start, end: styleRange.start, style: range.style});
				}
				args.push({start: Math.max(range.start, styleRange.start), end: Math.min(range.end, styleRange.end), style: mergedStyle});
				if (styleRange.end < range.end) {
					args.push({start: styleRange.end, end: range.end, style: range.style});
				}
				if (styleRange.end > range.end) {
					styleRange = {start: range.end, end: styleRange.end, style: styleRange.style};
				} else {
					styleRange = null;
				}
				Array.prototype.splice.apply(ranges, args);
			}
			if (styleRange) {
				mergedStyle = this._mergeStyle({}, styleRange.style);
				ranges.splice(i, 0, {start: styleRange.start, end: styleRange.end, style: mergedStyle});
			}
			return ranges;
		},
		_onAnnotationModelChanged: function(e) {
			var view = this._view;
			if (!view) { return; }
			var self = this;
			var model = view.getModel();
			function redrawRange(start, end) {
				if (model.getBaseModel) {
					start = model.mapOffset(start, true);
					end = model.mapOffset(end, true);
				}
				if (start !== -1 && end !== -1) {
					view.redrawRange(start, end);
				}
			}
			function redraw(changes, changed) {
				for (var i = 0; i < changes.length; i++) {
					if (!self.isAnnotationTypeVisible(changes[i].type)) { continue; }
					var change = changes[i];
					redrawRange(change.start, change.end);
					if (changed && change._oldStart !== undefined && change._oldEnd) {
						redrawRange(change._oldStart, change._oldEnd);
					}
				}
			}
			redraw(e.added);
			redraw(e.removed);
			redraw(e.changed, true);
		},
		_onDestroy: function(e) {
			this.destroy();
		},
		_onLineStyle: function (e) {
			var annotationModel = this._annotationModel;
			var viewModel = e.textView.getModel();
			var baseModel = annotationModel.getTextModel();
			var start = e.lineStart;
			var end = e.lineStart + e.lineText.length;
			if (baseModel !== viewModel) {
				start = viewModel.mapOffset(start);
				end = viewModel.mapOffset(end);
			}
			var annotations = annotationModel.getAnnotations(start, end);
			while (annotations.hasNext()) {
				var annotation = annotations.next();
				if (!this.isAnnotationTypeVisible(annotation.type)) { continue; }
				if (annotation.rangeStyle) {
					var annotationStart = annotation.start;
					var annotationEnd = annotation.end;
					if (baseModel !== viewModel) {
						annotationStart = viewModel.mapOffset(annotationStart, true);
						annotationEnd = viewModel.mapOffset(annotationEnd, true);
					}
					e.ranges = this._mergeStyleRanges(e.ranges, {start: annotationStart, end: annotationEnd, style: annotation.rangeStyle});
				}
				if (annotation.lineStyle) {
					e.style = this._mergeStyle({}, e.style);
					e.style = this._mergeStyle(e.style, annotation.lineStyle);
				}
			}
		}
	};
	AnnotationTypeList.addMixin(AnnotationStyler.prototype);

	return {
		FoldingAnnotation: FoldingAnnotation,
		AnnotationType: AnnotationType,
		AnnotationTypeList: AnnotationTypeList,
		AnnotationModel: AnnotationModel,
		AnnotationStyler: AnnotationStyler
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2009, 2015 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/

/*eslint-env browser, amd*/
define("orion/editor/editor", [ //$NON-NLS-0$
	'i18n!orion/editor/nls/messages', //$NON-NLS-0$
	'orion/editor/eventTarget', //$NON-NLS-0$
	'orion/editor/tooltip', //$NON-NLS-0$
	'orion/editor/annotations', //$NON-NLS-0$
	'orion/objects', //$NON-NLS-0$
	'orion/util' //$NON-NLS-0$
], function(messages, mEventTarget, mTooltip, mAnnotations, objects, util) {
	
	var AT = mAnnotations.AnnotationType;

	var HIGHLIGHT_ERROR_ANNOTATION = "orion.annotation.highlightError"; //$NON-NLS-0$

	/**
	 * @name orion.editor.BaseEditor
	 * @class This is the base interface for text and visual editors based on a text buffer.
	 *
	 * @description Creates a new Base Editor with the given options.
	 * @param {Object} options Creation options for this editor.
	 * @param {Object} options.domNode
	 * @param {Object} options.statusReporter
	 *
	 * @borrows orion.editor.EventTarget#addEventListener as #addEventListener
	 * @borrows orion.editor.EventTarget#removeEventListener as #removeEventListener
	 * @borrows orion.editor.EventTarget#dispatchEvent as #dispatchEvent
	 */
	function BaseEditor(options) {
		options = options || {};
		this._domNode = options.domNode;
		this._model = options.model;
		this._undoStack = options.undoStack;
		this._statusReporter = options.statusReporter;
		this._title = null;
		var self = this;
		this._listener = {
			onChanged: function(e) {
				self.onChanged(e);
			}
		};
		if (this._model) {
			this._model.addEventListener("Changed", this._listener.onChanged); //$NON-NLS-0$
		}
		this.checkDirty();
	}
	BaseEditor.prototype = /** @lends orion.editor.BaseEditor.prototype */ {
		/**
		 * Destroys the editor. Uninstall the editor view.
		 */
		destroy: function() {
			this.uninstall();
			this._statusReporter = this._domNode = null;
			if (this._model) {
				this._model.removeEventListener("Changed", this._listener.onChanged); //$NON-NLS-0$
			}
		},

		/** @private */
		checkDirty : function() {
			this.setDirty(this._undoStack && !this._undoStack.isClean());
		},
		/**
		 * Focus the the editor view. The default implementation does nothing.
		 */
		focus: function() {
		},
		/**
		 * Returns the text model of the editor.
		 *
		 * @returns {orion.editor.TextModel} the text model of the view.
		 */
		getModel: function() {
			return this._model;
		},
		/**
		 * Returns the text for the given range.
		 * <p>
		 * The text does not include the character at the end offset.
		 * </p>
		 *
		 * @param {Number} [start=0] the start offset of text range.
		 * @param {Number} [end=char count] the end offset of text range.
		 *
		 * @see orion.editor.TextView#setText
		 */
		getText: function(start, end) {
			return this.getModel().getText(start, end);
		},
		/**
		 * Returns the editor title.
		 *
		 * @returns {String} the editor title.
		 */
		getTitle: function() {
			return this._title;
		},
		/**
		 * Returns the editor undo stack.
		 *
		 * @returns {orion.editor.UndoStack} the editor undo stack.
		 */
		getUndoStack: function() {
			return this._undoStack;
		},
		/**
		 * Creates the DOM hierarchy of the editor and add it to the document.
		 */
		install: function() {
			this.installed = true;
		},
		/**
		 * Returns <code>true</code> if the editor is dirty; <code>false</code> otherwise.
		 * @returns {Boolean} whether the editor is dirty
		 */
		isDirty: function() {
			if (this._undoStack) return !this._undoStack.isClean();
			return this._dirty;
		},
		/**
		 * Marks the current state of the editor as clean. Meaning there are no unsaved modifications.
		 */
		markClean: function() {
			this.getUndoStack().markClean();
			this.setDirty(false);
		},
		/**
		 * Called when the dirty state of the editor changes.
		 * @param {Event} dirtyChangedEvent
		 */
		onDirtyChanged: function(dirtyChangedEvent) {
			return this.dispatchEvent(dirtyChangedEvent);
		},
		/**
		 * Called when the editor's contents have been changed or saved.
		 * @param {Event} inputChangedEvent
		 */
		onInputChanged: function (inputChangedEvent) {
			return this.dispatchEvent(inputChangedEvent);
		},
		/**
		 * Called when the editor's text model has been changed.
		 * @param {Event} inputChangedEvent
		 */
		onChanged: function (modelChangedEvent) {
			this.checkDirty();
		},
		/**
		 * Report the message to the user.
		 *
		 * @param {String} message the message to show
		 * @param {String} [type] the message type. Either normal or "progress" or "error";
		 * @param {Boolean} [isAccessible] If <code>true</code>, a screen reader will read this message.
		 * Otherwise defaults to the domNode default.
		 */
		reportStatus: function(message, type, isAccessible) {
			if (this._statusReporter) {
				this._statusReporter(message, type, isAccessible);
			}
		},
		/**
		 * Resizes the editor view. The default implementation does nothing.
		 */
		resize: function() {
		},
		/**
		 * Sets whether the editor is dirty.
		 *
		 * @param {Boolean} dirty
		 */
		setDirty: function(dirty) {
			if (this._dirty === dirty) { return; }
			this._dirty = dirty;
			this.onDirtyChanged({type: "DirtyChanged"}); //$NON-NLS-0$
		},
		/**
		 * @private
		 */
		_setModelText: function(contents) {
			if (this._model) {
				this._model.setText(contents);
			}
		},
		/**
		 * Sets the editor's contents.
		 *
		 * @param {String} title the editor title
		 * @param {String} message an error message
		 * @param {String} contents the editor contents
		 * @param {Boolean} contentsSaved whether the editor contents was saved.
		 */
		setInput: function(title, message, contents, contentsSaved) {
			this._title = title;
			if (!contentsSaved) {
				if (message) {
					this.reportStatus(message, "error"); //$NON-NLS-0$
				} else {
					if (contents !== null && contents !== undefined) {
						if (typeof contents === "string") { //$NON-NLS-0$
							this._setModelText(contents);
						}
						if (this._undoStack) {
							this._undoStack.reset();
						}
					}
				}
			}
			this.checkDirty();
			this.onInputChanged({
				type: "InputChanged", //$NON-NLS-0$
				title: title,
				message: message,
				contents: contents,
				contentsSaved: contentsSaved
			});
		},
		/**
		 * Replaces the text in the given range with the given text.
		 * <p>
		 * The character at the end offset is not replaced.
		 * </p>
		 *
		 * @param {String} text the new text.
		 * @param {Number} [start=0] the start offset of text range.
		 * @param {Number} [end=char count] the end offset of text range.
		 */
		setText: function(text, start, end) {
			this.getModel().setText(text, start, end);
		},
		/**
		 * Removes the DOM hierarchy of the editor from the document.
		 */
		uninstall: function() {
			this.installed = false;
		}
	};
	mEventTarget.EventTarget.addMixin(BaseEditor.prototype);

	/**
	 * @name orion.editor.Editor
	 * @augments orion.editor.BaseEditor
	 * @class An <code>Editor</code> is a user interface for editing text that provides additional features over the basic {@link orion.editor.TextView}.
	 * Some of <code>Editor</code>'s features include:
	 * <ul>
	 * <li>Additional actions and key bindings for editing text</li>
	 * <li>Content assist</li>
	 * <li>Find and Incremental Find</li>
	 * <li>Rulers for displaying line numbers and annotations</li>
	 * <li>Status reporting</li>
	 * </ul>
	 *
	 * @description Creates a new Editor with the given options.
	 * @param {Object} options Options controlling the features of this Editor.
	 * @param {Object} options.annotationFactory
	 * @param {Object} options.contentAssistFactory
	 * @param {Object} options.domNode
	 * @param {Object} options.keyBindingFactory
	 * @param {Object} options.lineNumberRulerFactory
	 * @param {Object} options.zoomRulerFactory
	 * @param {Object} options.foldingRulerFactory
	 * @param {Object} options.statusReporter
	 * @param {Object} options.textViewFactory
	 * @param {Object} options.undoStackFactory
	 * @param {Object} options.textDNDFactory
	 * @param {Object} options.hoverFactory
	 */
	function Editor(options) {
		options = options || {};
		BaseEditor.call(this, options);
		this._textViewFactory = options.textViewFactory;
		this._undoStackFactory = options.undoStackFactory;
		this._textDNDFactory = options.textDNDFactory;
		this._annotationFactory = options.annotationFactory;
		this._zoomRulerFactory = options.zoomRulerFactory;
		this._foldingRulerFactory = options.foldingRulerFactory;
		this._lineNumberRulerFactory = options.lineNumberRulerFactory;
		this._contentAssistFactory = options.contentAssistFactory;
		this._keyBindingFactory = options.keyBindingFactory;
		this._hoverFactory = options.hoverFactory;
		this._annotationStyler = null;
		this._annotationModel = null;
		this._annotationRuler = null;
		this._lineNumberRuler = null;
		this._overviewRuler = null;
		this._zoomRuler = null;
		this._foldingRuler = null;
		this._contentAssist = null;
	}
	Editor.prototype = new BaseEditor();
	objects.mixin(Editor.prototype, /** @lends orion.editor.Editor.prototype */ {
		/**
		 * Destroys the editor.
		 */
		destroy: function() {
			BaseEditor.prototype.destroy.call(this);
			this._textViewFactory = this._undoStackFactory = this._textDNDFactory = 
			this._annotationFactory = this._foldingRulerFactory = this._lineNumberRulerFactory = 
			this._contentAssistFactory = this._keyBindingFactory = this._hoverFactory = this._zoomRulerFactory = null;
		},
		/**
		 * Returns the annotation model of the editor.
		 *
		 * @returns {orion.editor.AnnotationModel}
		 */
		getAnnotationModel: function() {
			return this._annotationModel;
		},
		/**
		 * Returns the annotation ruler of the editor.
		 *
		 * @returns {orion.editor.AnnotationRuler}
		 */
		getAnnotationRuler: function() {
			return this._annotationRuler;
		},
		/**
		 * Returns the annotation styler of the editor.
		 *
		 * @returns {orion.editor.AnnotationStyler}
		 */
		getAnnotationStyler: function() {
			return this._annotationStyler;
		},
		/**
		 * Returns the content assist of the editor.
		 *
		 * @returns {orion.editor.LineNumberRuler}
		 */
		getContentAssist: function() {
			return this._contentAssist;
		},
		/**
		 * Returns the folding ruler of the editor.
		 *
		 * @returns {orion.editor.FoldingRuler}
		 */
		getFoldingRuler: function() {
			return this._foldingRuler;
		},
		/**
		 * Returns the line number ruler of the editor.
		 *
		 * @returns {orion.editor.LineNumberRuler}
		 */
		getLineNumberRuler: function() {
			return this._lineNumberRuler;
		},
		/**
		 * Returns the Tooltip instance for this editor
		 *
		 * @returns {orion.editor.Tooltip}
		*/
		getTooltip: function() {
			return mTooltip.Tooltip.getTooltip(this._textView);
		},
		/**
		 * Returns the zoom ruler of the editor.
		 *
		 * @returns {orion.editor.LineNumberRuler}
		 */
		getZoomRuler: function() {
			return this._zoomRuler;
		},
		/**
		 * Returns the base text model of this editor.
		 *
		 * @returns {orion.editor.TextModel}
		 */
		getModel: function() {
			if (!this._textView) {
				return null;
			}
			var model = this._textView.getModel();
			if (model.getBaseModel) {
				model = model.getBaseModel();
			}
			return model;
		},
		/**
		 * Returns the overview ruler of the editor.
		 *
		 * @returns {orion.editor.OverviewRuler}
		 */
		getOverviewRuler: function() {
			return this._overviewRuler;
		},
		/**
		 * Returns the underlying <code>TextView</code> used by this editor.
		 * @returns {orion.editor.TextView} the editor text view.
		 */
		getTextView: function() {
			return this._textView;
		},
		/**
		 * Returns the editor's key modes.
		 *
		 * @returns {Array} the editor key modes.
		 */
		getKeyModes: function() {
			return this._textView.getKeyModes();
		},
		/**
		 * Returns the editor source code actions.
		 *
		 * @returns {orion.editor.sourceCodeActions}
		 */
		getSourceCodeActions: function() {
			return this._sourceCodeActions;
		},
		/**
		 * Returns the editor linked mode.
		 *
		 * @returns {orion.editor.LinkedMode}
		 */
		getLinkedMode: function() {
			return this._linkedMode;
		},
		/**
		 * Returns the editor text actions.
		 *
		 * @returns {orion.editor.textActions}
		 */
		getTextActions: function() {
			return this._textActions;
		},
		/**
		 * Gives focus to the text view.
		 */
		focus: function() {
			if (this._textView) {
				this._textView.focus();
			}
		},
		/**
		 * Resizes the text view.
		 */
		resize: function() {
			if (this._textView) {
				this._textView.resize();
			}
		},
		/**
		 * Sets whether the annotation ruler is visible.
		 *
		 * @param {Boolean} visible <code>true</code> to show ruler, <code>false</code> otherwise
		 */
		setAnnotationRulerVisible: function(visible, force) {
			if (this._annotationRulerVisible === visible && !force) { return; }
			this._annotationRulerVisible = visible;
			if (!this._annotationRuler) { return; }
			var textView = this._textView;
			if (visible) {
				textView.addRuler(this._annotationRuler, 0);
			} else {
				textView.removeRuler(this._annotationRuler);
			}
		},
		/**
		 * Sets whether the folding ruler is visible.
		 *
		 * @param {Boolean} visible <code>true</code> to show ruler, <code>false</code> otherwise
		 */
		setFoldingRulerVisible: function(visible, force) {
			if (this._foldingRulerVisible === visible && !force) { return; }
			this._foldingRulerVisible = visible;
			if (!this._foldingRuler) { return; }
			var textView = this._textView;
			if (!textView.getModel().getBaseModel) { return; }
			if (visible) {
				textView.addRuler(this._foldingRuler);
			} else {
				textView.removeRuler(this._foldingRuler);
			}
		},
		/**
		 * Sets whether the line numbering ruler is visible.
		 *
		 * @param {Boolean} visible <code>true</code> to show ruler, <code>false</code> otherwise
		 */
		setLineNumberRulerVisible: function(visible, force) {
			if (this._lineNumberRulerVisible === visible && !force) { return; }
			this._lineNumberRulerVisible = visible;
			if (!this._lineNumberRuler) { return; }
			var textView = this._textView;
			if (visible) {
				textView.addRuler(this._lineNumberRuler, !this._annotationRulerVisible ? 0 : 1);
			} else {
				textView.removeRuler(this._lineNumberRuler);
			}
		},
		/**
		 * Sets whether the overview ruler is visible.
		 *
		 * @param {Boolean} visible <code>true</code> to show ruler, <code>false</code> otherwise
		 */
		setOverviewRulerVisible: function(visible, force) {
			if (this._overviewRulerVisible === visible && !force) { return; }
			this._overviewRulerVisible = visible;
			if (!this._overviewRuler) { return; }
			var textView = this._textView;
			if (visible) {
				textView.addRuler(this._overviewRuler);
			} else {
				textView.removeRuler(this._overviewRuler);
			}
		},
		/**
		 * Sets whether the zoom ruler is visible.
		 *
		 * @param {Boolean} visible <code>true</code> to show ruler, <code>false</code> otherwise
		 */
		setZoomRulerVisible: function(visible, force) {
			if (this._zoomRulerVisible === visible && !force) { return; }
			this._zoomRulerVisible = visible;
			if (!this._zoomRuler) { return; }
			var textView = this._textView;
			if (visible) {
				textView.addRuler(this._zoomRuler);
			} else {
				textView.removeRuler(this._zoomRuler);
			}
		},

		mapOffset: function(offset, parent) {
			var textView = this._textView;
			var model = textView.getModel();
			if (model.getBaseModel) {
				offset = model.mapOffset(offset, parent);
			}
			return offset;
		},
		/**
		 * @name getLineAtOffset
		 * @description Returns the line number corresponding to the given offset in the source
		 * @function
		 * @public
		 * @memberof orion.editor.Editor
		 * @param {Number} offset The offset into the source
		 * @returns {Number} The line number corresponding to the given offset or <code>-1</code> if out of range
		 * @since 5.0
		 */
		getLineAtOffset: function(offset) {
			return this.getModel().getLineAtOffset(this.mapOffset(offset));
		},
		/**
		 * @name getLineStart
		 * @description Compute the editor start offset of the given line number
		 * @function
		 * @public
		 * @memberof orion.editor.TextView
		 * @param {Number} line The line number in the editor
		 * @returns {Number} Returns the start offset of the given line number in the editor.
		 * @since 5.0
		 */
		getLineStart: function(line) {
			return this.getModel().getLineStart(line);
		},
		getCaretOffset: function() {
			return this.mapOffset(this._textView.getCaretOffset());
		},
		
		/**
		 * Returns the text view selection text.
		 * <p>
		 * If there are multiple selection ranges, the result is concatenated with the specified delimiter.
		 * </p>
		 * 
		 * @param {String} delimiter The offset into the editor
		 * @returns {String} the view selection text
		 * @since 10.0
		 * @see orion.editor.TextView#setSelection
		 */
		getSelectionText: function(delimiter) {
			var textView = this._textView;
			return textView.getSelectionText(delimiter);
		},
		
		getSelection: function() {
			var textView = this._textView;
			var selection = textView.getSelection();
			var model = textView.getModel();
			if (model.getBaseModel) {
				selection.start = model.mapOffset(selection.start);
				selection.end = model.mapOffset(selection.end);
			}
			return selection;
		},

		getSelections: function() {
			var textView = this._textView;
			var model = textView.getModel();
			var selections = textView.getSelections();
			selections.forEach(function(selection) {
				if (model.getBaseModel) {
					selection.start = model.mapOffset(selection.start);
					selection.end = model.mapOffset(selection.end);
				}
			});
			return selections;
		},

		_expandOffset: function(offset) {
			var model = this._textView.getModel();
			var annotationModel = this._annotationModel;
			if (!annotationModel || !model.getBaseModel) { return; }
			var annotations = annotationModel.getAnnotations(offset, offset + 1);
			while (annotations.hasNext()) {
				var annotation = annotations.next();
				if (annotation.type === AT.ANNOTATION_FOLDING) {
					if (annotation.expand) {
						annotation.expand();
					}
				}
			}
		},

		setCaretOffset: function(caretOffset, show, callback) {
			var textView = this._textView;
			var model = textView.getModel();
			if (model.getBaseModel) {
				this._expandOffset(caretOffset);
				caretOffset = model.mapOffset(caretOffset, true);
			}
			textView.setCaretOffset(caretOffset, show, callback);
		},

		setText: function(text, start, end, show, callback) {
			var textView = this._textView;
			var model = textView.getModel();
			if (model.getBaseModel) {
				if (start !== undefined) {
					this._expandOffset(start);
					start = model.mapOffset(start, true);
				}
				if (end !== undefined) {
					this._expandOffset(end);
					end = model.mapOffset(end, true);
				}
			}
			textView.setText(text, start, end, show, callback);
		},

		setSelection: function(start, end, show, callback) {
			var textView = this._textView;
			var model = textView.getModel();
			if (model.getBaseModel) {
				this._expandOffset(start);
				this._expandOffset(end);
				start = model.mapOffset(start, true);
				end = model.mapOffset(end, true);
			}
			textView.setSelection(start, end, show, callback);
		},
		setSelections: function(ranges, show, callback) {
			var self = this;
			var textView = this._textView;
			var model = textView.getModel();
			ranges.forEach(function(range) {
				var start = range.start;
				var end = range.end;
				if (model.getBaseModel) {
					self._expandOffset(start);
					self._expandOffset(end);
					start = model.mapOffset(start, true);
					end = model.mapOffset(end, true);
				}
				range.start = start;
				range.end = end;
			});
			textView.setSelections(ranges, show, callback);
		},

		/**
		 * @param {Number} start
		 * @param {Number} [end]
		 * @param {function} [callback] if callback is specified, scrolling to show the selection is animated and callback is called when the animation is done.
		 * @param {Boolean} [focus=true] whether the text view should be focused when the selection is done.
		 * @private
		 * @deprecated use #setSelection instead
		 */
		moveSelection: function(start, end, callback, focus) {
			end = end || start;
			var textView = this._textView;
			this.setSelection(start, end, 1 / 3, function() {
				if (focus === undefined || focus) {
					textView.focus();
				}
				if (callback) {
					callback();
				}
			});
		},

		/** @private */
		_getTooltipInfo: function(x, y) {
			var textView = this._textView;
			var annotationModel = this.getAnnotationModel();
			if (!annotationModel) { return null; }
			var annotationStyler = this._annotationStyler;
			if (!annotationStyler) { return null; }
			if (!textView.isValidTextPosition(x, y)) { return null; }
			var offset = textView.getOffsetAtLocation(x, y);
			if (offset === -1) { return null; }
			offset = this.mapOffset(offset);
			var annotations = annotationStyler.getAnnotationsByType(annotationModel, offset, offset + 1);
			var rangeAnnotations = [];
			for (var i = 0; i < annotations.length; i++) {
				if (annotations[i].rangeStyle) {
					rangeAnnotations.push(annotations[i]);
				}
			}
			var info = {
				contents: rangeAnnotations,
				position: "below", //$NON-NLS-0$
				context: {source: "editor", offset: offset} //$NON-NLS-0$
			};
			return info;
		},

		/** @private */
		_highlightCurrentLine: function(newSelections, oldSelections) {
			var annotationModel = this._annotationModel;
			if (!annotationModel) { return; }
			var textView = this._textView;
			if (textView.getOptions("singleMode")) { return; } //$NON-NLS-0$
			oldSelections = Array.isArray(oldSelections) ? oldSelections : [oldSelections];
			newSelections = Array.isArray(newSelections) ? newSelections : [newSelections];
			var model = textView.getModel();
			function getHighlightLines(selections) {
				var lines = {};
				if (selections && selections.some(function(selection) {
					if (selection && selection.isEmpty()) {
						lines[model.getLineAtOffset(selection.start).toString()] = true;
					} else {
						return true;
					}
					return false;
				})) return {};
				return lines;
			}
			var oldLines = getHighlightLines(oldSelections);
			var newLines = getHighlightLines(newSelections);
			function compare(o, n) {
				for (var p1 in o) {
					if (!n[p1]) {
						return true;
					}
				}
				return false;
			}
			if (!(compare(oldLines, newLines) || compare(newLines, oldLines))) return;
			var remove = this._currentLineAnnotations;
			var add = [];
			for (var p in newLines) {
				var lineIndex = p >> 0;
				var start = model.getLineStart(lineIndex);
				var end = model.getLineEnd(lineIndex);
				if (model.getBaseModel) {
					start = model.mapOffset(start);
					end = model.mapOffset(end);
				}
				var type = AT.ANNOTATION_CURRENT_LINE;
				var annotation = AT.createAnnotation(type, start, end);
				add.push(annotation);
			}
			this._currentLineAnnotations = add;
			annotationModel.replaceAnnotations(remove, add);
		},

		/**
		 * Creates the underlying TextView and installs the editor's features.
		 */
		installTextView: function() {
			this.install();
		},

		install : function() {
			if (this._textView) { return; }

			// Create textView and install optional features
			this._textView = this._textViewFactory();
			if (this._undoStackFactory) {
				this._undoStack = this._undoStackFactory.createUndoStack(this);
				this._textView.setOptions({undoStack: this._undoStack});
				this.checkDirty();
			}
			if (this._textDNDFactory) {
				this._textDND = this._textDNDFactory.createTextDND(this, this._undoStack);
			}
			if (this._contentAssistFactory) {
				var contentAssistMode = this._contentAssistFactory.createContentAssistMode(this);
				this._contentAssist = contentAssistMode.getContentAssist();
			}

			var tooltip = mTooltip.Tooltip.getTooltip(this._textView);
			if (this._hoverFactory) {
				this._hover = this._hoverFactory.createHover(this);
				tooltip.hover = this._hover;
			}
			
			var editor = this, textView = this._textView;

			var self = this;
			this._listener = {
				onModelChanged: function(e) {
					self.checkDirty();
				},
				onMouseOver: function(e) {
					self._listener.onMouseMove(e);
				},
				onMouseDown: function(e) {
					self._listener.mouseDown = true;
				},
				onMouseUp: function(e) {
					self._listener.mouseDown = false;
				},
				onMouseMove: function(e) {
					if (!tooltip || self._listener.mouseDown) { return; }

					// Prevent spurious mouse event (e.g. on a scroll)					
					if (e.event.clientX === self._listener.lastMouseX
						&& e.event.clientY === self._listener.lastMouseY) {
						return;
					}
					
					self._listener.lastMouseX = e.event.clientX;
					self._listener.lastMouseY = e.event.clientY;

					if (self._hoverTimeout) {
						window.clearTimeout(self._hoverTimeout);
						self._hoverTimeout = null;
					}
					self._hoverTimeout = window.setTimeout(function() {
						self._hoverTimeout = null;
						
						// Re-check incase editor closed...
						if (!self._listener){
							return;
						}
						
						tooltip.onHover({
							y: e.y,
							x: e.x,							
							getTooltipInfo: function() {
								return self._getTooltipInfo(this.x, this.y);
							}
						}, e.x, e.y);
					}, 175);
				},
				onMouseOut: function(e) {
					// When mouse leaves the editor, ignore any pending onMouseMove events
					if (self._hoverTimeout) {
						window.clearTimeout(self._hoverTimeout);
						self._hoverTimeout = null;
					}
				},
				onSelection: function(e) {
					if (tooltip) { tooltip.hide(); }
					self._updateCursorStatus();
					self._highlightCurrentLine(e.newValue, e.oldValue);
				}
			};
			textView.addEventListener("ModelChanged", this._listener.onModelChanged); //$NON-NLS-0$
			textView.addEventListener("Selection", this._listener.onSelection); //$NON-NLS-0$
			textView.addEventListener("MouseOver", this._listener.onMouseOver); //$NON-NLS-0$
			textView.addEventListener("MouseOut", this._listener.onMouseOut); //$NON-NLS-0$
			textView.addEventListener("MouseDown", this._listener.onMouseDown); //$NON-NLS-0$
			textView.addEventListener("MouseUp", this._listener.onMouseUp); //$NON-NLS-0$
			textView.addEventListener("MouseMove", this._listener.onMouseMove); //$NON-NLS-0$

			// Set up keybindings
			if (this._keyBindingFactory) {
				var keyBindings;
				if (typeof this._keyBindingFactory === "function") { //$NON-NLS-0$
					keyBindings = this._keyBindingFactory(this, this.getKeyModes(), this._undoStack, this._contentAssist);
				} else {
					keyBindings = this._keyBindingFactory.createKeyBindings(editor, this._undoStack, this._contentAssist);
				}
				if (keyBindings) {
					this._textActions = keyBindings.textActions;
					this._linkedMode = keyBindings.linkedMode;
					this._sourceCodeActions = keyBindings.sourceCodeActions;
				}
			}

			var addRemoveBookmark = function(lineIndex, e) {
				if (lineIndex === undefined) { return; }
				if (lineIndex === -1) { return; }
				var view = this.getView();
				var viewModel = view.getModel();
				var annotationModel = this.getAnnotationModel();
				var lineStart = editor.mapOffset(viewModel.getLineStart(lineIndex));
				var lineEnd = editor.mapOffset(viewModel.getLineEnd(lineIndex));
				var annotations = annotationModel.getAnnotations(lineStart, lineEnd);
				var bookmark = null;
				while (annotations.hasNext()) {
					var annotation = annotations.next();
					if (annotation.type === AT.ANNOTATION_BOOKMARK) {
						bookmark = annotation;
						break;
					}
				}
				if (bookmark) {
					annotationModel.removeAnnotation(bookmark);
				} else {
					bookmark = AT.createAnnotation(AT.ANNOTATION_BOOKMARK, lineStart, lineEnd, editor.getText(lineStart, lineEnd));
					annotationModel.addAnnotation(bookmark);
				}
			};

			// Create rulers, annotation model and styler
			if (this._annotationFactory) {
				var textModel = textView.getModel();
				if (textModel.getBaseModel) { textModel = textModel.getBaseModel(); }
				this._annotationModel = this._annotationFactory.createAnnotationModel(textModel);
				if (this._annotationModel) {
					var styler = this._annotationStyler = this._annotationFactory.createAnnotationStyler(textView, this._annotationModel);
					if (styler) {
						styler.addAnnotationType(AT.ANNOTATION_CURRENT_SEARCH);
						styler.addAnnotationType(AT.ANNOTATION_MATCHING_SEARCH);
						styler.addAnnotationType(AT.ANNOTATION_ERROR);
						styler.addAnnotationType(AT.ANNOTATION_WARNING);
						styler.addAnnotationType(AT.ANNOTATION_MATCHING_BRACKET);
						styler.addAnnotationType(AT.ANNOTATION_CURRENT_BRACKET);
						styler.addAnnotationType(AT.ANNOTATION_CURRENT_LINE);
						styler.addAnnotationType(AT.ANNOTATION_READ_OCCURRENCE);
						styler.addAnnotationType(AT.ANNOTATION_WRITE_OCCURRENCE);
						styler.addAnnotationType(AT.ANNOTATION_SELECTED_LINKED_GROUP);
						styler.addAnnotationType(AT.ANNOTATION_CURRENT_LINKED_GROUP);
						styler.addAnnotationType(AT.ANNOTATION_LINKED_GROUP);
						styler.addAnnotationType(HIGHLIGHT_ERROR_ANNOTATION);
					}
				}

				var rulers = this._annotationFactory.createAnnotationRulers(this._annotationModel);
				var ruler = this._annotationRuler = rulers.annotationRuler;
				if (ruler) {
					ruler.onDblClick = addRemoveBookmark;
					ruler.setMultiAnnotationOverlay({html: "<div class='annotationHTML overlay'></div>"}); //$NON-NLS-0$
					ruler.addAnnotationType(AT.ANNOTATION_ERROR);
					ruler.addAnnotationType(AT.ANNOTATION_WARNING);
					ruler.addAnnotationType(AT.ANNOTATION_TASK);
					ruler.addAnnotationType(AT.ANNOTATION_BOOKMARK);
					ruler.addAnnotationType(AT.ANNOTATION_DIFF_ADDED);
					ruler.addAnnotationType(AT.ANNOTATION_DIFF_DELETED);
					ruler.addAnnotationType(AT.ANNOTATION_DIFF_MODIFIED);
				}
				this.setAnnotationRulerVisible(this._annotationRulerVisible || this._annotationRulerVisible === undefined, true);

				// Overview Ruler Annotation Type
				ruler = this._overviewRuler = rulers.overviewRuler;
				if (ruler) {
					ruler.addAnnotationType(AT.ANNOTATION_CURRENT_SEARCH);
					ruler.addAnnotationType(AT.ANNOTATION_MATCHING_SEARCH);
					ruler.addAnnotationType(AT.ANNOTATION_READ_OCCURRENCE);
					ruler.addAnnotationType(AT.ANNOTATION_WRITE_OCCURRENCE);
					ruler.addAnnotationType(AT.ANNOTATION_CURRENT_BLAME);
					ruler.addAnnotationType(AT.ANNOTATION_ERROR);
					ruler.addAnnotationType(AT.ANNOTATION_WARNING);
					ruler.addAnnotationType(AT.ANNOTATION_TASK);
					ruler.addAnnotationType(AT.ANNOTATION_BOOKMARK);
					ruler.addAnnotationType(AT.ANNOTATION_MATCHING_BRACKET);
					ruler.addAnnotationType(AT.ANNOTATION_CURRENT_BRACKET);
					ruler.addAnnotationType(AT.ANNOTATION_CURRENT_LINE);
					ruler.addAnnotationType(AT.ANNOTATION_DIFF_ADDED);
					ruler.addAnnotationType(AT.ANNOTATION_DIFF_DELETED);
					ruler.addAnnotationType(AT.ANNOTATION_DIFF_MODIFIED);

				}
				this.setOverviewRulerVisible(this._overviewRulerVisible || this._overviewRulerVisible === undefined, true);
			}

			if (this._zoomRulerFactory) {
				this._zoomRuler = this._zoomRulerFactory.createZoomRuler(this._annotationModel);
				this.setZoomRulerVisible(this._zoomRulerVisible, true);
			}

			if (this._lineNumberRulerFactory) {
				this._lineNumberRuler = this._lineNumberRulerFactory.createLineNumberRuler(this._annotationModel);
				this._lineNumberRuler.addAnnotationType(AT.ANNOTATION_CURRENT_BLAME);
				this._lineNumberRuler.addAnnotationType(AT.ANNOTATION_BLAME);
		        this._lineNumberRuler.addAnnotationType(AT.ANNOTATION_DIFF_ADDED);
		        this._lineNumberRuler.addAnnotationType(AT.ANNOTATION_DIFF_MODIFIED);
		        this._lineNumberRuler.addAnnotationType(AT.ANNOTATION_DIFF_DELETED);
				this._lineNumberRuler.onDblClick = addRemoveBookmark;
				this.setLineNumberRulerVisible(this._lineNumberRulerVisible || this._lineNumberRulerVisible === undefined, true);
			}

			if (this._foldingRulerFactory) {
				this._foldingRuler = this._foldingRulerFactory.createFoldingRuler(this._annotationModel);
				this._foldingRuler.addAnnotationType(AT.ANNOTATION_FOLDING);
				this.setFoldingRulerVisible(this._foldingRulerVisible || this._foldingRulerVisible === undefined, true);
			}

			var textViewInstalledEvent = {
				type: "TextViewInstalled", //$NON-NLS-0$
				textView: textView
			};
			this.dispatchEvent(textViewInstalledEvent);
			BaseEditor.prototype.install.call(this);
		},

		/**
		 * Destroys the underlying TextView.
		 */
		uninstallTextView: function() {
			this.uninstall();
		},

		uninstall: function() {
			var textView = this._textView;
			if (!textView) { return; }

			textView.destroy();

			if (this._annotationModel) {
				this._annotationModel.setTextModel(null);
			}
			this._textView = this._undoStack = this._textDND = this._contentAssist =
				this._listener = this._annotationModel = this._annotationStyler =
				this._annotationRuler = this._overviewRuler = this._zoomRuler = this._lineNumberRuler =
				this._foldingRuler = this._currentLineAnnotations = this._title = null;
			this._dirty = false;
			this._foldingRulerVisible = this._overviewRulerVisible = this._zoomRulerVisible =
				this._lineNumberRulerVisible = this._annotationRulerVisible = undefined;

			var textViewUninstalledEvent = {
				type: "TextViewUninstalled", //$NON-NLS-0$
				textView: textView
			};
			this.dispatchEvent(textViewUninstalledEvent);
			BaseEditor.prototype.uninstall.call(this);
		},

		_updateCursorStatus: function() {
			// If we are in a mode and it owns status reporting, we bail out from reporting the cursor position.
			var keyModes = this.getKeyModes();
			for (var i=0; i<keyModes.length; i++) {
				var mode = keyModes[i];
				if (mode.isActive() && mode.isStatusActive && mode.isStatusActive()) {
					return;
				}
			}
			var status;
			var model = this.getModel();
			var selections = this.getSelections();
			if (selections.length > 1) {
				status = util.formatMessage(messages.multiSelections, selections.length);
			} else {
				var caretOffset = selections[0].getCaret();
				var lineIndex = model.getLineAtOffset(caretOffset);
				var lineStart = model.getLineStart(lineIndex);
				var offsetInLine = caretOffset - lineStart;
				status = util.formatMessage(messages.lineColumn, lineIndex + 1, offsetInLine + 1);
			}
			this.reportStatus(status);
		},

		showAnnotations: function(annotations, types, createAnnotation, getType) {
			var annotationModel = this._annotationModel;
			if (!annotationModel) {
				return;
			}
			var remove = [], add = [];
			var model = annotationModel.getTextModel();
			var iter = annotationModel.getAnnotations(), annotation;
			while (iter.hasNext()) {
				annotation = iter.next();
				if (types.indexOf(annotation.type) !== -1) {
					if (annotation.creatorID === this) {
						remove.push(annotation);
					}
				}
			}
			if (annotations) {
				for (var i = 0; i < annotations.length; i++) {
					annotation = annotations[i];
					if (!annotation) { continue; }
					if (createAnnotation) {
						annotation = createAnnotation(annotation);
					} else {
						var start, end;
						if (annotation.lineStart && annotation.lineEnd){
							start = model.getLineStart(annotation.lineStart);
							// If the closing line number of the modified range is on the last line,
							// get the line ending offset of the previous line
							end = model.getLineCount() === annotation.lineEnd
										? model.getLineEnd(annotation.lineEnd - 1)
										: model.getLineStart(annotation.lineEnd);
						}
						else if (typeof annotation.line === "number") { //$NON-NLS-0$
							// line/column
							var lineIndex = annotation.line - 1;
							var lineStart = model.getLineStart(lineIndex);
							start = lineStart + annotation.start - 1;
							end = lineStart + annotation.end - 1;
						} else {
							// document offsets
							start = annotation.start;
							end = annotation.end;
						}
						var type = getType(annotation);
						if (!type) { continue; }
						annotation = AT.createAnnotation(type, start, end, annotation.description);
					}
					annotation.id = annotations[i].id; //allow consumers to tag the annotation with their own identifier
					if(annotations[i].data) {
						annotation.data = annotations[i].data;
					}
					annotation.creatorID = this;
					add.push(annotation);

				}
			}
			annotationModel.replaceAnnotations(remove, add);
		},

		showProblems: function(problems) {
			this.showAnnotations(problems, [
				AT.ANNOTATION_ERROR,
				AT.ANNOTATION_WARNING,
				AT.ANNOTATION_TASK
			], null, function(annotation) {
				switch (annotation.severity) {
					case "error": return AT.ANNOTATION_ERROR; //$NON-NLS-0$
					case "warning": return AT.ANNOTATION_WARNING; //$NON-NLS-0$
					case "task": return AT.ANNOTATION_TASK; //$NON-NLS-0$
				}
				return null;
			});
		},

		showOccurrences: function(occurrences) {
			this.showAnnotations(occurrences, [
				AT.ANNOTATION_READ_OCCURRENCE,
				AT.ANNOTATION_WRITE_OCCURRENCE
			], null, function(annotation) {
				return annotation.readAccess ? AT.ANNOTATION_READ_OCCURRENCE : AT.ANNOTATION_WRITE_OCCURRENCE;
			});
		},

		showBlame : function(blameMarkers) {
			var blameRGB = this._blameRGB;
			var document = this.getTextView().getOptions("parent").ownerDocument; //$NON-NLS-0$
			if (!blameRGB) {
				var div = util.createElement(document, "div"); //$NON-NLS-0$
				div.className = "annotation blame"; //$NON-NLS-0$
				document.body.appendChild(div);
				var window = document.defaultView || document.parentWindow;
				var blameStyle = window.getComputedStyle(div);
				var color = blameStyle.getPropertyValue("background-color"); //$NON-NLS-0$
				div.parentNode.removeChild(div);
				var i1 = color.indexOf("("); //$NON-NLS-0$
				var i2 = color.indexOf(")"); //$NON-NLS-0$
				color = color.substring(i1 + 1, i2);
				this._blameRGB = blameRGB = color.split(",").slice(0,3); //$NON-NLS-0$
			}
			var createGroup = function() {
				var annotation = mAnnotations.AnnotationType.createAnnotation(this.groupType, this.start, this.end, this.title);
				annotation.style = objects.mixin({}, annotation.style);
				annotation.style.style = objects.mixin({}, annotation.style.style);
				annotation.style.style.backgroundColor = "";
				this.groupAnnotation = annotation;
				annotation.blame = this.blame;
				annotation.html = this.html;
				annotation.creatorID = this.creatorID;
				return annotation;
			};
			var title = function() {
				var div = util.createElement(document, "div"); //$NON-NLS-0$
				div.className = "tooltipTitle"; //$NON-NLS-0$
				var index = this.blame.Message.indexOf("\n"); //$NON-NLS-0$
				if (index === -1) { index = this.blame.Message.length; }
				var commitLink = util.createElement(document, "a"); //$NON-NLS-0$
				commitLink.href = this.blame.CommitLink;
				commitLink.appendChild(document.createTextNode(this.blame.Message.substring(0, index)));
				div.appendChild(commitLink);
				div.appendChild(util.createElement(document, "br")); //$NON-NLS-0$
				div.appendChild(document.createTextNode(util.formatMessage(messages.committerOnTime, this.blame.AuthorName, this.blame.Time)));
				return div;
			};
			var model = this.getModel();
			this.showAnnotations(blameMarkers, [
				AT.ANNOTATION_BLAME,
				AT.ANNOTATION_CURRENT_BLAME
			], function (blameMarker) {
				var start = model.getLineStart(blameMarker.Start - 1);
				var end = model.getLineEnd(blameMarker.End - 1, true);
				var annotation = mAnnotations.AnnotationType.createAnnotation(AT.ANNOTATION_BLAME, start, end, title);
				var blameColor = blameRGB.slice(0);
				blameColor.push(blameMarker.Shade);
				annotation.style = objects.mixin({}, annotation.style);
				annotation.style.style = objects.mixin({}, annotation.style.style);
				annotation.style.style.backgroundColor = "rgba(" + blameColor.join() + ")"; //$NON-NLS-0$ //$NON-NLS-1$
				annotation.groupId = blameMarker.Name;
				annotation.groupType = AT.ANNOTATION_CURRENT_BLAME;
				annotation.createGroupAnnotation = createGroup;
				annotation.html = '<img class="annotationHTML blame" src="' + blameMarker.AuthorImage + '"/>'; //$NON-NLS-0$ //$NON-NLS-1$
				annotation.blame = blameMarker;
				return annotation;
			});
		},

		/**
		 * Display git diff annotation on the editor's annotation ruler and overview ruler.
		 *
		 * @param diffs [] with types "added", "modified", "deleted"
		 * 		Each property in diffs contains an array of objects { lineStart, lineEnd } that
		 * 		provides the starting and ending line index for the specified property.
		 */
		showDiffAnnotations: function(diffs) {
			this.showAnnotations(diffs, [
				AT.ANNOTATION_DIFF_ADDED,
				AT.ANNOTATION_DIFF_MODIFIED,
				AT.ANNOTATION_DIFF_DELETED
			], null, function(annotation) {
				if(annotation.type === "added")//$NON-NLS-0$
					return AT.ANNOTATION_DIFF_ADDED;
				else if (annotation.type === "modified")//$NON-NLS-0$
					return AT.ANNOTATION_DIFF_MODIFIED;
				return AT.ANNOTATION_DIFF_DELETED; // assume deleted if not added or modified
			});
		},

		/**
		 * Reveals and selects a portion of text.
		 * @param {Number} start
		 * @param {Number} end
		 * @param {Number} line
		 * @param {Number} offset
		 * @param {Number} length
		 */
		showSelection: function(start, end, line, offset, length) {
			// We use typeof because we need to distinguish the number 0 from an undefined or null parameter
			if (typeof(start) === "number") { //$NON-NLS-0$
				if (typeof(end) !== "number") { //$NON-NLS-0$
					end = start;
				}
				this.moveSelection(start, end);
				return true;
			} else if (typeof(line) === "number") { //$NON-NLS-0$
				var model = this.getModel();
				var pos = model.getLineStart(line-1);
				if (typeof(offset) === "number") { //$NON-NLS-0$
					pos = pos + offset;
				}
				if (typeof(length) !== "number") { //$NON-NLS-0$
					length = 0;
				}
				this.moveSelection(pos, pos+length);
				return true;
			}
			return false;
		},

		/**
		 * @private
		 */
		_setModelText: function(contents) {
			if (this._textView) {
				this._textView.setText(contents);
				this._textView.getModel().setLineDelimiter("auto"); //$NON-NLS-0$
				this._highlightCurrentLine(this._textView.getSelections());
			}
		},

		/**
		 * Sets the editor's contents.
		 *
		 * @param {String} title
		 * @param {String} message
		 * @param {String} contents
		 * @param {Boolean} contentsSaved
		 * @param {Boolean} noFocus
		 */
		setInput: function(title, message, contents, contentsSaved, noFocus) {
			BaseEditor.prototype.setInput.call(this, title, message, contents, contentsSaved);
			if (this._textView && !contentsSaved && !noFocus) {
				this._textView.focus();
			}
		},
		/**
		 * Reveals a line in the editor, and optionally selects a portion of the line.
		 * @param {Number} line - document base line index
		 * @param {Number|String} column
		 * @param {Number} [end]
		 */
		onGotoLine: function(line, column, end, callback) {
			if (this._textView) {
				var model = this.getModel();
				line = Math.max(0, Math.min(line, model.getLineCount() - 1));
				var lineStart = model.getLineStart(line);
				var start = 0;
				if (end === undefined) {
					end = 0;
				}
				if (typeof column === "string") { //$NON-NLS-0$
					var index = model.getLine(line).indexOf(column);
					if (index !== -1) {
						start = index;
						end = start + column.length;
					}
				} else {
					start = column;
					var lineLength = model.getLineEnd(line) - lineStart;
					start = Math.min(start, lineLength);
					end = Math.min(end, lineLength);
				}
				this.moveSelection(lineStart + start, lineStart + end, callback);
			}
		}
	});

	return {
		BaseEditor: BaseEditor,
		Editor: Editor
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define("orion/editor/find", [ //$NON-NLS-0$
	'i18n!orion/editor/nls/messages', //$NON-NLS-0$
	'orion/keyBinding', //$NON-NLS-0$
	'orion/editor/keyModes', //$NON-NLS-0$
	'orion/editor/annotations', //$NON-NLS-0$
	'orion/regex', //$NON-NLS-0$
	'orion/objects', //$NON-NLS-0$
	'orion/util' //$NON-NLS-0$
], function(messages, mKeyBinding, mKeyModes, mAnnotations, mRegex, objects, util) {

	var exports = {};
	
	function IncrementalFind(editor) {
		var textView = editor.getTextView();
		mKeyModes.KeyMode.call(this, textView);
		this.editor = editor;
		this._active = false;
		this._success = true;
		this._ignoreSelection = false;
		this._prefix = "";
		
		textView.setAction("incrementalFindCancel", function() { //$NON-NLS-0$
			this.setActive(false);
			return true;
		}.bind(this));
		textView.setAction("incrementalFindBackspace", function() { //$NON-NLS-0$
			return this._backspace();
		}.bind(this));
		
		var self = this;
		this._listener = {
			onVerify: function(e){
				var editor = self.editor;
				var model = editor.getModel();
				var start = editor.mapOffset(e.start), end = editor.mapOffset(e.end);
				var txt = model.getText(start, end);
				var prefix = self._prefix;
				// TODO: mRegex is pulled in just for this one call so we can get case-insensitive search
				// is it really necessary
				var match = prefix.match(new RegExp("^" + mRegex.escape(txt), "i")); //$NON-NLS-1$ //$NON-NLS-0$
				if (match && match.length > 0) {
					prefix = self._prefix += e.text;
					self._success = true;
					self._status();
					self.find(self._forward, true);
					e.text = null;
				}
			},
			onSelection: function() {
				if (!self._ignoreSelection) {
					self.setActive(false);
				}
			}
		};
	}
	IncrementalFind.prototype = new mKeyModes.KeyMode();
	objects.mixin(IncrementalFind.prototype, {
		createKeyBindings: function() {
			var KeyBinding = mKeyBinding.KeyBinding;
			var bindings = [];
			bindings.push({actionID: "incrementalFindBackspace", keyBinding: new KeyBinding(8)}); //$NON-NLS-0$
			bindings.push({actionID: "incrementalFindCancel", keyBinding: new KeyBinding(13)}); //$NON-NLS-0$
			bindings.push({actionID: "incrementalFindCancel", keyBinding: new KeyBinding(27)}); //$NON-NLS-0$
			bindings.push({actionID: "incrementalFindReverse", keyBinding: new KeyBinding(38)}); //$NON-NLS-0$
			bindings.push({actionID: "incrementalFind", keyBinding: new KeyBinding(40)}); //$NON-NLS-0$
			bindings.push({actionID: "incrementalFindReverse", keyBinding: new KeyBinding('k', true, true)}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "incrementalFind", keyBinding: new KeyBinding('k', true)}); //$NON-NLS-1$ //$NON-NLS-0$
			return bindings;
		},
		find: function(forward, incremental) {
			this._forward = forward;
			if (!this.isActive()) {
				this.setActive(true);
				return false;
			}
			var prefix = this._prefix;
			if (prefix.length === 0) {
				return false;
			}
			var editor = this.editor;
			var model = editor.getModel();
			var start;
			if (forward) {
				if (this._success) {
					start = incremental ? this._start : editor.getCaretOffset() + 1;
				} else {
					start = 0;
				}
			} else {
				if (this._success) {
					start = incremental ? this._start : editor.getCaretOffset();
				} else {
					start = model.getCharCount() - 1;
				}
			}
			var result = editor.getModel().find({
				string: prefix,
				start: start,
				reverse: !forward,
				caseInsensitive: prefix.toLowerCase() === prefix}).next();
			if (result) {
				if (!incremental) {
					this._start = start;
				}
				this._success = true;
				this._ignoreSelection = true;
				editor.moveSelection(forward ? result.start : result.end, forward ? result.end : result.start);
				this._ignoreSelection = false;
			} else {
				this._success = false;
			}
			this._status();
			return true;
		},
		isActive: function() {
			return this._active;
		},
		isStatusActive: function() {
			return this.isActive();
		},
		setActive: function(active) {
			if (this._active === active) {
				return;
			}
			this._active = active;
			this._prefix = "";
			this._success = true;
			var editor = this.editor;
			var textView = editor.getTextView();
			this._start = this.editor.getCaretOffset();
			this.editor.setCaretOffset(this._start);
			if (this._active) {
				textView.addEventListener("Verify", this._listener.onVerify); //$NON-NLS-0$
				textView.addEventListener("Selection", this._listener.onSelection); //$NON-NLS-0$
				textView.addKeyMode(this);
			} else {
				textView.removeEventListener("Verify", this._listener.onVerify); //$NON-NLS-0$
				textView.removeEventListener("Selection", this._listener.onSelection); //$NON-NLS-0$
				textView.removeKeyMode(this);
			}
			this._status();
		},
		_backspace: function() {
			var prefix = this._prefix;
			prefix = this._prefix = prefix.substring(0, prefix.length-1);
			if (prefix.length === 0) {
				this._success = true;
				this._ignoreSelection = true;
				this.editor.setCaretOffset(this.editor.getSelection().start);
				this._ignoreSelection = false;
				this._status();
				return true;
			}
			return this.find(this._forward, true);
		},
		_status: function() {
			if (!this.isActive()) {
				this.editor.reportStatus("");
				return;
			}
			var msg;
			if (this._forward) {
				msg = this._success ? messages.incrementalFindStr : messages.incrementalFindStrNotFound;
			} else {
				msg = this._success ? messages.incrementalFindReverseStr : messages.incrementalFindReverseStrNotFound;
			}
			msg = util.formatMessage(msg, this._prefix);
			this.editor.reportStatus(msg, this._success ? "" : "error"); //$NON-NLS-0$
		}
	});
	exports.IncrementalFind = IncrementalFind;
	
	
	function Find(editor, undoStack, options) {
		if (!editor) { return; }	
		this._editor = editor;
		this._undoStack = undoStack;
		this._showAll = true;
		this._visible = false;
		this._caseInsensitive = true;
		this._wrap = true;
		this._wholeWord = false;
		this._incremental = true;
		this._regex = false;
		this._findAfterReplace = true;
		this._hideAfterFind = false;
		this._reverse = false;
		this._start = undefined;
		this._end = undefined;
		this._timer = undefined;
		this._lastString = "";
		var that = this;
		this._listeners = {
			onEditorFocus: function(e) {
				that._removeCurrentAnnotation(e);
			}
		};
		this.setOptions(options);
	}
	Find.prototype = {
		find: function (forward, tempOptions, incremental) {
			this.setOptions({
				reverse : !forward
			});
			var string = this.getFindString();
			var count;
			if (tempOptions) {
				string = tempOptions.findString || string;
				count =  tempOptions.count;
			}
			var savedOptions = this.getOptions();
			this.setOptions(tempOptions);
			var startOffset = incremental ? this._startOffset : this.getStartOffset();
			var result = this._doFind(string, startOffset, count);
			if (result) {
				if (!incremental) {
					this._startOffset = result.start;
				}
			}
			this.setOptions(savedOptions);
			if (this._hideAfterFind) {
				this.hide();
			}
			return result;
		},
		getStartOffset: function() {
			if (this._start !== undefined) {
				return this._start;
			}
			if (this._reverse) {
				return this._editor.getSelection().start - 1;
			}
			return this._editor.getCaretOffset();
		},
		getFindString: function() {
			var selection = this._editor.getSelection();
			var searchString = this._editor.getText(selection.start, selection.end);
			if (this._regex) {
				searchString = mRegex.escape(searchString);
			}
			return searchString || this._lastString;
		},
		getOptions: function() {
			return {
				showAll: this._showAll, 
				caseInsensitive: this._caseInsensitive, 
				wrap: this._wrap, 
				wholeWord: this._wholeWord, 
				incremental: this._incremental,
				regex: this._regex,
				findAfterReplace: this._findAfterReplace,
				hideAfterFind: this._hideAfterFind,
				reverse: this._reverse,
				findCallback: this._findCallback,
				start: this._start,
				end: this._end
			};
		},
		getReplaceString: function() {
			return "";
		},
		hide: function() {
			this._visible = false;
			if (this._savedOptions) {
				this.setOptions(this._savedOptions.pop());
				if (this._savedOptions.length === 0) {
					this._savedOptions = null;
				}
			}
			this._removeAllAnnotations();
			var textView = this._editor.getTextView();
			if (textView) {
				textView.removeEventListener("Focus", this._listeners.onEditorFocus); //$NON-NLS-0$
				textView.focus();
			}
		},
		_processReplaceString: function(str) {
			var newStr = str;
			if (this._regex) {
				newStr = "";
				var escape = false;
				var delimiter = this._editor.getModel().getLineDelimiter();
				for (var i=0; i<str.length; i++) {
					var c = str.substring(i, i + 1);
					if (escape) {
						switch (c) {
							case "R": newStr += delimiter; break;
							case "r": newStr += "\r"; break;
							case "n": newStr += "\n"; break;
							case "t": newStr += "\t"; break;
							case "\\": newStr += "\\"; break;
							default: newStr += "\\" + c;
						}
						escape = false;
					} else if (c === "\\") {
						escape = true;
					} else {
						newStr += c;
					}
				}
				if (escape) {
					newStr += "\\";
				}
			}
			return newStr;
		},
		isVisible: function() {
			return this._visible;
		},
		replace: function() {
			var string = this.getFindString();
			if (string) {
				var editor = this._editor;
				var replaceString = this._processReplaceString(this.getReplaceString());
				var selection = editor.getSelection();
				var start = selection.start;
				var result = editor.getModel().find({
					string: string,
					start: start,
					reverse: false,
					wrap: this._wrap,
					regex: this._regex,
					wholeWord: this._wholeWord,
					caseInsensitive: this._caseInsensitive
				}).next();
				if (result) {
					this.startUndo();
					this._doReplace(result.start, result.end, string, replaceString);
					this.endUndo();
				}
			}
			if (this._findAfterReplace && string){
				this._doFind(string, this.getStartOffset());
			}
		},
		replaceAll : function() {
			var string = this.getFindString();
			if (string) {
				this._replacingAll = true;
				var editor = this._editor;
				var textView = editor.getTextView();
				editor.reportStatus(messages.replacingAll);
				var replaceString = this._processReplaceString(this.getReplaceString());
				var self = this;
				window.setTimeout(function() {
					var startPos = 0;
					var count = 0;
					while (true) {
						//For replace all, we need to ignore the wrap search from the user option
						//Otherwise the loop will be dead, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=411813
						var result = self._doFind(string, startPos, null, true);
						if (!result) {
							break;
						}
						count++;
						if (count === 1) {
							textView.setRedraw(false);
							self.startUndo();
						}
						self._doReplace(result.start, result.end, string, replaceString);
						startPos = self.getStartOffset();
					}
					if (count > 0) {
						self.endUndo();
						textView.setRedraw(true);
					}
					if (startPos > 0) {
						editor.reportStatus(util.formatMessage(messages.replacedMatches, count));
					} else {
						editor.reportStatus(messages.nothingReplaced, "error"); //$NON-NLS-0$ 
					}
					self._replacingAll = false;
				}, 100);				
			}
		},
		/**
		 * @property {String} string the search string to be found.
		 * @property {Boolean} [regex=false] whether or not the search string is a regular expression.
		 * @property {Boolean} [wrap=false] whether or not to wrap search.
		 * @property {Boolean} [wholeWord=false] whether or not to search only whole words.
		 * @property {Boolean} [caseInsensitive=false] whether or not search is case insensitive.
		 * @property {Boolean} [reverse=false] whether or not to search backwards.
		 * @property {Number} [start=0] The start offset to start searching
		 * @property {Number} [end=charCount] The end offset of the search. Used to search in a given range.	
		 */
		setOptions : function(options) {
			if (options) {
				if ((options.showAll === true || options.showAll === false) && this._showAll !== options.showAll) {
					this._showAll = options.showAll;
					if (this.isVisible()) {
						if (this._showAll) {
							this._markAllOccurrences();
						} else {
							var annotationModel = this._editor.getAnnotationModel();
							if (annotationModel) {
								annotationModel.removeAnnotations(mAnnotations.AnnotationType.ANNOTATION_MATCHING_SEARCH);
							}
						}
					}
				}
				if (options.caseInsensitive === true || options.caseInsensitive === false) {
					this._caseInsensitive = options.caseInsensitive;
				}
				if (options.wrap === true || options.wrap === false) {
					this._wrap = options.wrap;
				}
				if (options.wholeWord === true || options.wholeWord === false) {
					this._wholeWord = options.wholeWord;
				}
				if (options.incremental === true || options.incremental === false) {
					this._incremental = options.incremental;
				}
				if (options.regex === true || options.regex === false) {
					this._regex = options.regex;
				}
				if (options.findAfterReplace === true || options.findAfterReplace === false) {
					this._findAfterReplace = options.findAfterReplace;
				}
				if (options.hideAfterFind === true || options.hideAfterFind === false) {
					this._hideAfterFind = options.hideAfterFind;
				}
				if (options.reverse === true || options.reverse === false) {
					this._reverse = options.reverse;
				}
				if (options.hasOwnProperty("findCallback")) { //$NON-NLS-0$
					this._findCallback = options.findCallback;
				}
				if (options.hasOwnProperty("start")) { //$NON-NLS-0$	
					this._start = options.start;
				}
				if (options.hasOwnProperty("end")) { //$NON-NLS-0$
					this._end = options.end;
				}
			}
		},
		show: function(tempOptions) {
			this._visible = true;
			if (tempOptions) {
				if (!this._savedOptions) {
					this._savedOptions = [];
				}	
				this._savedOptions.push(this.getOptions());
				this.setOptions(tempOptions);
			}
			this._startOffset = this._editor.getSelection().start;
			this._editor.getTextView().addEventListener("Focus", this._listeners.onEditorFocus); //$NON-NLS-0$
			var self = this;
			window.setTimeout(function() {
				if (self._incremental) {
					self.find(true, null, true);
				}
			}, 0);
		},
		startUndo: function() {
			if (this._undoStack) {
				this._undoStack.startCompoundChange();
			}
		}, 
		endUndo: function() {
			if (this._undoStack) {
				this._undoStack.endCompoundChange();
			}
		},
		_find: function(string, startOffset, noWrap) {
			return this._editor.getModel().find({
				string: string,
				start: startOffset,
				end: this._end,
				reverse: this._reverse,
				wrap: (noWrap ? false: this._wrap),
				regex: this._regex,
				wholeWord: this._wholeWord,
				caseInsensitive: this._caseInsensitive
			});
		},
		_doFind: function(string, startOffset, count, noWrap) {
			count = count || 1;
			var editor = this._editor;
			if (!string) {
				this._removeAllAnnotations();
				return null;
			}
			this._lastString = string;
			var result, iterator;
			if (this._regex) {
				try {
					iterator = this._find(string, startOffset, noWrap);
				} catch (ex) {
					editor.reportStatus(ex.message, "error"); //$NON-NLS-0$
					return;
				}
			} else {
				iterator = this._find(string, startOffset, noWrap);
			}
			for (var i=0; i<count && iterator.hasNext(); i++) {
				result = iterator.next();
			}
			if (!this._replacingAll) {
				if (result) {
					this._editor.reportStatus("");
				} else {
					this._editor.reportStatus(messages.notFound, "error"); //$NON-NLS-0$
				}
				if (this.isVisible()) {
					var type = mAnnotations.AnnotationType.ANNOTATION_CURRENT_SEARCH;
					var annotationModel = editor.getAnnotationModel();
					if (annotationModel) {
						annotationModel.removeAnnotations(type);
						if (result) {
							annotationModel.addAnnotation(mAnnotations.AnnotationType.createAnnotation(type, result.start, result.end));
						}
					}
					if (this._showAll) {
						if (this._timer) {
							window.clearTimeout(this._timer);
						}
						var that = this;
						this._timer = window.setTimeout(function(){
							that._markAllOccurrences();
							that._timer = null;
						}, 500);
					}
				}
				if (this._findCallback) {
					this._findCallback(result);
				}
				else if (result) {
					editor.moveSelection(result.start, result.end, null, false);
				}
			}
			return result;
		},
		_doReplace: function(start, end, searchStr, newStr) {
			var editor = this._editor;
			if (this._regex) {
				newStr = editor.getText(start, end).replace(new RegExp(searchStr, this._caseInsensitive ? "i" : ""), newStr); //$NON-NLS-0$
			}
			editor.setText(newStr, start, end);
			editor.setSelection(start, start + newStr.length, true);
		},
		_markAllOccurrences: function() {
			var annotationModel = this._editor.getAnnotationModel();
			if (!annotationModel) {
				return;
			}
			var type = mAnnotations.AnnotationType.ANNOTATION_MATCHING_SEARCH;
			var iter = annotationModel.getAnnotations();
			var remove = [], add;
			while (iter.hasNext()) {
				var annotation = iter.next();
				if (annotation.type === type) {
					remove.push(annotation);
				}
			}
			if (this.isVisible()) {
				var string = this.getFindString();
				iter = this._editor.getModel().find({
					string: string,
					regex: this._regex,
					wholeWord: this._wholeWord,
					caseInsensitive: this._caseInsensitive
				});
				add = [];
				while (iter.hasNext()) {
					var range = iter.next();
					add.push(mAnnotations.AnnotationType.createAnnotation(type, range.start, range.end));
				}
			}
			annotationModel.replaceAnnotations(remove, add);
		},
		_removeAllAnnotations: function() {
			var annotationModel = this._editor.getAnnotationModel();
			if (annotationModel) {
				annotationModel.removeAnnotations(mAnnotations.AnnotationType.ANNOTATION_CURRENT_SEARCH);
				annotationModel.removeAnnotations(mAnnotations.AnnotationType.ANNOTATION_MATCHING_SEARCH);
			}
		},
		_removeCurrentAnnotation: function(evt){
			var annotationModel = this._editor.getAnnotationModel();
			if (annotationModel) {
				annotationModel.removeAnnotations(mAnnotations.AnnotationType.ANNOTATION_CURRENT_SEARCH);
			}
		}
	};
	exports.Find = Find;
	
	return exports;
});

/*******************************************************************************
 * @license
 * Copyright (c) 2014 IBM Corporation and others. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 * 
 * Contributors: 
 *	IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/editor/findUI',[
	'i18n!orion/editor/nls/messages',
	'orion/editor/find',
	'orion/objects',
	'orion/editor/util',
	'orion/util', 
], function(messages, mFind, objects, textUtil, util) {
	
	function FindUI(editor, undoStack, options) {
		mFind.Find.call(this, editor, undoStack, options);
	}
	
	FindUI.prototype = new mFind.Find();
	
	objects.mixin(FindUI.prototype, {
		getFindString: function() {
			var input = this._findInput;
			if (!input) {
				return mFind.Find.prototype.getFindString.call(this);
			}
			return input.value;
		},
		getReplaceString: function() {
			var input = this._replaceInput;
			if (!input) {
				return mFind.Find.prototype.getReplaceString(this);
			}
			return input.value;
		},
		hide: function() {
			var visible = this.isVisible();
			mFind.Find.prototype.hide.call(this);
			if (visible) {
				this._rootDiv.className = "textViewFind"; //$NON-NLS-0$
			}
		},
		show: function(options) {
			mFind.Find.prototype.show.call(this, options);
			var findString = options.findString;
			var replaceString = options.replaceString;
			var input = this._findInput;
			if (!input) {
				this._create();
				input = this._findInput;
			}
			if (findString) {
				input.value = findString;
			}
			if (replaceString) {
				var replaceInput = this._replaceInput;
				replaceInput.value = replaceString;
			}
			var that = this;
			window.setTimeout(function() {
				that._rootDiv.className = "textViewFind show"; //$NON-NLS-0$
				input.select();
				input.focus();
			}, 0);
		},
		_create: function() {
			var that = this;
			var view = this._editor.getTextView();
			var parent = view.getOptions("parent"); //$NON-NLS-0$
			var document = parent.ownerDocument; //$NON-NLS-0$
			var root = util.createElement(document, 'div'); //$NON-NLS-0$
			root.className = "textViewFind"; //$NON-NLS-0$
			textUtil.addEventListener(root, "keydown", function(e) { that._handleKeyDown(e); }); //$NON-NLS-0$
			this._rootDiv = root;
			this._createContents(document, root);
			view._rootDiv.insertBefore(root, view._rootDiv.firstChild);
		},
		_createContents: function(document, parent) {
			var that = this;
			var fintInput = util.createElement(document, 'input'); //$NON-NLS-0$
			fintInput.className = "textViewFindInput"; //$NON-NLS-0$
			this._findInput = fintInput;
			fintInput.type = "text"; //$NON-NLS-0$
			fintInput.placeholder = messages.findWith;
			textUtil.addEventListener(fintInput, "input", function(evt) { //$NON-NLS-0$
				return that._handleInput(evt);
			});
			parent.appendChild(fintInput);

			var group = util.createElement(document, 'span'); //$NON-NLS-0$
			that._createButton(document, group, messages.next, function() { that.find(true); });
			that._createButton(document, group, messages.previous, function() { that.find(false); });
			parent.appendChild(group);
			
			var readonly = that._editor.getTextView().getOptions("readonly"); //$NON-NLS-0$
			if (!readonly) {
				// create replace text
				var replaceInput = util.createElement(document, 'input'); //$NON-NLS-0$
				replaceInput.className = "textViewReplaceInput"; //$NON-NLS-0$
				this._replaceInput = replaceInput;
				replaceInput.type = "text"; //$NON-NLS-0$
				replaceInput.placeholder = messages.replaceWith;
				parent.appendChild(replaceInput);
				group = util.createElement(document, 'span'); //$NON-NLS-0$
				that._createButton(document, group, messages.replace, function() { that.replace(); });
				that._createButton(document, group, messages.replaceAll, function() { that.replaceAll(); });
				parent.appendChild(group);
			}

			group = util.createElement(document, 'span'); //$NON-NLS-0$
			that._createButton(document, group, messages.regex, function(evt) { that._toggle("regex", evt.target); }, this._regex, messages.regexTooltip); //$NON-NLS-0$
			that._createButton(document, group, messages.caseInsensitive, function(evt) { that._toggle("caseInsensitive", evt.target); }, this._caseInsensitive, messages.caseInsensitiveTooltip); //$NON-NLS-0$
			that._createButton(document, group, messages.wholeWord, function(evt) { that._toggle("wholeWord", evt.target); }, this._wholeWord, messages.wholeWordTooltip); //$NON-NLS-0$
			parent.appendChild(group);

			var close = that._createButton(document, parent, "", function() { that.hide(); }); //$NON-NLS-0$
			close.className = "textViewFindCloseButton"; //$NON-NLS-0$
			close.title = messages.closeTooltip;
		},
		_createButton: function(document, parent, text, callback, checked, tooltip) {
			var button  = document.createElement("button"); //$NON-NLS-0$
			this._checked(checked, button);
			if (tooltip) button.title = tooltip;
			textUtil.addEventListener(button, "click", function(evt) { callback.call(this, evt); }, false); //$NON-NLS-0$
			if (text) {
				button.appendChild(document.createTextNode(text)); //$NON-NLS-0$
			}
			parent.appendChild(button);
			return button;
		},
		_toggle: function(prop, button) {
			var options = {};
			options[prop] = !this["_" + prop]; //$NON-NLS-0$
			this.setOptions(options);
			this._checked(options[prop], button);
		},
		_checked: function(checked, button) {
			button.className = "textViewFindButton"; //$NON-NLS-0$
			if (checked) {
				button.className += " checked"; //$NON-NLS-0$
			}
		},
		_handleInput: function() {
			if (this._incremental) {
				this.find(true, null, true);
			}
			return true;
		},
		_handleKeyDown: function(evt) {
			var handled;
			var ctrlKeyOnly = (util.isMac ? evt.metaKey : evt.ctrlKey) && !evt.altKey && !evt.shiftKey;
			if (ctrlKeyOnly && evt.keyCode === 70/*"f"*/ ) {
				handled = true;
			}
			//We can't use ctrlKeyOnly on "k" because ctrl+shift+k means find previous match when the find bar gets focus
			if (((util.isMac ? evt.metaKey : evt.ctrlKey) && !evt.altKey && evt.keyCode === 75/*"k"*/) || evt.keyCode === 13/*enter*/){
				if (evt.keyCode === 13) {
					this.find(this._reverse ? evt.shiftKey : !evt.shiftKey);
				} else {
					this.find(!evt.shiftKey);
				}
				handled = true;
			}
			if (ctrlKeyOnly &&  evt.keyCode === 82 /*"r"*/){
				this.replace();
				handled = true;
			}
			if (evt.keyCode === 27/*ESC*/){
				this.hide();
				handled = true;
			}
			if (handled) {
				if (evt.stopPropagation) { 
					evt.stopPropagation(); 
				}
				if (evt.preventDefault) { 
					evt.preventDefault(); 
				}
				evt.cancelBubble = true;
				return false;
			}
			return true;
		}
	});

	return {FindUI: FindUI};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define("orion/editor/actions", [ //$NON-NLS-0$
	'i18n!orion/editor/nls/messages', //$NON-NLS-0$
	'orion/keyBinding', //$NON-NLS-0$
	'orion/editor/annotations', //$NON-NLS-0$
	'orion/editor/tooltip', //$NON-NLS-0$
	'orion/editor/find', //$NON-NLS-0$
	'orion/editor/findUI', //$NON-NLS-0$
	'orion/util' //$NON-NLS-0$
], function(messages, mKeyBinding, mAnnotations, mTooltip, mFind, mFindUI, util) {

	var AT = mAnnotations.AnnotationType;

	var exports = {};
	
	function forEachSelection(actions, compound, callback, noUndo) {
		var offset = 0;
		var editor = actions.editor;
		var selections = editor.getSelections();
		if (!noUndo && (compound || selections.length > 1)){ actions.startUndo(); }
		function setText(text, start, end) {
			editor.setText(text, start, end);
			offset += (start - end) + text.length;
		}
		selections.forEach(function(selection) {
			selection.start += offset;
			selection.end += offset;
			callback(selection, setText);
		});
		editor.setSelections(selections);
		if (!noUndo && (compound || selections.length > 1)){ actions.endUndo(); }
	}

	/**
	 * TextActions connects common text editing keybindings onto an editor.
	 */
	function TextActions(editor, undoStack, find) {
		this.editor = editor;
		this.undoStack = undoStack;
		this._incrementalFind = new mFind.IncrementalFind(editor);
		this._find = find ? find : new mFindUI.FindUI(editor, undoStack);
		this._lastEditLocation = null;
		this.init();
	}

	TextActions.prototype = {
		init: function() {
			var textView = this.editor.getTextView();

			this._lastEditListener = {
				onModelChanged: function(e) {
					if (this.editor.isDirty()) {
						this._lastEditLocation = e.start + e.addedCharCount;
					}
				}.bind(this)
			};
			textView.addEventListener("ModelChanged", this._lastEditListener.onModelChanged); //$NON-NLS-0$

			textView.setAction("undo", function(data) { //$NON-NLS-0$
				if (this.undoStack) {
					var count = 1;
					if (data && data.count) {
						count = data.count;
					}
					while (count > 0) {
						this.undoStack.undo();
						--count;
					}
					return true;
				}
				return false;
			}.bind(this), {name: messages.undo});

			textView.setAction("redo", function(data) { //$NON-NLS-0$
				if (this.undoStack) {
					var count = 1;
					if (data && data.count) {
						count = data.count;
					}
					while (count > 0) {
						this.undoStack.redo();
						--count;
					}
					return true;
				}
				return false;
			}.bind(this), {name: messages.redo});

			textView.setKeyBinding(new mKeyBinding.KeyBinding("f", true), "find"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("find", function() { //$NON-NLS-0$
				if (this._find) {
					var selection = this.editor.getSelection();
					this._find.show({findString:this.editor.getText(selection.start, selection.end)});
					return true;
				}
				return false;
			}.bind(this), {name: messages.find});

			textView.setKeyBinding(new mKeyBinding.KeyBinding("k", true), "findNext"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("findNext", function(options) { //$NON-NLS-0$
				if (this._find){
					this._find.find(true, options);
					return true;
				}
				return false;
			}.bind(this), {name: messages.findNext});

			textView.setKeyBinding(new mKeyBinding.KeyBinding("k", true, true), "findPrevious"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("findPrevious", function(options) { //$NON-NLS-0$
				if (this._find){
					this._find.find(false, options);
					return true;
				}
				return false;
			}.bind(this), {name: messages.findPrevious});

			textView.setKeyBinding(new mKeyBinding.KeyBinding("j", true), "incrementalFind"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("incrementalFind", function() { //$NON-NLS-0$
				if (this._incrementalFind) {
					this._incrementalFind.find(true);
				}
				return true;
			}.bind(this), {name: messages.incrementalFind});

			textView.setKeyBinding(new mKeyBinding.KeyBinding("j", true, true), "incrementalFindReverse"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("incrementalFindReverse", function() { //$NON-NLS-0$
				if (this._incrementalFind) {
					this._incrementalFind.find(false);
				}
				return true;
			}.bind(this), {name: messages.incrementalFindReverse});

			textView.setAction("tab", function() { //$NON-NLS-0$
				return this.indentLines();
			}.bind(this));

			textView.setAction("shiftTab", function() { //$NON-NLS-0$
				return this.unindentLines();
			}.bind(this), {name: messages.unindentLines});

			textView.setKeyBinding(new mKeyBinding.KeyBinding(38, false, false, true), "moveLinesUp"); //$NON-NLS-0$
			textView.setAction("moveLinesUp", function() { //$NON-NLS-0$
				return this.moveLinesUp();
			}.bind(this), {name: messages.moveLinesUp});

			textView.setKeyBinding(new mKeyBinding.KeyBinding(40, false, false, true), "moveLinesDown"); //$NON-NLS-0$
			textView.setAction("moveLinesDown", function() { //$NON-NLS-0$
				return this.moveLinesDown();
			}.bind(this), {name: messages.moveLinesDown});

			textView.setKeyBinding(new mKeyBinding.KeyBinding(38, true, false, true), "copyLinesUp"); //$NON-NLS-0$
			textView.setAction("copyLinesUp", function() { //$NON-NLS-0$
				return this.copyLinesUp();
			}.bind(this), {name: messages.copyLinesUp});

			textView.setKeyBinding(new mKeyBinding.KeyBinding(40, true, false, true), "copyLinesDown"); //$NON-NLS-0$
			textView.setAction("copyLinesDown", function() { //$NON-NLS-0$
				return this.copyLinesDown();
			}.bind(this), {name: messages.copyLinesDown});

			textView.setKeyBinding(new mKeyBinding.KeyBinding('d', true, false, false), "deleteLines"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("deleteLines", function(data) { //$NON-NLS-0$
				return this.deleteLines(data);
			}.bind(this), {name: messages.deleteLines});

			textView.setKeyBinding(new mKeyBinding.KeyBinding("l", !util.isMac, false, false, util.isMac), "gotoLine"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("gotoLine", function() { //$NON-NLS-0$
				return this.gotoLine();
			}.bind(this), {name: messages.gotoLine});

			textView.setKeyBinding(new mKeyBinding.KeyBinding(190, true), "nextAnnotation"); //$NON-NLS-0$
			textView.setAction("nextAnnotation", function() { //$NON-NLS-0$
				return this.nextAnnotation(true);
			}.bind(this), {name: messages.nextAnnotation});

			textView.setKeyBinding(new mKeyBinding.KeyBinding(188, true), "previousAnnotation"); //$NON-NLS-0$
			textView.setAction("previousAnnotation", function() { //$NON-NLS-0$
				return this.nextAnnotation(false);
			}.bind(this), {name: messages.prevAnnotation});

			textView.setKeyBinding(new mKeyBinding.KeyBinding("e", true, false, true, false), "expand"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("expand", function() { //$NON-NLS-0$
				return this.expandAnnotation(true);
			}.bind(this), {name: messages.expand});

			textView.setKeyBinding(new mKeyBinding.KeyBinding("c", true, false, true, false), "collapse"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("collapse", function() { //$NON-NLS-0$
				return this.expandAnnotation(false);
			}.bind(this), {name: messages.collapse});

			textView.setKeyBinding(new mKeyBinding.KeyBinding("e", true, true, true, false), "expandAll"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("expandAll", function() { //$NON-NLS-0$
				return this.expandAnnotations(true);
			}.bind(this), {name: messages.expandAll});

			textView.setKeyBinding(new mKeyBinding.KeyBinding("c", true, true, true, false), "collapseAll"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("collapseAll", function() { //$NON-NLS-0$
				return this.expandAnnotations(false);
			}.bind(this), {name: messages.collapseAll});

			textView.setKeyBinding(new mKeyBinding.KeyBinding("q", !util.isMac, false, false, util.isMac), "lastEdit"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("lastEdit", function() { //$NON-NLS-0$
				return this.gotoLastEdit();
			}.bind(this), {name: messages.lastEdit});
		},
		copyLinesDown: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var model = editor.getModel();
			forEachSelection(this, false, function(selection, setText) {
				var firstLine = model.getLineAtOffset(selection.start);
				var lastLine = model.getLineAtOffset(selection.end > selection.start ? selection.end - 1 : selection.end);
				var lineStart = model.getLineStart(firstLine);
				var lineEnd = model.getLineEnd(lastLine, true);
				var lineCount = model.getLineCount();
				var delimiter = "";
				var text = model.getText(lineStart, lineEnd);
				if (lastLine === lineCount-1) {
					text = (delimiter = model.getLineDelimiter()) + text;
				}
				var insertOffset = lineEnd;
				setText(text, insertOffset, insertOffset);
				selection.start = insertOffset + delimiter.length;
				selection.end = insertOffset + text.length;
			});
			return true;
		},
		copyLinesUp: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var model = editor.getModel();
			forEachSelection(this, false, function(selection, setText) {
				var firstLine = model.getLineAtOffset(selection.start);
				var lastLine = model.getLineAtOffset(selection.end > selection.start ? selection.end - 1 : selection.end);
				var lineStart = model.getLineStart(firstLine);
				var lineEnd = model.getLineEnd(lastLine, true);
				var lineCount = model.getLineCount();
				var delimiter = "";
				var text = model.getText(lineStart, lineEnd);
				if (lastLine === lineCount-1) {
					text += (delimiter = model.getLineDelimiter());
				}
				var insertOffset = lineStart;
				setText(text, insertOffset, insertOffset);
				selection.start = insertOffset;
				selection.end = insertOffset + text.length - delimiter.length;
			});
			return true;
		},
		deleteLines: function(data) {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var count = 1;
			if (data && data.count) {
				count = data.count;
			}
			var model = editor.getModel();
			forEachSelection(this, false, function(selection, setText) {
				var firstLine = model.getLineAtOffset(selection.start);
				var lineStart = model.getLineStart(firstLine);
				var lastLine;
				if (selection.start !== selection.end || count === 1) {
					lastLine = model.getLineAtOffset(selection.end > selection.start ? selection.end - 1 : selection.end);
				} else {
					lastLine = Math.min(firstLine + count - 1, model.getLineCount() - 1);
				}	
				var lineEnd = model.getLineEnd(lastLine, true);
				setText("", lineStart, lineEnd);
				selection.start = selection.end = lineStart;
			});
			return true;
		},
		expandAnnotation: function(expand) {
			var editor = this.editor;
			var annotationModel = editor.getAnnotationModel();
			if(!annotationModel) { return true; }
			var model = editor.getModel();
			var currentOffset = editor.getCaretOffset();
			var lineIndex = model.getLineAtOffset(currentOffset);
			var start = model.getLineStart(lineIndex);
			var end = model.getLineEnd(lineIndex, true);
			if (model.getBaseModel) {
				start = model.mapOffset(start);
				end = model.mapOffset(end);
				model = model.getBaseModel();
			}
			var annotation, iter = annotationModel.getAnnotations(start, end);
			while (!annotation && iter.hasNext()) {
				var a = iter.next();
				if (a.type !== mAnnotations.AnnotationType.ANNOTATION_FOLDING) { continue; }
				annotation = a;
			}
			if (annotation) {
				if (expand !== annotation.expanded) {
					if (expand) {
						annotation.expand();
					} else {
						editor.setCaretOffset(annotation.start);
						annotation.collapse();
					}
				}
			}
			return true;
		},
		expandAnnotations: function(expand) {
			var editor = this.editor;
			var textView = editor.getTextView();
			var annotationModel = editor.getAnnotationModel();
			if(!annotationModel) { return true; }
			var annotation, iter = annotationModel.getAnnotations();
			textView.setRedraw(false);
			while (iter.hasNext()) {
				annotation = iter.next();
				if (annotation.type !== mAnnotations.AnnotationType.ANNOTATION_FOLDING) { continue; }
				if (expand !== annotation.expanded) {
					if (expand) {
						annotation.expand();
					} else {
						annotation.collapse();
					}
				}
			}
			textView.setRedraw(true);
			return true;
		},
		indentLines: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			if(!textView.getOptions("tabMode")) { return; } //$NON-NLS-0$
			var model = editor.getModel();
			var offset = 0;
			var selections = editor.getSelections();
			if (selections.length > 1) { this.startUndo(); }
			if (selections.some(
				function(selection) {
					selection.start += offset;
					selection.end += offset;
					var firstLine = model.getLineAtOffset(selection.start);
					var lastLine = model.getLineAtOffset(selection.end > selection.start ? selection.end - 1 : selection.end);
					if (firstLine !== lastLine || selections.length > 1) {
						var lines = [];
						lines.push("");
						for (var i = firstLine; i <= lastLine; i++) {
							lines.push(model.getLine(i, true));
						}
						var lineStart = model.getLineStart(firstLine);
						var lineEnd = model.getLineEnd(lastLine, true);
						var options = textView.getOptions("tabSize", "expandTab"); //$NON-NLS-1$ //$NON-NLS-2$
						var tab = options.expandTab ? new Array(options.tabSize + 1).join(" ") : "\t"; //$NON-NLS-1$ //$NON-NLS-2$
						var text = lines.join(tab);
						editor.setText(text, lineStart, lineEnd);
						var start = lineStart === selection.start ? selection.start : selection.start + tab.length;
						var end = selection.end + ((lastLine - firstLine + 1) * tab.length);
						offset += (lineStart - lineEnd) + text.length;
						selection.start = start;
						selection.end = end;
					} else {
						return true;
					}
					return false;
				}
			)){
				return false;
			}
			editor.setSelections(selections);
			if (selections.length > 1) { this.endUndo(); }
			return true;
		},
		gotoLastEdit: function() {
			if (typeof this._lastEditLocation === "number")  { //$NON-NLS-0$
				this.editor.showSelection(this._lastEditLocation);
			}
			return true;
		},
		gotoLine: function() {
			var editor = this.editor;
			var model = editor.getModel();
			var line = model.getLineAtOffset(editor.getCaretOffset());
			line = prompt(messages.gotoLinePrompty, line + 1);
			if (line) {
				line = parseInt(line, 10);
				editor.onGotoLine(line - 1, 0);
			}
			return true;
		},
		moveLinesDown: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var model = editor.getModel();
			forEachSelection(this, true, function(selection, setText) {
				var firstLine = model.getLineAtOffset(selection.start);
				var lastLine = model.getLineAtOffset(selection.end > selection.start ? selection.end - 1 : selection.end);
				var lineCount = model.getLineCount();
				if (lastLine !== lineCount-1) {
					var lineStart = model.getLineStart(firstLine);
					var lineEnd = model.getLineEnd(lastLine, true);
					var insertOffset = model.getLineEnd(lastLine+1, true) - (lineEnd - lineStart);
					var text, delimiterLength = 0;
					if (lastLine !== lineCount-2) {
						text = model.getText(lineStart, lineEnd);
					} else {
						// Move delimiter following selection to front of the text
						var lineEndNoDelimiter = model.getLineEnd(lastLine);
						text = model.getText(lineEndNoDelimiter, lineEnd) + model.getText(lineStart, lineEndNoDelimiter);
						delimiterLength += lineEnd - lineEndNoDelimiter;
					}
					setText("", lineStart, lineEnd);
					setText(text, insertOffset, insertOffset);
					selection.start = insertOffset + delimiterLength;
					selection.end = insertOffset + delimiterLength + text.length;
				}
			});
			return true;
		},
		moveLinesUp: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var model = editor.getModel();
			forEachSelection(this, true, function(selection, setText) {
				var firstLine = model.getLineAtOffset(selection.start);
				if (firstLine !== 0) {
					var lastLine = model.getLineAtOffset(selection.end > selection.start ? selection.end - 1 : selection.end);
					var lineCount = model.getLineCount();
					var insertOffset = model.getLineStart(firstLine - 1);
					var lineStart = model.getLineStart(firstLine);
					var lineEnd = model.getLineEnd(lastLine, true);
					var text = model.getText(lineStart, lineEnd);
					var delimiterLength = 0;
					if (lastLine === lineCount-1) {
						// Move delimiter preceding selection to end of text
						var delimiterStart = model.getLineEnd(firstLine - 1);
						var delimiterEnd = model.getLineEnd(firstLine - 1, true);
						text += model.getText(delimiterStart, delimiterEnd);
						lineStart = delimiterStart;
						delimiterLength = delimiterEnd - delimiterStart;
					}
					setText("", lineStart, lineEnd);
					setText(text, insertOffset, insertOffset);
					selection.start = insertOffset;
					selection.end = insertOffset + text.length - delimiterLength;
				}
			});
			return true;
		},
		nextAnnotation: function (forward) {
			var editor = this.editor;
			var annotationModel = editor.getAnnotationModel();
			if (!annotationModel) { return true; }
			var list = editor.getOverviewRuler() || editor.getAnnotationStyler();
			if (!list) { return true; }
			function ignore(annotation) {
				return !!annotation.lineStyle ||
					annotation.type === AT.ANNOTATION_MATCHING_BRACKET ||
					annotation.type === AT.ANNOTATION_CURRENT_BRACKET ||
					!list.isAnnotationTypeVisible(annotation.type);
			}
			var model = editor.getModel();
			var currentOffset = editor.getCaretOffset();
			var annotations = annotationModel.getAnnotations(forward ? currentOffset : 0, forward ? model.getCharCount() : currentOffset);
			var foundAnnotation = null;
			while (annotations.hasNext()) {
				var annotation = annotations.next();
				if (forward) {
					if (annotation.start <= currentOffset) { continue; }
				} else {
					if (annotation.start >= currentOffset) { continue; }
				}
				if (ignore(annotation)) {
					continue;
				}
				foundAnnotation = annotation;
				if (forward) {
					break;
				}
			}
			if (foundAnnotation) {
				var foundAnnotations = [foundAnnotation];
				annotations = annotationModel.getAnnotations(foundAnnotation.start, foundAnnotation.start);
				while (annotations.hasNext()) {
					annotation = annotations.next();
					if (annotation !== foundAnnotation && !ignore(annotation)) {
						foundAnnotations.push(annotation);
					}
				}
				var view = editor.getTextView();
				var nextLine = model.getLineAtOffset(foundAnnotation.start);
				var tooltip = mTooltip.Tooltip.getTooltip(view);
				if (!tooltip) {
					editor.moveSelection(foundAnnotation.start);
					return true;
				}
				editor.moveSelection(foundAnnotation.start, foundAnnotation.start, function() {
					setTimeout(function() {
						var tv = editor.getTextView();
						var offset = foundAnnotation.start;
						var pos = tv.getLocationAtOffset(offset);
						tooltip.show({
							x: pos.x,
							y: pos.y,
							getTooltipInfo: function() {
								return editor._getTooltipInfo(this.x, this.y);
							}
						}, false, false);						
					}, 0);
				});
			}
			return true;
		},
		unindentLines: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			if(!textView.getOptions("tabMode")) { return; } //$NON-NLS-0$
			var model = editor.getModel();
			forEachSelection(this, false, function(selection, setText) {
				var firstLine = model.getLineAtOffset(selection.start);
				var lastLine = model.getLineAtOffset(selection.end > selection.start ? selection.end - 1 : selection.end);
				var tabSize = textView.getOptions("tabSize"); //$NON-NLS-0$
				var spaceTab = new Array(tabSize + 1).join(" "); //$NON-NLS-0$
				var lines = [], removeCount = 0, firstRemoveCount = 0;
				for (var i = firstLine; i <= lastLine; i++) {
					var line = model.getLine(i, true);
					if (model.getLineStart(i) !== model.getLineEnd(i)) {
						if (line.indexOf("\t") === 0) { //$NON-NLS-0$
							line = line.substring(1);
							removeCount++;
						} else if (line.indexOf(spaceTab) === 0) {
							line = line.substring(tabSize);
							removeCount += tabSize;
						} else {
							return true;
						}
					}
					if (i === firstLine) {
						firstRemoveCount = removeCount;
					}
					lines.push(line);
				}
				var lineStart = model.getLineStart(firstLine);
				var lineEnd = model.getLineEnd(lastLine, true);
				var lastLineStart = model.getLineStart(lastLine);
				var text = lines.join("");
				setText(text, lineStart, lineEnd);
				var start = lineStart === selection.start ? selection.start : selection.start - firstRemoveCount;
				var end = Math.max(start, selection.end - removeCount + (selection.end === lastLineStart+1 && selection.start !== selection.end ? 1 : 0));
				selection.start = start;
				selection.end = end;
			});
			return true;
		},
		startUndo: function() {
			if (this.undoStack) {
				this.undoStack.startCompoundChange();
			}
		},
		endUndo: function() {
			if (this.undoStack) {
				this.undoStack.endCompoundChange();
			}
		}
	};
	exports.TextActions = TextActions;

	/**
	 * @param {orion.editor.Editor} editor
	 * @param {orion.editor.UndoStack} undoStack
	 * @param {orion.editor.ContentAssist} [contentAssist]
	 * @param {orion.editor.LinkedMode} [linkedMode]
	 */
	function SourceCodeActions(editor, undoStack, contentAssist, linkedMode) {
		this.editor = editor;
		this.undoStack = undoStack;
		this.contentAssist = contentAssist;
		this.linkedMode = linkedMode;
		if (this.contentAssist) {
			this.contentAssist.addEventListener("ProposalApplied", this.contentAssistProposalApplied.bind(this)); //$NON-NLS-0$
		}
		this.init();
	}
	SourceCodeActions.prototype = {
		init: function() {
			var textView = this.editor.getTextView();

			textView.setAction("lineStart", function() { //$NON-NLS-0$
				return this.lineStart();
			}.bind(this));

			textView.setAction("enter", function() { //$NON-NLS-0$
				return this.autoIndent();
			}.bind(this));

			textView.setKeyBinding(new mKeyBinding.KeyBinding("t", true, false, true), "trimTrailingWhitespaces"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("trimTrailingWhitespaces", function() { //$NON-NLS-0$
				return this.trimTrailingWhitespaces();
			}.bind(this), {name: messages.trimTrailingWhitespaces});

			textView.setKeyBinding(new mKeyBinding.KeyBinding(191, true), "toggleLineComment"); //$NON-NLS-0$
			textView.setAction("toggleLineComment", function() { //$NON-NLS-0$
				return this.toggleLineComment();
			}.bind(this), {name: messages.toggleLineComment});

			textView.setKeyBinding(new mKeyBinding.KeyBinding(191, true, !util.isMac, false, util.isMac), "addBlockComment"); //$NON-NLS-0$
			textView.setAction("addBlockComment", function() { //$NON-NLS-0$
				return this.addBlockComment();
			}.bind(this), {name: messages.addBlockComment});

			textView.setKeyBinding(new mKeyBinding.KeyBinding(220, true, !util.isMac, false, util.isMac), "removeBlockComment"); //$NON-NLS-0$
			textView.setAction("removeBlockComment", function() { //$NON-NLS-0$
				return this.removeBlockComment();
			}.bind(this), {name: messages.removeBlockComment});

			// Autocomplete square brackets []
			textView.setKeyBinding(new mKeyBinding.KeyBinding("[", false, false, false, false, "keypress"), "autoPairSquareBracket"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("autoPairSquareBracket", function() { //$NON-NLS-0$
				return this.autoPairBrackets("[", "]"); //$NON-NLS-1$ //$NON-NLS-2$
			}.bind(this));

			textView.setKeyBinding(new mKeyBinding.KeyBinding(']', false, false, false, false, "keypress"), "skipClosingSquareBracket"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("skipClosingSquareBracket", function() { //$NON-NLS-0$
				return this.handleClosingBracket(']'); //$NON-NLS-0$
			}.bind(this));

			// Autocomplete angle brackets <>
			textView.setKeyBinding(new mKeyBinding.KeyBinding("<", false, false, false, false, "keypress"), "autoPairAngleBracket"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("autoPairAngleBracket", function() { //$NON-NLS-0$
				return this.autoPairBrackets("<", ">"); //$NON-NLS-1$ //$NON-NLS-2$
			}.bind(this));

			textView.setKeyBinding(new mKeyBinding.KeyBinding('>', false, false, false, false, "keypress"), "skipClosingAngleBracket"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("skipClosingAngleBracket", function() { //$NON-NLS-0$
				return this.handleClosingBracket('>'); //$NON-NLS-0$
			}.bind(this));

			// Autocomplete parentheses ()
			textView.setKeyBinding(new mKeyBinding.KeyBinding("(", false, false, false, false, "keypress"), "autoPairParentheses"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("autoPairParentheses", function() { //$NON-NLS-0$
				return this.autoPairBrackets("(", ")"); //$NON-NLS-1$ //$NON-NLS-2$
			}.bind(this));

			textView.setKeyBinding(new mKeyBinding.KeyBinding(')', false, false, false, false, "keypress"), "skipClosingParenthesis"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("skipClosingParenthesis", function() { //$NON-NLS-0$
				return this.handleClosingBracket(")"); //$NON-NLS-0$
			}.bind(this));

			// Autocomplete braces {}
			textView.setKeyBinding(new mKeyBinding.KeyBinding("{", false, false, false, false, "keypress"), "autoPairBraces"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("autoPairBraces", function() { //$NON-NLS-0$
				return this.autoPairBrackets("{", "}"); //$NON-NLS-1$ //$NON-NLS-2$
			}.bind(this));

			textView.setKeyBinding(new mKeyBinding.KeyBinding('}', false, false, false, false, "keypress"), "skipClosingBrace"); //$NON-NLS-1$ //$NON-NLS-2$
			textView.setAction("skipClosingBrace", function() { //$NON-NLS-0$
				return this.handleClosingBracket("}"); //$NON-NLS-0$
			}.bind(this));

			// Autocomplete single quotations
			textView.setKeyBinding(new mKeyBinding.KeyBinding("'", false, false, false, false, "keypress"), "autoPairSingleQuotation"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			textView.setAction("autoPairSingleQuotation", function() { //$NON-NLS-0$
				return this.autoPairQuotations("'"); //$NON-NLS-1$ //$NON-NLS-2$
			}.bind(this));

			// Autocomplete double quotations
			textView.setKeyBinding(new mKeyBinding.KeyBinding('"', false, false, false, false, "keypress"), "autoPairDblQuotation"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			textView.setAction("autoPairDblQuotation", function() { //$NON-NLS-0$
				return this.autoPairQuotations('"'); //$NON-NLS-1$ //$NON-NLS-2$
			}.bind(this));

			textView.setAction("deletePrevious", function() { //$NON-NLS-0$
				return this.deletePrevious();
			}.bind(this));
		},
		autoIndent: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			if (textView.getOptions("singleMode")) { return false; } //$NON-NLS-0$
			var model = editor.getModel();
			var options = textView.getOptions("tabSize", "expandTab"); //$NON-NLS-1$ //$NON-NLS-2$
			var tab = options.expandTab ? new Array(options.tabSize + 1).join(" ") : "\t"; //$NON-NLS-1$ //$NON-NLS-2$
			var lineDelimiter = model.getLineDelimiter();
			var matchCommentStart = /^[\s]*\/\*[\*]*[\s]*$/;
			var matchCommentDelimiter = /^[\s]*\*/;
			var matchCommentEnd = /\*\/[\s]*$/;
			var self = this;
			function indent(selection, setText) {
				if (selection.start === selection.end) {
					var lineIndex = model.getLineAtOffset(selection.start);
					var lineText = model.getLine(lineIndex, false);
					var lineStart = model.getLineStart(lineIndex);
					var index = 0;
					var lineOffset = selection.start - lineStart;
					var c;
					while (index < lineOffset && ((c = lineText.charCodeAt(index)) === 32 || c === 9)) { index++; }
					var prefix = lineText.substring(0, index);
					var lineTextBeforeCaret = lineText.substring(0, lineOffset);
					var lineTextAfterCaret = lineText.substring(lineOffset);
					var text;
					// If the character before the caret is an opening brace, smart indent the next line.
					var prevCharIdx;
					if (self.smartIndentation && lineText.charCodeAt(prevCharIdx = lineTextBeforeCaret.trimRight().length - 1) === 123) {
						// Remove any extra whitespace
						var whitespaceBeforeCaret = lineOffset - prevCharIdx - 1;
						var whitespaceAfterCaret = lineTextAfterCaret.length - lineTextAfterCaret.trimLeft().length;
	
						text = lineText.charCodeAt(lineOffset + whitespaceAfterCaret) === 125 ?
							   lineDelimiter + prefix + tab + lineDelimiter + prefix :
							   lineDelimiter + prefix + tab;
	
						setText(text, selection.start - whitespaceBeforeCaret, selection.end + whitespaceAfterCaret);
						selection.start = selection.end = selection.start + lineDelimiter.length + prefix.length + tab.length - whitespaceBeforeCaret;
						return true;
					// Proceed with autocompleting multi-line comment if the text before the caret matches
					// the start or comment delimiter (*) of a multi-line comment
					} else if (self.autoCompleteComments && !matchCommentEnd.test(lineTextBeforeCaret) &&
								(matchCommentStart.test(lineTextBeforeCaret) || matchCommentDelimiter.test(lineTextBeforeCaret))) {
						var caretOffset;
	
						/**
						 * Matches the start of a multi-line comment. Autocomplete the multi-line block comment,
						 * moving any text after the caret into the block comment and setting the caret to be
						 * after the comment delimiter.
						 */
						var match = matchCommentStart.exec(lineTextBeforeCaret);
						if (match) {
							text = lineDelimiter + prefix + " * "; //$NON-NLS-0$
							// Text added into the comment block are trimmed of all preceding and trailing whitespaces.
							// If the text after the caret contains the ending of a block comment, exclude the ending.
							if (matchCommentEnd.test(lineTextAfterCaret)) {
								text += lineTextAfterCaret.substring(0, lineTextAfterCaret.length - 2).trim();
							} else {
								text += lineTextAfterCaret.trim();
							}
							// Add the closing to the multi-line block comment if the next line is not a
							// comment delimiter.
							if ((model.getLineCount() === lineIndex + 1) ||
								!matchCommentDelimiter.test(model.getLine(lineIndex + 1))) {
								text += lineDelimiter + prefix + " */"; //$NON-NLS-0$
							}
							setText(text, selection.start, selection.end + lineTextAfterCaret.length);
							selection.start = selection.end = selection.start + lineDelimiter.length + prefix.length + 3;
							return true;
						}
	
						/**
						 * Matches a comment delimiter (*) as the start of the line, and traverses up the lines to confirm if
						 * it is a multi-line comment by matching the start of a block comment. If so, continue the
						 * multi-line comment in the next line. Any text that follows after the caret is moved to the newly
						 * added comment delimiter.
						 */
						match = matchCommentDelimiter.exec(lineTextBeforeCaret);
						if (match) {
							for (var i = lineIndex - 1; i >= 0; i--) {
								var prevLine = model.getLine(i, false);
								if (matchCommentStart.test(prevLine)) {
									/**
									 * If the text after the caret matches the end of a comment block or the character in front of the
									 * caret is a forward slash, continue the block comment with the caret and text after the caret on
									 * the next line directly in front of the star (*).
									 */
									if (matchCommentEnd.test(lineTextAfterCaret) || lineText.charCodeAt(lineOffset) === 47) {
										text = lineDelimiter + prefix + "*" + lineTextAfterCaret; //$NON-NLS-0$
										caretOffset = selection.start + lineDelimiter.length + prefix.length + 1;
									} else {
										text = lineDelimiter + prefix + "* " + lineTextAfterCaret; //$NON-NLS-0$
										caretOffset = selection.start + lineDelimiter.length + prefix.length + 2;
									}
									setText(text, selection.start, selection.end + lineTextAfterCaret.length);
									selection.start = selection.end = caretOffset;
									return true;
								} else if (!matchCommentDelimiter.test(prevLine)) {
									return false;
								}
							}
						}
						return false;
					} else if (matchCommentEnd.test(lineTextBeforeCaret) && prefix.charCodeAt(prefix.length - 1) === 32) {
						// Matches the end of a block comment. Fix the indentation for the following line.
						text = lineDelimiter + prefix.substring(0, prefix.length - 1);
						setText(text, selection.start, selection.end);
						selection.start = selection.end = selection.start + text.length;
						return true;
					} else if (index > 0) {
						//TODO still wrong when typing inside folding
						index = lineOffset;
						while (index < lineText.length && ((c = lineText.charCodeAt(index++)) === 32 || c === 9)) { selection.end++; }
						text = model.getLineDelimiter() + prefix;
						setText(text, selection.start, selection.end);
						selection.start = selection.end = selection.start + text.length;
						return true;
					}
				}
				return false;
			}
			forEachSelection(this, true, function(selection, setText) {
				if (!indent(selection, setText)) {
					setText(lineDelimiter, selection.start, selection.end);
					selection.start = selection.end = selection.start + lineDelimiter.length;
				}
			});
			return true;
		},
		addBlockComment: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var model = editor.getModel();
			var open = "/*", close = "*/", commentTags = new RegExp("/\\*" + "|" + "\\*/", "g"); //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-2$
			var self = this;
			forEachSelection(this, false, function(selection, setText) {
				var result = self._findEnclosingComment(model, selection.start, selection.end);
				if (result.commentStart !== undefined && result.commentEnd !== undefined) {
					return; // Already in a comment
				}
	
				var text = model.getText(selection.start, selection.end);
				if (text.length === 0) { return; }
	
				var oldLength = text.length;
				text = text.replace(commentTags, "");
				var newLength = text.length;
				text = open + text + close;
	
				setText(text, selection.start, selection.end);
				selection.start += open.length;
				selection.end +=  open.length + (newLength-oldLength);
			});
			return true;
		},
		/**
		 * Called on an opening bracket keypress.
		 * Automatically inserts the specified opening and closing brackets around the caret or selected text.
		 */
		autoPairBrackets: function(openBracket, closeBracket) {
			if (openBracket === "[" && !this.autoPairSquareBrackets) { //$NON-NLS-0$
				return false;
			} else if (openBracket === "{" && !this.autoPairBraces) { //$NON-NLS-0$
				return false;
			} else if (openBracket === "(" && !this.autoPairParentheses) { //$NON-NLS-0$
				return false;
			} else if (openBracket === "<" && !this.autoPairAngleBrackets) { //$NON-NLS-0$
				return false;
			}

			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var isClosingBracket = new RegExp("^$|[)}\\]>]"); //$NON-NLS-0$ // matches any empty string and closing bracket
			var model = editor.getModel();
			forEachSelection(this, false, function(selection, setText) {
				var nextChar = (selection.start === model.getCharCount()) ? "" : model.getText(selection.start, selection.start + 1).trim(); //$NON-NLS-0$
				var text;
				if (selection.start === selection.end && isClosingBracket.test(nextChar)) {
					// No selection and subsequent character is not a closing bracket - wrap the caret with the opening and closing brackets,
					// and maintain the caret position inbetween the brackets
					text = openBracket + closeBracket;
					setText(text, selection.start, selection.start);
					selection.start = selection.end = selection.start + 1;
				} else if (selection.start !== selection.end) {
					// Wrap the selected text with the specified opening and closing brackets and keep selection on text
					text = openBracket + model.getText(selection.start, selection.end) + closeBracket;
					setText(text, selection.start, selection.end);
					selection.start += 1;
					selection.end += 1;
				} else {
					setText(openBracket, selection.start, selection.end);
					selection.start = selection.end = selection.start + openBracket.length;
				}
			});
			return true;
		},
		/**
		 * Called on a quotation mark keypress.
		 * Automatically inserts a pair of the specified quotation around the caret the caret or selected text.
		 */
		autoPairQuotations: function(quotation) {
			if (!this.autoPairQuotation) { return false; }
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var isQuotation = new RegExp("^\"$|^'$"); //$NON-NLS-0$
			var isAlpha = new RegExp("\\w"); //$NON-NLS-0$
			var isClosingBracket = new RegExp("^$|[)}\\]>]"); //$NON-NLS-0$ // matches any empty string and closing bracket
			var model = editor.getModel();
			forEachSelection(this, false, function(selection, setText) {
				var prevChar = (selection.start === 0) ? "" : model.getText(selection.start - 1, selection.start).trim(); //$NON-NLS-0$
				var nextChar = (selection.start === model.getCharCount()) ? "" : model.getText(selection.start, selection.start + 1).trim(); //$NON-NLS-0$
				function insertQuotation() {
					setText(quotation, selection.start, selection.end);
					selection.start = selection.end = selection.start + quotation.length;
				}
				// Wrap the selected text with the specified opening and closing quotation marks and keep selection on text
				if (selection.start !== selection.end) {
					var text = model.getText(selection.start, selection.end);
					if (isQuotation.test(text)) {
						insertQuotation();
					} else {
						setText(quotation + text + quotation, selection.start, selection.end);
						selection.start += 1;
						selection.end += 1;
					}
				} else if (nextChar === quotation) {
					// Skip over the next character if it matches the specified quotation mark
					selection.start = selection.end = selection.start + 1;
				} else if (prevChar === quotation || isQuotation.test(nextChar) || isAlpha.test(prevChar) || !isClosingBracket.test(nextChar)) {
					insertQuotation();
				} else {
					// No selection - wrap the caret with the opening and closing quotation marks, and maintain the caret position inbetween the quotations
					setText(quotation + quotation, selection.start, selection.end);
					selection.start = selection.end = selection.start + quotation.length;
				}
			});
			return true;
		},
		/**
		 * Called when a content assist proposal has been applied. Inserts the proposal into the
		 * document. Activates Linked Mode if applicable for the selected proposal.
		 * @param {orion.editor.ContentAssist#ProposalAppliedEvent} event
		 */
		contentAssistProposalApplied: function(event) {
			/*
			 * The event.proposal is an object with this shape:
			 * {   proposal: "[proposal string]", // Actual text of the proposal
			 *     description: "diplay string", // Optional
			 *     positions: [{
			 *         offset: 10, // Offset of start position of parameter i
			 *         length: 3  // Length of parameter string for parameter i
			 *     }], // One object for each parameter; can be null
			 *     escapePosition: 19, // Optional; offset that caret will be placed at after exiting Linked Mode.
			 *     style: 'emphasis', // Optional: either emphasis, noemphasis, hr to provide custom styling for the proposal
			 *     unselectable: false // Optional: if set to true, then this proposal cannnot be selected through the keyboard
			 * }
			 * Offsets are relative to the text buffer.
			 */
			var proposal = event.data.proposal;

			// If escapePosition is not provided, positioned the cursor at the end of the inserted text 
			function escapePosition() {
				if (typeof proposal.escapePosition === "number") { //$NON-NLS-0$
					return proposal.escapePosition;
				}
				return event.data.start + proposal.proposal.length;
			}

			//if the proposal specifies linked positions, build the model and enter linked mode
			if (proposal.positions && proposal.positions.length > 0 && this.linkedMode) {
				var positionGroups = [];
				for (var i = 0; i < proposal.positions.length; ++i) {
					positionGroups[i] = {
						positions: [{
							offset: proposal.positions[i].offset,
							length: proposal.positions[i].length
						}]
					};
				}
				this.linkedMode.enterLinkedMode({
					groups: positionGroups,
					escapePosition: escapePosition()
				});
			} else if (proposal.groups && proposal.groups.length > 0 && this.linkedMode) {
				this.linkedMode.enterLinkedMode({
					groups: proposal.groups,
					escapePosition: escapePosition()
				});
			} else if (typeof proposal.escapePosition === "number") { //$NON-NLS-0$
				//we don't want linked mode, but there is an escape position, so just set cursor position
				var textView = this.editor.getTextView();
				textView.setCaretOffset(proposal.escapePosition);
			}
			return true;
		},
		// On backspace keypress, checks if there are a pair of brackets or quotation marks to be deleted
		deletePrevious: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var model = editor.getModel();
			forEachSelection(this, false, function(selection, setText) {
				if (selection.start !== selection.end) { return; }
			
				var prevChar = (selection.start === 0) ? "" : model.getText(selection.start - 1, selection.start); //$NON-NLS-0$
				var nextChar = (selection.start === model.getCharCount()) ? "" : model.getText(selection.start, selection.start + 1); //$NON-NLS-0$
	
				if ((prevChar === "(" && nextChar === ")") || //$NON-NLS-1$ //$NON-NLS-2$
					(prevChar === "[" && nextChar === "]") || //$NON-NLS-1$ //$NON-NLS-2$
					(prevChar === "{" && nextChar === "}") || //$NON-NLS-1$ //$NON-NLS-2$
					(prevChar === "<" && nextChar === ">") || //$NON-NLS-1$ //$NON-NLS-2$
					(prevChar === '"' && nextChar === '"') || //$NON-NLS-1$ //$NON-NLS-2$
					(prevChar === "'" && nextChar === "'")) { //$NON-NLS-1$ //$NON-NLS-2$
					setText("", selection.start, selection.start + 1); //$NON-NLS-0$
				}
			}, true);
			return false;
		},
		_findEnclosingComment: function(model, start, end) {
			var open = "/*", close = "*/"; //$NON-NLS-1$ //$NON-NLS-2$
			var firstLine = model.getLineAtOffset(start);
			var lastLine = model.getLineAtOffset(end);
			var i, line, extent, openPos, closePos;
			var commentStart, commentEnd;
			for (i=firstLine; i >= 0; i--) {
				line = model.getLine(i);
				extent = (i === firstLine) ? start - model.getLineStart(firstLine) : line.length;
				openPos = line.lastIndexOf(open, extent);
				closePos = line.lastIndexOf(close, extent);
				if (closePos > openPos) {
					break; // not inside a comment
				} else if (openPos !== -1) {
					commentStart = model.getLineStart(i) + openPos;
					break;
				}
			}
			for (i=lastLine; i < model.getLineCount(); i++) {
				line = model.getLine(i);
				extent = (i === lastLine) ? end - model.getLineStart(lastLine) : 0;
				openPos = line.indexOf(open, extent);
				closePos = line.indexOf(close, extent);
				if (openPos !== -1 && openPos < closePos) {
					break;
				} else if (closePos !== -1) {
					commentEnd = model.getLineStart(i) + closePos;
					break;
				}
			}
			return {commentStart: commentStart, commentEnd: commentEnd};
		},
		lineStart: function() {
			var editor = this.editor;
			var model = editor.getModel();
			forEachSelection(this, false, function(selection/*, setText*/) {
				var caretOffset = selection.getCaret();
				var lineIndex = model.getLineAtOffset(caretOffset);
				var lineOffset = model.getLineStart(lineIndex);
				var lineText = model.getLine(lineIndex);
				var offset;
				for (offset=0; offset<lineText.length; offset++) {
					var c = lineText.charCodeAt(offset);
					if (!(c === 32 || c === 9)) {
						break;
					}
				}
				offset += lineOffset;
				if (caretOffset !== offset) {
					selection.setCaret(offset);
				} else {
					selection.setCaret(lineOffset);
				}
			});
			return true;
		},
		removeBlockComment: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var model = editor.getModel();
			var open = "/*", close = "*/"; //$NON-NLS-1$ //$NON-NLS-2$
			var self  = this;
			forEachSelection(this, false, function(selection, setText) {

				// Try to shrink selection to a comment block
				var selectedText = model.getText(selection.start, selection.end);
				var newStart, newEnd;
				var i;
				for(i=0; i < selectedText.length; i++) {
					if (selectedText.substring(i, i + open.length) === open) {
						newStart = selection.start + i;
						break;
					}
				}
				for (; i < selectedText.length; i++) {
					if (selectedText.substring(i, i + close.length) === close) {
						newEnd = selection.start + i;
						break;
					}
				}
	
				var text;
				if (newStart !== undefined && newEnd !== undefined) {
					text = model.getText(newStart + open.length, newEnd);
					setText(text, newStart, newEnd + close.length);
					selection.start = newStart;
					selection.end = newEnd;
				} else {
					// Otherwise find enclosing comment block
					var result = self._findEnclosingComment(model, selection.start, selection.end);
					if (!(result.commentStart === undefined || result.commentEnd === undefined)) {
						text = model.getText(result.commentStart + open.length, result.commentEnd);
						setText(text, result.commentStart, result.commentEnd + close.length);
						selection.start = selection.start - open.length;
						selection.end = selection.end - close.length;
					}
				}
			});
			return true;
		},
		toggleLineComment: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var comment = this.lineComment || "//"; //$NON-NLS-0$
			var model = editor.getModel();
			textView.setRedraw(false);
			forEachSelection(this, true, function(selection, setText) {
				var firstLine = model.getLineAtOffset(selection.start);
				var lastLine = model.getLineAtOffset(selection.end > selection.start ? selection.end - 1 : selection.end);
				var uncomment = true, lineIndices = [], index;
				for (var i = firstLine; i <= lastLine; i++) {
					var lineText = model.getLine(i, true);
					index = lineText.indexOf(comment);
					lineIndices.push(index);
					if (!uncomment || index === -1) {
						uncomment = false;
					} else {
						if (index !== 0) {
							var j;
							for (j=0; j<index; j++) {
								var c = lineText.charCodeAt(j);
								if (!(c === 32 || c === 9)) {
									break;
								}
							}
							uncomment = j === index;
						}
					}
				}
				var selStart, selEnd, l = comment.length, k;
				var lineStart = model.getLineStart(firstLine);
				if (uncomment) {
					for (k = lineIndices.length - 1; k >= 0; k--) {
						index = lineIndices[k] + model.getLineStart(firstLine + k);
						setText("", index, index + l);
					}
					var lastLineStart = model.getLineStart(lastLine);
					selStart = lineStart === selection.start ? selection.start : selection.start - l;
					selEnd = selection.end - (l * (lastLine - firstLine + 1)) + (selection.end === lastLineStart+1 ? l : 0);
				} else {
					for (k = lineIndices.length - 1; k >= 0; k--) {
						index = model.getLineStart(firstLine + k);
						setText(comment, index, index);
					}
					selStart = lineStart === selection.start ? selection.start : selection.start + l;
					selEnd = selection.end + (l * (lastLine - firstLine + 1));
				}
				selection.start = selStart;
				selection.end = selEnd;
			});
			textView.setRedraw(true);
			return true;
		},
		trimTrailingWhitespaces: function() {
			var editor = this.editor;
			var model = editor.getModel();
			var selections = editor.getSelections();
			editor.getTextView().setRedraw(false);
			this.startUndo();
			var matchTrailingWhiteSpace = /(\s+$)/;
			var lineCount = model.getLineCount();
			for (var i = 0; i < lineCount; i++) {
				var lineText = model.getLine(i);
				var match = matchTrailingWhiteSpace.exec(lineText);
				if (match) {
					var lineStartOffset = model.getLineStart(i);
					var matchLength = match[0].length;
					var start = lineStartOffset + match.index;
					model.setText("", start, start + matchLength);
					/**
					 * Move the caret to its original position prior to the save. If the caret
					 * was in the trailing whitespaces, move the caret to the end of the line.
					 */
					selections.forEach(function(selection) {
						if (selection.start > start) {
							selection.start = Math.max(start, selection.start - matchLength);
						}
						if (selection.start !== selection.end && selection.end > start) {
							selection.end = Math.max(start, selection.end - matchLength);
						}
					});
				}
			}
			this.endUndo();
			editor.setSelections(selections, false);
			editor.getTextView().setRedraw(true);
		},
		startUndo: function() {
			if (this.undoStack) {
				this.undoStack.startCompoundChange();
			}
		},
		/**
		 * @name handleClosingBracket
		 * @description When the user types a closing bracket we should skip auto-paired brackets and reduce indenting if
		 * 				we are on a new line.
		 * @function
		 * @param closingChar {String} the closing bracket character
		 * @returns returns <code>true</code> to skip printing the closing bracket, false to print it
		 */
		handleClosingBracket: function(closingChar) {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var model = editor.getModel();
			var selections = editor.getSelections();
			if (selections.length === 1 && selections[0].start === selections[0].end) {
				// If the next character is the same bracket close, skip it
				var nextChar = selections[0].start === model.getCharCount() ? "" : model.getText(selections[0].start, selections[0].start + 1); //$NON-NLS-0$
				if (nextChar === closingChar) {
					selections[0].start = selections[0].end = selections[0].start + 1;
					editor.setSelections(selections);
					return true;
				}
				// If the close bracket is on a new line, reduce the indentation
				var lineNum = model.getLineAtOffset(selections[0].start);
				var line = model.getLine(lineNum, true);
				if (line.match(/^\s*$/)){
					if(textView.getOptions("tabMode")){ //$NON-NLS-1$
						var tabSize = textView.getOptions("tabSize"); //$NON-NLS-1$
						var spaceTab = new Array(tabSize + 1).join(" "); //$NON-NLS-1$
						var lineStart = model.getLineStart(lineNum);
						var lineEnd = model.getLineEnd(lineNum);
						if (lineStart !== lineEnd) {
							if (line.indexOf("\t") === 0) { //$NON-NLS-1$
								line = line.substring(1);
								model.setText(line, lineStart, lineEnd);
								editor.setSelection(selections[0].start-1, selections[0].end-1);
								return false;
							} else if (line.indexOf(spaceTab) === 0) {
								line = line.substring(tabSize);
								model.setText(line, lineStart, lineEnd);
								editor.setSelection(selections[0].start-tabSize, selections[0].end-tabSize);
								return false;
							}
						}
					}
				}
			}
			return false;
		},
		endUndo: function() {
			if (this.undoStack) {
				this.undoStack.endCompoundChange();
			}
		},
		setAutoPairParentheses: function(enabled) {
			this.autoPairParentheses = enabled;
		},
		setAutoPairBraces: function(enabled) {
			this.autoPairBraces = enabled;
		},
		setAutoPairSquareBrackets: function(enabled) {
			this.autoPairSquareBrackets = enabled;
		},
		setAutoPairAngleBrackets: function(enabled) {
			this.autoPairAngleBrackets = enabled;
		},
		setAutoPairQuotations: function(enabled) {
			this.autoPairQuotation = enabled;
		},
		setAutoCompleteComments: function(enabled) {
			this.autoCompleteComments = enabled;
		},
		setLineComment: function(lineComment) {
			this.lineComment = lineComment;
		},
		setSmartIndentation: function(enabled) {
			this.smartIndentation = enabled;
		}
	};
	exports.SourceCodeActions = SourceCodeActions;

	if (!String.prototype.trimLeft) {
		String.prototype.trimLeft = function(){
			return this.replace(/^\s+/g, '');
		};
	}
	if (!String.prototype.trimRight) {
		String.prototype.trimRight = function(){
			return this.replace(/\s+$/g, '');
		};
	}

	return exports;
});
/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2015 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/

/**
 * @class Provides the various rulers that show up on the left and right sides of the editor.  The rulers
 * 			contain annotations with varying styles, hovers and on click behaviours.
 */
/*eslint-env browser, amd*/
define("orion/editor/rulers", [
	'i18n!orion/editor/nls/messages',
	'orion/editor/textView',
	'orion/editor/annotations',
	'orion/editor/tooltip', 
	'orion/objects',
	'orion/editor/util',
	'orion/util'
], function(messages, mTextView, mAnnotations, mTooltip, objects, textUtil, util) {

	function BaseRuler (rulerLocation, rulerOverview, rulerStyle) {
		this._location = rulerLocation || "left"; //$NON-NLS-0$
		this._overview = rulerOverview || "page"; //$NON-NLS-0$
		this._rulerStyle = rulerStyle;
		this._view = null;
	}
	BaseRuler.prototype = /** @lends orion.editor.BaseRuler.prototype */ {
		/**
		 * Returns the ruler location.
		 *
		 * @returns {String} the ruler location, which is either "left" or "right" or "margin".
		 *
		 * @see orion.editor.Ruler#getOverview
		 */
		getLocation: function() {
			return this._location;
		},
		/**
		 * Returns the ruler overview type.
		 *
		 * @returns {String} the overview type, which is either "page" or "document" or "fixed".
		 *
		 * @see orion.editor.Ruler#getLocation
		 */
		getOverview: function() {
			return this._overview;
		},
		/**
		 * Returns the style information for the ruler.
		 *
		 * @returns {orion.editor.Style} the style information.
		 */
		getRulerStyle: function() {
			return this._rulerStyle;
		},
		/**
		 * Returns the text view.
		 *
		 * @returns {orion.editor.TextView} the text view.
		 *
		 * @see orion.editor.Ruler#setView
		 */
		getView: function() {
			return this._view;
		},
		/**
		 * Sets the view for the ruler.
		 * <p>
		 * This method is called by the text view when the ruler
		 * is added to the view.
		 * </p>
		 *
		 * @param {orion.editor.TextView} view the text view.
		 */
		setView: function (view) {
			if (this._onTextModelChanged && this._view) {
				this._view.removeEventListener("ModelChanged", this._listener.onTextModelChanged); //$NON-NLS-0$
			}
			this._view = view;
			if (this._onTextModelChanged && this._view) {
				this._view.addEventListener("ModelChanged", this._listener.onTextModelChanged); //$NON-NLS-0$
			}
		},
	};

	/**
	 * Constructs a new ruler. 
	 * <p>
	 * The default implementation does not implement all the methods in the interface
	 * and is useful only for objects implementing rulers.
	 * <p/>
	 * 
	 * @param {orion.editor.AnnotationModel} annotationModel the annotation model for the ruler.
	 * @param {String} [rulerLocation="left"] the location for the ruler.
	 * @param {String} [rulerOverview="page"] the overview for the ruler.
	 * @param {orion.editor.Style} [rulerStyle] the style for the ruler. 
	 * 
	 * @class This interface represents a ruler for the text view.
	 * <p>
	 * A Ruler is a graphical element that is placed either on the left or on the right side of 
	 * the view. It can be used to provide the view with per line decoration such as line numbering,
	 * bookmarks, breakpoints, folding disclosures, etc. 
	 * </p><p>
	 * There are two types of rulers: page and document. A page ruler only shows the content for the lines that are
	 * visible, while a document ruler always shows the whole content.
	 * </p>
	 * <b>See:</b><br/>
	 * {@link orion.editor.LineNumberRuler}<br/>
	 * {@link orion.editor.AnnotationRuler}<br/>
	 * {@link orion.editor.OverviewRuler}<br/> 
	 * {@link orion.editor.TextView}<br/>
	 * {@link orion.editor.TextView#addRuler}
	 * </p>		 
	 * @name orion.editor.Ruler
	 * @borrows orion.editor.AnnotationTypeList#addAnnotationType as #addAnnotationType
	 * @borrows orion.editor.AnnotationTypeList#getAnnotationTypePriority as #getAnnotationTypePriority
	 * @borrows orion.editor.AnnotationTypeList#getAnnotationsByType as #getAnnotationsByType
	 * @borrows orion.editor.AnnotationTypeList#isAnnotationTypeVisible as #isAnnotationTypeVisible
	 * @borrows orion.editor.AnnotationTypeList#removeAnnotationType as #removeAnnotationType
	 */
	function Ruler (annotationModel, rulerLocation, rulerOverview, rulerStyle) {
		BaseRuler.call(this, rulerLocation, rulerOverview, rulerStyle);
		var self = this;
		this._listener = {
			onTextModelChanged: function(e) {
				self._onTextModelChanged(e);
			},
			onAnnotationModelChanged: function(e) {
				self._onAnnotationModelChanged(e);
			}
		};
		this.setAnnotationModel(annotationModel);
	}
	Ruler.prototype = objects.mixin(new BaseRuler(), /** @lends orion.editor.Ruler.prototype */ {
		/**
		 * Returns the annotations for a given line range merging multiple
		 * annotations when necessary.
		 * <p>
		 * This method is called by the text view when the ruler is redrawn.
		 * </p>
		 *
		 * @param {Number} startLine the start line index
		 * @param {Number} endLine the end line index
		 * @return {orion.editor.Annotation[]} the annotations for the line range. The array might be sparse.
		 */
		getAnnotations: function(startLine, endLine) {
			var annotationModel = this._annotationModel;
			if (!annotationModel) { return []; }
			var model = this._view.getModel();
			var start = model.getLineStart(startLine);
			var end = model.getLineEnd(endLine - 1);
			var baseModel = model;
			if (model.getBaseModel) {
				baseModel = model.getBaseModel();
				start = model.mapOffset(start);
				end = model.mapOffset(end);
			}
			var result = [];
			var annotations = this.getAnnotationsByType(annotationModel, start, end);
			for (var i = 0; i < annotations.length; i++) {
				var annotation = annotations[i];
				var annotationLineStart = baseModel.getLineAtOffset(annotation.start);
				var annotationLineEnd = baseModel.getLineAtOffset(Math.max(annotation.start, annotation.end - 1));
				for (var lineIndex = annotationLineStart; lineIndex<=annotationLineEnd; lineIndex++) {
					var visualLineIndex = lineIndex;
					if (model !== baseModel) {
						var ls = baseModel.getLineStart(lineIndex);
						ls = model.mapOffset(ls, true);
						if (ls === -1) { continue; }
						visualLineIndex = model.getLineAtOffset(ls);
					}
					if (!(startLine <= visualLineIndex && visualLineIndex < endLine)) { continue; }
					var rulerAnnotation = this._mergeAnnotation(result[visualLineIndex], annotation, lineIndex - annotationLineStart, annotationLineEnd - annotationLineStart + 1);
					if (rulerAnnotation) {
						result[visualLineIndex] = rulerAnnotation;
					}
				}
			}
			if (!this._multiAnnotation && this._multiAnnotationOverlay) {
				for (var k in result) {
					if (result[k]._multiple) {
						result[k].html = result[k].html + this._multiAnnotationOverlay.html;
					}
				}
			}
			return result;
		},
		/**
		 * Returns the annotation model.
		 *
		 * @returns {orion.editor.AnnotationModel} the ruler annotation model.
		 *
		 * @see orion.editor.Ruler#setAnnotationModel
		 */
		getAnnotationModel: function() {
			return this._annotationModel;
		},
		/**
		 * Returns the widest annotation which determines the width of the ruler.
		 * <p>
		 * If the ruler does not have a fixed width it should provide the widest
		 * annotation to avoid the ruler from changing size as the view scrolls.
		 * </p>
		 * <p>
		 * This method is called by the text view when the ruler is redrawn.
		 * </p>
		 *
		 * @returns {orion.editor.Annotation} the widest annotation.
		 *
		 * @see orion.editor.Ruler#getAnnotations
		 */
		getWidestAnnotation: function() {
			return null;
		},
		/**
		 * Sets the annotation model for the ruler.
		 *
		 * @param {orion.editor.AnnotationModel} annotationModel the annotation model.
		 *
		 * @see orion.editor.Ruler#getAnnotationModel
		 */
		setAnnotationModel: function (annotationModel) {
			if (this._annotationModel) {
				this._annotationModel.removEventListener("Changed", this._listener.onAnnotationModelChanged); //$NON-NLS-0$
			}
			this._annotationModel = annotationModel;
			if (this._annotationModel) {
				this._annotationModel.addEventListener("Changed", this._listener.onAnnotationModelChanged); //$NON-NLS-0$
			}
		},
		/**
		 * Sets the annotation that is displayed when a given line contains multiple
		 * annotations.  This annotation is used when there are different types of
		 * annotations in a given line.
		 *
		 * @param {orion.editor.Annotation} annotation the annotation for lines with multiple annotations.
		 * 
		 * @see orion.editor.Ruler#setMultiAnnotationOverlay
		 */
		setMultiAnnotation: function(annotation) {
			this._multiAnnotation = annotation;
		},
		/**
		 * Sets the annotation that overlays a line with multiple annotations.  This annotation is displayed on
		 * top of the computed annotation for a given line when there are multiple annotations of the same type
		 * in the line. It is also used when the multiple annotation is not set.
		 *
		 * @param {orion.editor.Annotation} annotation the annotation overlay for lines with multiple annotations.
		 * 
		 * @see orion.editor.Ruler#setMultiAnnotation
		 */
		setMultiAnnotationOverlay: function(annotation) {
			this._multiAnnotationOverlay = annotation;
		},
		/**
		 * This event is sent when the user clicks a line annotation. We select an annotation on the line using
		 * the following logic:
		 * 1) If no selection or selection is on another line, select the first annotation
		 * 2) If an annotation is selected, select the next annotation in the model
		 * 3) If there is a selection that does not match an annotation, select the first annotation after the selection start
		 *
		 * @event
		 * @param {Number} lineIndex the line index of the annotation under the pointer.
		 * @param {DOMEvent} e the click event.
		 */
		onClick: function(lineIndex, e) {
			if (lineIndex === undefined) { return; }
			var view = this._view;
			
			var model = view.getModel();
			var lineStart = model.getLineStart(lineIndex);
			var lineEnd = model.getLineEnd(lineIndex, true);
			var selectionStart = view.getSelection().start;
			var selectionEnd = view.getSelection().end;
			
			var annotationModel = this._annotationModel;
			var annotation, start, end;
			if (annotationModel){

				if (model.getBaseModel) {
					lineStart = model.mapOffset(lineStart);
					lineEnd = model.mapOffset(lineEnd);
					selectionStart = model.mapOffset(selectionStart);
					selectionEnd = model.mapOffset(selectionEnd);
				}
				
				var self = this;
				annotation = this._findNextAnnotation(annotationModel, lineStart, lineEnd, selectionStart, selectionEnd, 
					function(annotationType){
						return self.isAnnotationTypeVisible(annotationType);
					}
				);
				// Select the annotation or the start of the line
				start = annotation ? annotation.start : lineStart;
				end = annotation ? annotation.end : lineStart;
				
				if (model.getBaseModel) {
					start = model.mapOffset(start, true);
					end = model.mapOffset(end, true);
				}
				
				// TODO What does this code do
				if (annotation && annotation.groupId !== undefined) {
					if (this._currentClickGroup === annotation.groupId) {
						this._currentClickGroup = null;
					} else {
						this._currentClickGroup = annotation.groupId;
					}
					this._setCurrentGroup(lineIndex);
				}
			}
			
			// Set the selection before opening the tooltip otherwise the tooltip will be closed immediately
			this._view.setSelection(end, start, 1/3, function(){});
			
			// Open the tooltip for the selected annotation in the same location as the multi-annotation ruler tooltip.
			var tooltip = mTooltip.Tooltip.getTooltip(this._view);
			if (tooltip) {
				if (annotation && this.getLocation() === "left"){ //$NON-NLS-0$
					tooltip.show({getTooltipInfo: function() {
							return self._getTooltipInfo([annotation]);
						}
					}, false, false);
				} else {
					tooltip.hide();
				}
			}
		},
		/**
		 * This event is sent when the user double clicks a line annotation.
		 *
		 * @event
		 * @param {Number} lineIndex the line index of the annotation under the pointer.
		 * @param {DOMEvent} e the double click event.
		 * @callback
		 */
		onDblClick: function(lineIndex, e) {
		},
		/**
		 * This event is sent when the user moves the mouse over a line annotation.
		 *
		 * @event
		 * @param {Number} lineIndex the line index of the annotation under the pointer.
		 * @param {DOMEvent} e the mouse move event.
		 */
		onMouseMove: function(lineIndex, e) {
			var tooltip = mTooltip.Tooltip.getTooltip(this._view);
			if (!tooltip) { return; }
			if (tooltip.isVisible() && this._tooltipLineIndex === lineIndex) { return; }
			this._tooltipLineIndex = lineIndex;
			
			// Prevent spurious mouse event (e.g. on a scroll)					
			if (e.clientX === this._lastMouseX
				&& e.clientY === this._lastMouseY) {
				return;
			}
			
			this._lastMouseX = e.clientX;
			this._lastMouseY = e.clientY;

			if (this._hoverTimeout) {
				window.clearTimeout(this._hoverTimeout);
				this._hoverTimeout = null;
			}
			
			var target = e.target ? e.target : e.srcElement;
			var bounds = target.getBoundingClientRect();
			this._curElementBounds = Object.create(null);
			this._curElementBounds.top = bounds.top;
			this._curElementBounds.left = bounds.left;
			this._curElementBounds.height = bounds.height;
			this._curElementBounds.width = bounds.width;
			
			// If we have the entire ruler selected, just use a 1 pixel high area in the ruler (Bug 463486)
			if (target === this.node){
				this._curElementBounds.top = e.clientY;
				this._curElementBounds.height = 1;
			}
			
			var self = this;
			self._hoverTimeout = window.setTimeout(function() {
				self._hoverTimeout = null;
				tooltip.onHover({
					getTooltipInfo: function() {
						var annotations = self._getAnnotationsAtLineIndex(self._tooltipLineIndex);
						var content = self._getTooltipContents(self._tooltipLineIndex, annotations);
						return self._getTooltipInfo(content, e.clientY, {source: "ruler", rulerLocation: self.getLocation()}); //$NON-NLS-0$
					}
				}, e.clientX, e.clientY);
			}, 175);
		},
		/**
		 * This event is sent when the mouse pointer enters a line annotation.
		 *
		 * @event
		 * @param {Number} lineIndex the line index of the annotation under the pointer.
		 * @param {DOMEvent} e the mouse over event.
		 */
		onMouseOver: function(lineIndex, e) {
			this.onMouseMove(lineIndex, e);
			if (!this._currentClickGroup) {
				this._setCurrentGroup(lineIndex);
			}
		},
		/**
		 * This event is sent when the mouse pointer exits a line annotation.
		 *
		 * @event
		 * @param {Number} lineIndex the line index of the annotation under the pointer.
		 * @param {DOMEvent} e the mouse out event.
		 * @callback
		 */
		onMouseOut: function(lineIndex, e) {
			if (!this._currentClickGroup) {
				this._setCurrentGroup(-1);
			}

			if (this._hoverTimeout) {
				window.clearTimeout(this._hoverTimeout);
				this._hoverTimeout = null;
			}
		},
		/**
		 * @name _findNextAnnotation
		 * @description Looks at all annotations in the given range and attempt to find the next valid
		 * 				annotation after the current selection.
		 * @function
		 * @private
		 * @param annotationModel The annotation model to lookup annotations in
		 * @param rangeStart The start range to search for annotations (required)
		 * @param rangeEnd The end range to search for annotations (required)
		 * @param selectionStart The start of the current selection (optional)
		 * @param selectionEnd The end of the current selection (optional)
		 * @param isAnnotationTypeVisible A function callback to check if a given annotation type is valid or visible (optional)
		 * @returns The next annotation in the list or <code>null</code>
		 */
		_findNextAnnotation: function(annotationModel, rangeStart, rangeEnd, selectionStart, selectionEnd, isAnnotationTypeVisible){
			var annotation = null;
			var selectedAnnotation;
			var searchStart = rangeStart;
			
			if (selectionStart >= 0 && selectionEnd >= 0){
				if (selectionStart >= rangeStart && selectionStart < rangeEnd){
					searchStart = selectionStart;
					var selectedAnnotations = annotationModel.getAnnotations(selectionStart, selectionEnd);
					while (!selectedAnnotation && selectedAnnotations.hasNext()){
						var current = selectedAnnotations.next();
						if (isAnnotationTypeVisible && !isAnnotationTypeVisible(current.type)) { continue; }
						if (current.start === selectionStart && current.end === selectionEnd){
							selectedAnnotation = current;
						}
					}
				}
			}
			
			var iter = annotationModel.getAnnotations(searchStart, rangeEnd);
			var useNextValid;
			while (iter.hasNext()){
				current = iter.next();
				if (isAnnotationTypeVisible && !isAnnotationTypeVisible(current.type)) { continue; }
				// Default to first visible annotation
				if (!annotation){
					annotation = current;
				}
				// If no selected annotation, use the first in the list
				if (!selectedAnnotation){
					annotation = current;
					break;
				}
				// If the selected annotation was found, use the next annotation
				// NOTE: If two annotations have the same range, we skip to the next annotation so don't flip between them
				if (useNextValid && (selectedAnnotation.start !== current.start || selectedAnnotation.end !== current.end)){
					useNextValid = false;
					annotation = current;
					break;
				}
				// Found the selected annotation, use the next in the list
				if (selectedAnnotation && selectedAnnotation === current){
					useNextValid = true;
				}
			}
			if (useNextValid){
				annotation = null; // Last annotation on the line was selected, go to line start
			}
			
			return annotation;
		},
		
		_getAnnotationsAtLineIndex: function _getAnnotationsAtLineIndex(lineIndex){
			if (lineIndex === undefined) { return; }
			var view = this._view;
			var model = view.getModel();
			var annotationModel = this._annotationModel;
			var annotations = [];
			if (annotationModel) {
				var start = model.getLineStart(lineIndex);
				var end = model.getLineEnd(lineIndex);
				if (model.getBaseModel) {
					start = model.mapOffset(start);
					end = model.mapOffset(end);
				}
				annotations = this.getAnnotationsByType(annotationModel, start, end);
			}
			return annotations;
		},
		/** @ignore */
		_getTooltipInfo: function(contents, y, context) {
			if (!contents) { return null; } // TODO: shouldn't this check the length, it'll never be null
		
			var hoverArea = Object.create(null);
			hoverArea.top = this._curElementBounds.top;
			hoverArea.left = this._curElementBounds.left;
			hoverArea.height = this._curElementBounds.height;
			hoverArea.width = this._curElementBounds.width;
			
			if (typeof contents === 'string' && y) { //$NON-NLS-0$
				// Hack for line numbers
				hoverArea.top = y;
				hoverArea.height = 1;
			}
			
			var rulerLocation = this.getLocation();
			var rulerStyle = this.getRulerStyle();
			// The tooltip is positioned opposite to where the ruler is
			var position = rulerLocation === "left" ? "right" : "left"; //$NON-NLS-0$ //$NON-NLS-1$ //$NON-NLS-2$
			
			
			var viewRect = this._view._clientDiv.getBoundingClientRect();
			var offsetX = 0;
			var offsetY = 0;
			offsetX = viewRect.left - (hoverArea.left + hoverArea.width);
			offsetY = hoverArea.height;
			if (position === "left") { //$NON-NLS-0$
				offsetX = -25;
				// Hack for when the hoverArea is a sliver of the ruler, ruler is 2px wider than annotations
				if (hoverArea.height === 1){
					offsetX += 2;
				}
			}
			// Adjust the tooltip for folding comments to exactly cover the unfolded text location
			if (rulerStyle.styleClass.indexOf("folding") >= 0){ //$NON-NLS-0$
				offsetY -= 14;
			}
			
			var info = {
				contents: contents,
				position: position,
				tooltipOffsetX: offsetX,
				tooltipOffsetY: offsetY,
				anchorArea: hoverArea,
				context: context
			};
			return info;
		},
		/**
		 * @name _getTooltipContents
		 * @description Overridden by different rulers to provide customer tooltip content
		 * @function
		 * @private
		 * @param lineIndex
		 * @param annotations
		 * @returns returns
		 * @callback
		 */
		_getTooltipContents: function _getTooltipContents(lineIndex, annotations){
			return annotations;
		},
		
		_getOnClickTooltipInfo: function(annotation) {
			var view = this._view;
			var hoverArea = Object.create(null);
			hoverArea.top = this._curElementBounds.top;
			hoverArea.left = this._curElementBounds.left;
			hoverArea.height = this._curElementBounds.height;
			hoverArea.width = this._curElementBounds.width;
			var rulerLocation = this.getLocation();
			var position = rulerLocation === "left" ? "right" : "left"; //$NON-NLS-0$ //$NON-NLS-1$ //$NON-NLS-2$
			var info = {
				contents: [annotation],
				position: position,
				anchorArea: hoverArea
			};
			
			var viewRect = view._clientDiv.getBoundingClientRect();

			info.offsetX = viewRect.left - (hoverArea.left + hoverArea.width);
			info.offsetY = hoverArea.height;
			if (info.position === "left") { //$NON-NLS-0$
				info.offsetX = 20;
			}
			return info;
		},
		/** @ignore */
		_onAnnotationModelChanged: function(e) {
			var view = this._view;
			if (!view) { return; }
			var model = view.getModel(), self = this;
			var lineCount = model.getLineCount();
			if (e.textModelChangedEvent) {
				var start = e.textModelChangedEvent.start;
				if (model.getBaseModel) { start = model.mapOffset(start, true); }
				var startLine = model.getLineAtOffset(start);
				view.redrawLines(startLine, lineCount, self);
				return;
			}
			function redraw(changes) {
				for (var i = 0; i < changes.length; i++) {
					if (!self.isAnnotationTypeVisible(changes[i].type)) { continue; }
					start = changes[i].start;
					var end = changes[i].end;
					if (model.getBaseModel) {
						start = model.mapOffset(start, true);
						end = model.mapOffset(end, true);
					}
					if (start !== -1 && end !== -1) {
						view.redrawLines(model.getLineAtOffset(start), model.getLineAtOffset(Math.max(start, end - 1)) + 1, self);
					}
				}
			}
			redraw(e.added);
			redraw(e.removed);
			redraw(e.changed);
		},
		/** @ignore */
		_mergeAnnotation: function(result, annotation, annotationLineIndex, annotationLineCount) {
			if (!result) { result = {}; }
			if (annotationLineIndex === 0) {
				if (result.html && annotation.html) {
					if (annotation.html !== result.html) {
						if (!result._multiple && this._multiAnnotation) {
							result.html = this._multiAnnotation.html;
						}
					} 
					result._multiple = true;
				} else {
					result.html = annotation.html;
				}
			}
			result.style = this._mergeStyle(result.style, annotation.style);
			return result;
		},
		/** @ignore */
		_mergeStyle: function(result, style) {
			if (style) {
				if (!result) { result = {}; }
				if (result.styleClass && style.styleClass && result.styleClass !== style.styleClass) {
					result.styleClass += " " + style.styleClass; //$NON-NLS-0$
				} else {
					result.styleClass = style.styleClass;
				}
				var prop;
				if (style.style) {
					if (!result.style) { result.style  = {}; }
					for (prop in style.style) {
						if (result.style[prop] === undefined) {
							result.style[prop] = style.style[prop];
						}
					}
				}
				if (style.attributes) {
					if (!result.attributes) { result.attributes  = {}; }
					for (prop in style.attributes) {
						if (result.attributes[prop] === undefined) {
							result.attributes[prop] = style.attributes[prop];
						}
					}
				}
			}
			return result;
		},
		_setCurrentGroup: function(lineIndex) {
			var annotationModel = this._annotationModel;
			var groupAnnotation = null;
			var model = annotationModel.getTextModel();
			var annotation;
			var annotations;
			var currentGroupAnnotation = this._currentGroupAnnotation;
			if (lineIndex !== -1) {
				var start = model.getLineStart(lineIndex);
				var end = model.getLineEnd(lineIndex);
				if (model.getBaseModel) {
					start = model.mapOffset(start);
					end = model.mapOffset(end);
				}
				annotations = annotationModel.getAnnotations(start, end);
				while(annotations.hasNext()){
					annotation = annotations.next();
					if (!this.isAnnotationTypeVisible(annotation.type)) { continue; }
					if (annotation.start <= start && annotation.end >= end){
						if (annotation.groupId !== undefined) {
							groupAnnotation = annotation;
							break;
						}
					}
				}
				if (currentGroupAnnotation && groupAnnotation) {
					if (currentGroupAnnotation.groupId === groupAnnotation.groupId) {
						return;
					}
				}
			}
			this._currentGroupAnnotation = null;
			if (currentGroupAnnotation) {
				annotationModel.removeAnnotations(currentGroupAnnotation.groupType);
			}
			if (!groupAnnotation) { return; }
			
			if (lineIndex === -1) { return; }
			this._currentGroupAnnotation = groupAnnotation;
			annotations = annotationModel.getAnnotations();
			var add = [];
			while (annotations.hasNext()) {
				annotation = annotations.next();
				delete annotation.groupAnnotation;
				if (annotation.groupId === groupAnnotation.groupId) {
					annotation = annotation.createGroupAnnotation();
					add.push(annotation);
				}
			}
			annotationModel.replaceAnnotations(null, add);
		}
	});
	mAnnotations.AnnotationTypeList.addMixin(Ruler.prototype);

	/**
	 * Constructs a new line numbering ruler. 
	 *
	 * @param {orion.editor.AnnotationModel} annotationModel the annotation model for the ruler.
	 * @param {String} [rulerLocation="left"] the location for the ruler.
	 * @param {orion.editor.Style} [rulerStyle=undefined] the style for the ruler.
	 * @param {orion.editor.Style} [oddStyle={style: {backgroundColor: "white"}] the style for lines with odd line index.
	 * @param {orion.editor.Style} [evenStyle={backgroundColor: "white"}] the style for lines with even line index.
	 *
	 * @augments orion.editor.Ruler
	 * @class This objects implements a line numbering ruler.
	 *
	 * <p><b>See:</b><br/>
	 * {@link orion.editor.Ruler}
	 * </p>
	 * @name orion.editor.LineNumberRuler
	 */
	function LineNumberRuler (annotationModel, rulerLocation, rulerStyle, oddStyle, evenStyle) {
		Ruler.call(this, annotationModel, rulerLocation, "page", rulerStyle); //$NON-NLS-0$
		this._oddStyle = oddStyle || {style: {backgroundColor: "white"}}; //$NON-NLS-0$
		this._evenStyle = evenStyle || {style: {backgroundColor: "white"}}; //$NON-NLS-0$
		this._numOfDigits = 0;
		this._firstLine = 1;
	}
	LineNumberRuler.prototype = new Ruler(); 
	/** @ignore */
	LineNumberRuler.prototype.getAnnotations = function(startLine, endLine) {
		var result = Ruler.prototype.getAnnotations.call(this, startLine, endLine);
		var model = this._view.getModel();
		for (var lineIndex = startLine; lineIndex < endLine; lineIndex++) {
			var style = (lineIndex - this._firstLine) & 1 ? this._oddStyle : this._evenStyle;
			var mapLine = lineIndex;
			if (model.getBaseModel) {
				var lineStart = model.getLineStart(mapLine);
				mapLine = model.getBaseModel().getLineAtOffset(model.mapOffset(lineStart));
			}
			if (!result[lineIndex]) { result[lineIndex] = {}; }
			result[lineIndex].html = (this._firstLine + mapLine) + "";
			if (!result[lineIndex].style) { result[lineIndex].style = style; }
		}
		return result;
	};
	/** @ignore */
	LineNumberRuler.prototype.getWidestAnnotation = function() {
		var lineCount = this._view.getModel().getLineCount();
		return this.getAnnotations(lineCount - 1, lineCount)[lineCount - 1];
	};
	/**
	 * Sets the line index displayed for the first line. The default value is
	 * <code>1</code>.
	 *
	 * @param {Number} [lineIndex=1] the first line index displayed
	 */
	LineNumberRuler.prototype.setFirstLine = function(lineIndex) {
		this._firstLine = lineIndex !== undefined ? lineIndex : 1;
	};
	/** @ignore */
	LineNumberRuler.prototype._onTextModelChanged = function(e) {
		var start = e.start;
		var model = this._view.getModel();
		var lineCount = model.getBaseModel ? model.getBaseModel().getLineCount() : model.getLineCount();
		var numOfDigits = ((this._firstLine + lineCount - 1)+"").length;
		if (this._numOfDigits !== numOfDigits) {
			this._numOfDigits = numOfDigits;
			var startLine = model.getLineAtOffset(start);
			this._view.redrawLines(startLine,  model.getLineCount(), this);
		}
	};
	
	/** 
	 * @class This is class represents an annotation for the AnnotationRuler. 
	 * <p> 
	 * <b>See:</b><br/> 
	 * {@link orion.editor.AnnotationRuler}
	 * </p> 
	 * 
	 * @name orion.editor.Annotation 
	 * 
	 * @property {String} [html=""] The html content for the annotation, typically contains an image.
	 * @property {orion.editor.Style} [style] the style for the annotation.
	 * @property {orion.editor.Style} [overviewStyle] the style for the annotation in the overview ruler.
	 */ 
	/**
	 * Constructs a new annotation ruler. 
	 *
	 * @param {orion.editor.AnnotationModel} annotationModel the annotation model for the ruler.
	 * @param {String} [rulerLocation="left"] the location for the ruler.
	 * @param {orion.editor.Style} [rulerStyle=undefined] the style for the ruler.
	 * @param {orion.editor.Annotation} [defaultAnnotation] the default annotation.
	 *
	 * @augments orion.editor.Ruler
	 * @class This objects implements an annotation ruler.
	 *
	 * <p><b>See:</b><br/>
	 * {@link orion.editor.Ruler}<br/>
	 * {@link orion.editor.Annotation}
	 * </p>
	 * @name orion.editor.AnnotationRuler
	 */
	function AnnotationRuler (annotationModel, rulerLocation, rulerStyle) {
		Ruler.call(this, annotationModel, rulerLocation, "page", rulerStyle); //$NON-NLS-0$
	}
	AnnotationRuler.prototype = new Ruler();
	
	/**
	 * Constructs a new overview ruler. 
	 * <p>
	 * The overview ruler is used in conjunction with a AnnotationRuler, for each annotation in the 
	 * AnnotationRuler this ruler displays a mark in the overview. Clicking on the mark causes the 
	 * view to scroll to the annotated line.
	 * </p>
	 *
	 * @param {orion.editor.AnnotationModel} annotationModel the annotation model for the ruler.
	 * @param {String} [rulerLocation="left"] the location for the ruler.
	 * @param {orion.editor.Style} [rulerStyle=undefined] the style for the ruler.
	 *
	 * @augments orion.editor.Ruler
	 * @class This objects implements an overview ruler.
	 *
	 * <p><b>See:</b><br/>
	 * {@link orion.editor.AnnotationRuler} <br/>
	 * {@link orion.editor.Ruler} 
	 * </p>
	 * @name orion.editor.OverviewRuler
	 */
	function OverviewRuler (annotationModel, rulerLocation, rulerStyle) {
		Ruler.call(this, annotationModel, rulerLocation, "document", rulerStyle); //$NON-NLS-0$
	}
	OverviewRuler.prototype = new Ruler();
	
	/** @ignore */
	OverviewRuler.prototype.getRulerStyle = function() {
		var result = {style: {lineHeight: "1px", fontSize: "1px"}}; //$NON-NLS-1$ //$NON-NLS-0$
		result = this._mergeStyle(result, this._rulerStyle);
		return result;
	};
	/** @ignore */
	OverviewRuler.prototype._getTooltipContents = function(lineIndex, annotations) {
		if (annotations && annotations.length === 0) {
			var model = this._view.getModel();
			var mapLine = lineIndex;
			if (model.getBaseModel) {
				var lineStart = model.getLineStart(mapLine);
				mapLine = model.getBaseModel().getLineAtOffset(model.mapOffset(lineStart));
			}
			return util.formatMessage(messages.line, mapLine + 1);
		}
		return Ruler.prototype._getTooltipContents.call(this, lineIndex, annotations);
	};
	/** @ignore */
	OverviewRuler.prototype._mergeAnnotation = function(previousAnnotation, annotation, annotationLineIndex, annotationLineCount) {
		if (annotationLineIndex !== 0) { return undefined; }
		var result = previousAnnotation;
		if (!result) {
			//TODO annotationLineCount does not work when there are folded lines
			var height = 3 * annotationLineCount;
			result = {html: "&nbsp;", style: { style: {height: height + "px"}}}; //$NON-NLS-1$ //$NON-NLS-0$
			result.style = this._mergeStyle(result.style, annotation.overviewStyle);
		}
		return result;
	};

	/**
	 * Constructs a new folding ruler. 
	 *
	 * @param {orion.editor.AnnotationModel} annotationModel the annotation model for the ruler.
	 * @param {String} [rulerLocation="left"] the location for the ruler.
	 * @param {orion.editor.Style} [rulerStyle=undefined] the style for the ruler.
	 *
	 * @augments orion.editor.Ruler
	 * @class This objects implements an overview ruler.
	 *
	 * <p><b>See:</b><br/>
	 * {@link orion.editor.AnnotationRuler} <br/>
	 * {@link orion.editor.Ruler} 
	 * </p>
	 * @name orion.editor.OverviewRuler
	 */
	function FoldingRuler (annotationModel, rulerLocation, rulerStyle) {
		AnnotationRuler.call(this, annotationModel, rulerLocation, rulerStyle);
	}
	FoldingRuler.prototype = new AnnotationRuler();
	
	/** @ignore */
	FoldingRuler.prototype.onClick =  /* @callback */ function(lineIndex, e) {
		if (lineIndex === undefined) { return; }
		var annotationModel = this._annotationModel;
		if (!annotationModel) { return; }
		var view = this._view;
		var model = view.getModel();
		var start = model.getLineStart(lineIndex);
		var end = model.getLineEnd(lineIndex, true);
		if (model.getBaseModel) {
			start = model.mapOffset(start);
			end = model.mapOffset(end);
			model = model.getBaseModel();
		}
		var annotation, iter = annotationModel.getAnnotations(start, end);
		while (!annotation && iter.hasNext()) {
			var a = iter.next();
			if (!this.isAnnotationTypeVisible(a.type)) { continue; }
			if (model.getLineAtOffset(a.start) !== model.getLineAtOffset(start)) { continue; }
			annotation = a;
		}
		if (annotation) {
			var tooltip = mTooltip.Tooltip.getTooltip(this._view);
			if (tooltip) {
				tooltip.hide();
			}
			if (annotation.expanded) {
				annotation.collapse();
			} else {
				annotation.expand();
			}
		}
	};
	/** @ignore */
	FoldingRuler.prototype._getTooltipContents = function(lineIndex, annotations) {
		if (annotations && annotations.length > 0) {
			var view = this._view;
			var model = view.getModel();
			var start = model.getLineStart(lineIndex);
			if (model.getBaseModel) {
				start = model.mapOffset(start);
				model = model.getBaseModel();
			}
			var mapLineIndex = model.getLineAtOffset(start);
			for (var i = 0; i < annotations.length; i++) {
				var a = annotations[i];
				if (!this.isAnnotationTypeVisible(a.type)) { continue; }
				if (model.getLineAtOffset(a.start) !== mapLineIndex) { continue; }
				if (annotations[i].expanded) {
					break;
				}
				return AnnotationRuler.prototype._getTooltipContents.call(this, lineIndex, [a]);
			}
		}
		return null;
	};
	/** @ignore */
	FoldingRuler.prototype._onAnnotationModelChanged = function(e) {
		if (e.textModelChangedEvent) {
			AnnotationRuler.prototype._onAnnotationModelChanged.call(this, e);
			return;
		}
		var view = this._view;
		if (!view) { return; }
		var model = view.getModel(), self = this, i;
		var lineCount = model.getLineCount(), lineIndex = lineCount;
		function redraw(changes) {
			for (i = 0; i < changes.length; i++) {
				if (!self.isAnnotationTypeVisible(changes[i].type)) { continue; }
				var start = changes[i].start;
				if (model.getBaseModel) {
					start = model.mapOffset(start, true);
				}
				if (start !== -1) {
					lineIndex = Math.min(lineIndex, model.getLineAtOffset(start));
				}
			}
		}
		redraw(e.added);
		redraw(e.removed);
		redraw(e.changed);
		var rulers = view.getRulers();
		for (i = 0; i < rulers.length; i++) {
			view.redrawLines(lineIndex, lineCount, rulers[i]);
		}
	};
	

	/**
	 * Constructs a new zoom ruler. 
	 *
	 * @param {String} [rulerLocation="left"] the location for the ruler.
	 * @param {orion.editor.Style} [rulerStyle=undefined] the style for the ruler.
	 *
	 * @augments orion.editor.Ruler
	 * @class This objects implements an overview ruler.
	 *
	 * <p><b>See:</b><br/>
	 * {@link orion.editor.AnnotationRuler} <br/>
	 * {@link orion.editor.Ruler} 
	 * </p>
	 * @name orion.editor.OverviewRuler
	 */
	var ZoomRuler = function(rulerLocation, rulerStyle) {
		BaseRuler.call(this, rulerLocation, "fixed", rulerStyle); //$NON-NLS-0$
	};
	
	ZoomRuler.prototype = objects.mixin(new BaseRuler(), {
		setView: function (view) {
			this._destroy();
			BaseRuler.prototype.setView.call(this, view);
			this._create();
		},
		_create: function() {
			var textView = this.getView();
			if (!textView) return;
			function getOptions(options) {
				var rulerTheme = "textviewZoom"; //$NON-NLS-0$
				var theme = options.themeClass;
				if (theme) {
					theme = theme.replace(rulerTheme, "");
					if (theme) { theme = " " + theme; } //$NON-NLS-0$
					theme = rulerTheme + theme;
				} else {
					theme = rulerTheme;
				}
				options.themeClass = theme;
				options.noScroll = true;
				options.readonly = true;
				return options;
			}
			var options = getOptions(textView.getOptions());
			options.parent = this.node;
			var zoomView = this._zoomView = new mTextView.TextView(options);
			zoomView._clientDiv.contentEditable = false;
			zoomView.setModel(textView.getModel());
			var document = textView.getOptions("parent").ownerDocument; //$NON-NLS-0$
			var windowDiv = this._windowDiv = util.createElement(document, "div"); //$NON-NLS-0$
			windowDiv.className ="rulerZoomWindow"; //$NON-NLS-0$
			this.node.appendChild(windowDiv);
			var border = parseInt(textUtil.getNodeStyle(windowDiv, "border-top-width", 0)) + //$NON-NLS-0$
					parseInt(textUtil.getNodeStyle(windowDiv, "border-bottom-width", 0)); //$NON-NLS-0$
			var that = this;
			function updateWindow(scroll, p) {
				var top = scroll.y * p.zoomFactor;
				var height = p.clientHeight * p.zoomFactor;
				that.top = top;
				that.bottom = top + height;
				top = zoomView.convert({y: top}, "document", "page").y; //$NON-NLS-1$ //$NON-NLS-0$
				top = top - that.node.getBoundingClientRect().top;
				windowDiv.style.top = top + "px"; //$NON-NLS-0$
				windowDiv.style.height = (height - border) + "px"; //$NON-NLS-0$
			}
			function getProps() {
				var padding = textView._metrics.viewPadding;
				var zoomPadding = textView._metrics.viewPadding;
				var lineHeight = textView.getLineHeight();
				var zoomLineHeight = zoomView.getLineHeight();
				var lineCount = textView.getModel().getLineCount();
				var documentHeight = textView._lineHeight ? textView._scrollHeight : lineCount * lineHeight;
				var zoomDocumentHeight = zoomView._lineHeight ? zoomView._scrollHeight : lineCount * zoomLineHeight;
				var zoomFactor = zoomDocumentHeight / documentHeight;
				var clientHeight = textView.getClientArea().height + padding.top + padding.bottom;
				var zoomClientHeight = zoomView.getClientArea().height + zoomPadding.top + zoomPadding.bottom;
				var windowHeight = clientHeight * zoomFactor;
				var scrollWidth = textView._metrics.scrollWidth;
				return {
					zoomFactor: zoomFactor,
					documentHeight: documentHeight,
					zoomDocumentHeight: zoomDocumentHeight,
					clientHeight: clientHeight,
					zoomClientHeight: zoomClientHeight,
					scrollWidth: scrollWidth,
					windowHeight: windowHeight,
					padding: padding
				};
			}
			function toZoom(scroll, p) {
				return scroll.y * (p.zoomFactor + (p.windowHeight - p.clientHeight - p.scrollWidth) / p.documentHeight);
			}
			function updateScroll(scroll) {
				scroll = scroll || {y: textView.getTopPixel()};
				var p = getProps();
				var y = toZoom(scroll, p);
				zoomView.setTopPixel(y);
				updateWindow(scroll, p);
			}
			function updateWidth(options) {
				var width;
				if (options.wrapMode && !options.wrapOffset && textView._metrics.charWidth) {
					var div1 = util.createElement(document, "div"); //$NON-NLS-0$
					div1.style.position = "fixed"; //$NON-NLS-0$
					div1.style.left = "-1000px"; //$NON-NLS-0$
					zoomView._clientDiv.appendChild(div1);
					div1.innerHTML = new Array(Math.ceil(textView.getClientArea().width / textView._metrics.charWidth) + 1).join("a"); //$NON-NLS-0$
					var rect1 = div1.getBoundingClientRect();
					width = Math.min(150, Math.ceil(rect1.right - rect1.left)) + "px"; //$NON-NLS-0$
				} else {
					width = "";
				}
				var oldWidth = that.node.style.width;
				that.node.style.width = width;
				return oldWidth !== width;
			}
			textView.addEventListener("Scroll", this._scrollListener = function(event) { //$NON-NLS-0$
				updateScroll(event.newValue);
			});
			textView.addEventListener("Resize", this._resizeListener = function() { //$NON-NLS-0$
				updateWidth(zoomView.getOptions());
				updateScroll();
			});
			textView.addEventListener("Redraw", this._redrawListener = function(event) { //$NON-NLS-0$
				if (!event.ruler) {
					zoomView.redrawLines(event.startLine, event.endLine);
				}
			});
			textView.addEventListener("Options", this._optionsListener = function(event) { //$NON-NLS-0$
				var options = getOptions(event.options);
				zoomView.setOptions(options);
				updateWidth(zoomView.getOptions());
			});
			zoomView.addEventListener("LineStyle", this._lineListener = function(e) { //$NON-NLS-0$
				textView.onLineStyle(e);
			});
			function down(event, clientY, touch) {
				if (touch || (that.top <= event.y && event.y <= that.bottom)) {
					that.mouseDown = true;
					that.delta = clientY - windowDiv.getBoundingClientRect().top + that.node.getBoundingClientRect().top;
				} else {
					var offset = zoomView.getOffsetAtLocation(event.x, event.y);
					textView.setSelection(offset, offset, 0.5, function() {});
				}
			}
			function up() {
				that.mouseDown = false;
			}
			function move(clientY) {
				if (that.mouseDown) {
					var p = getProps();
					var thumbPos = Math.min(p.zoomClientHeight - p.windowHeight, Math.max(0, clientY - that.delta));
					textView.setTopPixel(thumbPos * (p.documentHeight - p.clientHeight) / Math.min(p.zoomDocumentHeight, p.zoomClientHeight - p.windowHeight));
				}
			}
			function stop(event) {
				event.preventDefault();
			}
			if (util.isIOS || util.isAndroid) {
				windowDiv.addEventListener("touchstart", function(event) { //$NON-NLS-0$
					var touches = event.touches;
					if (touches.length === 1) {
						down(event, event.touches[0].clientY, true);
						event.preventDefault();
					}
				});
				windowDiv.addEventListener("touchend", function(event) { //$NON-NLS-0$
					var touches = event.touches;
					if (touches.length === 0) {
						up(event);
					}
				});
				windowDiv.addEventListener("touchmove", function(event) { //$NON-NLS-0$
					var touches = event.touches;
					if (touches.length === 1) {
						move(event.touches[0].clientY);
					}
				});
				zoomView.addEventListener("TouchStart", function(event) { //$NON-NLS-0$
					if (event.touchCount === 1) {
						down(event, event.event.touches[0].clientY);
						stop(event);
					}
				});
//				windowDiv.style.pointerEvents = "none"; //$NON-NLS-0$
//				zoomView.addEventListener("TouchEnd", function(event) { //$NON-NLS-0$
//					if (event.touchCount === 0) {
//						up(event);
//					}
//				});
//				zoomView.addEventListener("TouchMove", function(event) { //$NON-NLS-0$
//					if (event.touchCount === 1) {
//						move(event.event.touches[0].clientY);
//					}
//				});
			} else {
				windowDiv.style.pointerEvents = "none"; //$NON-NLS-0$
				zoomView.addEventListener("MouseDown", function(event) { //$NON-NLS-0$
					var e = event.event;
					if (e.which ? e.button === 0 : e.button === 1) {
						down(event, e.clientY);
					}
					stop(event);
				});
				zoomView.addEventListener("MouseUp", function(event) { //$NON-NLS-0$
					up(event);
					stop(event);
				});
				zoomView.addEventListener("MouseMove", function(event) { //$NON-NLS-0$
					move(event.event.clientY);
					stop(event);
				});
			}
			(document.defaultView || document.parentWindow).setTimeout(function() {
				updateScroll();
			}, 0);
		},
		_destroy: function() {
			var textView = this.getView();
			if (textView) {
				textView.removeEventListener("Scroll", this._scrollListener); //$NON-NLS-0$
				this._scrollListener = null;
				textView.removeEventListener("Resize", this._resizeListener); //$NON-NLS-0$
				this._resizeListener = null;
				textView.removeEventListener("Redraw", this._redrawListener); //$NON-NLS-0$
				this._redrawListener = null;
				textView.removeEventListener("Options", this._optionsListener); //$NON-NLS-0$
				this._optionsListener = null;
			}
			var zoomView = this._zoomView;
			if (zoomView) {
				zoomView.removeEventListener("LineStyle", this._lineListener); //$NON-NLS-0$
				zoomView.setModel(null);
				zoomView.destroy();
				this._zoomView = null;
			}
		}
	});
	
	return {
		BaseRuler: BaseRuler,
		Ruler: Ruler,
		AnnotationRuler: AnnotationRuler,
		LineNumberRuler: LineNumberRuler,
		OverviewRuler: OverviewRuler,
		FoldingRuler: FoldingRuler,
		ZoomRuler: ZoomRuler,
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: 
 *		Felipe Heidrich (IBM Corporation) - initial API and implementation
 *		Silenio Quarti (IBM Corporation) - initial API and implementation
 ******************************************************************************/
 
/*eslint-env browser, amd*/
define("orion/editor/textDND", ['orion/util'], function(util) { //$NON-NLS-1$ //$NON-NLS-0$

	function TextDND(view, undoStack) {
		this._view = view;
		this._undoStack = undoStack;
		this._dragSelection = null;
		this._dropOffset = -1;
		this._dropText = null;
		var self = this;
		this._listener = {
			onDragStart: function (evt) {
				self._onDragStart(evt);
			},
			onDragEnd: function (evt) {
				self._onDragEnd(evt);
			},
			onDragEnter: function (evt) {
				self._onDragEnter(evt);
			},
			onDragOver: function (evt) {
				self._onDragOver(evt);
			},
			onDrop: function (evt) {
				self._onDrop(evt);
			},
			onDestroy: function (evt) {
				self._onDestroy(evt);
			}
		};
		view.addEventListener("DragStart", this._listener.onDragStart); //$NON-NLS-0$
		view.addEventListener("DragEnd", this._listener.onDragEnd); //$NON-NLS-0$
		view.addEventListener("DragEnter", this._listener.onDragEnter); //$NON-NLS-0$
		view.addEventListener("DragOver", this._listener.onDragOver); //$NON-NLS-0$
		view.addEventListener("Drop", this._listener.onDrop); //$NON-NLS-0$
		view.addEventListener("Destroy", this._listener.onDestroy); //$NON-NLS-0$
	}
	TextDND.prototype = {
		destroy: function() {
			var view = this._view;
			if (!view) { return; }
			view.removeEventListener("DragStart", this._listener.onDragStart); //$NON-NLS-0$
			view.removeEventListener("DragEnd", this._listener.onDragEnd); //$NON-NLS-0$
			view.removeEventListener("DragEnter", this._listener.onDragEnter); //$NON-NLS-0$
			view.removeEventListener("DragOver", this._listener.onDragOver); //$NON-NLS-0$
			view.removeEventListener("Drop", this._listener.onDrop); //$NON-NLS-0$
			view.removeEventListener("Destroy", this._listener.onDestroy); //$NON-NLS-0$
			this._view = null;
		},
		_onDestroy: function(e) {
			this.destroy();
		},
		_onDragStart: function(e) {
			var view = this._view;
			var text = view.getSelectionText();
			if (text) {
				this._dragSelection = view.getSelections();
				e.event.dataTransfer.effectAllowed = "copyMove"; //$NON-NLS-0$
				e.event.dataTransfer.setData("Text", text); //$NON-NLS-0$
			}
		},
		_onDragEnd: function(e) {
			if (this._dragSelection) {
				var view = this._view;
				var dropEffect = e.event.dataTransfer.dropEffect;
				if (!util.isFirefox) {
					if (dropEffect !== "none" || this._dropText) { //$NON-NLS-0$
						dropEffect = e.event.dataTransfer.dropEffect = this._dropEffect;
					}
				}
				if (this._undoStack) { this._undoStack.startCompoundChange(); }
				var move = dropEffect === "move"; //$NON-NLS-0$
				if (move) {
					view.setText({text: "", selection: this._dragSelection});
				}
				if (this._dropText) {
					var text = this._dropText;
					var offset = this._dropOffset;
					if (move) {
						this._dragSelection.forEach(function(selection) {
							if (offset >= selection.end) {
								offset -= selection.end - selection.start;
							} else if (offset >= selection.start) {
								offset = selection.start;
							}
						});
					}
					view.setText(text, offset, offset);
					view.setSelection(offset, offset + text.length);
					this._dropText = null;
					this._dropOffset = -1;
				}
				if (this._undoStack) { this._undoStack.endCompoundChange(); }
				this._dragSelection = null;
			}
		},
		_onDragEnter: function(e) {
			this._onDragOver(e);
		},
		_onDragOver: function(e) {
			var types = e.event.dataTransfer.types;
			var allowed = !this._view.getOptions("readonly"); //$NON-NLS-0$
			if (allowed) {
				if (types) {
					allowed = types.contains ? 
						types.contains("text/plain") || types.contains("Text") : //$NON-NLS-1$ //$NON-NLS-0$
						types.indexOf("text/plain") !== -1 || types.indexOf("Text") !== -1; //$NON-NLS-1$ //$NON-NLS-0$
					}
			}
			if (!allowed) {
				e.event.dataTransfer.dropEffect = "none"; //$NON-NLS-0$
			} else {
				if (!util.isFirefox) {
					var copy = util.isMac ? e.event.altKey : e.event.ctrlKey;
					this._dropEffect = e.event.dataTransfer.dropEffect = copy ? "copy" : "move"; //$NON-NLS-1$ //$NON-NLS-0$
				}
			}
		},
		_onDrop: function(e) {
			var view = this._view;
			var text = e.event.dataTransfer.getData("Text"); //$NON-NLS-0$
			if (text) {
				if (!util.isFirefox) {
					e.event.dataTransfer.dropEffect = this._dropEffect; //$NON-NLS-1$ //$NON-NLS-0$
				}
				var offset = view.getOffsetAtLocation(e.x, e.y);
				if (this._dragSelection) {
					this._dropOffset = offset;
					this._dropText = text;
				} else {
					view.setText(text, offset, offset);
					view.setSelection(offset, offset + text.length);
				}
			}
		}
	};

	return {TextDND: TextDND};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define("orion/editor/linkedMode", [ //$NON-NLS-0$
	'i18n!orion/editor/nls/messages', //$NON-NLS-0$
	'orion/keyBinding', //$NON-NLS-0$
	'orion/editor/keyModes', //$NON-NLS-0$
	'orion/editor/annotations', //$NON-NLS-0$
	'orion/objects', //$NON-NLS-0$
	'orion/util' //$NON-NLS-0$
], function(messages, mKeyBinding, mKeyModes, mAnnotations, objects) {

	var exports = {};

	/**
	 * @name LinkedContentAssist
	 * @description Creates a new LinkedContentAssist object which is used for linked data in templates
	 * @constructor 
	 * @param {Object} linkedData The linked data
	 * @returns A new LinkedContentAssist object
	 * @since 9.0
	 */
	function LinkedContentAssist(linkedData) {
		this._data = linkedData;
	}
	
	LinkedContentAssist.prototype = {
		chop: function(prefix, string) {
			return string.substring(prefix.length);
		},
		
		/**
		 * @callback
		 */
		computeProposals: function(buffer, offset, context) {
			var prefix = context.prefix;
			var proposals = [];
			var linkedstyle = this._data.style ? this._data.style : null;
			linkedstyle = linkedstyle ? linkedstyle : 'emphasis';
			var values = this._data.values;
			for (var i = 0; i < values.length; i++) {
				var val = values[i];
				if(typeof(val) === 'string' && val.indexOf(prefix) === 0) {
					proposals.push({proposal: this.chop(prefix, val), 
						description: val,
						hover: val,
						style: linkedstyle
					});
				} else if(typeof(val) === 'object' && val.proposal) {
					proposals.push({proposal: this.chop(prefix, val.proposal), 
						description: val.description ? val.description : val.proposal,
						hover: val.hover ? val.hover : val.proposal,
						style: linkedstyle
					});
				}
			}
			var linkedtitle = this._data.title ? this._data.title : null;
			if (0 < proposals.length) {
				proposals.splice(0, 0,{
					proposal: '',
					description: linkedtitle ? linkedtitle : 'Options', //$NON-NLS-0$
					style: 'noemphasis_title', //$NON-NLS-0$
					unselectable: true
				});	
			}
			return proposals;
		}
	};

	function LinkedMode(editor, undoStack, contentAssist) {
		var textView = editor.getTextView();
		mKeyModes.KeyMode.call(this, textView);
		this.editor = editor;
		this.undoStack = undoStack;
		this.contentAssist = contentAssist;
		
		this.linkedModeModel = null;
		
		textView.setAction("linkedModeEnter", function() { //$NON-NLS-0$
			this.exitLinkedMode(true);
			return true;
		}.bind(this));
		textView.setAction("linkedModeCancel", function() { //$NON-NLS-0$
			this.exitLinkedMode(false);
			return true;
		}.bind(this));
		textView.setAction("linkedModeNextGroup", function() { //$NON-NLS-0$
			var model = this.linkedModeModel;
			this.selectLinkedGroup((model.selectedGroupIndex + 1) % model.groups.length);
			return true;
		}.bind(this));
		textView.setAction("linkedModePreviousGroup", function() { //$NON-NLS-0$
			var model = this.linkedModeModel;
			this.selectLinkedGroup(model.selectedGroupIndex > 0 ? model.selectedGroupIndex-1 : model.groups.length-1);
			return true;
		}.bind(this));
		
		/**
		 * Listener called when Linked Mode is active. Updates position's offsets and length
		 * on user change. Also escapes the Linked Mode if the text buffer was modified outside of the Linked Mode positions.
		 */
		this.linkedModeListener = {
		
			onActivating: function(event) {
				if (this._groupContentAssistProvider) {
					this.contentAssist.setProviders([this._groupContentAssistProvider]);
					this.contentAssist.setProgress(null);
				}
			}.bind(this),
			
			onModelChanged: function(event) {
				if (this.ignoreVerify) { return; }

				// Get the position being modified
				var start = this.editor.mapOffset(event.start);
				var model = this.linkedModeModel, positionChanged, changed;
				while (model) {
					positionChanged = this._getPositionChanged(model, start, start + event.removedCharCount);
					changed = positionChanged.position;
					if (changed === undefined || changed.model !== model) {
						// The change has been done outside of the positions, exit the Linked Mode
						this.exitLinkedMode(false);
						model = this.linkedModeModel;
					} else {
						break;
					}
				}
				if (!model) { return; }

				// Update position offsets for this change. Group changes are done in #onVerify
				var deltaCount = 0;
				var changeCount = event.addedCharCount - event.removedCharCount;
				var sortedPositions = positionChanged.positions, position, pos;
				for (var i = 0; i < sortedPositions.length; ++i) {
					pos = sortedPositions[i];
					position = pos.position;
					var inside = position.offset <= start && start <= position.offset + position.length;
					if (inside && !pos.ansestor) {
						position.offset += deltaCount;
						position.length += changeCount;
						deltaCount += changeCount;
					} else {
						position.offset += deltaCount;
						if (pos.ansestor && inside) {
							position.length += changeCount;
						}
					}
					if (pos.escape) {
						pos.model.escapePosition = position.offset;
					}
				}
				this._updateAnnotations(sortedPositions);
			}.bind(this),

			onVerify: function(event) {
				if (this.ignoreVerify) { return; }

				// Get the position being modified
				var editor = this.editor;
				var start = editor.mapOffset(event.start);
				var end = this.editor.mapOffset(event.end);
				var model = this.linkedModeModel, positionChanged, changed;
				while (model) {
					positionChanged = this._getPositionChanged(model, start, end);
					changed = positionChanged.position;
					if (changed === undefined || changed.model !== model) {
						// The change has been done outside of the positions, exit the Linked Mode
						this.exitLinkedMode(false);
						model = this.linkedModeModel;
					} else {
						break;
					}
				}
				if (!model) { return; }
				
				// Make sure changes in a same group are compound
				var undo = this._compoundChange;
				if (undo) {
					if (!(undo.owner.model === model && undo.owner.group === changed.group)) {
						this.endUndo();
						this.startUndo();
					}
				} else {
					this.startUndo();
				}

				model.selectedGroupIndex = changed.group;
				
				// Update position offsets taking into account all positions in the same changing group
				var deltaCount = 0;
				var changeCount = event.text.length - (end - start);
				var sortedPositions = positionChanged.positions, position, pos;
				var deltaStart = start - changed.position.offset, deltaEnd = end - changed.position.offset;
				for (var i = 0; i < sortedPositions.length; ++i) {
					pos = sortedPositions[i];
					position = pos.position;
					pos.oldOffset = position.offset;
					if (pos.model === model && pos.group === changed.group) {
						position.offset += deltaCount;
						position.length += changeCount;
						deltaCount += changeCount;
					} else {
						position.offset += deltaCount;
						if (pos.ansestor) {
							position.length += changed.count * changeCount;
						}
					}
					if (pos.escape) {
						pos.model.escapePosition = position.offset;
					}
				}
				
				// Cancel this modification and apply same modification to all positions in changing group
				this.ignoreVerify = true;
				for (i = sortedPositions.length - 1; i >= 0; i--) {
					pos = sortedPositions[i];
					if (pos.model === model && pos.group === changed.group) {
						editor.setText(event.text, pos.oldOffset + deltaStart , pos.oldOffset + deltaEnd, false);
					}
				}
				this.ignoreVerify = false;
				event.text = null;
				this._updateAnnotations(sortedPositions);
			}.bind(this)
		};
	}
	LinkedMode.prototype = new mKeyModes.KeyMode();
	objects.mixin(LinkedMode.prototype, {
		createKeyBindings: function() {
			var KeyBinding = mKeyBinding.KeyBinding;
			var bindings = [];
			bindings.push({actionID: "linkedModeEnter", keyBinding: new KeyBinding(13)}); //$NON-NLS-0$
			bindings.push({actionID: "linkedModeCancel", keyBinding: new KeyBinding(27)}); //$NON-NLS-0$
			bindings.push({actionID: "linkedModeNextGroup", keyBinding: new KeyBinding(9)}); //$NON-NLS-0$
			bindings.push({actionID: "linkedModePreviousGroup", keyBinding: new KeyBinding(9, false, true)}); //$NON-NLS-0$
			return bindings;
		},
		/**
		 * Starts Linked Mode, selects the first position and registers the listeners.
		 * @param {Object} linkedModeModel An object describing the model to be used by linked mode.
		 * Contains one or more position groups. If a position in a group is edited, the other positions in
		 * the same group are edited the same way. The model structure is as follows:
		 * <pre>{
		 *		groups: [{
		 *			data: {},
		 *			positions: [{
		 *				offset: 10, // Relative to the text buffer
		 *				length: 3
		 *			}]
		 *		}],
		 *		escapePosition: 19, // Relative to the text buffer
		 * }</pre>
		 *
		 * Each group in the model has an optional <code>data</code> property which can be
		 * used to provide additional content assist for the group.  The <code>type</code> in
		 * data determines what kind of content assist is provided. These are the support
		 * structures for the <code>data</code> property.
		 * <pre>{
		 *		type: "link"
		 *		values: ["proposal0", "proposal1", ...]
		 * }</pre>
		 *
		 * The "link" data struture provides static content assist proposals stored in the
		 * <code>values</code> property.
		 *
		 * <p>
		 * <b>See:</b><br/>
		 * {@link orion.editor.Template}<br/>
		 * {@link orion.editor.TemplateContentAssist}<br/>
		 * </p>
		 */
		enterLinkedMode: function(linkedModeModel) {
			if (!this.linkedModeModel) {
				var textView = this.editor.getTextView();
				textView.addKeyMode(this);
				textView.addEventListener("Verify", this.linkedModeListener.onVerify); //$NON-NLS-0$
				textView.addEventListener("ModelChanged", this.linkedModeListener.onModelChanged); //$NON-NLS-0$
				var contentAssist = this.contentAssist;
				contentAssist.addEventListener("Activating", this.linkedModeListener.onActivating); //$NON-NLS-0$
				this.editor.reportStatus(messages.linkedModeEntered, null, true);
			}
			this._sortedPositions = null;
			if (this.linkedModeModel) {
				linkedModeModel.previousModel = this.linkedModeModel;
				linkedModeModel.parentGroup = this.linkedModeModel.selectedGroupIndex;
				this.linkedModeModel.nextModel = linkedModeModel;
			}
			this.linkedModeModel = linkedModeModel;
			this.selectLinkedGroup(0);
		},
		/** 
		 * Exits Linked Mode. Optionally places the caret at linkedMode escapePosition. 
		 * @param {Boolean} [escapePosition=false] if true, place the caret at the  escape position.
		 */
		exitLinkedMode: function(escapePosition) {
			if (!this.isActive()) {
				return;
			}
			if (this._compoundChange) {
				this.endUndo();
				this._compoundChange = null;
			}
			this._sortedPositions = null;
			var model = this.linkedModeModel;
			this.linkedModeModel = model.previousModel;
			model.parentGroup = model.previousModel = undefined;
			if (this.linkedModeModel) {
				this.linkedModeModel.nextModel = undefined;
			}
			if (!this.linkedModeModel) {
				var editor = this.editor;
				var textView = editor.getTextView();
				textView.removeKeyMode(this);
				textView.removeEventListener("Verify", this.linkedModeListener.onVerify); //$NON-NLS-0$
				textView.removeEventListener("ModelChanged", this.linkedModeListener.onModelChanged); //$NON-NLS-0$
				var contentAssist = this.contentAssist;
				contentAssist.removeEventListener("Activating", this.linkedModeListener.onActivating); //$NON-NLS-0$
				contentAssist.offset = undefined;
				this.editor.reportStatus(messages.linkedModeExited, null, true);
				if (escapePosition && typeof model.escapePosition === "number") { //$NON-NLS-0$
					editor.setCaretOffset(model.escapePosition, false);
				}
			}
			this.selectLinkedGroup(0);
		},
		startUndo: function() {
			if (this.undoStack) {
				var self = this;
				var model = this.linkedModeModel;
				this._compoundChange = this.undoStack.startCompoundChange({
					model: model,
					group: model.selectedGroupIndex,
					end: function() {
						self._compoundChange = null;
					}
				});
			}
		}, 
		endUndo: function() {
			if (this.undoStack) {
				this.undoStack.endCompoundChange();
			}
		},
		isActive: function() {
			return !!this.linkedModeModel;
		},
		isStatusActive: function() {
			return !!this.linkedModeModel;
		},
		selectLinkedGroup: function(index) {
			var model = this.linkedModeModel;
			if (model) {
				model.selectedGroupIndex = index;
				var group = model.groups[index];
				var position = group.positions[0];
				var editor = this.editor;
				editor.setSelection(position.offset, position.offset + position.length);
				var contentAssist = this.contentAssist;
				if (contentAssist) {
					contentAssist.offset = undefined;
					if (group.data && group.data.type === "link" && group.data.values) { //$NON-NLS-0$
						var provider = this._groupContentAssistProvider = new LinkedContentAssist(group.data);
						provider.getPrefix = function() {
							var selection = editor.getSelection();
							if (selection.start === selection.end) {
								var caretOffset = editor.getCaretOffset();
								if (position.offset <= caretOffset && caretOffset <= position.offset + position.length) {
									return editor.getText(position.offset, caretOffset);
								}
							}
							return "";
						};
						contentAssist.offset = position.offset;
						contentAssist.deactivate();
						contentAssist.activate();
					} else if (this._groupContentAssistProvider) {
						this._groupContentAssistProvider = null;
						contentAssist.deactivate();
					}
				}
			}
			this._updateAnnotations();
		},
		_getModelPositions: function(all, model, delta) {
			var groups = model.groups;
			for (var i = 0; i < groups.length; i++) {
				var positions = groups[i].positions;
				for (var j = 0; j < positions.length; j++) {
					var position = positions[j];
					if (delta) {
						position = {offset: position.offset + delta, length: position.length};
					}
					var pos = {
						index: j,
						group: i,
						count: positions.length,
						model: model,
						position: position
					};
					all.push(pos);
					if (model.nextModel && model.nextModel.parentGroup === i) {
						pos.ansestor = true;
						this._getModelPositions(all, model.nextModel, (delta || 0) + positions[j].offset - positions[0].offset);
					}
				}
			}
		},
		_getSortedPositions: function(model) {
			var all = this._sortedPositions;
			if (!all) {
				all = [];
				// Get the root linked model
				while (model.previousModel) {
					model = model.previousModel;
				}
				// Get all positions under model expanding group positions of stacked linked modes
				this._getModelPositions(all, model);
				// Add escape position for all models
				while (model) {
					if (typeof model.escapePosition === "number") { //$NON-NLS-0$
						all.push({
							escape: true,
							model: model,
							position: {offset: model.escapePosition, length: 0}
						});
					}
					model = model.nextModel;
				}
				all.sort(function(a, b) {
					return a.position.offset - b.position.offset;
				});
				this._sortedPositions = all;
			}
			return all;
		},
		_getPositionChanged: function(model, start, end) {
			var changed;
			var sortedPositions = this._getSortedPositions(model);
			for (var i = sortedPositions.length - 1; i >= 0; i--) {
				if (sortedPositions[i].escape)
					continue;
				var position = sortedPositions[i].position;
				if (position.offset <= start && end <= position.offset + position.length) {
					changed = sortedPositions[i];
					break;
				}
			}
			return {position: changed, positions: sortedPositions};
		},
		_updateAnnotations: function(positions) {
			var annotationModel = this.editor.getAnnotationModel();
			if (!annotationModel) { return; }
			var remove = [], add = [];
			var annotations = annotationModel.getAnnotations(), annotation;
			while (annotations.hasNext()) {
				annotation = annotations.next();
				switch (annotation.type) {
					case mAnnotations.AnnotationType.ANNOTATION_LINKED_GROUP:
					case mAnnotations.AnnotationType.ANNOTATION_CURRENT_LINKED_GROUP:
					case mAnnotations.AnnotationType.ANNOTATION_SELECTED_LINKED_GROUP:
						remove.push(annotation);
				}
			}
			var model = this.linkedModeModel;
			if (model) {
				positions = positions || this._getSortedPositions(model);
				for (var i = 0; i < positions.length; i++) {
					var position = positions[i];
					if (position.model !== model) { continue; }
					if (position.escape) { continue; }
					var type = mAnnotations.AnnotationType.ANNOTATION_LINKED_GROUP;
					if (position.group === model.selectedGroupIndex) {
						if (position.index === 0) {
							type = mAnnotations.AnnotationType.ANNOTATION_SELECTED_LINKED_GROUP;
						} else {
							type = mAnnotations.AnnotationType.ANNOTATION_CURRENT_LINKED_GROUP;
						}
					}
					position = position.position;
					annotation = mAnnotations.AnnotationType.createAnnotation(type, position.offset, position.offset + position.length, "");
					add.push(annotation);
				}
			}
			annotationModel.replaceAnnotations(remove, add);
		}
	});
	exports.LinkedMode = LinkedMode;

	return exports;
});

/*******************************************************************************
 * @license
 * Copyright (c) 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define("orion/editor/factories", [ //$NON-NLS-0$
	'orion/editor/actions', //$NON-NLS-0$
	'orion/editor/undoStack', //$NON-NLS-0$
	'orion/editor/rulers', //$NON-NLS-0$
	'orion/editor/annotations', //$NON-NLS-0$
	'orion/editor/textDND', //$NON-NLS-0$
	'orion/editor/linkedMode', //$NON-NLS-0$
	'orion/util' //$NON-NLS-0$
], function(mActions, mUndoStack, mRulers, mAnnotations, mTextDND, mLinkedMode, util) {

	var exports = {};
	
	function KeyBindingsFactory() {
	}
	KeyBindingsFactory.prototype = {
		createKeyBindings: function(editor, undoStack, contentAssist, searcher) {
			// Create keybindings for generic editing, no dependency on the service model
			var textActions = new mActions.TextActions(editor, undoStack , searcher);
			// Linked Mode
			var linkedMode = new mLinkedMode.LinkedMode(editor, undoStack, contentAssist);
			// create keybindings for source editing
			// TODO this should probably be something that happens more dynamically, when the editor changes input
			var sourceCodeActions = new mActions.SourceCodeActions(editor, undoStack, contentAssist, linkedMode);
			return {
				textActions: textActions,
				linkedMode: linkedMode,
				sourceCodeActions: sourceCodeActions
			};
		}
	};
	exports.KeyBindingsFactory = KeyBindingsFactory;
	
	function UndoFactory() {
	}
	UndoFactory.prototype = {
		createUndoStack: function(editor) {
			var textView = editor.getTextView();
			return new mUndoStack.UndoStack(textView, 200);
		}
	};
	exports.UndoFactory = UndoFactory;

	function LineNumberRulerFactory() {
	}
	LineNumberRulerFactory.prototype = {
		createLineNumberRuler: function(annotationModel) {
			return new mRulers.LineNumberRuler(annotationModel, "left", {styleClass: "ruler lines"}, {styleClass: "rulerLines odd"}, {styleClass: "rulerLines even"}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		}
	};
	exports.LineNumberRulerFactory = LineNumberRulerFactory;
	
	function FoldingRulerFactory() {
	}
	FoldingRulerFactory.prototype = {
		createFoldingRuler: function(annotationModel) {
			return new mRulers.FoldingRuler(annotationModel, "left", {styleClass: "ruler folding"}); //$NON-NLS-1$ //$NON-NLS-0$
		}
	};
	exports.FoldingRulerFactory = FoldingRulerFactory;
	
	function ZoomRulerFactory() {
	}
	ZoomRulerFactory.prototype = {
		createZoomRuler: function(annotationModel) {
			return new mRulers.ZoomRuler(util.isIOS || util.isAndroid ? "right" : "innerRight", {styleClass: "ruler zoom"}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		}
	};
	exports.ZoomRulerFactory = ZoomRulerFactory;
	
	function AnnotationFactory() {
	}
	AnnotationFactory.prototype = {
		createAnnotationModel: function(model) {
			return new mAnnotations.AnnotationModel(model);
		},
		createAnnotationStyler: function(annotationModel, view) {
			return new mAnnotations.AnnotationStyler(annotationModel, view);
		},
		createAnnotationRulers: function(annotationModel) {
			var annotationRuler = new mRulers.AnnotationRuler(annotationModel, "left", {styleClass: "ruler annotations"}); //$NON-NLS-1$ //$NON-NLS-0$
			var overviewRuler = new mRulers.OverviewRuler(annotationModel, "right", {styleClass: "ruler overview"}); //$NON-NLS-1$ //$NON-NLS-0$
			return {annotationRuler: annotationRuler, overviewRuler: overviewRuler};
		}
	};
	exports.AnnotationFactory = AnnotationFactory;
	
	function TextDNDFactory() {
	}
	TextDNDFactory.prototype = {
		createTextDND: function(editor, undoStack) {
			return new mTextDND.TextDND(editor.getTextView(), undoStack);
		}
	};
	exports.TextDNDFactory = TextDNDFactory;
	
	return exports;
});
/*******************************************************************************
 * @license
 * Copyright (c) 2011, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define("orion/editor/editorFeatures", [ //$NON-NLS-0$
	'orion/editor/factories', //$NON-NLS-0$
	'orion/editor/actions', //$NON-NLS-0$
	'orion/editor/linkedMode', //$NON-NLS-0$
	'orion/objects' //$NON-NLS-0$
], function(mFactories, mActions, mLinkedMode, objects) {
	return objects.mixin({}, mFactories, mActions, mLinkedMode);
});

/**
 * marked - a markdown parser
 * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
 * https://github.com/chjj/marked
 */

;(function() {

/**
 * Block-Level Grammar
 */

var block = {
  newline: /^\n+/,
  code: /^( {4}[^\n]+\n*)+/,
  fences: noop,
  hr: /^( *[-*_]){3,} *(?:\n+|$)/,
  heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
  nptable: noop,
  lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
  blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,
  list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
  html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
  def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
  table: noop,
  paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
  text: /^[^\n]+/
};

block.bullet = /(?:[*+-]|\d+\.)/;
block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
block.item = replace(block.item, 'gm')
  (/bull/g, block.bullet)
  ();

block.list = replace(block.list)
  (/bull/g, block.bullet)
  ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))')
  ('def', '\\n+(?=' + block.def.source + ')')
  ();

block.blockquote = replace(block.blockquote)
  ('def', block.def)
  ();

block._tag = '(?!(?:'
  + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
  + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
  + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b';

block.html = replace(block.html)
  ('comment', /<!--[\s\S]*?-->/)
  ('closed', /<(tag)[\s\S]+?<\/\1>/)
  ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
  (/tag/g, block._tag)
  ();

block.paragraph = replace(block.paragraph)
  ('hr', block.hr)
  ('heading', block.heading)
  ('lheading', block.lheading)
  ('blockquote', block.blockquote)
  ('tag', '<' + block._tag)
  ('def', block.def)
  ();

/**
 * Normal Block Grammar
 */

block.normal = merge({}, block);

/**
 * GFM Block Grammar
 */

block.gfm = merge({}, block.normal, {
  fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
  paragraph: /^/
});

block.gfm.paragraph = replace(block.paragraph)
  ('(?!', '(?!'
    + block.gfm.fences.source.replace('\\1', '\\2') + '|'
    + block.list.source.replace('\\1', '\\3') + '|')
  ();

/**
 * GFM + Tables Block Grammar
 */

block.tables = merge({}, block.gfm, {
  nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
  table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
});

/**
 * Block Lexer
 */

function Lexer(options) {
  this.tokens = [];
  this.tokens.links = {};
  this.options = options || marked.defaults;
  this.rules = block.normal;

  if (this.options.gfm) {
    if (this.options.tables) {
      this.rules = block.tables;
    } else {
      this.rules = block.gfm;
    }
  }
}

/**
 * Expose Block Rules
 */

Lexer.rules = block;

/**
 * Static Lex Method
 */

Lexer.lex = function(src, options) {
  var lexer = new Lexer(options);
  return lexer.lex(src);
};

/**
 * Preprocessing
 */

Lexer.prototype.lex = function(src) {
  src = src
    .replace(/\r\n|\r/g, '\n')
    .replace(/\t/g, '    ')
    .replace(/\u00a0/g, ' ')
    .replace(/\u2424/g, '\n');

  return this.token(src, true);
};

/**
 * Lexing
 */

Lexer.prototype.token = function(src, top, bq) {
  var src = src.replace(/^ +$/gm, '')
    , next
    , loose
    , cap
    , bull
    , b
    , item
    , space
    , i
    , l;

  while (src) {
    // newline
    if (cap = this.rules.newline.exec(src)) {
      src = src.substring(cap[0].length);
      if (cap[0].length > 1) {
        this.tokens.push({
          type: 'space'
        });
      }
    }

    // code
    if (cap = this.rules.code.exec(src)) {
      src = src.substring(cap[0].length);
      cap = cap[0].replace(/^ {4}/gm, '');
      this.tokens.push({
        type: 'code',
        text: !this.options.pedantic
          ? cap.replace(/\n+$/, '')
          : cap
      });
      continue;
    }

    // fences (gfm)
    if (cap = this.rules.fences.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'code',
        lang: cap[2],
        text: cap[3]
      });
      continue;
    }

    // heading
    if (cap = this.rules.heading.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'heading',
        depth: cap[1].length,
        text: cap[2]
      });
      continue;
    }

    // table no leading pipe (gfm)
    if (top && (cap = this.rules.nptable.exec(src))) {
      src = src.substring(cap[0].length);

      item = {
        type: 'table',
        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
        cells: cap[3].replace(/\n$/, '').split('\n')
      };

      for (i = 0; i < item.align.length; i++) {
        if (/^ *-+: *$/.test(item.align[i])) {
          item.align[i] = 'right';
        } else if (/^ *:-+: *$/.test(item.align[i])) {
          item.align[i] = 'center';
        } else if (/^ *:-+ *$/.test(item.align[i])) {
          item.align[i] = 'left';
        } else {
          item.align[i] = null;
        }
      }

      for (i = 0; i < item.cells.length; i++) {
        item.cells[i] = item.cells[i].split(/ *\| */);
      }

      this.tokens.push(item);

      continue;
    }

    // lheading
    if (cap = this.rules.lheading.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'heading',
        depth: cap[2] === '=' ? 1 : 2,
        text: cap[1]
      });
      continue;
    }

    // hr
    if (cap = this.rules.hr.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'hr'
      });
      continue;
    }

    // blockquote
    if (cap = this.rules.blockquote.exec(src)) {
      src = src.substring(cap[0].length);

      this.tokens.push({
        type: 'blockquote_start'
      });

      cap = cap[0].replace(/^ *> ?/gm, '');

      // Pass `top` to keep the current
      // "toplevel" state. This is exactly
      // how markdown.pl works.
      this.token(cap, top, true);

      this.tokens.push({
        type: 'blockquote_end'
      });

      continue;
    }

    // list
    if (cap = this.rules.list.exec(src)) {
      src = src.substring(cap[0].length);
      bull = cap[2];

      this.tokens.push({
        type: 'list_start',
        ordered: bull.length > 1
      });

      // Get each top-level item.
      cap = cap[0].match(this.rules.item);

      next = false;
      l = cap.length;
      i = 0;

      for (; i < l; i++) {
        item = cap[i];

        // Remove the list item's bullet
        // so it is seen as the next token.
        space = item.length;
        item = item.replace(/^ *([*+-]|\d+\.) +/, '');

        // Outdent whatever the
        // list item contains. Hacky.
        if (~item.indexOf('\n ')) {
          space -= item.length;
          item = !this.options.pedantic
            ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
            : item.replace(/^ {1,4}/gm, '');
        }

        // Determine whether the next list item belongs here.
        // Backpedal if it does not belong in this list.
        if (this.options.smartLists && i !== l - 1) {
          b = block.bullet.exec(cap[i + 1])[0];
          if (bull !== b && !(bull.length > 1 && b.length > 1)) {
            src = cap.slice(i + 1).join('\n') + src;
            i = l - 1;
          }
        }

        // Determine whether item is loose or not.
        // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
        // for discount behavior.
        loose = next || /\n\n(?!\s*$)/.test(item);
        if (i !== l - 1) {
          next = item.charAt(item.length - 1) === '\n';
          if (!loose) loose = next;
        }

        this.tokens.push({
          type: loose
            ? 'loose_item_start'
            : 'list_item_start'
        });

        // Recurse.
        this.token(item, false, bq);

        this.tokens.push({
          type: 'list_item_end'
        });
      }

      this.tokens.push({
        type: 'list_end'
      });

      continue;
    }

    // html
    if (cap = this.rules.html.exec(src)) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: this.options.sanitize
          ? 'paragraph'
          : 'html',
        pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style',
        text: cap[0],
        /* the following line is added by Orion (10/03/2015) */
        isHTML: true
      });
      continue;
    }

    // def
    if ((!bq && top) && (cap = this.rules.def.exec(src))) {
      src = src.substring(cap[0].length);
      this.tokens.links[cap[1].toLowerCase()] = {
        href: cap[2],
        title: cap[3]
      };

      /* the following line is added by Orion (14/10/2014) */
      this.tokens.push({
        type: 'def',
        id: cap[1].toLowerCase(),
        href: cap[2],
        title: cap[3]
      });

      continue;
    }

    // table (gfm)
    if (top && (cap = this.rules.table.exec(src))) {
      src = src.substring(cap[0].length);

      item = {
        type: 'table',
        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
        cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
      };

      for (i = 0; i < item.align.length; i++) {
        if (/^ *-+: *$/.test(item.align[i])) {
          item.align[i] = 'right';
        } else if (/^ *:-+: *$/.test(item.align[i])) {
          item.align[i] = 'center';
        } else if (/^ *:-+ *$/.test(item.align[i])) {
          item.align[i] = 'left';
        } else {
          item.align[i] = null;
        }
      }

      for (i = 0; i < item.cells.length; i++) {
        item.cells[i] = item.cells[i]
          .replace(/^ *\| *| *\| *$/g, '')
          .split(/ *\| */);
      }

      this.tokens.push(item);

      continue;
    }

    // top-level paragraph
    if (top && (cap = this.rules.paragraph.exec(src))) {
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'paragraph',
        text: cap[1].charAt(cap[1].length - 1) === '\n'
          ? cap[1].slice(0, -1)
          : cap[1]
      });
      continue;
    }

    // text
    if (cap = this.rules.text.exec(src)) {
      // Top-level should never reach here.
      src = src.substring(cap[0].length);
      this.tokens.push({
        type: 'text',
        text: cap[0]
      });
      continue;
    }

    if (src) {
      throw new
        Error('Infinite loop on byte: ' + src.charCodeAt(0));
    }
  }

  return this.tokens;
};

/**
 * Inline-Level Grammar
 */

var inline = {
  escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
  autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
  url: noop,
  tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
  link: /^!?\[(inside)\]\(href\)/,
  reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
  nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
  strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
  em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
  code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
  br: /^ {2,}\n(?!\s*$)/,
  del: noop,
  text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
};

inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;

inline.link = replace(inline.link)
  ('inside', inline._inside)
  ('href', inline._href)
  ();

inline.reflink = replace(inline.reflink)
  ('inside', inline._inside)
  ();

/**
 * Normal Inline Grammar
 */

inline.normal = merge({}, inline);

/**
 * Pedantic Inline Grammar
 */

inline.pedantic = merge({}, inline.normal, {
  strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
  em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
});

/**
 * GFM Inline Grammar
 */

inline.gfm = merge({}, inline.normal, {
  escape: replace(inline.escape)('])', '~|])')(),
  url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
  del: /^~~(?=\S)([\s\S]*?\S)~~/,
  text: replace(inline.text)
    (']|', '~]|')
    ('|', '|https?://|')
    ()
});

/**
 * GFM + Line Breaks Inline Grammar
 */

inline.breaks = merge({}, inline.gfm, {
  br: replace(inline.br)('{2,}', '*')(),
  text: replace(inline.gfm.text)('{2,}', '*')()
});

/**
 * Inline Lexer & Compiler
 */

function InlineLexer(links, options) {
  this.options = options || marked.defaults;
  this.links = links;
  this.rules = inline.normal;
  this.renderer = this.options.renderer || new Renderer;
  this.renderer.options = this.options;

  if (!this.links) {
    throw new
      Error('Tokens array requires a `links` property.');
  }

  if (this.options.gfm) {
    if (this.options.breaks) {
      this.rules = inline.breaks;
    } else {
      this.rules = inline.gfm;
    }
  } else if (this.options.pedantic) {
    this.rules = inline.pedantic;
  }
}

/**
 * Expose Inline Rules
 */

InlineLexer.rules = inline;

/**
 * Static Lexing/Compiling Method
 */

InlineLexer.output = function(src, links, options) {
  var inline = new InlineLexer(links, options);
  return inline.output(src);
};

/**
 * Lexing/Compiling
 */

InlineLexer.prototype.output = function(src) {
  var out = ''
    , link
    , text
    , href
    , cap;

  while (src) {
    // escape
    if (cap = this.rules.escape.exec(src)) {
      src = src.substring(cap[0].length);
      out += cap[1];
      continue;
    }

    // autolink
    if (cap = this.rules.autolink.exec(src)) {
      src = src.substring(cap[0].length);
      if (cap[2] === '@') {
        text = cap[1].charAt(6) === ':'
          ? this.mangle(cap[1].substring(7))
          : this.mangle(cap[1]);
        href = this.mangle('mailto:') + text;
      } else {
        text = escape(cap[1]);
        href = text;
      }
      out += this.renderer.link(href, null, text);
      continue;
    }

    // url (gfm)
    if (!this.inLink && (cap = this.rules.url.exec(src))) {
      src = src.substring(cap[0].length);
      text = escape(cap[1]);
      href = text;
      out += this.renderer.link(href, null, text);
      continue;
    }

    // tag
    if (cap = this.rules.tag.exec(src)) {
      if (!this.inLink && /^<a /i.test(cap[0])) {
        this.inLink = true;
      } else if (this.inLink && /^<\/a>/i.test(cap[0])) {
        this.inLink = false;
      }
      src = src.substring(cap[0].length);
      out += this.options.sanitize
        ? escape(cap[0])
        : cap[0];
      continue;
    }

    // link
    if (cap = this.rules.link.exec(src)) {
      src = src.substring(cap[0].length);
      this.inLink = true;
      out += this.outputLink(cap, {
        href: cap[2],
        title: cap[3]
      });
      this.inLink = false;
      continue;
    }

    // reflink, nolink
    if ((cap = this.rules.reflink.exec(src))
        || (cap = this.rules.nolink.exec(src))) {
      src = src.substring(cap[0].length);
      link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
      link = this.links[link.toLowerCase()];
      if (!link || !link.href) {
        out += cap[0].charAt(0);
        src = cap[0].substring(1) + src;
        continue;
      }
      this.inLink = true;
      out += this.outputLink(cap, link);
      this.inLink = false;
      continue;
    }

    // strong
    if (cap = this.rules.strong.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.strong(this.output(cap[2] || cap[1]));
      continue;
    }

    // em
    if (cap = this.rules.em.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.em(this.output(cap[2] || cap[1]));
      continue;
    }

    // code
    if (cap = this.rules.code.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.codespan(escape(cap[2], true));
      continue;
    }

    // br
    if (cap = this.rules.br.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.br();
      continue;
    }

    // del (gfm)
    if (cap = this.rules.del.exec(src)) {
      src = src.substring(cap[0].length);
      out += this.renderer.del(this.output(cap[1]));
      continue;
    }

    // text
    if (cap = this.rules.text.exec(src)) {
      src = src.substring(cap[0].length);
      out += escape(this.smartypants(cap[0]));
      continue;
    }

    if (src) {
      throw new
        Error('Infinite loop on byte: ' + src.charCodeAt(0));
    }
  }

  return out;
};

/**
 * Compile Link
 */

InlineLexer.prototype.outputLink = function(cap, link) {
  var href = escape(link.href)
    , title = link.title ? escape(link.title) : null;

  return cap[0].charAt(0) !== '!'
    ? this.renderer.link(href, title, this.output(cap[1]))
    : this.renderer.image(href, title, escape(cap[1]));
};

/**
 * Smartypants Transformations
 */

InlineLexer.prototype.smartypants = function(text) {
  if (!this.options.smartypants) return text;
  return text
    // em-dashes
    .replace(/--/g, '\u2014')
    // opening singles
    .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
    // closing singles & apostrophes
    .replace(/'/g, '\u2019')
    // opening doubles
    .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
    // closing doubles
    .replace(/"/g, '\u201d')
    // ellipses
    .replace(/\.{3}/g, '\u2026');
};

/**
 * Mangle Links
 */

InlineLexer.prototype.mangle = function(text) {
  var out = ''
    , l = text.length
    , i = 0
    , ch;

  for (; i < l; i++) {
    ch = text.charCodeAt(i);
    if (Math.random() > 0.5) {
      ch = 'x' + ch.toString(16);
    }
    out += '&#' + ch + ';';
  }

  return out;
};

/**
 * Renderer
 */

function Renderer(options) {
  this.options = options || {};
}

Renderer.prototype.code = function(code, lang, escaped) {
  if (this.options.highlight) {
    var out = this.options.highlight(code, lang);
    if (out != null && out !== code) {
      escaped = true;
      code = out;
    }
  }

  if (!lang) {
    return '<pre><code>'
      + (escaped ? code : escape(code, true))
      + '\n</code></pre>';
  }

  return '<pre><code class="'
    + this.options.langPrefix
    + escape(lang, true)
    + '">'
    + (escaped ? code : escape(code, true))
    + '\n</code></pre>\n';
};

Renderer.prototype.blockquote = function(quote) {
  return '<blockquote>\n' + quote + '</blockquote>\n';
};

/* the following function definition is added by Orion (14/10/2014) */
Renderer.prototype.def = function(id, href, title) {
  return '';
};

Renderer.prototype.html = function(html) {
  return html;
};

Renderer.prototype.heading = function(text, level, raw) {
  return '<h'
    + level
    + ' id="'
    + this.options.headerPrefix
    + raw.toLowerCase().replace(/[^\w]+/g, '-')
    + '">'
    + text
    + '</h'
    + level
    + '>\n';
};

Renderer.prototype.hr = function() {
  return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
};

Renderer.prototype.list = function(body, ordered) {
  var type = ordered ? 'ol' : 'ul';
  return '<' + type + '>\n' + body + '</' + type + '>\n';
};

Renderer.prototype.listitem = function(text) {
  return '<li>' + text + '</li>\n';
};

Renderer.prototype.paragraph = function(text) {
  return '<p>' + text + '</p>\n';
};

Renderer.prototype.table = function(header, body) {
  return '<table>\n'
    + '<thead>\n'
    + header
    + '</thead>\n'
    + '<tbody>\n'
    + body
    + '</tbody>\n'
    + '</table>\n';
};

Renderer.prototype.tablerow = function(content) {
  return '<tr>\n' + content + '</tr>\n';
};

Renderer.prototype.tablecell = function(content, flags) {
  var type = flags.header ? 'th' : 'td';
  var tag = flags.align
    ? '<' + type + ' style="text-align:' + flags.align + '">'
    : '<' + type + '>';
  return tag + content + '</' + type + '>\n';
};

// span level renderer
Renderer.prototype.strong = function(text) {
  return '<strong>' + text + '</strong>';
};

Renderer.prototype.em = function(text) {
  return '<em>' + text + '</em>';
};

Renderer.prototype.codespan = function(text) {
  return '<code>' + text + '</code>';
};

Renderer.prototype.br = function() {
  return this.options.xhtml ? '<br/>' : '<br>';
};

Renderer.prototype.del = function(text) {
  return '<del>' + text + '</del>';
};

Renderer.prototype.link = function(href, title, text) {
  if (this.options.sanitize) {
    try {
      var prot = decodeURIComponent(unescape(href))
        .replace(/[^\w:]/g, '')
        .toLowerCase();
    } catch (e) {
      return '';
    }
    if (prot.indexOf('javascript:') === 0) {
      return '';
    }
  }
  var out = '<a href="' + href + '"';
  if (title) {
    out += ' title="' + title + '"';
  }
  out += '>' + text + '</a>';
  return out;
};

Renderer.prototype.image = function(href, title, text) {
  var out = '<img src="' + href + '" alt="' + text + '"';
  if (title) {
    out += ' title="' + title + '"';
  }
  out += this.options.xhtml ? '/>' : '>';
  return out;
};

/**
 * Parsing & Compiling
 */

function Parser(options) {
  this.tokens = [];
  this.token = null;
  this.options = options || marked.defaults;
  this.options.renderer = this.options.renderer || new Renderer;
  this.renderer = this.options.renderer;
  this.renderer.options = this.options;
}

/**
 * Static Parse Method
 */

Parser.parse = function(src, options, renderer) {
  var parser = new Parser(options, renderer);
  return parser.parse(src);
};

/**
 * Parse Loop
 */

Parser.prototype.parse = function(src) {
  this.inline = new InlineLexer(src.links, this.options, this.renderer);
  this.tokens = src.reverse();

  var out = '';
  while (this.next()) {
    out += this.tok();
  }

  return out;
};

/**
 * Next Token
 */

Parser.prototype.next = function() {
  return this.token = this.tokens.pop();
};

/**
 * Preview Next Token
 */

Parser.prototype.peek = function() {
  return this.tokens[this.tokens.length - 1] || 0;
};

/**
 * Parse Text Tokens
 */

Parser.prototype.parseText = function() {
  var body = this.token.text;

  while (this.peek().type === 'text') {
    body += '\n' + this.next().text;
  }

  return this.inline.output(body);
};

/**
 * Parse Current Token
 */

Parser.prototype.tok = function() {
  switch (this.token.type) {
    case 'space': {
      return '';
    }

    /* the following case is added by Orion (14/10/2014) */
    case 'def': {
      return this.renderer.def(
        this.token.id,
        this.token.href,
        this.token.title);
    }

    case 'hr': {
      return this.renderer.hr();
    }
    case 'heading': {
      return this.renderer.heading(
        this.inline.output(this.token.text),
        this.token.depth,
        this.token.text);
    }
    case 'code': {
      return this.renderer.code(this.token.text,
        this.token.lang,
        this.token.escaped);
    }
    case 'table': {
      var header = ''
        , body = ''
        , i
        , row
        , cell
        , flags
        , j;

      // header
      cell = '';
      for (i = 0; i < this.token.header.length; i++) {
        flags = { header: true, align: this.token.align[i] };
        cell += this.renderer.tablecell(
          this.inline.output(this.token.header[i]),
          { header: true, align: this.token.align[i] }
        );
      }
      header += this.renderer.tablerow(cell);

      for (i = 0; i < this.token.cells.length; i++) {
        row = this.token.cells[i];

        cell = '';
        for (j = 0; j < row.length; j++) {
          cell += this.renderer.tablecell(
            this.inline.output(row[j]),
            { header: false, align: this.token.align[j] }
          );
        }

        body += this.renderer.tablerow(cell);
      }
      return this.renderer.table(header, body);
    }
    case 'blockquote_start': {
      var body = '';

      while (this.next().type !== 'blockquote_end') {
        body += this.tok();
      }

      return this.renderer.blockquote(body);
    }
    case 'list_start': {
      var body = ''
        , ordered = this.token.ordered;

      while (this.next().type !== 'list_end') {
        body += this.tok();
      }

      return this.renderer.list(body, ordered);
    }
    case 'list_item_start': {
      var body = '';

      while (this.next().type !== 'list_item_end') {
        body += this.token.type === 'text'
          ? this.parseText()
          : this.tok();
      }

      return this.renderer.listitem(body);
    }
    case 'loose_item_start': {
      var body = '';

      while (this.next().type !== 'list_item_end') {
        body += this.tok();
      }

      return this.renderer.listitem(body);
    }
    case 'html': {
      var html = !this.token.pre && !this.options.pedantic
        ? this.inline.output(this.token.text)
        : this.token.text;
      return this.renderer.html(html);
    }
    case 'paragraph': {
      return this.renderer.paragraph(this.inline.output(this.token.text));
    }
    case 'text': {
      return this.renderer.paragraph(this.parseText());
    }
  }
};

/**
 * Helpers
 */

function escape(html, encode) {
  return html
    .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function unescape(html) {
  return html.replace(/&([#\w]+);/g, function(_, n) {
    n = n.toLowerCase();
    if (n === 'colon') return ':';
    if (n.charAt(0) === '#') {
      return n.charAt(1) === 'x'
        ? String.fromCharCode(parseInt(n.substring(2), 16))
        : String.fromCharCode(+n.substring(1));
    }
    return '';
  });
}

function replace(regex, opt) {
  regex = regex.source;
  opt = opt || '';
  return function self(name, val) {
    if (!name) return new RegExp(regex, opt);
    val = val.source || val;
    val = val.replace(/(^|[^\[])\^/g, '$1');
    regex = regex.replace(name, val);
    return self;
  };
}

function noop() {}
noop.exec = noop;

function merge(obj) {
  var i = 1
    , target
    , key;

  for (; i < arguments.length; i++) {
    target = arguments[i];
    for (key in target) {
      if (Object.prototype.hasOwnProperty.call(target, key)) {
        obj[key] = target[key];
      }
    }
  }

  return obj;
}


/**
 * Marked
 */

function marked(src, opt, callback) {
  if (callback || typeof opt === 'function') {
    if (!callback) {
      callback = opt;
      opt = null;
    }

    opt = merge({}, marked.defaults, opt || {});

    var highlight = opt.highlight
      , tokens
      , pending
      , i = 0;

    try {
      tokens = Lexer.lex(src, opt)
    } catch (e) {
      return callback(e);
    }

    pending = tokens.length;

    var done = function() {
      var out, err;

      try {
        out = Parser.parse(tokens, opt);
      } catch (e) {
        err = e;
      }

      opt.highlight = highlight;

      return err
        ? callback(err)
        : callback(null, out);
    };

    if (!highlight || highlight.length < 3) {
      return done();
    }

    delete opt.highlight;

    if (!pending) return done();

    for (; i < tokens.length; i++) {
      (function(token) {
        if (token.type !== 'code') {
          return --pending || done();
        }
        return highlight(token.text, token.lang, function(err, code) {
          if (code == null || code === token.text) {
            return --pending || done();
          }
          token.text = code;
          token.escaped = true;
          --pending || done();
        });
      })(tokens[i]);
    }

    return;
  }
  try {
    if (opt) opt = merge({}, marked.defaults, opt);
    return Parser.parse(Lexer.lex(src, opt), opt);
  } catch (e) {
    e.message += '\nPlease report this to https://github.com/chjj/marked.';
    if ((opt || marked.defaults).silent) {
      return '<p>An error occured:</p><pre>'
        + escape(e.message + '', true)
        + '</pre>';
    }
    throw e;
  }
}

/**
 * Options
 */

marked.options =
marked.setOptions = function(opt) {
  merge(marked.defaults, opt);
  return marked;
};

marked.defaults = {
  gfm: true,
  tables: true,
  breaks: false,
  pedantic: false,
  sanitize: false,
  smartLists: false,
  silent: false,
  highlight: null,
  langPrefix: 'lang-',
  smartypants: false,
  headerPrefix: '',
  renderer: new Renderer,
  xhtml: false
};

/**
 * Expose
 */

marked.Parser = Parser;
marked.parser = Parser.parse;

marked.Renderer = Renderer;

marked.Lexer = Lexer;
marked.lexer = Lexer.lex;

marked.InlineLexer = InlineLexer;
marked.inlineLexer = InlineLexer.output;

marked.parse = marked;

if (typeof exports === 'object') {
  module.exports = marked;
} else if (typeof define === 'function' && define.amd) {
  define('marked/marked',[],function() { return marked; });
} else {
  this.marked = marked;
}

}).call(function() {
  return this || (typeof window !== 'undefined' ? window : global);
}());

/*******************************************************************************
 * @license
 * Copyright (c) 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/

define ('orion/hover',[
	'marked/marked' //$NON-NLS-0$
], function(Markdown) {

	function Hover(editor, hoverFactory) {
		this.editor = editor;
		this.hoverFactory = hoverFactory;
		this.inputManager = hoverFactory.inputManager;
		this.serviceRegistry = hoverFactory.serviceRegistry;
		this.commandRegistry = hoverFactory.commandRegistry;
		
		this._qfToolbars = [];
	}
	
	Hover.prototype = {
		computeHoverInfo: function (context) {
			var hoverInfo = [];
			this.hoverFactory._applicableProviders.forEach(function(provider) {
				var providerImpl = this.serviceRegistry.getService(provider);
				if (providerImpl && providerImpl.computeHoverInfo) {
					var editorContext = this.editor.getEditorContext();
					hoverInfo.push(providerImpl.computeHoverInfo(editorContext, context));
				}
			}.bind(this));

			return hoverInfo;
		},
		
		renderMarkDown: function(markDown) {
			return Markdown(markDown, {
				sanitize: true
			});
		},
		
		clearQuickFixes: function() {
			this._qfToolbars.forEach(function(qfTB) {
				qfTB.destroy();
			});
			this._qfToolbars = [];
		},
				
		renderQuickFixes: function(annotation, parentDiv) {
			if  (!annotation || !parentDiv){
				return;
			}

			var actionsDiv = document.createElement("div"); //$NON-NLS-0$
			actionsDiv.className = "commandList"; //$NON-NLS-0$ 
//			this._qfToolbars.push(actionsDiv);
			
			var nodeList = [];
			var metadata = this.inputManager.getFileMetadata();
			metadata.annotation = annotation;
			this.commandRegistry.renderCommands("orion.edit.quickfix", actionsDiv, metadata, this.editor, 'quickfix', annotation, nodeList); //$NON-NLS-1$ //$NON-NLS-0$
			delete metadata.annotation;
			parentDiv.appendChild(actionsDiv);
		}

	};

	function HoverFactory(serviceRegistry, inputManager, commandRegistry) {
		this.serviceRegistry = serviceRegistry;
		this.inputManager = inputManager;
		this.commandRegistry = commandRegistry;
		
		// Filter the plugins based on contentType...
		this.filterHoverPlugins();

		// Track changes to the input type and re-filter
		this.inputManager.addEventListener("InputChanged", function() { //$NON-NLS-0$
			this.filterHoverPlugins();
		}.bind(this));
	}
	HoverFactory.prototype = {
		createHover: function(editor) {
			return new Hover(editor, this);
		},
	
		filterHoverPlugins: function () {
			this._applicableProviders = [];
			var infoProviders = this.serviceRegistry.getServiceReferences("orion.edit.hover"); //$NON-NLS-0$
			for (var i = 0; i < infoProviders.length; i++) {
				var providerRef = infoProviders[i];
				var contentType = this.inputManager.getContentType();
				if (contentType) {
					var validTypes = providerRef.getProperty('contentType'); //$NON-NLS-0$
					if (validTypes && validTypes.indexOf(contentType.id) !== -1) {
						this._applicableProviders.push(providerRef);
					}
				}
			}
		}
	};

	return {HoverFactory: HoverFactory}; 
});



/*******************************************************************************
 * @license
 * Copyright (c) 2011, 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define("orion/editor/contentAssist", [ //$NON-NLS-0$
	'i18n!orion/editor/nls/messages', //$NON-NLS-0$
	'orion/keyBinding', //$NON-NLS-0$
	'orion/editor/keyModes', //$NON-NLS-0$
	'orion/editor/eventTarget', //$NON-NLS-0$
	'orion/Deferred', //$NON-NLS-0$
	'orion/objects', //$NON-NLS-0$
	'orion/editor/tooltip', //$NON-NLS-0$
	'orion/editor/util', //$NON-NLS-0$
	'orion/util', //$NON-NLS-0$
	'orion/webui/littlelib', //$NON-NLS-0$
	'orion/metrics' //$NON-NLS-0$
], function(messages, mKeyBinding, mKeyModes, mEventTarget, Deferred, objects, mTooltip, textUtil, util, lib, mMetrics) {
	/**
	 * @name orion.editor.ContentAssistProvider
	 * @class Interface defining a provider of content assist proposals.
	 */
	/**
	 * @memberOf orion.editor.ContentAssistProvider.prototype
	 * @function
	 * @name computeProposals
	 * @param {String} buffer The buffer being edited.
	 * @param {Number} offset The position in the buffer at which content assist is being requested.
	 * @param {orion.editor.ContentAssistProvider.Context} context
	 * @returns {Object[]} This provider's proposals for the given buffer and offset.
	 */
	/**
	 * @name orion.editor.ContentAssistProvider.Context
	 * @class
	 * @property {String} line The text of the line on which content assist is being requested.
	 * @property {String} prefix Any non-whitespace, non-symbol characters preceding the offset.
	 * @property {orion.editor.Selection} selection The current selection.
	 */

	/**
	 * @name orion.editor.ContentAssist
	 * @class Provides content assist for a TextView.
	 * @description Creates a <code>ContentAssist</code> for a TextView. A ContentAssist consults a set of 
	 * {@link orion.editor.ContentAssistProvider}s to obtain proposals for text that may be inserted into a
	 * TextView at a given offset.<p>
	 * A ContentAssist is generally activated by its TextView action, at which point it computes the set of 
	 * proposals available. It will re-compute the proposals in response to subsequent changes on the TextView 
	 * (for example, user typing) for as long as the ContentAssist is active. A proposal may be applied by calling 
	 * {@link #apply}, after which the ContentAssist becomes deactivated. An active ContentAssist may be deactivated
	 * by calling {@link #deactivate}.<p>
	 * A ContentAssist dispatches events when it becomes activated or deactivated, and when proposals have been computed.
	 * @param {orion.editor.TextView} textView The TextView to provide content assist for.
	 * @borrows orion.editor.EventTarget#addEventListener as #addEventListener
	 * @borrows orion.editor.EventTarget#removeEventListener as #removeEventListener
	 * @borrows orion.editor.EventTarget#dispatchEvent as #dispatchEvent
	 */
	/**
	 * Dispatched when a ContentAssist is about to be activated.
	 * @name orion.editor.ContentAssist#ActivatingEvent
	 * @event
	 */
	/**
	 * Dispatched when a ContentAssist is about to be deactivated.
	 * @name orion.editor.ContentAssist#DeactivatingEvent
	 * @event
	 */
	/**
	 * Dispatched when a ContentAssist has applied a proposal. <p>This event's <code>data</code> field gives information
	 * about the proposal that was applied.
	 * @name orion.editor.ContentAssist#ProposalAppliedEvent
	 * @event
	 */
	/**
	 * Dispatched whenever a ContentAssist has obtained proposals from its providers. <p>This event's
	 * <code>data</code> field gives information about the proposals.
	 * @name orion.editor.ContentAssist#ProposalsComputedEvent
	 * @event
	 */
		
	// INACTIVE --Ctrl+Space--> ACTIVE --ModelChanging--> FILTERING
	var State = {
		INACTIVE: 1,
		ACTIVE: 2,
		FILTERING: 3
	};
	
	var STYLES = {
		selected : "selected", //$NON-NLS-0$
		hr : "proposal-hr", //$NON-NLS-0$
		emphasis : "proposal-emphasis", //$NON-NLS-0$
		noemphasis : "proposal-noemphasis", //$NON-NLS-0$
		noemphasis_keyword : "proposal-noemphasis-keyword", //$NON-NLS-0$
		noemphasis_title : "proposal-noemphasis-title", //$NON-NLS-0$
		noemphasis_title_keywords : "proposal-noemphasis-title-keywords", //$NON-NLS-0$
		dfault : "proposal-default" //$NON-NLS-0$
	};
	
	/**
	 * @name ContentAssist
	 * @description Creates a new content assist manager for the given text ciew
	 * @param textView The text view to provide content assist for
	 * @param serviceRegistry Optional, used to look up page message service for status
	 */
	function ContentAssist(textView, serviceRegistry) {
		this.textView = textView;
		if (serviceRegistry){
			this.pageMessage = serviceRegistry.getService("orion.page.message"); //$NON-NLS-1$
		}
		this.state = State.INACTIVE;
		this.clearProviders();
		var self = this;
		this._textViewListeners = {
			onModelChanging: (function(event) {
				this._latestModelChangingEvent = event;
				if (event) {
					this._updateFilterText(event);	
				}
			}).bind(this),
			onSelection: (function(event) {
				if (this.isDeactivatingChange(this._latestModelChangingEvent, event)) {
					this.setState(State.INACTIVE);
				} else {
					if (this.isActive()) {
						if (this.state === State.ACTIVE) {
							this.setState(State.FILTERING);
						}
						this.filterProposals();
					}
				}
				this._latestModelChangingEvent = null;
			}).bind(this),
			onScroll: (function(event) {
				this.setState(State.INACTIVE);
			}).bind(this)
		};
		
		textView.setKeyBinding(util.isMac ? new mKeyBinding.KeyBinding(' ', false, false, false, true) : new mKeyBinding.KeyBinding(' ', true), "contentAssist"); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		textView.setKeyBinding(util.isMac ? new mKeyBinding.KeyBinding(' ', false, false, true, true) : new mKeyBinding.KeyBinding(' ', true, false, true), "contentAssist"); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		textView.setAction("contentAssist", function() { //$NON-NLS-0$
			if (!textView.getOptions("readonly")) { //$NON-NLS-0$
				self.activate();
			}
			return true;
		}, {name: messages.contentAssist});
	}
	ContentAssist.prototype = /** @lends orion.editor.ContentAssist.prototype */ {
		/**
		 * Applies the given proposal to the TextView.
		 * @param {Object} [proposal]
		 * @returns {Boolean} <code>true</code> if the proposal was applied; <code>false</code> if no proposal was provided.
		 */
		apply: function(proposal) {
			if (!proposal) {
				return false;
			}
	
			// now handle prefixes
			// if there is a non-empty selection, then replace it,
			// if overwrite is truthy, then also replace the prefix
			var view = this.textView;
			var sel = view.getSelection();
			var start = this._initialCaretOffset;
			var mapStart = start;
			var end = Math.max(sel.start, sel.end), mapEnd = end;
			var model = view.getModel();
			if (model.getBaseModel) {
				mapStart = model.mapOffset(mapStart);
				mapEnd = model.mapOffset(mapEnd);
				model = model.getBaseModel();
			}
			
			if (proposal.overwrite) {
			    if(typeof proposal.prefix === 'string') {
			        start = mapStart-proposal.prefix.length;
			    } else {
				    start = this.getPrefixStart(model, mapStart);
				}
			}

			var data = {
				proposal: proposal,
				start: mapStart,
				end: mapEnd
			};
			this.setState(State.INACTIVE);
			var proposalText = typeof proposal === "string" ? proposal : proposal.proposal; //$NON-NLS-0$
			view.setText(proposalText, start, end);
			if (proposal.additionalEdits) {
				var edit;
				for (var i = 0; i < proposal.additionalEdits.length; i++) {
					edit = proposal.additionalEdits[i];
					view.setText(edit.text, edit.offset, edit.offset + edit.length);
				}
			}
			this.dispatchEvent({type: "ProposalApplied", data: data}); //$NON-NLS-0$
			mMetrics.logEvent("contentAssist", "apply"); //$NON-NLS-1$ //$NON-NLS-0$
			return true;
		},
		activate: function(providers, autoTriggered) {
			if (this.state === State.INACTIVE) {
				mMetrics.logEvent("contentAssist", "activate", undefined, autoTriggered ? 0 : 1); //$NON-NLS-1$ //$NON-NLS-0$
				this._autoTriggered = autoTriggered ? true : false;
				this.setState(State.ACTIVE, providers);
			}
		},
		deactivate: function() {
			this.setState(State.INACTIVE);
		},
		/** @returns {orion.editor.TextView} */
		getTextView: function() {
			return this.textView;
		},
		/** @returns {Boolean} */
		isActive: function() {
			return this.state === State.ACTIVE || this.state === State.FILTERING;
		},
		/** @returns {Boolean} <code>true</code> if the event describes a change that should deactivate content assist. */
		isDeactivatingChange: function(/**orion.editor.ModelChangingEvent*/ event, selectionEvent) {
			var isDeactivating = false;
			
			var selections = Array.isArray(selectionEvent.newValue) ? selectionEvent.newValue : [selectionEvent.newValue];
			var isPriorToInitialCaretOffset = selections[0].start < this._initialCaretOffset;
			
			if (isPriorToInitialCaretOffset || !event) {
				isDeactivating = true;
			} else if (event) {
				isDeactivating = (event.removedLineCount > 0) || (event.addedLineCount > 0);
			}
			
			return isDeactivating;
		},
		/** @private */
		setState: function(state, /* Optional. Array of providers to pass to dispatched event.*/ providers) {
			var eventType;
			if (state === State.ACTIVE) {
				eventType = "Activating"; //$NON-NLS-0$
				if (this._mode) { this._mode.setActive(true); }
			} else if (state === State.INACTIVE) {
				eventType = "Deactivating"; //$NON-NLS-0$
				if (this._mode) { this._mode.setActive(false); }
			}
			if (eventType) {
				this.dispatchEvent({type: eventType, providers: providers});
			}
			this.state = state;
			this.onStateChange(state);
		},
		setMode: function(mode) {
			this._mode = mode;
		},
		/** @private */
		onStateChange: function(state) {
			if (state === State.INACTIVE) {
				this._removeTextViewListeners();
				this._filterText = "";
				this._initialCaretOffset = -1;
				this._computedProposals = null;
			} else if (state === State.ACTIVE) {
				this._filterText = "";
				this._addTextViewListeners();
				this.computeProposals();
			}
		},
		/**
		 * Computes the proposals at the TextView's current caret offset.
		 */
		computeProposals: function() {
			// figure out initial offset, it should be the minimum between 
			// the beginning of the selection and the current caret offset
			var offset = this.textView.getCaretOffset();
			var sel = this.textView.getSelection();
			var selectionStart = Math.min(sel.start, sel.end);			
			this._initialCaretOffset = Math.min(offset, selectionStart);
			this._computedProposals = null;
			
			this._computeProposals(this._initialCaretOffset).then(function(proposals) {
				if (this.isActive()) {
					var flatProposalArray = this._flatten(proposals);
					//check if flattened proposals form a valid array with at least one entry
					if (flatProposalArray && Array.isArray(flatProposalArray) && (0 < flatProposalArray.length)) {
						this._computedProposals = proposals;
					}
					this.dispatchEvent({type: "ProposalsComputed", data: {proposals: flatProposalArray}, autoApply: !this._autoTriggered}); //$NON-NLS-0$
					if (this._computedProposals && this._filterText) {
						// force filtering here because user entered text after
						// computeProposals() was called but before the plugins
						// returned the computed proposals
						this.filterProposals(true);
					}
				}
			}.bind(this));
		},
		/** @private */
		getPrefixStart: function(model, end) {
			var index = end;
			while (index > 0 && /[A-Za-z0-9_]/.test(model.getText(index - 1, index))) {
				index--;
			}
			return index;
		},
		handleError: function(error) {
			if (typeof console !== "undefined") { //$NON-NLS-0$
				console.log("Error retrieving content assist proposals"); //$NON-NLS-0$
				console.log(error && error.stack);
			}
		},
		/**
		 * Initializes the providers. A provider must define an <tt>initialize()</tt> method to be initialized.
		 * @since 6.0
		 */
		initialize: function() {
			this._providers.forEach(function(info) {
				var provider = info.provider;
				if (typeof provider.initialize === "function") {//$NON-NLS-0$
					provider.initialize();
				}
			});
		},
		/**
		 * Retrieves the proposals at the given offset.
		 * @private
		 * @param {Number} offset The caret offset.
		 * @returns {Deferred} A promise that will provide the proposals.
		 */
		_computeProposals: function(offset) {
			var providerInfoArray = this._providers;
			var textView = this.textView;
			var sel = textView.getSelection();
			var model = textView.getModel(), mapOffset = offset;
			if (model.getBaseModel) {
				mapOffset = model.mapOffset(mapOffset);
				sel.start = model.mapOffset(sel.start);
				sel.end = model.mapOffset(sel.end);
				model = model.getBaseModel();
			}
			var line = model.getLine(model.getLineAtOffset(mapOffset));
			var index = 0;
			while (index < line.length && /\s/.test(line.charAt(index))) {
				index++;
			}
			var indentation = line.substring(0, index);
			var options = textView.getOptions("tabSize", "expandTab"); //$NON-NLS-1$ //$NON-NLS-0$
			var tab = options.expandTab ? new Array(options.tabSize + 1).join(" ") : "\t"; //$NON-NLS-1$ //$NON-NLS-0$
			var params = {
				line: line,
				offset: mapOffset,
				prefix: model.getText(this.getPrefixStart(model, mapOffset), mapOffset),
				selection: sel,
				delimiter: model.getLineDelimiter(),
				tab: tab,
				indentation: indentation
			};
			var self = this;
			var promises = providerInfoArray.map(function(providerInfo) {
				var provider = providerInfo.provider;
				var proposals;
				try {
					var func, promise;
					if ((func = provider.computeContentAssist)) {
						var ecProvider = self.editorContextProvider, editorContext = ecProvider.getEditorContext();
						params = objects.mixin(params, ecProvider.getOptions());
						promise = func.apply(provider, [editorContext, params]);
					} else if ((func = provider.getProposals || provider.computeProposals)) {
						// old API
						promise = func.apply(provider, [model.getText(), mapOffset, params]);
					}
					proposals = self.progress ? self.progress.progress(promise, "Generating content assist proposal") : promise; //$NON-NLS-0$
				} catch (e) {
					return new Deferred().reject(e);
				}
				return Deferred.when(proposals);
			});
			
			// TODO should we allow error to propagate instead of handling here?
			var allPromises = Deferred.all(promises, this.handleError);
			
			if (this.pageMessage){
				allPromises = Deferred.when(allPromises, function(proposals){
					self.pageMessage.close();					
					var foundProposal = false;
					if (proposals && proposals.length > 0){
						for (var i=0; i<proposals.length; i++) {
							if (Array.isArray(proposals[i]) && proposals[i].length > 0){
								foundProposal = true;
								break;
							}
						}
					}
					if (!foundProposal){
						self.pageMessage.setErrorMessage(messages["noProposals"]);
					}
					return proposals;
				});
				self.pageMessage.showWhile(allPromises, messages["computingProposals"]);
			}
			
			return allPromises;
		},

		filterProposals: function(force) {
			if (this._computedProposals && (this._latestModelChangingEvent || force)) {
				var model = this.textView.getModel();
				if (model.getBaseModel) {
					model = model.getBaseModel();
				}
				var prefixStart = this.getPrefixStart(model, this._initialCaretOffset);
				var defaultPrefix = this.textView.getText(prefixStart, this._initialCaretOffset);
				var prefixText = defaultPrefix;
				// filter proposals based on prefixes and _filterText
				var proposals = []; //array of arrays of proposals
				this._computedProposals.forEach(function(proposalArray) {
					if (proposalArray && Array.isArray(proposalArray)) {
						var includedProposals = proposalArray.filter(function(proposal) {
							if (!proposal) {
								return false;
							}
							if(typeof proposal.prefix === 'string') {
							    prefixText = proposal.prefix;
							} else {
							    prefixText = defaultPrefix;
							}
							if ((STYLES[proposal.style] === STYLES.hr)
								|| (STYLES[proposal.style] === STYLES.noemphasis_title)) {
								return true;
							}
							
							var proposalString = "";
							if (proposal.overwrite) {
								if (proposal.name) {
									proposalString = proposal.name;
								} else if (proposal.proposal) {
									proposalString = proposal.proposal;
								} else {
									return false; // unknown format
								}
			
								return (0 === proposalString.indexOf(prefixText + this._filterText));
								
							} else if (proposal.name || proposal.proposal) {
								var activated = false;
								// try matching name
								if (proposal.name) {
									activated = (0 === proposal.name.indexOf(prefixText + this._filterText));	
								}
								
								// try matching proposal text
								if (!activated && proposal.proposal) {
									activated = (0 === proposal.proposal.indexOf(this._filterText));
								}
								
								return activated;
							} else if (typeof proposal === "string") { //$NON-NLS-0$
								return 0 === proposal.indexOf(this._filterText);
							} else {
								return false;
							}
						}, this);
						
						if (includedProposals.length > 0) {
							proposals.push(includedProposals);	
						}
					}
				}, this);
				
				var flatProposalArray = [];
				if (proposals) {
					// filter out extra separators and titles
					proposals = this._removeExtraUnselectableElements(proposals);
					flatProposalArray = this._flatten(proposals);
				}
				
				this.dispatchEvent({type: "ProposalsComputed", data: {proposals: flatProposalArray}, autoApply: false}); //$NON-NLS-0$
			}
		},
		
		/**
		 * Helper method which removes extra separators and titles from
		 * an array containing arrays of proposals from the various providers.
		 * @param{Array[]} proposals An array with each element containing an array of proposals
		 * @returns {Array} An array without the extra unselectable elements
		 */
		_removeExtraUnselectableElements: function(proposals) {
			// get rid of extra separators and titles
			var mappedProposals = proposals.map(function(proposalArray) {
				var element = proposalArray.filter(function(proposal, index) {
					var keepElement = true;
					if (STYLES[proposal.style] === STYLES.hr) {
						if ((0 === index) || ((proposalArray.length - 1) === index)) {
							keepElement = false; // remove separators at first or last element
						} else if (STYLES.hr === STYLES[proposalArray[index - 1].style]) {
							keepElement = false; // remove separator preceeded by another separator
						}
					} else if (STYLES[proposal.style] === STYLES.noemphasis_title) {
						var nextProposal = proposalArray[index + 1];
						if (nextProposal) {
							// remove titles that preceed other titles, all of their subelements have already been filtered out
							if (STYLES[nextProposal.style] === STYLES.noemphasis_title) {
								keepElement = false;
							}
						} else {
							keepElement = false; //remove titles that are at the end of the array
						}
					}
					return keepElement;
				});
				return element;
			});
			
			return mappedProposals;
		},
		
		/**
		 * Sets the provider that will be invoked to generate the Editor Context service and options to any
		 * content assist providers that implement the v4.0 content assist API.
		 * @param {Object} editorContextProvider
		 */
		setEditorContextProvider: function(editorContextProvider) {
			this.editorContextProvider = editorContextProvider;
		},
		
		/**
		 * Helper method used to generate a unique ID for a provider.
		 * Note that the uniqueness of the ID is only guaranteed for the life of this
		 * object and if all of the other IDs are also generated using this method.
		 */
		_generateProviderId: function() {
			if (this._idcount) {
				this._idcount++;
			} else {
				this._idcount = 0;
			}
			return "ContentAssistGeneratedID_" +  this._idcount; //$NON-NLS-0$
		},

		/**
		 * Sets whether or not automatic content assist triggering is enabled.
		 * @param {Boolean} enableAutoTrigger
		 */
		setAutoTriggerEnabled: function(enableAutoTrigger) {
			this._autoTriggerEnabled = enableAutoTrigger;
			this._updateAutoTriggerListenerState();
		},

		/**
		 * @name orion.editor.ContentAssistProviderInfo
		 * @class Encapsulates a content assist provider and its automatic triggers.
		 *
		 * @property {String} id Unique ID of this provider.
		 * @property {RegExp} charTriggers A regular expression matching the characters that, when typed,
		 * will cause this provider to be activated automatically by the content assist engine.
		 * @property {RegExp} excludedStyles A regular expression matching the style names that are
		 * exclusions to this provider's <tt>charTriggers</tt> matching.
		 * @property {orion.editor.ContentAssistProvider} provider The actual content assist provider.
		 */

		/**
		 * Sets the content assist providers that this ContentAssist will consult to obtain proposals.
		 *
		 * @param {orion.editor.ContentAssistProvider[]|orion.edit.ContentAssistProviderInfo[]} providers The
		 * providers. Each element may be either a plain {@link orion.editor.ContentAssistProvider}, or a
		 * {@link orion.edit.ContentAssistProviderInfo}.
		 */
		setProviders: function(providers) {
			var _self = this;
			this.setProviderInfoArray(providers.map(function(p) {
				// Wrap any plain Provider into a ProviderInfo
				return p.id ? p : {
					provider: p,
					id: _self._generateProviderId()
				};
			}));
		},

		/**
		 * @private
		 */
		setProviderInfoArray: function(providerInfoArray) {
			this.clearProviders();
			
			this._providers = providerInfoArray;
			this._charTriggersInstalled = providerInfoArray.some(function(info){
				return info.charTriggers;
			});
			this._updateAutoTriggerListenerState();
		},

		/**
		 * @returns orion.edit.ContentAssistProviderInfo[]
		 */
		getProviders: function() {
			return this._providers.slice();
		},

		clearProviders: function() {
			this._providers = [];
			this._charTriggersInstalled = false;
			this._updateAutoTriggerListenerState();
		},

		/**
		 * Sets the progress handler that will display progress information, if any are generated by content assist providers.
		 */
		setProgress: function(progress){
			this.progress = progress;
		},
		
		setStyleAccessor: function(styleAccessor) {
			this._styleAccessor = styleAccessor;
		},
		
		/**
		 * Flattens an array of arrays into a one-dimensional array.
		 * @param {Array[]} array
		 * @returns {Array}
		 */
		_flatten: function(arrayOrObjectArray) {
			
			return arrayOrObjectArray.reduce(function(prev, curr) {
				var returnValue = prev;
				var filteredArray = null;
				
				if (curr && Array.isArray(curr)) {
					filteredArray = curr.filter(function(element){
						return element; //filter out falsy elements
					});	
				}
				
				// add current proposal array to flattened array
				// skip current elements that are not arrays
				if (filteredArray && Array.isArray(filteredArray) && (filteredArray.length > 0)) {
					var first = filteredArray;
					var last = prev;
					var filteredArrayStyle = filteredArray[0].style;
					
					if (filteredArrayStyle && STYLES[filteredArrayStyle] && (0 === STYLES[filteredArrayStyle].indexOf(STYLES.noemphasis))) {
						// the style of the first element starts with noemphasis
						// add these proposals to the end of the array
						first = prev;
						last = filteredArray;
					}
					
					if (first.length > 0) {
						var firstArrayStyle = first[first.length - 1].style;
						if (firstArrayStyle && (STYLES.hr !== STYLES[firstArrayStyle])) {
							// add separator between proposals from different providers 
							// if the previous array didn't already end with a separator
							first = first.concat({
								proposal: '',
								name: '',
								description: '---------------------------------', //$NON-NLS-0$
								style: 'hr', //$NON-NLS-0$
								unselectable: true
							});
						}
					}
					
					returnValue = first.concat(last);
				}
				
				return returnValue;
			}, []);
		},
		
		_triggerListener: function(/*event*/) {
			var caretOffset = this.textView.getCaretOffset();
			var stylesAtOffset = null;
			var providerInfosToActivate = [];

			if (this._charTriggersInstalled) {
				var currentChar = this.textView.getText(caretOffset - 1, caretOffset);

				this._providers.forEach(function(info) {
					// check if the charTriggers RegExp matches the currentChar
					// we're assuming that this will fail much more often than
					// the excludedStyles test so do this first for better performance
					var charTriggers = info.charTriggers;
					if (charTriggers && charTriggers.test(currentChar)) {
						var isExcluded = false;
						var excludedStyles = info.excludedStyles;
						if (this._styleAccessor && excludedStyles) {
							if (!stylesAtOffset) {
								// lazily initialize this variable to avoid getting the styles
								// for every model modification, only ones that may trigger
								stylesAtOffset = this._styleAccessor.getStyles(caretOffset - 1);
							}
							// check if any of the styles match the excludedStyles RegExp
							isExcluded = stylesAtOffset.some(function (element) {
								return excludedStyles.test(element.style);
							});
						}
						if (!isExcluded) {
							providerInfosToActivate.push(info);
						}
					}
				}, this);

				if (providerInfosToActivate.length > 0) {
					this.activate(providerInfosToActivate, true);
				}
			}
		},
		
		/**
		 * Private helper to install/uninstall the automatic trigger
		 * listener based on the state of the relevant booleans
		 */
		_updateAutoTriggerListenerState: function() {
			if (!this._boundTriggerListener) {
				this._boundTriggerListener = this._triggerListener.bind(this);
			}
			
			if (this._triggerListenerInstalled) {
				// uninstall the listener if necessary
				if (!this._autoTriggerEnabled || !this._charTriggersInstalled) {
					this.textView.removeEventListener("Modify", this._boundTriggerListener); //$NON-NLS-0$
					this._triggerListenerInstalled = false;
				}
			} else if (this._autoTriggerEnabled && this._charTriggersInstalled){
				// install the listener if necessary
				this.textView.addEventListener("Modify", this._boundTriggerListener); //$NON-NLS-0$
				this._triggerListenerInstalled = true;
			}
		},
		
		_addTextViewListeners: function() {
			if (!this._textViewListenersAdded) {
				this.textView.addEventListener("ModelChanging", this._textViewListeners.onModelChanging); //$NON-NLS-0$
				this.textView.addEventListener("Scroll", this._textViewListeners.onScroll); //$NON-NLS-0$
				this.textView.addEventListener("Selection", this._textViewListeners.onSelection); //$NON-NLS-0$
				this._textViewListenersAdded = true;
			}
		},
		
		_removeTextViewListeners: function() {
			if (this._textViewListenersAdded) {
				this._latestModelChangingEvent = null;
				this.textView.removeEventListener("ModelChanging", this._textViewListeners.onModelChanging); //$NON-NLS-0$
				this.textView.removeEventListener("Scroll", this._textViewListeners.onScroll); //$NON-NLS-0$
				this.textView.removeEventListener("Selection", this._textViewListeners.onSelection); //$NON-NLS-0$
				this._textViewListenersAdded = false;
			}
		},
		
		_updateFilterText: function(modelChangingEvent) {
			// update this._filterText based on the modification info
			// contained in the event
			var removedCharCount = modelChangingEvent.removedCharCount;
			if (removedCharCount) {
				var lastIndex = this._filterText.length - removedCharCount;
				this._filterText = this._filterText.substring(0, lastIndex);
			}
			var text = modelChangingEvent.text;
			if (text) {
				this._filterText = this._filterText.concat(text);
			}
		}
	};
	mEventTarget.EventTarget.addMixin(ContentAssist.prototype);

	/**
	 * @name orion.editor.ContentAssistMode
	 * @class Editor mode for interacting with content assist proposals.
	 * @description Creates a ContentAssistMode. A ContentAssistMode is a key mode for {@link orion.editor.Editor}
	 * that provides interaction with content assist proposals retrieved from an {@link orion.editor.ContentAssist}. 
	 * Interaction is performed via the {@link #lineUp}, {@link #lineDown}, and {@link #enter} actions. An 
	 * {@link orion.editor.ContentAssistWidget} may optionally be provided to display which proposal is currently selected.
	 * @param {orion.editor.ContentAssist} contentAssist
	 * @param {orion.editor.ContentAssistWidget} [ContentAssistWidget]
	 */
	function ContentAssistMode(contentAssist, ContentAssistWidget) {
		var textView = contentAssist.textView;
		mKeyModes.KeyMode.call(this, textView);
		this.contentAssist = contentAssist;
		this.widget = ContentAssistWidget;
		this.proposals = [];
		var self = this;
		this.contentAssist.addEventListener("ProposalsComputed", function(event) { //$NON-NLS-0$
			self.proposals = event.data.proposals;
			if (self.proposals.length === 0) {
				self.selectedIndex = -1;
				self.cancel();
			} else {
				self.selectedIndex = 0;
				while(self.proposals[self.selectedIndex] && self.proposals[self.selectedIndex].unselectable) {
					self.selectedIndex++;
				}
				if (self.proposals[self.selectedIndex]) {
					if (self.widget) {
						var showWidget = true;
						
						if (event.autoApply) {
							var nextIndex = self.selectedIndex + 1;
							while (self.proposals[nextIndex] && self.proposals[nextIndex].unselectable) {
								nextIndex++;
							}
							if (!self.proposals[nextIndex]) {
								// if there is only one selectable proposal apply it automatically
								showWidget = false;
								self.contentAssist.apply(self.proposals[self.selectedIndex]);
							}
						}
						
						if (showWidget) {
							self.widget.show();
							self.widget.selectNode(self.selectedIndex);
						}
					}
				} else {
					self.selectedIndex = -1; // didn't find any selectable items
					self.cancel();
				}
			}
		});
		textView.setAction("contentAssistApply", function() { //$NON-NLS-0$
			return this.enter();
		}.bind(this));
		textView.setAction("contentAssistCancel", function() { //$NON-NLS-0$
			return this.cancel();
		}.bind(this));
		textView.setAction("contentAssistNextProposal", function() { //$NON-NLS-0$
			return this.lineDown();
		}.bind(this));
		textView.setAction("contentAssistPreviousProposal", function() { //$NON-NLS-0$
			return this.lineUp();
		}.bind(this));
		textView.setAction("contentAssistNextPage", function() { //$NON-NLS-0$
			return this.pageDown();
		}.bind(this));
		textView.setAction("contentAssistPreviousPage", function() { //$NON-NLS-0$
			return this.pageUp();
		}.bind(this));
		textView.setAction("contentAssistHome", function() { //$NON-NLS-0$
			if (this.widget) {
				this.widget.scrollIndex(0, true);
			}
			return this.lineDown(0); // select first selectable element starting at the top and moving downwards
		}.bind(this));
		textView.setAction("contentAssistEnd", function() { //$NON-NLS-0$
			return this.lineUp(this.proposals.length - 1); // select first selectable element starting at the bottom and moving up
		}.bind(this));
		textView.setAction("contentAssistTab", function() { //$NON-NLS-0$
			return this.tab();
		}.bind(this));
		
		if (this.widget) {
			this.widget.setContentAssistMode(this);
			this.widget.createAccessible();
		}
	}
	ContentAssistMode.prototype = new mKeyModes.KeyMode();
	objects.mixin(ContentAssistMode.prototype, {
		createKeyBindings: function() {
			var KeyBinding = mKeyBinding.KeyBinding;
			var bindings = [];
			bindings.push({actionID: "contentAssistApply", keyBinding: new KeyBinding(13)}); //$NON-NLS-0$
			bindings.push({actionID: "contentAssistCancel", keyBinding: new KeyBinding(27)}); //$NON-NLS-0$
			bindings.push({actionID: "contentAssistNextProposal", keyBinding: new KeyBinding(40)}); //$NON-NLS-0$
			bindings.push({actionID: "contentAssistPreviousProposal", keyBinding: new KeyBinding(38)}); //$NON-NLS-0$
			bindings.push({actionID: "contentAssistNextPage", keyBinding: new KeyBinding(34)}); //$NON-NLS-0$
			bindings.push({actionID: "contentAssistPreviousPage", keyBinding: new KeyBinding(33)}); //$NON-NLS-0$
			bindings.push({actionID: "contentAssistHome", keyBinding: new KeyBinding(lib.KEY.HOME)}); //$NON-NLS-0$
			bindings.push({actionID: "contentAssistEnd", keyBinding: new KeyBinding(lib.KEY.END)}); //$NON-NLS-0$
			bindings.push({actionID: "contentAssistTab", keyBinding: new KeyBinding(9)}); //$NON-NLS-0$
			return bindings;
		},
		cancel: function() {
			this.getContentAssist().deactivate();
		},
		/** @private */
		getContentAssist: function() {
			return this.contentAssist;
		},
		getProposals: function() {
			return this.proposals;	
		},
		isActive: function() {
			return this.getContentAssist().isActive();
		},
		setActive: function(active) {
			if (active) {
				this.contentAssist.textView.addKeyMode(this);
			} else {
				this.contentAssist.textView.removeKeyMode(this);
			}
		},
		/**
		 * Selects a selectable item in the content assist widget
		 * iterating backwards for .
		 * 
		 * @param index {number} Optional. The index of the item to try and select. 
		 */
		lineUp: function(index, noWrap) {
			return this.selectNew(index, noWrap, false);
		},
		/**
		 * Selects the item at the specified index or the next
		 * selectable item
		 */
		lineDown: function(index, noWrap) {
			return this.selectNew(index, noWrap, true);
		},
		selectNew: function(index, noWrap, forward) {
			var newIndex = index;
			
			if (forward) {
				if (undefined === newIndex) {
					newIndex = this.selectedIndex + 1;
				}
				// handle wrap around
				if (newIndex >= this.proposals.length) {
					if (noWrap) {
						return true; // do nothing
					} else {
						newIndex = 0;	
					}
				}
			} else {
				if (undefined === newIndex) {
					newIndex = this.selectedIndex - 1;
				}
				// handle wrap around
				if (0 > newIndex) {
					if (noWrap) {
						return true; // do nothing
					} else {
						newIndex = this.proposals.length - 1;	
					}
				}
			}
			
			var startIndex = newIndex;
			while (this.proposals[newIndex] && this.proposals[newIndex].unselectable) {
				if (forward) {
					newIndex++;
					// handle wrap around
					if (newIndex >= this.proposals.length) {
						if (noWrap) {
							return true; // do nothing
						} else {
							newIndex = 0;	
						}
					}
				} else {
					newIndex--;
					// handle wrap around
					if (0 > newIndex) {
						if (noWrap) {
							return true; // do nothing
						} else {
							newIndex = this.proposals.length - 1;	
						}
					}
				}
				
				if (newIndex === startIndex) {
					// looped through all nodes and didn't find any that were selectable
					newIndex = -1;
					break;
				}
			}
			
			this.selectedIndex = newIndex;
			
			if (this.widget) {
				this.widget.selectNode(newIndex);
			}
			
			this._showTooltip(true);
			
			return true;
		},
		
		_showTooltip: function(update, noContent) {
			var tooltip = mTooltip.Tooltip.getTooltip(this.contentAssist.textView);
			var self = this;
			var target = {
				getTooltipInfo: function() {
					var bounds = self.widget.parentNode.getBoundingClientRect();
					var tipArea = {width: 350, height: bounds.height, top: bounds.top};
					if ((bounds.left + bounds.width) >= document.documentElement.clientWidth){
						tipArea.left = bounds.left - tipArea.width;
						tipArea.left -= 10;
					} else {
						tipArea.left = bounds.left + bounds.width;
						tipArea.left += 10;
					}
					var info = {
						context: {proposal: self.proposals[self.selectedIndex]},
						anchorArea: bounds,
						tooltipArea: tipArea
					};
					return info;
				}
			};
			
			if (update) {
				tooltip.update(target, noContent);
			} else {
				tooltip.show(target, true, false);
			}
		},
		
		_hideTooltip: function() {
			var tooltip = mTooltip.Tooltip.getTooltip(this.contentAssist.textView);
			tooltip.hide(true); // Clear the lock
		},

		pageUp: function() {
			//TODO find out why this doesn't always go to the very top
			if (this.widget) {
				var newSelected = this.widget.getTopIndex();
				if (newSelected === this.selectedIndex) {
					this.widget.scrollIndex(newSelected, false);
					newSelected = this.widget.getTopIndex();
				}
				if (0 === newSelected) {
					// if we're attempting to select the first item in the list
					// move down to the next one if it is not selectable
					return this.lineDown(newSelected, true);	
				}
				return this.lineUp(newSelected, true);
			} else {
				return this.lineUp();
			}
		},
		pageDown: function() {
			if (this.widget) {
				var newSelected = this.widget.getBottomIndex();
				if (newSelected === this.selectedIndex) {
					this.widget.scrollIndex(newSelected, true);
					newSelected = this.widget.getBottomIndex();
				}
				return this.lineDown(newSelected, true);
			} else {
				return this.lineDown();
			}
		},
		enter: function() {
			var proposal = this.proposals[this.selectedIndex] || null;
			return this.contentAssist.apply(proposal);
		},
		tab: function() {
			if (this.widget) {
				this.widget.parentNode.focus();
				return true;
			} else {
				return false;
			}
		}
	});

	/**
	 * @name orion.editor.ContentAssistWidget
	 * @class Displays proposals from a {@link orion.editor.ContentAssist}.
	 * @description Creates a ContentAssistWidget that will display proposals from the given {@link orion.editor.ContentAssist}
	 * in the given <code>parentNode</code>. Clicking a proposal will cause the ContentAssist to apply that proposal.
	 * @param {orion.editor.ContentAssist} contentAssist
	 * @param {String|DomNode} [parentNode] The ID or DOM node to use as the parent for displaying proposals. If not provided,
	 * a new DIV will be created inside &lt;body&gt; and assigned the CSS class <code>contentassist</code>.
	 */
	function ContentAssistWidget(contentAssist, parentNode) {
		this.contentAssist = contentAssist;
		this.textView = this.contentAssist.getTextView();
		this.textViewListenerAdded = false;
		this.isShowing = false;
		this._useResizeTimer = false;
		var document = this.textView.getOptions("parent").ownerDocument; //$NON-NLS-0$
		this.parentNode = typeof parentNode === "string" ? document.getElementById(parentNode) : parentNode; //$NON-NLS-0$
		if (!this.parentNode) {
			this.parentNode = util.createElement(document, "div"); //$NON-NLS-0$
			this.parentNode.className = "contentassist"; //$NON-NLS-0$
			var body = document.getElementsByTagName("body")[0]; //$NON-NLS-0$
			if (body) {
				body.appendChild(this.parentNode);
			} else {
				throw new Error("parentNode is required"); //$NON-NLS-0$
			}
		}
		var MO = window.MutationObserver || window.MozMutationObserver;
		if (MO && util.isFirefox) {//MutationObserver does not work in chrome for resize https://code.google.com/p/chromium/issues/detail?id=293948
			this._mutationObserver = new MO(function(mutations) {
				//TODO: code edit widget : separate a finer API to only reposition and resize the tooltips
				this._contentAssistMode._showTooltip(true);
			}.bind(this));
		} else {
			this._useResizeTimer = true;
		}
		
		textUtil.addEventListener(this.parentNode, "scroll", this.onScroll.bind(this)); //$NON-NLS-0$
		
		var self = this;
		this.textViewListener = {
			onMouseDown: function(event) {
				var target = event.event.target || event.event.srcElement;
				if (target.parentElement !== self.parentNode) {
					self.contentAssist.deactivate();
				}
				// ignore the event if this is a click inside of the parentNode
				// the click is handled by the onClick() function
			}
		};
		this.contentAssist.addEventListener("Deactivating", function(event) { //$NON-NLS-0$
			self.hide();
		});
		this.scrollListener = function(e) {
			if (self.isShowing) {
				self.position();
				//TODO: code edit widget : redo the tooltip positioning by the same new api
			}
		};
		//TODO: code edit widget : clean up the code to remove the listener here
		textUtil.addEventListener(document, "scroll", this.scrollListener); //$NON-NLS-0$
	}
	ContentAssistWidget.prototype = /** @lends orion.editor.ContentAssistWidget.prototype */ {
		/** @private */
		onClick: function(e) {
			if (!e) { e = window.event; }
			this.contentAssist.apply(this.getProposal(e.target || e.srcElement));
			this.textView.focus();
		},
		/** @private */
		onScroll: function(e) {
			if (this.previousCloneNode && !this.preserveCloneThroughScroll) {
				this._removeCloneNode();
				this.previousSelectedNode.classList.add(STYLES.selected);
			}
			this.preserveCloneThroughScroll = false;
		},
		/** @private */
		createDiv: function(proposal, parent, itemIndex) {
			var document = parent.ownerDocument;
			var div = util.createElement(document, "div"); //$NON-NLS-0$
			div.id = "contentoption" + itemIndex; //$NON-NLS-0$
			div.setAttribute("role", "option"); //$NON-NLS-1$ //$NON-NLS-2$
			div.className = STYLES[proposal.style] ? STYLES[proposal.style] : STYLES.dfault;
			var node;
			if (proposal.style === "hr") { //$NON-NLS-0$
				node = util.createElement(document, "hr"); //$NON-NLS-0$
			} else {
				node = this._createDisplayNode(proposal, itemIndex);
				div.contentAssistProposalIndex = itemIndex; // make div clickable
			}
			div.appendChild(node);
			parent.appendChild(div);
		},
		/** @private */
		createAccessible: function() {
			var mode = this._contentAssistMode;
			var self = this;
			textUtil.addEventListener(this.parentNode, "keydown", function(evt) { //$NON-NLS-0$
				if (!evt) { evt = window.event; }
				if (evt.preventDefault) {
					evt.preventDefault();
				}
				if(evt.keyCode === lib.KEY.ESCAPE) {
					return mode.cancel(); 
				} else if(evt.keyCode === lib.KEY.UP) {
					return mode.lineUp();
				} else if(evt.keyCode === lib.KEY.DOWN) {
					return mode.lineDown();
				} else if(evt.keyCode === lib.KEY.ENTER) {
					return mode.enter(); 
				} else if(evt.keyCode === lib.KEY.PAGEDOWN) {
					return mode.pageDown();
				} else if(evt.keyCode === lib.KEY.PAGEUP) {
					return mode.pageUp();
				} else if(evt.keyCode === lib.KEY.HOME) {
					self.scrollIndex(0, true);
					return mode.lineDown(0); // select first selectable element starting at the top and moving downwards
				} else if(evt.keyCode === lib.KEY.END) {
					return mode.lineUp(mode.getProposals().length - 1); // select first selectable element starting at the bottom and moving up
				}
				return false;
			});
		},
		/** @private */
		_createDisplayNode: function(proposal, index) {
			var node = document.createElement("span"); //$NON-NLS-0$
			
			if (!proposal){
				return node;
			}
			
			if (typeof proposal === "string"){
				var simpleName = this._createNameNode(proposal);
				simpleName.contentAssistProposalIndex = index;
				return simpleName;
			}
			
			var nameNode;
			var usingDescription; // Proposals are allowed to use the description as the name
			if (proposal.name && typeof proposal.name === "string") {
				nameNode = this._createNameNode(proposal.name);
			} else if (proposal.description && typeof proposal.description === "string"){
				nameNode = this._createNameNode(proposal.description);
				usingDescription = true;
			} else if (proposal.proposal && typeof proposal.proposal === "string"){
				nameNode = this._createNameNode(proposal.proposal);
			} else {
				// Must have a name
				return node;
			}
			
			var tagsNode = this._createTagsNode(proposal.tags);
			
			var descriptionNode;
			if (!usingDescription && proposal.description && typeof proposal.description === "string") {
				descriptionNode = document.createTextNode(proposal.description);
			}

			if (tagsNode) { node.appendChild(tagsNode); }
			node.appendChild(nameNode);
			if (descriptionNode) { node.appendChild(descriptionNode); }

			nameNode.contentAssistProposalIndex = index;
			node.contentAssistProposalIndex = index;
			
			return node;		
		},
		/** @private */
		_stopResizeTimer: function() {
			if (this._resizeTimer) {
				window.clearInterval(this._resizeTimer);
				this._resizeTimer = null; 
			}
		},
		_startResizeTimer: function() {
			this._stopResizeTimer();
			this._cachedResizeBound = this.parentNode.getBoundingClientRect();
			this._resizeTimer = window.setInterval(function() {
				if(this._contentAssistMode) {
					var bound = this.parentNode.getBoundingClientRect();
					if(bound.left === this._cachedResizeBound.left && 
					   bound.top === this._cachedResizeBound.top &&
					   bound.width === this._cachedResizeBound.width &&
					   bound.height === this._cachedResizeBound.height) {
						return;   	
					}
					this._cachedResizeBound = bound;
					this._contentAssistMode._showTooltip(true, true);
				}
			}.bind(this), 100);
		},
		/** @private */
		_createNameNode: function(name) {
			var node = document.createElement("span"); //$NON-NLS-0$
			node.classList.add("proposal-name"); //$NON-NLS-0$
			node.appendChild(document.createTextNode(name));
			return node;
		},
		/**
		 * @private
		 * @param tags {Array} The array of tags to display
		 * @returns {Object} the dom node for the tags or <code>null</code>
		 */
		_createTagsNode: function(tags) {
			var tagsNode = null;
			if (tags && tags.constructor === Array && tags.length > 0){
				tagsNode = document.createElement("span");	 //$NON-NLS-1$
				for (var i=0; i<tags.length; i++) {
					var tag = tags[i];
					if (tag.content || tag.cssClass){
						var tagNode = document.createElement("span"); //$NON-NLS-1$
						if (tag.cssClass){
							tagNode.classList.add(tag.cssClass);
						} else {
							tagNode.classList.add('proposalTag'); //$NON-NLS-1$
						}
						if (tag.content){
							tagNode.textContent = tag.content;
						}
						tagsNode.appendChild(tagNode);
					}
				}
			}
			return tagsNode;
		},
		/**
		 * @private
		 * @returns {Object} The proposal represented by the given node.
		 */
		getProposal: function(/**DOMNode*/ node) {
			var proposal = null;
			
			var nodeIndex = node.contentAssistProposalIndex;
			
			if (undefined !== nodeIndex){
				proposal = this._contentAssistMode.getProposals()[nodeIndex] || null;
			}
			
			return proposal;
		},
		/** @private */
		getTopIndex: function() {
			var nodes = this.parentNode.childNodes;
			for (var i=0; i < nodes.length; i++) {
				var child = nodes[i];
				if (child.offsetTop >= this.parentNode.scrollTop) {
					return i;
				}
			}
			return 0;
		},
		/** @private */
		getBottomIndex: function() {
			var nodes = this.parentNode.childNodes;
			for (var i=0; i < nodes.length; i++) {
				var child = nodes[i];
				if ((child.offsetTop + child.offsetHeight) > (this.parentNode.scrollTop + this.parentNode.clientHeight)) {
					return Math.max(0, i - 1);
				}
			}
			return nodes.length - 1;
		},
		/** @private */
		scrollIndex: function(index, top) {
			var nodeAtIndex = this.parentNode.childNodes[index];
			if (nodeAtIndex){
				nodeAtIndex.scrollIntoView(top);
				this.preserveCloneThroughScroll = true;
			}
		},
		/**
		 * Visually selects the node at the specified nodeIndex
		 * by updating its CSS class and scrolling it into view
		 * if necessary.
		 * @param{Number} nodeIndex The index of the node to select
		 */
		selectNode: function(nodeIndex) {
			var node = null;
			
			if (this._hideTimeout) {
				window.clearTimeout(this._hideTimeout);
				this._hideTimeout = null;
			}
			if (this._fadeTimer) {
				window.clearTimeout(this._fadeTimer);
				this._fadeTimer = null;
			}
			if (this.previousSelectedNode) {
				this.previousSelectedNode.classList.remove(STYLES.selected);
				this.previousSelectedNode = null;
				if (this.previousCloneNode) {
					this._removeCloneNode();
				}
			}
			
			if (-1 !== nodeIndex) {
				node = this.parentNode.childNodes[nodeIndex];
				if (node){
					node.classList.add(STYLES.selected);
					this.parentNode.setAttribute("aria-activedescendant", node.id); //$NON-NLS-0$
					node.focus();
					if (node.offsetTop < this.parentNode.scrollTop) {
						node.scrollIntoView(true);
						this.preserveCloneThroughScroll = true;
					} else if ((node.offsetTop + node.offsetHeight) > (this.parentNode.scrollTop + this.parentNode.clientHeight)) {
						node.scrollIntoView(false);
						this.preserveCloneThroughScroll = true;
					}
					
					var textNode = node.firstChild || node;  
					var textBounds = textNode.getBoundingClientRect();
					var parentWidth = this.parentNode.clientWidth ? this.parentNode.clientWidth : this.parentNode.getBoundingClientRect(); // Scrollbar can cover text
					var parentStyle = window.getComputedStyle(this.parentNode);
					var nodeStyle = window.getComputedStyle(node);
					var allPadding = parseInt(parentStyle.paddingLeft) + parseInt(parentStyle.paddingRight) + parseInt(nodeStyle.paddingLeft) + parseInt(nodeStyle.paddingRight);
					if (textBounds.width >= (parentWidth - allPadding)) {
						var parentTop = parseInt(parentStyle.top);
						
						// create clone node
						var clone = node.cloneNode(true); // deep clone
						clone.classList.add("cloneProposal"); //$NON-NLS-0$
						clone.style.top = parentTop + node.offsetTop - this.parentNode.scrollTop + "px"; //$NON-NLS-0$
						clone.style.left = parentStyle.left;
						clone.setAttribute("id", clone.id + "_clone"); //$NON-NLS-1$ //$NON-NLS-0$
						
						// try to fit clone node on page horizontally
						var viewportWidth = document.documentElement.clientWidth;
						var horizontalOffset = (textBounds.left + textBounds.width) - parseInt(viewportWidth);
						if (horizontalOffset > 0) {
							var cloneLeft = parseInt(parentStyle.left) - horizontalOffset;
							if (0 > cloneLeft) {
								cloneLeft = 0;
							}
							clone.style.left = cloneLeft + "px";
						}
	
						// create wrapper parent node (to replicate css class hierarchy)
						var parentClone = document.createElement("div");
						parentClone.id = "clone_contentassist";  //$NON-NLS-0$
						parentClone.classList.add("contentassist"); //$NON-NLS-0$
						parentClone.classList.add("cloneWrapper"); //$NON-NLS-0$
						parentClone.appendChild(clone);
						parentClone.onclick = this.parentNode.onclick;
						this.parentNode.parentNode.insertBefore(parentClone, this.parentNode);
						
						// make all the cloned nodes clickable by setting their contentAssistProposalIndex
						var recursiveSetIndex = function(cloneNode){
							cloneNode.contentAssistProposalIndex = node.contentAssistProposalIndex;
							if (cloneNode.hasChildNodes()) {
								for (var i = 0 ; i < cloneNode.childNodes.length ; i++){
									recursiveSetIndex(cloneNode.childNodes[i]);
								}
							}
						};
						recursiveSetIndex(parentClone);
						
						var self = this;
						this._hideTimeout = window.setTimeout(function() {
							self._hideTimeout = null;
							node.classList.add(STYLES.selected);
							var opacity = 1;
							self._fadeTimer = window.setInterval(function() {
								if (!self.previousCloneNode || opacity <= 0.01){
									self._removeCloneNode();
									window.clearInterval(self._fadeTimer);
									self._fadeTimer = null;
								} else {
									parentClone.style.opacity = opacity;
									parentClone.style.filter = 'alpha(opacity=' + opacity * 100 + ")";
	        						opacity -= opacity * 0.1;
	        					}
							}, 50);
						}, 1500);
						
						node.classList.remove(STYLES.selected);
						
						this.previousCloneNode = parentClone;				
					}
				}
			}
			
			this.previousSelectedNode = node;
		},
		setContentAssistMode: function(mode) {
			this._contentAssistMode = mode;
		},
		show: function() {
			var proposals = this._contentAssistMode.getProposals();
			if (proposals.length === 0) {
				this.hide();
			} else {
				this.parentNode.innerHTML = "";
				for (var i = 0; i < proposals.length; i++) {
					this.createDiv(proposals[i], this.parentNode, i);
				}
				this.position();
				this.parentNode.onclick = this.onClick.bind(this);
				this.isShowing = true;
				
				this._contentAssistMode._showTooltip(false);
				
				if(this._useResizeTimer) {
					this._startResizeTimer();
				} else if(this._mutationObserver){
					this._mutationObserver.observe(this.parentNode, {attributes: true});
				}
				if (!this.textViewListenerAdded) {
					this.textView.addEventListener("MouseDown", this.textViewListener.onMouseDown); //$NON-NLS-0$
					this.textViewListenerAdded = true;
				}
			}
		},
		hide: function() {
			if(this.parentNode.ownerDocument.activeElement === this.parentNode) {
				this.textView.focus();
			}
			this.parentNode.style.display = "none"; //$NON-NLS-0$
			this.parentNode.onclick = null;
			this.isShowing = false;
			
			if(this._useResizeTimer) {
				this._stopResizeTimer();
			} else if(this._mutationObserver){
				this._mutationObserver.disconnect();
			}
			
			this._contentAssistMode._hideTooltip();
			
			if (this.textViewListenerAdded) {
				this.textView.removeEventListener("MouseDown", this.textViewListener.onMouseDown); //$NON-NLS-0$
				this.textViewListenerAdded = false;
			}
			
			if (this.previousSelectedNode) {
				this.previousSelectedNode = null;
				if (this.previousCloneNode) {
					this._removeCloneNode();
				}
			}
		},
		position: function() {
			var contentAssist = this.contentAssist;
			var offset;
			var view = this.textView;
			if (contentAssist.offset !== undefined) {
				offset = contentAssist.offset;
				var model = view.getModel();
				if (model.getBaseModel) {
					offset = model.mapOffset(offset, true);
				}
			} else {
				offset = this.textView.getCaretOffset();
			}
			var caretLocation = view.getLocationAtOffset(offset);
			caretLocation.y += view.getLineHeight();
			this.textView.convert(caretLocation, "document", "page"); //$NON-NLS-1$ //$NON-NLS-0$
			this.parentNode.style.position = "fixed"; //$NON-NLS-0$
			this.parentNode.style.left = caretLocation.x + "px"; //$NON-NLS-0$
			this.parentNode.style.top = caretLocation.y + "px"; //$NON-NLS-0$
			this.parentNode.style.display = "block"; //$NON-NLS-0$
			this.parentNode.scrollTop = 0;

			// Make sure that the panel is never outside the viewport
			var document = this.parentNode.ownerDocument;
			var viewportWidth = document.documentElement.clientWidth,
			    viewportHeight =  document.documentElement.clientHeight;
			    
			var spaceBelow = viewportHeight - caretLocation.y;			    
			if (this.parentNode.offsetHeight > spaceBelow) {
				// Check if div is too large to fit above
				var spaceAbove = caretLocation.y - this.textView.getLineHeight();
				if (this.parentNode.offsetHeight > spaceAbove){
					// Squeeze the div into the larger area
					if (spaceBelow > spaceAbove) {
						this.parentNode.style.maxHeight = spaceBelow + "px"; //$NON-NLS-0$
					} else {
						this.parentNode.style.maxHeight = spaceAbove + "px"; //$NON-NLS-0$
						this.parentNode.style.top = "0"; //$NON-NLS-0$
					}
				} else {
					// Put the div above the line
					this.parentNode.style.top = (caretLocation.y - this.parentNode.offsetHeight - this.textView.getLineHeight()) + "px"; //$NON-NLS-0$
					this.parentNode.style.maxHeight = spaceAbove + "px"; //$NON-NLS-0$
				}
			} else {
				this.parentNode.style.maxHeight = spaceBelow + "px"; //$NON-NLS-0$
			}
			
			if (caretLocation.x + this.parentNode.offsetWidth > viewportWidth) {
				var leftSide = viewportWidth - this.parentNode.offsetWidth;
				if (leftSide < 0) {
					leftSide = 0;
				}
				this.parentNode.style.left = leftSide + "px"; //$NON-NLS-0$
				this.parentNode.style.maxWidth = viewportWidth - leftSide;
			} else {
				this.parentNode.style.maxWidth = viewportWidth + caretLocation.x + "px"; //$NON-NLS-0$
			}
		},
		_removeCloneNode: function(){
			if (this.parentNode.parentNode.contains(this.previousCloneNode)) {
				this.parentNode.parentNode.removeChild(this.previousCloneNode);	
			}
			this.previousCloneNode = null;
		}
	};
	return {
		ContentAssist: ContentAssist,
		ContentAssistMode: ContentAssistMode,
		ContentAssistWidget: ContentAssistWidget
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
 
/*eslint-env browser, amd*/
define("orion/editor/emacs", [ //$NON-NLS-0$
	'i18n!orion/editor/nls/messages', //$NON-NLS-0$
	"orion/editor/keyModes", //$NON-NLS-0$
	"orion/keyBinding", //$NON-NLS-0$
	"orion/util" //$NON-NLS-0$
], function(messages, mKeyMode, mKeyBinding, util) {

	function EmacsMode(textView) {
		mKeyMode.KeyMode.call(this, textView);
	}
	EmacsMode.prototype = new mKeyMode.KeyMode();
	EmacsMode.prototype.createKeyBindings = function () {
		//no duplicate keybindings
		var bindings = [];

		// Changing the Location of Point
		bindings.push({actionID: "emacs-beginning-of-line", keyBinding: this._createStroke('a', !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-end-of-line", keyBinding: this._createStroke('e', !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-forward-char", keyBinding: this._createStroke('f', !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-backward-char", keyBinding: this._createStroke('b', !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-forward-word", keyBinding: this._createStroke('f', false, false, true)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-backward-word", keyBinding: this._createStroke('b', false, false, true)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-next-line", keyBinding: this._createStroke('n', !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-previous-line", keyBinding: this._createStroke('p', !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		//TODO these 2 are not right
		bindings.push({actionID: "emacs-beginning-of-buffer", keyBinding: this._createStroke(188, false, true, true)}); //$NON-NLS-2$  //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-end-of-buffer", keyBinding: this._createStroke(190, false, true, true)}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		
		// Erasing Text
		bindings.push({actionID: "emacs-delete-backward-char", keyBinding: this._createStroke(46, !util.isMac, false, false, util.isMac)}); //$NON-NLS-0$
		bindings.push({actionID: "emacs-delete-char", keyBinding: this._createStroke('d', !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-kill-line", keyBinding: this._createStroke('k', !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-kill-word", keyBinding: this._createStroke('d', false, false, true)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-backward-kill-word", keyBinding: this._createStroke(46, false, false, true)}); //$NON-NLS-0$

		// Undoing Changes
		bindings.push({actionID: "undo", keyBinding: this._createSequence([this._createStroke('x', true), this._createStroke('u')])}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "redo", keyBinding: this._createSequence([this._createStroke('x', true), this._createStroke('r')])}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$

		//Scrolling 
		bindings.push({actionID: "emacs-scroll-up", keyBinding: this._createStroke('v', !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-scroll-down", keyBinding: this._createStroke('v', false, false, true)}); //$NON-NLS-1$ //$NON-NLS-0$

		bindings.push({actionID: "emacs-set-mark-command", keyBinding: this._createStroke(' ', !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-reset-mark-command", keyBinding: this._createStroke('g', !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-exchange-point-and-mark", keyBinding: this._createSequence([this._createStroke('x', !util.isMac, false, false, util.isMac), this._createStroke('x', !util.isMac, false, false, util.isMac)])}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		
		for (var c=0; c<=9; c++) { //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "emacs-digit-argument-" + c, keyBinding: this._createStroke(48 + c, !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "emacs-digit-argument-" + c, keyBinding: this._createStroke(48 + c, false, false, true)}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "emacs-digit-argument-" + c, keyBinding: this._createStroke(48 + c, !util.isMac, false, true, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		}
		bindings.push({actionID: "emacs-negative-argument", keyBinding: this._createStroke(189, !util.isMac, false, false, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-negative-argument", keyBinding: this._createStroke(189, false, false, true)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-negative-argument", keyBinding: this._createStroke(189, !util.isMac, false, true, util.isMac)}); //$NON-NLS-1$ //$NON-NLS-0$
		
		bindings.push({actionID: "emacs-uppercase", keyBinding: this._createStroke('u', false, false, true)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-lowercase", keyBinding: this._createStroke('l', false, false, true)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "emacs-capitalize", keyBinding: this._createStroke('c', false, false, true)}); //$NON-NLS-1$ //$NON-NLS-0$

		//TODO should these keys be done here?
		bindings.push({actionID: "contentAssist", keyBinding: this._createStroke(191, false, false, true)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "find", keyBinding: this._createStroke('r', false, false, true)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "incrementalFind", keyBinding: this._createStroke('s', true)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "incrementalFindReverse", keyBinding: this._createStroke('r', true)}); //$NON-NLS-1$ //$NON-NLS-0$
		bindings.push({actionID: "save", keyBinding: this._createSequence([this._createStroke('x', !util.isMac, false, false, util.isMac), this._createStroke('s', !util.isMac, false, false, util.isMac)])}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$

		//Create actions
		this._createActions(this.getView());
		
		return bindings;
	};
	
	EmacsMode.prototype._createStroke = function(keyCode, mod1, mod2, mod3, mod4) {
		var result = new mKeyBinding.KeyStroke(keyCode, mod1, mod2, mod3, mod4);
		result.scopeName = messages.emacs;
		return result;
	};
	
	EmacsMode.prototype._createSequence = function(keys) {
		var result = new mKeyBinding.KeySequence(keys);
		result.scopeName = messages.emacs;
		return result;
	};
	
	EmacsMode.prototype._getData = function() {
		var data = {
			count: (this._argument || 1) * (this._sign || 1)
		};
		this._argument = 0;
		this._sign = 1;
		return data;
	};
	
	EmacsMode.prototype._moveCursor = function (actionID) {
		var data = this._getData();
		if (this._marker) {
			data.select = true;
		}
		var view = this.getView();
		return view.invokeAction(actionID, false, data);
	};

	EmacsMode.prototype._digitArgument = function (n) {
		this._argument = (this._argument || 0) * 10 + n;
		return true;
	};

	EmacsMode.prototype._negativeArgument = function () {
		this._sign = (this._sign || 1) * -1;
		return true;
	};

	EmacsMode.prototype._createActions = function (view) {
		var self = this;
		
		// Changing the Location of Point
		view.setAction("emacs-beginning-of-line", function() { //$NON-NLS-0$
			return self._moveCursor("lineStart"); //$NON-NLS-0$
		}, {name: messages.lineStart});
		view.setAction("emacs-end-of-line", function() { //$NON-NLS-0$
			return self._moveCursor("lineEnd"); //$NON-NLS-0$
		}, {name: messages.lineEnd});
		view.setAction("emacs-forward-char", function() { //$NON-NLS-0$
			return self._moveCursor("charNext"); //$NON-NLS-0$
		}, {name: messages.charNext});
		view.setAction("emacs-backward-char", function() { //$NON-NLS-0$
			return self._moveCursor("charPrevious"); //$NON-NLS-0$
		}, {name: messages.charPrevious});
		view.setAction("emacs-forward-word", function() { //$NON-NLS-0$
			return self._moveCursor("wordNext"); //$NON-NLS-0$
		}, {name: messages.wordNext});
		view.setAction("emacs-backward-word", function() { //$NON-NLS-0$
			return self._moveCursor("wordPrevious"); //$NON-NLS-0$
		}, {name: messages.wordPrevious});
		view.setAction("emacs-next-line", function() { //$NON-NLS-0$
			return self._moveCursor("lineDown"); //$NON-NLS-0$
		}, {name: messages.lineDown});
		view.setAction("emacs-previous-line", function() { //$NON-NLS-0$
			return self._moveCursor("lineUp"); //$NON-NLS-0$
		}, {name: messages.lineUp});
		view.setAction("emacs-beginning-of-buffer", function() { //$NON-NLS-0$
			return self._moveCursor("textStart"); //$NON-NLS-0$
		}, {name: messages.textStart});
		view.setAction("emacs-end-of-buffer", function() { //$NON-NLS-0$
			return self._moveCursor("textEnd"); //$NON-NLS-0$
		}, {name: messages.textEnd});
		
		view.setAction("emacs-delete-backward-char", function() { //$NON-NLS-0$
			return view.invokeAction("deletePrevious"); //$NON-NLS-0$
		}, {name: messages.deletePrevious});
		view.setAction("emacs-delete-char", function() { //$NON-NLS-0$
			return view.invokeAction("deletePrevious"); //$NON-NLS-0$
		}, {name: messages.deletePrevious});
		view.setAction("emacs-kill-line", function() { //$NON-NLS-0$
			return view.invokeAction("deleteLineEnd"); //$NON-NLS-0$
		}, {name: messages.deleteLineEnd});
		view.setAction("emacs-kill-word", function() { //$NON-NLS-0$
			return view.invokeAction("deleteWordNext"); //$NON-NLS-0$
		}, {name: messages.deleteWordNext});
		view.setAction("emacs-backward-kill-word", function() { //$NON-NLS-0$
			return view.invokeAction("deleteWordPrevious"); //$NON-NLS-0$
		}, {name: messages.deleteWordPrevious});
		
		view.setAction("emacs-scroll-up", function() { //$NON-NLS-0$
			return self._moveCursor("pageDown"); //$NON-NLS-0$
		}, {name: messages.pageDown});
		view.setAction("emacs-scroll-down", function() { //$NON-NLS-0$
			return self._moveCursor("pageUp"); //$NON-NLS-0$
		}, {name: messages.pageUp});

		view.setAction("emacs-set-mark-command", function() { //$NON-NLS-0$
			var caretOffset = view.getCaretOffset();
			view.setCaretOffset(caretOffset);
			self._marker = caretOffset;
			return true;
		}, {name: messages.setMarkCommand});
		view.setAction("emacs-exchange-point-and-mark", function() { //$NON-NLS-0$
			if (self._marker !== undefined) {
				var caretOffset = view.getCaretOffset();
				var selection = view.getSelection();
				if (selection.end === caretOffset) {
					var temp = selection.start;
					selection.start = selection.end;
					selection.end = temp;
				}
				self._marker = caretOffset;
				view.setSelection(selection.start, selection.end);
			}
			return true;
		}, {name: messages.exchangeMarkPoint});
		view.setAction("emacs-reset-mark-command", function() { //$NON-NLS-0$
			var caretOffset = view.getCaretOffset();
			view.setCaretOffset(caretOffset);
			self._marker = undefined;
			return true;
		}, {name: messages.clearMark});

		view.setAction("emacs-digit-argument-0", function() { //$NON-NLS-0$
			return self._digitArgument(0);
		}, {name: util.formatMessage(messages.digitArgument, "0")}); //$NON-NLS-0$
		view.setAction("emacs-digit-argument-1", function() { //$NON-NLS-0$
			return self._digitArgument(1);
		}, {name: util.formatMessage(messages.digitArgument, "1")}); //$NON-NLS-0$
		view.setAction("emacs-digit-argument-2", function() { //$NON-NLS-0$
			return self._digitArgument(2);
		}, {name: util.formatMessage(messages.digitArgument, "2")}); //$NON-NLS-0$
		view.setAction("emacs-digit-argument-3", function() { //$NON-NLS-0$
			return self._digitArgument(3);
		}, {name: util.formatMessage(messages.digitArgument, "3")}); //$NON-NLS-0$
		view.setAction("emacs-digit-argument-4", function() { //$NON-NLS-0$
			return self._digitArgument(4);
		}, {name: util.formatMessage(messages.digitArgument, "4")}); //$NON-NLS-0$
		view.setAction("emacs-digit-argument-5", function() { //$NON-NLS-0$
			return self._digitArgument(5);
		}, {name: util.formatMessage(messages.digitArgument, "5")}); //$NON-NLS-0$
		view.setAction("emacs-digit-argument-6", function() { //$NON-NLS-0$
			return self._digitArgument(6);
		}, {name: util.formatMessage(messages.digitArgument, "6")}); //$NON-NLS-0$
		view.setAction("emacs-digit-argument-7", function() { //$NON-NLS-0$
			return self._digitArgument(7);
		}, {name: util.formatMessage(messages.digitArgument, "7")}); //$NON-NLS-0$
		view.setAction("emacs-digit-argument-8", function() { //$NON-NLS-0$
			return self._digitArgument(8);
		}, {name: util.formatMessage(messages.digitArgument, "8")}); //$NON-NLS-0$
		view.setAction("emacs-digit-argument-9", function() { //$NON-NLS-0$
			return self._digitArgument(9);
		}, {name: util.formatMessage(messages.digitArgument, "9")}); //$NON-NLS-0$
		view.setAction("emacs-negative-argument", function() { //$NON-NLS-0$
			return self._negativeArgument();
		}, {name: messages.negativeArgument});
		
		view.setAction("emacs-uppercase", function() { //$NON-NLS-0$
			var data = self._getData();
			data.unit = "word"; //$NON-NLS-0$
			return view.invokeAction("uppercase", false, data); //$NON-NLS-0$
		}, {name: messages.uppercase});
		view.setAction("emacs-lowercase", function() { //$NON-NLS-0$
			var data = self._getData();
			data.unit = "word"; //$NON-NLS-0$
			return view.invokeAction("lowercase", false, data); //$NON-NLS-0$
		}, {name: messages.lowercase});
		view.setAction("emacs-capitalize", function() { //$NON-NLS-0$
			var data = self._getData();
			data.unit = "word"; //$NON-NLS-0$
			return view.invokeAction("capitalize", false, data); //$NON-NLS-0$
		}, {name: messages.capitalize});
	};

	return {
		EmacsMode: EmacsMode
	};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define("orion/editor/vi", [ //$NON-NLS-0$
		"i18n!orion/editor/nls/messages", //$NON-NLS-0$
		"orion/editor/keyModes", //$NON-NLS-0$
		"orion/keyBinding",  //$NON-NLS-0$
		"orion/util" //$NON-NLS-0$
], function (messages, mKeyMode, mKeyBinding, util) {
	
	var yankText, yankEditLine;
	
	function mixin(object, proto) {
		for (var p in proto) {
			if (proto.hasOwnProperty(p)) {
				object[p] = proto[p];
			}
		}
	}
	
	function createStroke(keyCode, mod1, mod2, mod3, mod4, type, scope) {
		var result = new mKeyBinding.KeyStroke(keyCode, mod1, mod2, mod3, mod4, type);
		result.scopeName = scope || messages.vi;
		return result;
	}
		
	function createSequence(keys, scope) {
		var result = new mKeyBinding.KeySequence(keys);
		result.scopeName = scope || messages.vi;
		return result;
	}
	
		
	function NumberMode(textView, key, msg){
		this.key = key;
		this.msg = msg;
		this.number = "";
		mKeyMode.KeyMode.call(this, textView);
		//Create actions
		if (textView) {	
			this._createActions(textView);
		}
	}
	NumberMode.prototype = new mKeyMode.KeyMode(); 
	mixin(NumberMode.prototype, /** @lends orion.editor.viMode.NumberMode.prototype */ {
		_msg: function(key){
			return {name: util.formatMessage(messages[key], this.msg)};
		},
		createKeyBindings: function() {
			var bindings = [];
			var key = this.key;
			
			if (key) {
				key = "-" + key + "-"; //$NON-NLS-1$ //$NON-NLS-0$
			} else {
				key = "-"; //$NON-NLS-0$
			}
			
			//Numbers
			for (var i=0; i<=9; i++) {
				bindings.push({actionID: "vi" + key  + i,	keyBinding: createStroke(i+"", false, false, false, false, "keypress"), predefined: true}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			}
						
			//Movement
			//left
			bindings.push({actionID: "vi" + key + "Left",	keyBinding: createStroke("h", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "Left",	keyBinding: createStroke("h", true, false, false, false)}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "Left",	keyBinding: createStroke(8)}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "Left",	keyBinding: createStroke(37)});  //$NON-NLS-1$ //$NON-NLS-0$
			
			//down
			bindings.push({actionID: "vi" + key + "Down",	keyBinding: createStroke("j", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "Down",	keyBinding: createStroke(40)}); //$NON-NLS-1$ //$NON-NLS-0$
			
			//up
			bindings.push({actionID: "vi" + key + "Up",	keyBinding: createStroke("k", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "Up",	keyBinding: createStroke(38)});  //$NON-NLS-1$ //$NON-NLS-0$
			
			//right
			bindings.push({actionID: "vi" + key + "Right",	keyBinding: createStroke("l", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "Right",	keyBinding: createStroke(39)}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "Right",	keyBinding: createStroke(32)}); //$NON-NLS-1$ //$NON-NLS-0$
			
			//text movement
			bindings.push({actionID: "vi" + key + "w",	keyBinding: createStroke("w", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "b",	keyBinding: createStroke("b", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			//TODO: add
			bindings.push({actionID: "vi" + key + "W",	keyBinding: createStroke("W", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			//TODO: add
			bindings.push({actionID: "vi" + key + "B",	keyBinding: createStroke("B", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			//TODO: 
			bindings.push({actionID: "vi" + key + "e",	keyBinding: createStroke("e", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "E",	keyBinding: createStroke("E", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			//bindings.push({actionID: "vi" + key + ")",	keyBinding: createStroke(")", false, false, false, false, "keypress")}); //$NON-NLS-0$
			//bindings.push({actionID: "vi" + key + "(",	keyBinding: createStroke("(", false, false, false, false, "keypress")}); //$NON-NLS-0$
			//bindings.push({actionID: "vi" + key + "}",	keyBinding: createStroke("}", false, false, false, false, "keypress")}); //$NON-NLS-0$
			//bindings.push({actionID: "vi" + key + "{",	keyBinding: createStroke("{", false, false, false, false, "keypress")}); //$NON-NLS-0$
			//bindings.push({actionID: "vi" + key + "]]",	keyBinding: createStroke("]]", false, false, false, false, "keypress")}); //$NON-NLS-0$
			//bindings.push({actionID: "vi" + key + "[[",	keyBinding: createStroke("[[", false, false, false, false, "keypress")}); //$NON-NLS-0$
		
			//Lines
			bindings.push({actionID: "vi" + key + "$",	keyBinding: createStroke("$", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			
			bindings.push({actionID: "vi" + key + "^_",	keyBinding: createStroke("^", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "^_",	keyBinding: createStroke("_", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			
			bindings.push({actionID: "vi" + key + "+",	keyBinding: createStroke("+", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "+",	keyBinding:  createStroke(13)}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "-",	keyBinding:  createStroke("-", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "|",	keyBinding:  createStroke("|", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			
			bindings.push({actionID: "vi" + key + "H",	keyBinding: createStroke("H", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "M",	keyBinding: createStroke("M", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "L",	keyBinding: createStroke("L", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			
			//Screens
			
			//Searches
			bindings.push({actionID: "vi" + key + "/",	keyBinding: createStroke("/", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "?",	keyBinding: createStroke("?", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "n",	keyBinding: createStroke("n", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "N",	keyBinding: createStroke("N", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "f",	keyBinding: createStroke("f", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "F",	keyBinding: createStroke("F", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "t",	keyBinding: createStroke("t", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + "T",	keyBinding: createStroke("T", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + ",",	keyBinding: createStroke(",", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi" + key + ";",	keyBinding: createStroke(";", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			
			//Line numbering
			bindings.push({actionID: "vi" + key + "G",	keyBinding: createStroke("G", false, false, false, false, "keypress")}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			
			return bindings;
		},
		_createActions: function(view) {
		
			var key = this.key;
			if (key) {
				key = "-" + key + "-"; //$NON-NLS-1$ //$NON-NLS-0$
			} else {
				key = "-"; //$NON-NLS-0$
			}
			var self = this;
			view.setAction("vi" + key +"0", function() {return self._storeNumber(0);}); //$NON-NLS-1$ //$NON-NLS-0$
			view.setAction("vi" + key +"1", function() {return self._storeNumber(1);}); //$NON-NLS-1$ //$NON-NLS-0$
			view.setAction("vi" + key +"2", function() {return self._storeNumber(2);}); //$NON-NLS-1$ //$NON-NLS-0$
			view.setAction("vi" + key +"3", function() {return self._storeNumber(3);}); //$NON-NLS-1$ //$NON-NLS-0$
			view.setAction("vi" + key +"4", function() {return self._storeNumber(4);}); //$NON-NLS-1$ //$NON-NLS-0$
			view.setAction("vi" + key +"5", function() {return self._storeNumber(5);}); //$NON-NLS-1$ //$NON-NLS-0$
			view.setAction("vi" + key +"6", function() {return self._storeNumber(6);}); //$NON-NLS-1$ //$NON-NLS-0$
			view.setAction("vi" + key +"7", function() {return self._storeNumber(7);}); //$NON-NLS-1$ //$NON-NLS-0$
			view.setAction("vi" + key +"8", function() {return self._storeNumber(8);}); //$NON-NLS-1$ //$NON-NLS-0$
			view.setAction("vi" + key +"9", function() {return self._storeNumber(9);}); //$NON-NLS-1$ //$NON-NLS-0$
			
			//Utility
			function firstNonBlankChar(lineIndex) {
				var model = view.getModel();
				var lineText = model.getLine(lineIndex);
				var offsetInLine = 0;
				var c = lineText.charCodeAt(offsetInLine);
				while (c === 32 || c === 9) {
					offsetInLine++;
					c = lineText.charCodeAt(offsetInLine);
				}
				return offsetInLine;
			}

			function findCallback(range, data){
				if (range) {
					self.getView().setCaretOffset(range.start);
				}
				if (data.editDone) {
					data.editDone();
				}
			}
			//
			
			//Movement
			view.setAction("vi" + key + "Left", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke("charPrevious", {unit: "character"}); //$NON-NLS-1$ //$NON-NLS-0$
			}, this._msg("viLeft")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "Right", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke("charNext", {unit: "character"}); //$NON-NLS-1$ //$NON-NLS-0$
			}, this._msg("viRight")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "Up", function() {//$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke("lineUp", {editLine:true}); //$NON-NLS-0$
			}, this._msg("viUp")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "Down", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke("lineDown", {editLine:true}); //$NON-NLS-0$
			}, this._msg("viDown")); //$NON-NLS-0$
			
			//text movement
			view.setAction("vi" + key + "w", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke("wordNext", {unit: "word"}); //$NON-NLS-1$ //$NON-NLS-0$
			}, this._msg("viw")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "b", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke("wordPrevious", {unit: "word"}); //$NON-NLS-1$ //$NON-NLS-0$
			}, this._msg("vib")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "W", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke("wordNext", {unit: "wordWS"}); //$NON-NLS-1$ //$NON-NLS-0$
			}, this._msg("viW")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "B", function() {//$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke("wordPrevious", {unit: "wordWS"}); //$NON-NLS-1$ //$NON-NLS-0$
			}, this._msg("viB")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "e", function() { //$NON-NLS-1$ //$NON-NLS-0$
				self._invoke("charNext", {unit: "character"}); //$NON-NLS-1$ //$NON-NLS-0$
				self._invoke("wordNext", {unit: "wordend"}); //$NON-NLS-1$ //$NON-NLS-0$
				self._invoke("charPrevious", {unit: "character"}); //$NON-NLS-1$ //$NON-NLS-0$
				return true;
			}, this._msg("vie")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "E", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke("wordNext", {unit: "wordendWS"}); //$NON-NLS-1$ //$NON-NLS-0$
			}, this._msg("viE")); //$NON-NLS-0$
			
			//Lines
			view.setAction("vi" + key + "$", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke("lineEnd"); //$NON-NLS-0$
			}, this._msg("vi$")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "^_", function() {//$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke(function () {
					var model = view.getModel();
					var offset = view.getCaretOffset();
					var lineIndex = model.getLineAtOffset(offset);
					view.setCaretOffset(model.getLineStart(lineIndex) + firstNonBlankChar(lineIndex));
				});
			}, this._msg("vi^_")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "+", function() { ///$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke(function (data) {
					var model = view.getModel();
					var offset = view.getCaretOffset();
					var lastLineCount = model.getLineCount() - 1;
					var lineIndex = Math.min (model.getLineAtOffset(offset) + data.count, lastLineCount);
					view.setCaretOffset(model.getLineStart(lineIndex) + firstNonBlankChar(lineIndex));
				}, {editLine:true});
			}, this._msg("vi+")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "-", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke(function (data) {
					var model = view.getModel();
					var offset = view.getCaretOffset();
					var lineIndex = Math.max(model.getLineAtOffset(offset) - data.count, 0);
					view.setCaretOffset(model.getLineStart(lineIndex) + firstNonBlankChar(lineIndex));
				}, {editLine:true});
			}, this._msg("vi-")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "|", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke(function (data) {
					var model = view.getModel();
					var offset = view.getCaretOffset();
					var lineIndex = model.getLineAtOffset(offset);
					view.setCaretOffset(Math.min(model.getLineStart(lineIndex) + data.count - 1, model.getLineEnd(lineIndex)));
				});
			}, this._msg("vi|")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "H", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke(function (data) {
					var topIndex = view.getModel().getLineStart(view.getTopIndex(true) + (data.count - 1));
					view.setCaretOffset(topIndex);
				}, {editLine:true});
			}, this._msg("viH")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "M", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke(function (data) {
					var middleIndex = Math.ceil((view.getBottomIndex(true) - view.getTopIndex(true))/2) + view.getTopIndex(true);
					view.setCaretOffset(view.getModel().getLineStart(middleIndex));
				}, {editLine:true});
			}, this._msg("viM")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "L", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._invoke(function (data) {
					view.setCaretOffset(view.getModel().getLineStart(view.getBottomIndex(true) - (data.count - 1)));
				}, {editLine:true});
			}, this._msg("viL")); //$NON-NLS-0$
			
			//Searches
			view.setAction("vi" + key + "/", function() { //$NON-NLS-1$ //$NON-NLS-0$
				var data = {hideAfterFind:true, incremental:false, reverse:false, findCallback: function(range) {
					self._searchFwd = true;
					findCallback(range, data);
				}};
				return self._invoke("find", data); //$NON-NLS-0$
			}, this._msg("vi/")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "?", function() { //$NON-NLS-1$ //$NON-NLS-0$
				var data = {hideAfterFind:true, incremental:false, reverse:true, findCallback: function(range) {
					self._searchFwd = false;
					findCallback(range, data);
				}};
				return self._invoke("find", data); //$NON-NLS-0$
			}, this._msg("vi?")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "n", function() { //$NON-NLS-1$ //$NON-NLS-0$
				var findAction, start = view.getCaretOffset();
				if (self._searchFwd) {
					findAction = "findNext"; //$NON-NLS-0$
					start++;
				} else {
					findAction = "findPrevious"; //$NON-NLS-0$
				}
				var data = {start: start, findCallback: function(range) { 
					findCallback(range, data);
				}};
				return self._invoke(findAction, data); 
			}, this._msg("vin")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "N", function() { //$NON-NLS-1$ //$NON-NLS-0$
				var findAction, start = view.getCaretOffset();
				if (!self._searchFwd) {
					findAction = "findNext"; //$NON-NLS-0$
					start++;
				} else {
					findAction = "findPrevious"; //$NON-NLS-0$
				}
				var data = {start: start, findCallback: function(range) {
					findCallback(range, data);
				}};
				return self._invoke(findAction, data);
			}, this._msg("viN")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "f", function() {//$NON-NLS-1$ //$NON-NLS-0$
				var model = view.getModel();
				var start = view.getCaretOffset();
				return self._findChar(start, model.getLineEnd(model.getLineAtOffset(start)), false, 0);
			}, this._msg("vif")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "F", function() { //$NON-NLS-1$ //$NON-NLS-0$
				var model = view.getModel();
				var end = view.getCaretOffset();
				return self._findChar( model.getLineStart(model.getLineAtOffset(end)), end, true, 0);
			}, this._msg("viF")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "t", function() {//$NON-NLS-1$ //$NON-NLS-0$
				var model = view.getModel();
				var start = view.getCaretOffset();
				return self._findChar(start, model.getLineEnd(model.getLineAtOffset(start)), false, -1);
			}, this._msg("vit")); //$NON-NLS-0$
			
			view.setAction("vi" + key + "T", function() { //$NON-NLS-1$ //$NON-NLS-0$
				var model = view.getModel();
				var end = view.getCaretOffset();
				return self._findChar( model.getLineStart(model.getLineAtOffset(end)), end, true, 1);
			}, this._msg("viT")); //$NON-NLS-0$
			
			view.setAction("vi" + key + ",", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._findNextChar(self._charTempOptions.reverse);
			}, this._msg("vi,")); //$NON-NLS-0$
			
			view.setAction("vi" + key + ";", function() { //$NON-NLS-1$ //$NON-NLS-0$
				return self._findNextChar(!self._charTempOptions.reverse);
			}, this._msg("vi;")); //$NON-NLS-0$
			
			//Line numbering
			view.setAction("vi" + key + "G", function() { //$NON-NLS-1$ //$NON-NLS-0$
				if (self.number === "") { //$NON-NLS-0$
					var model = view.getModel();
					if (model.getBaseModel) {
						model = model.getBaseModel();
					}
					self.number = model.getLineCount();
				}
				return self._invoke(function (data) {
					data = data || {};
					data.line=data.count;
					data.editLine=true;
					data.callback = function() {
						if (data.editDone) {
							data.editDone();
						}
					};
					view.invokeAction("gotoLine", false, data); //$NON-NLS-0$
				});
				//TODO: this works if gotoLine is registered (not part of textview) - need to handle fail case
			}, this._msg("viG")); //$NON-NLS-0$
		},
		_invoke: function(action, data) {
			var view = this.getView();
			data =  data || {};
			data.count = this._getCount();
			if (typeof action === "function") { //$NON-NLS-0$
				action(data);
			} else {
				view.invokeAction(action, false, data);
			}
			return true;
		},
		_getCount: function() {
			var num = 1;
			if (this.number !== "") { //$NON-NLS-0$
				num = this.number >> 0;
			}
			this.number = ""; //$NON-NLS-0$
			return num;
		},
		_findChar: function (start, end, reverse, offset) {
			this._charTempOptions = {};
			this._charTempOptions.start  = start;
			this._charTempOptions.end= end;
			this._charTempOptions.hideAfterFind = true;
			this._charTempOptions.incremental = false;
			this._charTempOptions.reverse = reverse;
			this._charTempOptions.offset = offset;
			var data = this._charTempOptions;
			var self = this;
			this._charTempOptions.findCallback = function(range) {
				if (range) {
					self.getView().setCaretOffset(range.start + offset);
				}
				if (data.editDone) {
					data.editDone();
				}
			};
			return this._invoke("find", this._charTempOptions); //$NON-NLS-0$
		},
		_findNextChar: function (forward) {
			if (this._charTempOptions) {
				var view = this.getView();
				var tempTempOptions = {};
				tempTempOptions.hideAfterFind = this._charTempOptions.hideAfterFind;
				tempTempOptions.incremental = this._charTempOptions.incremental;
				tempTempOptions.reverse = this._charTempOptions.reverse;
				tempTempOptions.wrap = false;
				var data = tempTempOptions;
				var self = this;
				tempTempOptions.findCallback = function(range){
					if (range) {
						self.getView().setCaretOffset(range.start + self._charTempOptions.offset);
					}
					if (data.editDone) {
						data.editDone();
					}
				};
				var model = view.getModel();
				if (forward) {
					tempTempOptions.start  = view.getCaretOffset() + 1 - this._charTempOptions.offset;
					tempTempOptions.end= model.getLineEnd(model.getLineAtOffset(tempTempOptions.start));
					tempTempOptions.reverse = false;
					return this._invoke("findNext", tempTempOptions); //$NON-NLS-0$
				} else {
					tempTempOptions.start = view.getCaretOffset() - this._charTempOptions.offset;
					tempTempOptions.end  = model.getLineStart(model.getLineAtOffset(tempTempOptions.start));
					tempTempOptions.reverse = true;
					return this._invoke("findPrevious", tempTempOptions); //$NON-NLS-0$
				}
			}
			return true;
		},
		_storeNumber: function(index) {
			var self = this;
			if (index === 0 && !this.number) {
				return this._invoke(function() {
					self.getView().invokeAction("lineStart", true); //$NON-NLS-0$)
				});
			}
			this.number += index;
			return true;
		}
	});
	
	//Status Line Mode
	function StatusLineMode(viMode) {
		var view = viMode.getView();
		this.viMode = viMode;
		mKeyMode.KeyMode.call(this, view);
		this._createActions(view);
	}
	StatusLineMode.prototype = new mKeyMode.KeyMode(); 
	mixin(StatusLineMode.prototype, /** @lends orion.editor.viMode.StatusLineMode.prototype */ {
		createKeyBindings: function() {
			var bindings = [];
			bindings.push({actionID: "vi-:-ESC",		keyBinding: createStroke(27), predefined: true}); //$NON-NLS-0$
			return bindings;
		},
		_createActions: function(view) {
			var self = this;
			view.setAction("vi-:-ESC", function() { //$NON-NLS-0$
				view.removeKeyMode(self);
				view.addKeyMode(self.viMode);
				return true;
			});
		},
		match: function(e) {
			var result = mKeyMode.KeyMode.prototype.match.call(this, e);
			if (!result) {
				result = this.getView().getKeyModes()[0].match(e);
			}
			return result;
		},
		storeNumber: function(n) {
			this.number = n;
		}
	});
	
	
	//Edit Mode
	function EditMode(viMode, nextMode, key, msg) {
		this.viMode = viMode;
		this.nextMode = nextMode;
		NumberMode.call(this, viMode.getView(), key, msg);
	}
	
	EditMode.prototype = new NumberMode(); 
	mixin(EditMode.prototype, /** @lends orion.editor.viMode.EditMode.prototype */ {
		createKeyBindings: function() {
			var bindings = NumberMode.prototype.createKeyBindings.call(this);
			bindings.push({actionID: "vi-"+this.key+"ESC",		keyBinding: createStroke(27), predefined: true});//$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi-"+this.key+"-"+this.key,	keyBinding: createStroke(this.key, false, false, false, false, "keypress")});//$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			return bindings;
		},
		_invoke: function(action, data) {
			data = data || {};
			var view = this.getView();
			var caretOffset = view.getCaretOffset();
			var startOffset = caretOffset;
			var model = view.getModel();
			var self = this;
			data.editDone = function() {
				var endOffset = view.getCaretOffset();
				if (startOffset > endOffset) {
					var tmp = startOffset;
					startOffset = endOffset;
					endOffset = tmp;
				}
				if (data.editLine) {
					startOffset = model.getLineStart(model.getLineAtOffset(startOffset));
					endOffset = model.getLineEnd(model.getLineAtOffset(endOffset), self.key === "c" ? false : true); //$NON-NLS-0$
				}
				if (self.key === "y") { //$NON-NLS-0$
					yankText = view.getText(startOffset, endOffset); 
					yankEditLine = data.editLine;
					view.setCaretOffset(caretOffset);
				} else {
					view.setText("", startOffset, endOffset); 
				}
				view.removeKeyMode(self);	
				view.addKeyMode(self.nextMode);
			};
			NumberMode.prototype._invoke.call(this, action, data);
			data.editDone();			
			return true;
		},
		_getCount: function() {
			var num = 1;
			if (this.firstNumber !== "") {
				num = this.firstNumber >> 0;
			}
			var secondNum = 1;
			if (this.number !== "" ) {
				secondNum = this.number >> 0;
			}
			num *= secondNum;
			this.number = this.firstNumber = "";
			return num;
		},
		_createActions: function(view) {
			NumberMode.prototype._createActions.call(this, view);
			var self = this;
			view.setAction("vi-"+self.key+"ESC", function() { //$NON-NLS-1$ //$NON-NLS-0$
				view.removeKeyMode(self);
				view.addKeyMode(self.viMode);
				return true;
			});
			view.setAction("vi-"+self.key+"-"+self.key, function () { //$NON-NLS-1$ //$NON-NLS-0$ 
				return self._invoke("lineEnd", {editLine:true}); //$NON-NLS-0$
			}, this._msg("viycd")); //$NON-NLS-0$
		},
		storeNumber: function(number) {
			this.firstNumber = number;
		},
		_modeAdded: function() {
			this.secondNumber = "";
		},
		_modeRemoved: function() {
			this.command = "";
			this.firstNumber = "";
			this.number = "";
		}
	});
	
	//Insert Mode
	function InsertMode(viMode) {
		var view = viMode.getView();
		mKeyMode.KeyMode.call(this, view);
		this.viMode = viMode;
		this._createActions(view);
	}
	InsertMode.prototype = new mKeyMode.KeyMode(); 

	mixin(InsertMode.prototype, /** @lends orion.editor.viMode.InsertMode.prototype */ {
		createKeyBindings: function() {
			var bindings = [];
			bindings.push({actionID: "vi-insert-ESC",		keyBinding: createStroke(27), predefined: true}); //$NON-NLS-0$
			return bindings;
		},
		_createActions: function(view) {
			var self = this;
			view.setAction("vi-insert-ESC", function() { //$NON-NLS-0$
				view.removeKeyMode(self);
				view.addKeyMode(self.viMode);
				return true;
			});
		},
		match: function(e) {
			var result = mKeyMode.KeyMode.prototype.match.call(this, e);
			if (!result) {
				result = this.getView().getKeyModes()[0].match(e);
			}
			return result;
		},
		storeNumber: function(n) {
		}
	});

	function VIMode(textView, statusReporter){
		NumberMode.call(this, textView, "", messages.vimove); 
		this.insertMode = new InsertMode(this);
		this.changeMode = new EditMode(this, this.insertMode, "c", messages.vichange);  //$NON-NLS-0$
		this.deleteMode = new EditMode(this, this, "d",  messages.videlete); //$NON-NLS-0$
		this.yankMode = new EditMode(this, this, "y",  messages.viyank); //$NON-NLS-0$
		this.statusReporter = statusReporter;
	}
	VIMode.prototype = new NumberMode(); 
	mixin(VIMode.prototype, /** @lends orion.editor.viMode.VIMode.prototype */ {
		createKeyBindings: function() {
			var bindings = NumberMode.prototype.createKeyBindings.call(this);
		
			bindings.push({actionID: "vi-ctrl-f",	keyBinding: createStroke("f", true)}); //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi-ctrl-b",	keyBinding: createStroke("b", true)}); //$NON-NLS-1$ //$NON-NLS-0$
			
			bindings.push({actionID: "vi-ctrl-e",	keyBinding: createStroke("e", true)});  //$NON-NLS-1$ //$NON-NLS-0$
			bindings.push({actionID: "vi-ctrl-y",	keyBinding: createStroke("y", true)});  //$NON-NLS-1$ //$NON-NLS-0$

			//Status Line mode
			bindings.push({actionID: "statusLineMode",	keyBinding: createStroke(":", false, false, false, false, "keypress")});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			
			
			//Insert
			bindings.push({actionID: "vi-a",	keyBinding: createStroke("a", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			bindings.push({actionID: "vi-A",	keyBinding: createStroke("A", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
		
			bindings.push({actionID: "vi-i",	keyBinding: createStroke("i", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			bindings.push({actionID: "vi-I",	keyBinding: createStroke("I", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
	
			bindings.push({actionID: "vi-o",	keyBinding: createStroke("o", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			bindings.push({actionID: "vi-O",	keyBinding: createStroke("O", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
	
			bindings.push({actionID: "vi-R",	keyBinding: createStroke("R", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			bindings.push({actionID: "vi-s",	keyBinding: createStroke("s", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$	
			bindings.push({actionID: "vi-S",	keyBinding: createStroke("S", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			
			//Paste
			bindings.push({actionID: "vi-p",	keyBinding: createStroke("p", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			bindings.push({actionID: "vi-P",	keyBinding: createStroke("P", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			
			// Undo
			bindings.push({actionID: "vi-u",	keyBinding: createStroke("u", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			
			//Change
			bindings.push({actionID: "vi-c",	keyBinding: createStroke("c", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			bindings.push({actionID: "vi-d",	keyBinding: createStroke("d", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			bindings.push({actionID: "vi-y",	keyBinding: createStroke("y", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			bindings.push({actionID: "vi-~",	keyBinding: createStroke("~", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$

			bindings.push({actionID: "vi-x",	keyBinding: createStroke("x", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			bindings.push({actionID: "vi-X",	keyBinding: createStroke("X", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			
			bindings.push({actionID: "vi-C",	keyBinding: createStroke("C", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			bindings.push({actionID: "vi-D",	keyBinding: createStroke("D", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			bindings.push({actionID: "vi-*",  keyBinding: createStroke("*", false, false, false, false, "keypress"), predefined: true});  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$
			
			return bindings;
		},
		getKeyBindings: function (actionID) {
			var result = mKeyMode.KeyMode.prototype.getKeyBindings.call(this, actionID);
			result = result || [];
			var keyBindings = this.changeMode.getKeyBindings(actionID);
			var i;
			if (!this.changeMode.isActive()) {
				for (i=0; i<keyBindings.length; i++) {
					keyBindings[i] = createSequence([createStroke("c", false, false, false, false, "keypress"), keyBindings[i]]); //$NON-NLS-1$  //$NON-NLS-0$
					
				}
			}
			result = result.concat(keyBindings);
			keyBindings = this.deleteMode.getKeyBindings(actionID);
			if (!this.deleteMode.isActive()) {
				for (i=0; i<keyBindings.length; i++) {
					keyBindings[i] = createSequence([createStroke("d", false, false, false, false, "keypress"), keyBindings[i]]); //$NON-NLS-1$  //$NON-NLS-0$
					
				}
			}
			result = result.concat(keyBindings);
			keyBindings = this.yankMode.getKeyBindings(actionID);
			if (!this.yankMode.isActive()) {
				for (i=0; i<keyBindings.length; i++) {
					keyBindings[i] = createSequence([createStroke("y", false, false, false, false, "keypress"), keyBindings[i]]); //$NON-NLS-1$  //$NON-NLS-0$
					
				}
			}
			result = result.concat(keyBindings);
			return result;
		},
//		isStatusActive: function() {
//			return this.isActive();
//		},
		match: function(e) {
			var result = mKeyMode.KeyMode.prototype.match.call(this, e);
			if (!result && e.type === "keypress") { //$NON-NLS-0$
				result = "noop"; //$NON-NLS-0$
			}
			return result;
		},
		_createActions: function(view) {
			NumberMode.prototype._createActions.call(this, view);

			var self = this;
			
			view.setAction("vi-ctrl-f", function() { //$NON-NLS-0$
				return self._invoke("pageDown"); //$NON-NLS-0$
			}, {name: messages.pageDown});

			view.setAction("vi-ctrl-b", function() { //$NON-NLS-0$
				return self._invoke("pageUp"); //$NON-NLS-0$
			}, {name: messages.pageUp});

			view.setAction("vi-ctrl-e", function() { //$NON-NLS-0$
				return self._invoke("scrollLineDown"); //$NON-NLS-0$
			}, {name: messages.scrollLineDown});
			
			view.setAction("vi-ctrl-y", function() { //$NON-NLS-0$
				return self._invoke("scrollLineUp"); //$NON-NLS-0$
			}, {name: messages.scrollLineUp});
			
			//Insert
			view.setAction("vi-a", function() { //$NON-NLS-0$
				return self._toInsertMode("charNext"); //$NON-NLS-0$
			}, {name: messages.via});
			
			view.setAction("vi-A", function() { //$NON-NLS-0$
				return self._toInsertMode("lineEnd"); //$NON-NLS-0$
			}, {name: messages.viA});
			
			view.setAction("vi-i", function() { //$NON-NLS-0$
				return self._toInsertMode("noop"); //$NON-NLS-0$
			}, {name: messages.vii});
			
			view.setAction("vi-I", function() { //$NON-NLS-0$
				return self._toInsertMode("lineStart"); //$NON-NLS-0$
			}, {name: messages.viI});
			
			view.setAction("vi-O", function() { //$NON-NLS-0$
				self._invoke("lineUp"); //$NON-NLS-0$
				self._invoke("lineEnd"); //$NON-NLS-0$
				self._toInsertMode("enter"); //$NON-NLS-0$
				return true;
			}, {name: messages.viO});
			
			view.setAction("vi-o", function() { //$NON-NLS-0$
				self._invoke("lineEnd"); //$NON-NLS-0$
				self._toInsertMode("enter"); //$NON-NLS-0$
				return true;
			}, {name: messages.vio});
			
			view.setAction("vi-R", function() { //$NON-NLS-0$
				return self._toInsertMode("toggleOverwriteMode"); //$NON-NLS-0$
			}, {name: messages.viR});
			
			view.setAction("vi-s", function() { //$NON-NLS-0$
				return self._toInsertMode("deleteNext", {unit: "char"}); //$NON-NLS-1$ //$NON-NLS-0$
			}, {name: messages.vis});

			view.setAction("vi-S", function() { //$NON-NLS-0$
				return self._toInsertMode("deleteLines", {unit: "line"}); //$NON-NLS-1$ //$NON-NLS-0$
			}, {name: messages.viS});
			
			//Paste
			view.setAction("vi-p", function() { //$NON-NLS-0$
				return self._invoke(function() {
					var view = self.getView();
					var selection = view.getSelection();
					if (selection.start === selection.end) {
						var model = view.getModel();
						var caretOffset = view.getCaretOffset();
						if (yankEditLine) {
							selection.start = selection.end = model.getLineEnd(model.getLineAtOffset(caretOffset), true);
						} else {
							selection.start++;
							selection.end++;
						}
					}
					view.setText(yankText, selection.start, selection.end);
				}); 
			}, {name: messages.vip});

			view.setAction("vi-P", function() { //$NON-NLS-0$
				return self._invoke(function() {
					var view = self.getView();
					var selection = view.getSelection();
					if (selection.start === selection.end) {
						var model = view.getModel();
						var caretOffset = view.getCaretOffset();
						if (yankEditLine) {
							selection.start = selection.end = model.getLineStart(model.getLineAtOffset(caretOffset));
						} 
					}
					view.setText(yankText, selection.start, selection.end);
				}); 
			}, {name: messages.viP});
			
			// Undo
			view.setAction("vi-u", function() { //$NON-NLS-0$
				return self._invoke("undo"); //$NON-NLS-0$
			}, {name: messages.Undo});
			
			//Change actions
			view.setAction("vi-c", function() { //$NON-NLS-0$
				view.removeKeyMode(self);
				view.addKeyMode(self.changeMode);
				self.changeMode.storeNumber(self.number);
				self.number = "";
				return true;
			});
			
			view.setAction("vi-d", function() { //$NON-NLS-0$
				view.removeKeyMode(self);
				view.addKeyMode(self.deleteMode);
				self.deleteMode.storeNumber(self.number);
				self.number = "";
				return true;
			});
			
			view.setAction("vi-y", function() { //$NON-NLS-0$
				view.removeKeyMode(self);
				view.addKeyMode(self.yankMode);
				self.yankMode.storeNumber(self.number);
				self.number = "";
				return true;
			});
			
			view.setAction("vi-~", function() { //$NON-NLS-0$
				return self._invoke("reversecase"); //$NON-NLS-0$
			}, {name: messages.reversecase});
			
			view.setAction("vi-x", function() { //$NON-NLS-0$
				return self._invoke("deleteNext"); //$NON-NLS-0$
			}, {name: messages.deleteNext});
			
			view.setAction("vi-X", function() { //$NON-NLS-0$
				return self._invoke("deletePrevious"); //$NON-NLS-0$
			}, {name: messages.deletePrevious});
			
			view.setAction("vi-C", function() { //$NON-NLS-0$
				return self._toInsertMode("deleteLineEnd"); //$NON-NLS-0$
			}, {name: messages.viC});
			
			view.setAction("vi-D", function() { //$NON-NLS-0$
				return self._invoke("deleteLineEnd"); //$NON-NLS-0$
			}, {name: messages.deleteLineEnd});
			
			view.setAction("vi-*", function() { //$NON-NLS-0$
				// Get word under caret
				var view = self.getView();
				var caret = view.getCaretOffset();
				var wordStart = view.getNextOffset(caret + 1, {count: -1, unit: "word"}); //$NON-NLS-0$
				var wordEnd = view.getNextOffset(wordStart, {count: 1, unit: "wordend"}); //$NON-NLS-0$
				var text = view.getText(wordStart, wordEnd);
				// Search for the word
				self._searchFwd = true;
				var data = {
					hideAfterFind: true,
					incremental: false,
					reverse: false,
					findString: text
				};
				return self._invoke("find", data); //$NON-NLS-0$
			}, {name: messages.viStar});
				
//			Status Line Mode
//			view.setAction("statusLineMode", function() { //$NON-NLS-0$
//				self.insertMode.storeNumber(self.number);
//				view.removeKeyMode(self);
//				view.addKeyMode(self.insertMode);
//				self.number = "";
//				return true;
//			});
		},
		_reportStatus: function(msg) {
			if (this.statusReporter) {
				this.statusReporter(msg);
			}
		}, 
		_toInsertMode: function(action, data) {
			data =  data || {};
			var num = 1;
			if (this.number !== "") {
				num = this.number >> 0;
			}
			data.count = num;
			var view = this.getView();
			this.insertMode.storeNumber(this.number);
			view.invokeAction(action, false, data);
			view.removeKeyMode(this);
			view.addKeyMode(this.insertMode);
			this.number = "";
			return true;
		},
		_modeAdded: function() {
			this.getView().setOptions({blockCursorVisible: true});	
		},
		_modeRemoved: function() {
			var view = this.getView();
			view.setOptions({blockCursorVisible: false});
			view.removeKeyMode(this.insertMode);
			view.removeKeyMode(this.changeMode);
			view.removeKeyMode(this.deleteMode);
		}
	});
	
	return {
		VIMode: VIMode
	};
});

/*eslint-env browser, amd*/
define('orion/editorPreferences',[], function() {
	return {
	};
});
/*eslint-env browser, amd*/
define('orion/widgets/themes/ThemePreferences',[], function() {
	return {
	};
});
/*eslint-env browser, amd*/
define('orion/widgets/themes/editor/ThemeData',[], function() {
	return {
	};
});
/*eslint-env browser, amd*/
define('orion/widgets/settings/EditorSettings',[], function(){
	return null;
});
/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2014 IBM Corporation and others. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 * 
 * Contributors: 
 *	IBM Corporation - initial API and implementation
 *	Adrian Aichner - regular expression capture group support in replace
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/searchAndReplace/textSearcher',[], function(){
	return {TextSearcher: null};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: Anton McConville - IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/widgets/input/DropDownMenu',['orion/objects', 'orion/webui/littlelib'], function(objects, lib) {

	/**
	 * Creates a dropdown menu for a node and its associated trigger node (button)
	 * @param {Object} parent The dom object or string id of the node that will contain the dropdown menu
	 * @param {Object} triggerNode The dom object or string id of the dom node that will trigger the dropdown menu appearance
	 * @param {Object} [options] options for the drop down menu.
	 * @param {String} [options.selectionClass] CSS class to be appended when the trigger node is selected.
	 * @param {String} [options.noClick] Do not add the click handler to the trigger node.
	 * @param {String} [options.onShow] Callback called when the menu is shown.
	 * @param {String} [options.onHide] Callback called when the menu is hidden.
	 */
	function DropDownMenu( parent, triggerNode, options ){
		var node = lib.node(parent);
		if (node) {
			this._parent = node;
		} else {
			throw new Error("Parent node of dropdown menu not found"); //$NON-NLS-0$
		}
		
		options = options || {};
		this.options = options;
		
		// Assign dynamic ids to the dropdown menu node to support multiple drop down menus in the same page
		this.navDropDownId = this._parent.id + '_navdropdown'; //$NON-NLS-0$
		this.selectionClass = options.selectionClass;
		
		// Create dropdown container and append to parent dom
		var dropDownContainer = document.createElement("div"); //$NON-NLS-0$
		dropDownContainer.classList.add("dropdownMenu"); //$NON-NLS-0$
		dropDownContainer.classList.add("dropdownMenuOpen"); //$NON-NLS-0$
		dropDownContainer.id = this.navDropDownId; 
		dropDownContainer.style.display = 'none'; //$NON-NLS-0$
		this._parent.appendChild(dropDownContainer);
		this._dropdownMenu = dropDownContainer;
		
		// Display trigger node and bind on click event
		triggerNode = lib.node(triggerNode);
		if (triggerNode) {
			this._triggerNode = triggerNode;
		} else {
			throw "Trigger node of dropdown menu not found"; //$NON-NLS-0$
		}
		if (this._triggerNode.style.visibility === 'hidden') { //$NON-NLS-0$
			this._triggerNode.style.visibility = 'visible'; //$NON-NLS-0$
		}
		
		if (!options.noClick) {
			this._triggerNode.onclick = this.click.bind(this);
		}
		this._dropdownMenu.addEventListener("keydown", function (e) { //$NON-NLS-0$
			if (e.keyCode === lib.KEY.ESCAPE) {
				this.clearPanel();
			}
		}.bind(this));
	}
	
	objects.mixin(DropDownMenu.prototype, {
		click: function() {
			if( this._dropdownMenu.style.display === 'none' ){ //$NON-NLS-0$
				this.updateContent ( this.getContentNode() , function () {
					lib.setFramesEnabled(false);
					this._dropdownMenu.style.display = '';
					this._positionDropdown();
					if (this.selectionClass) {
						this._triggerNode.classList.add(this.selectionClass);
					}
					this.handle = lib.addAutoDismiss( [ this._triggerNode, this._dropdownMenu], this.clearPanel.bind(this) );
					if (this.options.onShow) {
						this.options.onShow();
					}
				}.bind(this));
			}else{
				this.clearPanel();
			}
		},
		
		clearPanel: function(){
			if (!this.isVisible()) { return; }
			this._dropdownMenu.style.display = 'none'; //$NON-NLS-0$
			lib.setFramesEnabled(true);
			if (this.selectionClass) {
				this._triggerNode.classList.remove(this.selectionClass);
			}
			if (this.options.onHide) {
				this.options.onHide();
			}
		},
		
		// Add content to the dropdown container
		addContent: function( content ){
			this._dropdownMenu.innerHTML = content;
		},
		
		getContentNode: function(){
			return this._dropdownMenu;
		},
		
		updateContent: function( contentNode, callback ){
			// to be overridden to update the contents before showing
			// the callback needs to be called once the content is up to date
			callback();
		},

		_positionDropdown: function() {
			this._dropdownMenu.style.right = "";
			var bounds = lib.bounds(this._dropdownMenu);
			var bodyBounds = lib.bounds(document.body);
			if (bounds.left + bounds.width > (bodyBounds.left + bodyBounds.width)) {
				var totalBounds = lib.bounds(this._boundingNode(this._triggerNode));
				var triggerBounds = lib.bounds(this._triggerNode);
				this._dropdownMenu.style.right = (totalBounds.width - ((triggerBounds.left - totalBounds.left) + triggerBounds.width)) + "px"; //$NON-NLS-0$
			}
		},
		
		_boundingNode: function(node) {
			var style = window.getComputedStyle(node, null);
			if (style === null) {
				return node;
			}
			var position = style.getPropertyValue("position"); //$NON-NLS-0$
			if (position === "absolute" || !node.parentNode || node === document.body) { //$NON-NLS-0$
				return node;
			}
			return this._boundingNode(node.parentNode);
		},
		
		isDestroyed: function() {
			return !this._dropdownMenu.parentNode;
		},

		isVisible: function() {
			return this._dropdownMenu.style.display !== "none" && !this.isDestroyed(); //$NON-NLS-0$
		},

		focus: function() {
			this._dropdownMenu.focus();
		},

		destroy: function() {
			if (this._parent) {
				lib.setFramesEnabled(true);
				lib.empty(this._parent);
				this._parent = this.select = null;
			}
		}
	});
	
	return DropDownMenu;
});

/*******************************************************************************
 * @license
 * Copyright (c) 2011, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define('orion/URITemplate',[],function(){
	
	var OPERATOR = {
		NUL: {first:"", sep:",", named: false, ifemp: "", allow: "U"}, //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"+": {first:"", sep:",", named: false, ifemp: "", allow: "U+R"}, //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		".": {first:".", sep:",", named: false, ifemp: "", allow: "U"}, //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"/": {first:"/", sep:"/", named: false, ifemp: "", allow: "U"}, //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		";": {first:";", sep:";", named: true, ifemp: "", allow: "U"}, //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"?": {first:"?", sep:"&", named: true, ifemp: "=", allow: "U"}, //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"&": {first:"&", sep:"&", named: true, ifemp: "=", allow: "U"}, //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"#": {first:"#", sep:",", named: false, ifemp: "", allow: "U+R"}, //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		",": {first:"", sep:",", named: false, ifemp: "", allow: "U+R-,"} //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
	};

	var VARSPEC_REGEXP = /^((?:(?:[a-zA-Z0-9_])|(?:%[0-9A-F][0-9A-F]))(?:(?:[a-zA-Z0-9_.])|(?:%[0-9A-F][0-9A-F]))*)(?:(\*)|:([0-9]+))?$/;
	var PCT_ENCODED_G = /%25[0-9A-F][0-9A-F]/g;

	function Literal(text) {
		this._text = text;
	}

	Literal.prototype = {
		expand: function(vars) {
			return encodeURI(this._text);
		}
	};
	
	function decodePercent(str) {
		return str.replace("%25", "%");
	}
	
	function encodeString(value, encoding) {
		if (encoding === "U") { //$NON-NLS-0$
			return encodeURIComponent(value).replace(/[!'()*]/g, function(str) {
				return '%' + str.charCodeAt(0).toString(16).toUpperCase(); //$NON-NLS-0$
			});
		}
		if (encoding === "U+R") { //$NON-NLS-0$
			return encodeURI(value).replace(/%5B/g, '[').replace(/%5D/g, ']').replace(PCT_ENCODED_G, decodePercent); //$NON-NLS-1$ //$NON-NLS-0$
		}
		if (encoding === "U+R-,") { //$NON-NLS-0$
			return encodeURI(value).replace(/%5B/g, '[').replace(/%5D/g, ']').replace(/,/g, '%2C'); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		}
		throw new Error("Unknown allowed character set: " + encoding);
	}
	
	function encodeArray(value, encoding, separator) {
		var result = [];
		for (var i=0; i < value.length; i++) {
			if (typeof(value[i]) !== "undefined") { //$NON-NLS-0$
				result.push(encodeString(value[i], encoding));
			}
		}
		return result.join(separator);
	}
	
	function encodeObject(value, encoding, nameValueSeparator, pairSeparator ) {
		var keys = Object.keys(value);
		var result = [];
		for (var i=0; i < keys.length; i++) {
			if (typeof(value[keys[i]]) !== "undefined") { //$NON-NLS-0$
				result.push(encodeString(keys[i], encoding) + nameValueSeparator + encodeString(value[keys[i]], encoding));
			}
		}
		return result.join(pairSeparator);
	}
	
	function parseVarSpecs(text) {
		var result = [];
		var rawSpecs = text.split(","); //$NON-NLS-0$
		for (var i=0; i < rawSpecs.length; i++) {
			var match = rawSpecs[i].match(VARSPEC_REGEXP);
			if (match === null) {
				throw new Error("Bad VarSpec: " + text); //$NON-NLS-0$
			}
			result.push({
				name: match[1], 
				explode: !!match[2], 
				prefix: match[3] ? parseInt(match[3], 10) : -1
			}); 
		}
		return result;
	}
	
	function Expression(text) {
		if (text.length === 0) {
			throw new Error("Invalid Expression: 0 length expression"); //$NON-NLS-0$
		}
		
		this._operator = OPERATOR[text[0]];
		if (this._operator) {
			text = text.substring(1);
		} else {
			this._operator = OPERATOR.NUL;
		}
		
		this._varSpecList = parseVarSpecs(text);
	}
	
	Expression.prototype = {
		expand: function(params) {
			var result = [];
			for (var i=0; i < this._varSpecList.length; i++) {
				var varSpec = this._varSpecList[i];
				var name = varSpec.name;
				var value = params[name];
				var valueType = typeof(value);
				if (valueType !== "undefined" && value !== null) { //$NON-NLS-0$
					var resultText = result.length === 0 ? this._operator.first: this._operator.sep;			
					if (valueType === "string") { //$NON-NLS-0$
						if (this._operator.named) {
							resultText += encodeString(name, "U+R"); //$NON-NLS-0$
							resultText += (value.length === 0) ? this._operator.ifemp : "="; //$NON-NLS-0$
						}
						if (varSpec.prefix !== -1 && varSpec.prefix < value.length) {
							value = value.substring(0, varSpec.prefix);
						}
						
						resultText += encodeString(value, this._operator.allow);
					} else if (Array.isArray(value)) {
						if (value.length === 0) {
							continue; // treated as undefined and skipped
						}
						if (!varSpec.explode) {
							var encodedArray = encodeArray(value, this._operator.allow, ","); //$NON-NLS-0$
							if (this._operator.named) {
								resultText += encodeString(name, "U+R"); //$NON-NLS-0$
								resultText += (encodedArray.length === 0) ? this._operator.ifemp : "="; //$NON-NLS-0$
							}
							resultText += encodedArray;
						} else {
							resultText += encodeArray(value, this._operator.allow, this._operator.sep);
						}				
					} else if (valueType === "object") { //$NON-NLS-0$
						if (Object.keys(value).length === 0) {
							continue; // treated as undefined and skipped
						}
						if (!varSpec.explode) {
							var encodedObject = encodeObject(value, this._operator.allow, ",", ","); //$NON-NLS-1$ //$NON-NLS-0$
							if (this._operator.named) {
								resultText += encodeString(name, "U+R"); //$NON-NLS-0$
								resultText += (encodedObject.length === 0) ? this._operator.ifemp : "="; //$NON-NLS-0$
							}
							resultText += encodedObject; //$NON-NLS-0$
						} else {
							resultText += encodeObject(value, this._operator.allow, "=", this._operator.sep); //$NON-NLS-0$
						}
					} else {
						throw new Error("bad param type: " + name + " : " + valueType); //$NON-NLS-1$ //$NON-NLS-0$
					}
					result.push(resultText);
				}
			}
			return result.join("");
		}
	};

	function parseTemplate(text) {
		var result = [];
		var current = 0;	
		var curlyStartIndex = text.indexOf("{", current); //$NON-NLS-0$
		while (curlyStartIndex !== -1) {
			result.push(new Literal(text.substring(current, curlyStartIndex)));
			var curlyEndIndex = text.indexOf("}", curlyStartIndex + 1); //$NON-NLS-0$
			if (curlyEndIndex === -1) {
				throw new Error("Invalid template: " + text); //$NON-NLS-0$
			}
			result.push(new Expression(text.substring(curlyStartIndex + 1, curlyEndIndex)));
			current = curlyEndIndex + 1;
			curlyStartIndex = text.indexOf("{", current);			 //$NON-NLS-0$
		}
		result.push(new Literal(text.substring(current)));
		return result;
	}

	/**
	 * @name orion.URITemplate
	 * @class A URITemplate describes a range of Uniform Resource Identifiers through variable expansion, and allows for particular URIs to 
	 * be generated by expanding variables to actual values.</p>
	 * <p>Because the syntax and encoding rules of URIs can be complex, URITemplates are recommended over manual construction of URIs through 
	 * string concatenation or other means.</p>
	 * <p>A URITemplate is created by invoking the constructor, passing a <em>template string</em>:</p>
	 * <p><code>new URITemplate(template)</code></p>
	 * <p>The <dfn>template string</dfn> is an expression following a well-defined syntax (see <a href="http://tools.ietf.org/html/rfc6570#section-1.2">here</a>
	 * for an introduction). Most notably, the template may include variables.</p>
	 * <p>Once created, a URITemplate's {@link #expand} method can be invoked to generate a URI. Arguments to {@link #expand} give the values to be 
	 * substituted for the template variables.</p>
	 * @description Creates a new URITemplate.
	 * @param {String} template The template string. Refer to <a href="http://tools.ietf.org/html/rfc6570#section-2">RFC 6570</a> for details
	 * of the template syntax.
	 */
	function URITemplate(template) {
		this._templateComponents = parseTemplate(template);
	}
	
	URITemplate.prototype = /** @lends orion.URITemplate.prototype */ {
		/**
		 * Expands this URITemplate to a URI.
		 * @param {Object} params The parameters to use for expansion. This object is a map of keys (variable names) to values (the variable's
		 * value in the <a href="http://tools.ietf.org/html/rfc6570#section-3.2.1">expansion algorithm</a>).
		 * @returns {String} The resulting URI.
		 */
		expand: function(params) {
			var result = [];
			for (var i = 0; i < this._templateComponents.length; i++) {
				result.push(this._templateComponents[i].expand(params));
			}
			return result.join("");
		}
	};

	return URITemplate;
});
/*******************************************************************************
 * @license
 * Copyright (c) 2012, 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 ******************************************************************************/
/*eslint-env browser, amd*/
/*global URL*/
define('orion/PageLinks',[
	"require",
	"orion/Deferred",
	"orion/PageUtil",
	"orion/URITemplate",
	"orion/i18nUtil",
	"orion/objects",
	"orion/URL-shim"
], function(require, Deferred, PageUtil, URITemplate, i18nUtil, objects) {

	/**
	 * Returns the value of the <code>{OrionHome}</code> variable.
	 * @memberOf orion.PageLinks
	 * @function
	 * @returns {String} The value of the <code>{OrionHome}</code> variable.
	 */
	function getOrionHome() {
		if(!require.toUrl){
			return new URL("/", self.location.href).href.slice(0, -1);
		} else {
			// The idea here is to find the path of `orion/*` modules from the loader, and go up one folder to
			// the servlet context path. Finally, return a complete URL, slicing off the trailing `/`.
			// RequireJS 2.1.15:
			var orionSrcURL = new URL(require.toUrl("orion/"), self.location.href); //$NON-NLS-0$
			return new URL("../", orionSrcURL).href.slice(0, -1); //$NON-NLS-0$
		}
	}

	/**
	 * Reads metadata from an <code>orion.page.xxxxx</code> service extension.
	 * @memberOf orion.PageLinks
	 * @function
	 * @param {orion.ServiceRegistry} serviceRegistry The service registry.
	 * @param {String} [serviceName="orion.page.link"] Service name to read extensions from.
	 * @return {orion.Promise} A promise that resolves to an {@link orion.PageLinks.PageLinksInfo} object.
	 */
	function getPageLinksInfo(serviceRegistry, serviceName) {
		return _readPageLinksMetadata(serviceRegistry, serviceName).then(function(metadata) {
			return new PageLinksInfo(metadata);
		});
	}

	function _getPropertiesMap(serviceRef) {
		var props = {};
		serviceRef.getPropertyKeys().forEach(function(key) {
			if (key !== "objectClass" && key !== "service.names" && key !== "service.id" && key !== "__plugin__") //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				props[key] = serviceRef.getProperty(key);
		});
		return props;
	}

	function _readPageLinksMetadata(serviceRegistry, serviceName) {
		serviceName = serviceName || "orion.page.link"; //$NON-NLS-0$

		// Read page links.
		// https://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Plugging_into_Orion_pages
		var navLinks= serviceRegistry.getServiceReferences(serviceName);
		var params = PageUtil.matchResourceParameters(window.location.href);
		// TODO: should not be necessary, see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=373450
		var orionHome = getOrionHome();
		var locationObject = {OrionHome: orionHome, Location: params.resource};
		var navLinkInfos = [];
		navLinks.forEach(function(navLink) {
			var info = _getPropertiesMap(navLink);
			if (!info.uriTemplate || (!info.nls && !info.name)) {
				return; // missing data, skip
			}

			var uriTemplate = new URITemplate(info.uriTemplate);
			var expandedHref = uriTemplate.expand(locationObject);
			expandedHref = PageUtil.validateURLScheme(expandedHref);
			info.href = expandedHref;

			info.textContent = info.name || info.nameKey;
			navLinkInfos.push(new Deferred().resolve(info));
		});
		return Deferred.all(navLinkInfos);
	}

	// Categories apply to all orion.page.link* serviceNames, so cache them.
	var _cachedCategories;
	/**
	 * Reads info about page link categories.
	 * @returns {orion.Promise} Resolving to {@link orion.PageLinks.CategoriesInfo}
	 */
	function getCategoriesInfo(serviceRegistry) {
		// Read categories.
		var categoryInfos;
		if (!_cachedCategories) {
			categoryInfos = [];
			var navLinkCategories = serviceRegistry.getServiceReferences("orion.page.link.category"); //$NON-NLS-0$
			navLinkCategories.forEach(function(serviceRef) {
				var info = _getPropertiesMap(serviceRef);
				if (!info.id || (!info.name && !info.nameKey)) {
					return;
				}
				info.service = serviceRegistry.getService(serviceRef);
				info.textContent = info.name;
				categoryInfos.push(new Deferred().resolve(info));				
			});
			return Deferred.all(categoryInfos).then(function(infos) {
				_cachedCategories = new CategoriesInfo(infos);
				return _cachedCategories;
			});
		}
		return new Deferred().resolve(_cachedCategories);
	}

	function CategoriesInfo(categoriesArray) {
		var categories = this.categories = Object.create(null); // Maps category id {String} to category {Object}

		categoriesArray.forEach(function(category) {
			categories[category.id] = category;
		});
	}
	objects.mixin(CategoriesInfo.prototype, /** @lends orion.CategoriesInfo.CategoriesInfo.prototype */ {
		/**
		 * Returns the category IDs.
		 * @returns {String[]} The category IDs.
		 */
		getCategoryIDs: function() {
			return Object.keys(this.categories);
		},
		/**
		 * Returns the data for a given category.
		 * @param {String} id The category ID.
		 * @returns {Object} The category data.
		 */
		getCategory: function(id) {
			return this.categories[id] || null;
		}
	});

	/**
	 * @name orion.PageLinks.PageLinksInfo
	 * @class
	 * @description Provides access to info about page links read from an extension point.
	 */
	function PageLinksInfo(allPageLinks) {
		this.allPageLinks = allPageLinks;
		this.allPageLinks.sort(_comparePageLinks);
	}
	objects.mixin(PageLinksInfo.prototype, /** @lends orion.PageLinks.PageLinksInfo.prototype */ {
		/**
		 * Builds DOM elements for links
		 * @returns {Element[]} The links.
		 */
		createLinkElements: function() {
			return this.allPageLinks.map(function(info) {
				return _createLink(info.href, "_self", info.textContent); //$NON-NLS-0$
			});
		},
		/**
		 * @returns {Object[]} The links.
		 */
		getAllLinks: function() {
			return this.allPageLinks;
		}
	});

	function _comparePageLinks(a, b) {
		var n1 = a.textContent && a.textContent.toLowerCase();
		var n2 = b.textContent && b.textContent.toLowerCase();
		if (n1 < n2) { return -1; }
		if (n1 > n2) { return 1; }
		return 0;
	}

	function _createLink(href, target, textContent) {
		var a = document.createElement("a");
		a.href = href;
		a.target = target;
		a.classList.add("targetSelector");
		a.textContent = textContent;
		return a;
	}

	/**
	 * @name orion.PageLinks
	 * @class Utilities for reading <code>orion.page.link</code> services.
	 * @description Utilities for reading <code>orion.page.link</code> services.
	 */
	return {
		getCategoriesInfo: getCategoriesInfo,
		getPageLinksInfo: getPageLinksInfo,
		getOrionHome: getOrionHome
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2011,2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define('orion/extensionCommands',["orion/Deferred", "orion/commands", "orion/contentTypes", "orion/URITemplate", "orion/i18nUtil", "orion/PageLinks", "i18n!orion/edit/nls/messages", "orion/URL-shim"],
	function(Deferred, mCommands, mContentTypes, URITemplate, i18nUtil, PageLinks, messages){

	/**
	 * Utility methods
	 * @class This class contains static utility methods for creating and managing commands from extension points
	 * related to file management.
	 * @name orion.extensionCommands
	 */
	var extensionCommandUtils  = {};
	
	// TODO working around https://bugs.eclipse.org/bugs/show_bug.cgi?id=373450
	var orionHome = PageLinks.getOrionHome();
	
	extensionCommandUtils._cloneItemWithoutChildren = function clone(item){
	    if (item === null || typeof(item) !== 'object') { //$NON-NLS-0$
	        return item;
	      }
	
	    var temp = item.constructor(); // changed
	
	    for(var key in item){
			if(key!=="children" && key!=="Children" && key!=="parent" && key!=="Project") { //$NON-NLS-1$ //$NON-NLS-0$
				temp[key] = clone(item[key]);
			}
	    }
	    return temp;
	};
	
	/*
	 * Helper function which returns an object containing all of the specified 
	 * serviceReference's properties.
	 * @name _getServiceProperties
	 * @param {orion.serviceregistry.ServiceReference} serviceReference
	 * @returns {Object} All the properties of the given <code>serviceReference</code>.
	 */
	function _getServiceProperties(serviceReference){
		var info = {};
		var propertyNames = serviceReference.getPropertyKeys();
		propertyNames.forEach(function(propertyName) {
			info[propertyName] = serviceReference.getProperty(propertyName);
		});
		return info;
	}
	
	/**
	 * Reads <code>"orion.navigate.openWith"</code> service contributions and returns corresponding <code>orion.navigate.command<code> extensions.
	 * @name orion.extensionCommands._getOpenWithNavCommandExtensions
	 * @function
	 * @returns {Object[]} The nav command extensions.
	 */
	extensionCommandUtils._getOpenWithNavCommandExtensions = function(serviceRegistry, contentTypes) {
		function getEditors() {
			var serviceReferences = serviceRegistry.getServiceReferences("orion.edit.editor"); //$NON-NLS-0$
			var editors = [];
			for (var i=0; i < serviceReferences.length; i++) {
				var serviceRef = serviceReferences[i], id = serviceRef.getProperty("id"); //$NON-NLS-0$
				editors.push({
					id: id,
					"default": serviceRef.getProperty("default") || false, //$NON-NLS-1$ //$NON-NLS-0$
					name: serviceRef.getProperty("name"), //$NON-NLS-0$
					nameKey: serviceRef.getProperty("nameKey"), //$NON-NLS-0$
					nls: serviceRef.getProperty("nls"), //$NON-NLS-0$
					uriTemplate: serviceRef.getProperty("orionTemplate") || serviceRef.getProperty("uriTemplate"), //$NON-NLS-1$ //$NON-NLS-0$
					validationProperties: serviceRef.getProperty("validationProperties") || [] //$NON-NLS-0$
				});
			}
			return editors;
		}

		function getEditorOpenWith(serviceRegistry, editor) {
			var openWithReferences = serviceRegistry.getServiceReferences("orion.navigate.openWith"); //$NON-NLS-0$
			var types = [];
			var excludedTypes = [];
			for (var i=0; i < openWithReferences.length; i++) {
				var ref = openWithReferences[i];
				if (ref.getProperty("editor") === editor.id) { //$NON-NLS-0$
					var ct = ref.getProperty("contentType"); //$NON-NLS-0$
					if (ct instanceof Array) {
						types = types.concat(ct);
					} else { //$NON-NLS-0$
						types.push(ct || "*/*");
					}
					
					var excludes = ref.getProperty("excludedContentTypes"); //$NON-NLS-0$
					if (excludes instanceof Array) {
						excludedTypes = excludedTypes.concat(excludes);
					} else if ((excludes !== null) && (typeof excludes !== "undefined")) { //$NON-NLS-0$
						excludedTypes.push(excludes);
					}
				}
			}
			if (0 === types.length) {
				types = null;
			}
			if (0 === excludedTypes.length) {
				excludedTypes = null;
			}
			return {contentTypes: types, excludedContentTypes: excludedTypes};
		}
		
		var editors = getEditors();
		var fileCommands = [];
		var genericEditorOpen;
		var orionEditorId = "orion.editor";

		for (var i=0; i < editors.length; i++) {
			var editor = editors[i];
			var editorContentTypeInfo = getEditorOpenWith(serviceRegistry, editor);
			var editorContentTypes = editorContentTypeInfo.contentTypes;
			var excludedContentTypes = editorContentTypeInfo.excludedContentTypes;
			var properties = {
				name: editor.name || editor.id,
				nameKey: editor.nameKey,
				id: "eclipse.openWithCommand." + editor.id, //$NON-NLS-0$
				contentType: editorContentTypes,
				excludedContentTypes: excludedContentTypes,
				uriTemplate: editor.uriTemplate,
				nls: editor.nls,
				forceSingleItem: true,
				isEditor: (editor["default"] ? "default": "editor"), // Distinguishes from a normal fileCommand //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				validationProperties: editor.validationProperties
			};
			var command = {properties: properties, service: {}};
			if (editor.id === orionEditorId) {
				genericEditorOpen = command;
			} else {
				fileCommands.push(command);
			}
		}
		if (genericEditorOpen) {
			fileCommands.push(genericEditorOpen);
		}
		return fileCommands;
	};
	
	/**
	 * Create a validator for a given set of service properties.  The validator should be able to 
	 * validate a given item using the "contentType" and "validationProperties" service properties.
	 * @name orion.extensionCommands._makeValidator
	 * @function
	 */
	extensionCommandUtils._makeValidator = function(info, serviceRegistry, contentTypes, validationItemConverter) {
		function checkItem(item, key, match, validationProperty, validator) {
			var valid = false;
			var value;
			// Match missing property
			if (key.charAt(0) === "!") { //$NON-NLS-0$
				return (typeof item[key.substring(1)] === "undefined"); //$NON-NLS-0$
			}
			// item has the specified property
			if (typeof(item[key]) !== "undefined") { //$NON-NLS-0$
				if (typeof(match) === "undefined") {  //$NON-NLS-0$ // value doesn't matter, just the presence of the property is enough				if (!match) {  // value doesn't matter, just the presence of the property is enough
					value = item[key];
					valid = true;
				} else if (typeof(match) === 'string') {  // the value is a regular expression that should match some string //$NON-NLS-0$
					if (!typeof(item[key] === 'string')) { //$NON-NLS-0$
						// can't pattern match on a non-string
						return false;
					}
					if (validationProperty.variableName) {
						var patternMatch = new RegExp(match).exec(item[key]);
						if (patternMatch) {
							var firstMatch = patternMatch[0];
							if (validationProperty.variableMatchPosition === "before") { //$NON-NLS-0$
								value = item[key].substring(0, patternMatch.index);
							} else if (validationProperty.variableMatchPosition === "after") { //$NON-NLS-0$
								value = item[key].substring(patternMatch.index + firstMatch.length);
							} else if (validationProperty.variableMatchPosition === "only") { //$NON-NLS-0$
								value = firstMatch;
							} else {  // "all"
								value = item[key];
							}
							valid = true;
						}
					} else {
						return new RegExp(match).test(item[key]);
					}
				} else {
					if (item[key] === match) {
						value = item[key];
						valid = true;
					}
				}
				// now store any variable values and look for replacements
				var variableName = validationProperty.variableName, replacements = validationProperty.replacements;
				if (valid && variableName) {
					// store the variable values in the validator, keyed by variable name.  Also remember which item this value applies to.
					validator[variableName] = value;
					validator.itemCached = item;
					if (replacements) {
						if (typeof value !== "string") {
							window.console.log("Cannot replace " + variableName + ", value is not a string: " + value);
							return valid;
						}
						for (var i=0; i<replacements.length; i++) {
							var invalid = false;
							if (replacements[i].pattern) {
								var from = replacements[i].pattern;
								var to = replacements[i].replacement || "";
								validator[variableName] = validator[variableName].replace(new RegExp(from), to).replace(new RegExp(from), to);
							} else {
								invalid = true;
							}
							if (invalid) {
								window.console.log("Invalid replacements specified in validation property.  " + replacements[i]); //$NON-NLS-0$
							}
						}
					}
				}
				return valid;
			}
			return false;
		}

		/**
		 * @param {Object|Array} item
		 * @param {String} propertyName
		 * @param {Object} validationProperty
		 * @param {Validator} validator
		 */
		function matchSinglePattern(item, propertyName, validationProperty, validator){
			var value = validationProperty.match;
			var key, keyLastSegments, pos1, pos2;
			// Initial processing to handle array indices
			if ((pos1 = propertyName.indexOf("[")) >= 0) { //$NON-NLS-0$
				if((pos2 = propertyName.indexOf("]")) < 0){ //$NON-NLS-0$
					return false;
				}
				// The [] is used to drill into a numeric property of an array
				var fieldName = propertyName.substring(0, pos1), array, arrayIndex;
				if(!(array = item[fieldName]) || !Array.isArray(array)){
					return false;
				}
				key = propertyName.substring(pos1 + 1, pos2);
				arrayIndex = parseInt(key, 10);
				if (isNaN(arrayIndex))
					return false;

				// Index may be < 0 in which case it counts backwards from object.length
				if (arrayIndex < 0)
					arrayIndex += array.length;

				// Just rewrite the [] expression into a nested property access and fall down to the ":" case
				keyLastSegments = propertyName.substring(pos2 + 1);
				propertyName = fieldName + ":" + String(arrayIndex) + keyLastSegments;
			}

			if (propertyName.indexOf("|") >= 0) { //$NON-NLS-0$
				// the pipe means that any one of the piped properties can match
				key = propertyName.substring(0, propertyName.indexOf("|")); //$NON-NLS-0$
				keyLastSegments = propertyName.substring(propertyName.indexOf("|")+1); //$NON-NLS-0$
				// if key matches, we can stop.  No match is not a failure, look in the next segments.
				if (matchSinglePattern(item, key, validationProperty, validator)) {
					return true;
				} else {
					return matchSinglePattern(item, keyLastSegments, validationProperty, validator);
				}
			} else if (propertyName.indexOf(":") >= 0) { //$NON-NLS-0$
				// the colon is used to drill into a property
				key = propertyName.substring(0, propertyName.indexOf(":")); //$NON-NLS-0$
				keyLastSegments = propertyName.substring(propertyName.indexOf(":")+1); //$NON-NLS-0$
				// must have key and then check the next value
				if (item[key]) {
					return matchSinglePattern(item[key], keyLastSegments, validationProperty, validator);
				} else {
					return false;
				}
			} else {
				// we are checking a single property
				return checkItem(item, propertyName, value, validationProperty, validator);
			}
		}
		
		function validateSingleItem(item, contentTypes, validator){
			// first validation properties
			if (validator.info.validationProperties) {
				for (var i=0; i<validator.info.validationProperties.length; i++) {
					var validationProperty = validator.info.validationProperties[i];
					if (typeof(validationProperty.source) !== "undefined") { //$NON-NLS-0$
						var matchFound = matchSinglePattern(item, validationProperty.source, validationProperty, validator);
						if (!matchFound){
							return false;
						} 
					} else {
						window.console.log("Invalid validationProperties in " + info.id + ".  No source property specified."); //$NON-NLS-1$ //$NON-NLS-0$
						return false;
					}
				}
			}
			
			// Check content type validation
			if (!validator.info.contentType && !validator.info.excludedContentTypes){
				// Validation doesn't care about content type
				return true;
			}
			
			// Directories don't match any content types except */*
			if (item.Directory) {
				// */* is a special case used by openWithCommand that applies to directories (Bug 445677)
				if (validator.info.contentType && validator.info.contentType.indexOf('*/*') >= 0){
					return true;
				}
				return false;
			}
			
			var showCommand = true;
			var contentType = contentTypes ? mContentTypes.getFilenameContentType(item.Name, contentTypes) : null;
			contentType = contentType || {
				id:"application/octet-stream"
			};
			if (validator.info.excludedContentTypes && contentTypes) {
				showCommand = validator.info.excludedContentTypes.every(function(excludedContentType){
					var filter = excludedContentType.replace(/([*?])/g, ".$1");	//$NON-NLS-0$ //convert user input * and ? to .* and .?
					if (-1 !== contentType.id.search(filter)) {
						// found a match, return false thereby hiding this command
						return false;
					}
					return true;
				});
			}
			
			if (showCommand && validator.info.contentType && contentTypes) {
				// the presence of validator.info.contentType means that we only 
				// want to show the command if the contentType matches
				showCommand = validator.info.contentType.some(function(includedContentType){
					var filter = includedContentType.replace(/([*?])/g, ".$1");	//$NON-NLS-0$ //convert user input * and ? to .* and .?
					if (-1 !== contentType.id.search(filter)) {
						// found a match, return true
						return true;
					}
					return false;
				});
			}			
			return showCommand;
		}
	
		var validator = {info: info};
		validator.validationFunction =  function(items){
			if (typeof validationItemConverter === "function") { //$NON-NLS-0$
				items = validationItemConverter.call(this, items);
			}
			if (items) {
				if (Array.isArray(items)){
					if ((this.info.forceSingleItem || this.info.uriTemplate) && items.length !== 1) {
						return false;
					}
					if (items.length < 1){
						return false;
					}
				} else {
					items = [items];
				}
				
				for (var i=0; i<items.length; i++){
					if(!validateSingleItem(items[i], contentTypes, this)){
						return false;
					}
				}
				return true;
			}
			return false;
		};
		validator.generatesURI = function() {
			return !!this.info.uriTemplate;
		};
		
		validator.getURI = function(item) {
			if (this.info.uriTemplate) {
				var variableExpansions = {};
				// we need the properties of the item
				for (var property in item){
					if(Object.prototype.hasOwnProperty.call(item, property)){
						variableExpansions[property] = item[property];
					}
				}
				// now we need the variable expansions collected during validation.  
				if (this.info.validationProperties) {
					for (var i=0; i<this.info.validationProperties.length; i++) {
						var validationProperty = this.info.validationProperties[i];
						if (validationProperty.source && validationProperty.variableName) {
							// we may have just validated this item.  If so, we don't need to recompute the variable value.
							var alreadyCached = this.itemCached === item && this[validationProperty.variableName];
							if (!alreadyCached) {
								matchSinglePattern(item, validationProperty.source, validationProperty, this);
							}
							if (!item[validationProperty.variableName]) {
								variableExpansions[validationProperty.variableName] = this[validationProperty.variableName];
							} else {
								window.console.log("Variable name " + validationProperty.variableName + " in the extension " + this.info.id + " conflicts with an existing property in the item metadata."); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
							}
						}
					}
				}
				// special properties.  Should already be in metadata.  See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=373450
				variableExpansions.OrionHome = orionHome;
				var uriTemplate = new URITemplate(this.info.uriTemplate);
				return uriTemplate.expand(variableExpansions);
			} 
			return null;
		};
		return validator;
	};
	
	/**
	 * Helper function which returns the id from an info object.
	 */
	function getIdFromInfo(info) {
		return info.id || info.name;
	}

	var DEFAULT_NAME = messages["UnnamedCommand"]; //$NON-NLS-0$
	var DEFAULT_TOOLTIP = ""; //$NON-NLS-0$
	// Turns an info object containing the service properties and the service (or reference) into Command options.
	extensionCommandUtils._createCommandOptions = function(/**Object*/ info, /**Service*/ serviceOrReference, serviceRegistry, contentTypesMap, /**boolean*/ createNavigateCommandCallback, /**optional function**/ validationItemConverter) {
		
		var deferred = new Deferred();
		
		function enhanceCommandOptions(commandOptions, deferred){
			var validator = extensionCommandUtils._makeValidator(info, serviceRegistry, contentTypesMap, validationItemConverter);
			commandOptions.visibleWhen = validator.validationFunction.bind(validator);
			
			if (createNavigateCommandCallback) {
				if (validator.generatesURI.bind(validator)()) {
					commandOptions.hrefCallback = function(data){
						var item = Array.isArray(data.items) ? data.items[0] : data.items;
						return validator.getURI.bind(validator)(item);
					};
				} else {
					var inf = info;
					commandOptions.callback = function(data){
						var shallowItemsClone;
						if (inf.forceSingleItem) {
							var item = Array.isArray(data.items) ? data.items[0] : data.items;
							shallowItemsClone = extensionCommandUtils._cloneItemWithoutChildren(item);
						} else {
							if (Array.isArray(data.items)) {
								shallowItemsClone = [];
								for (var j = 0; j<data.items.length; j++) {
									shallowItemsClone.push(extensionCommandUtils._cloneItemWithoutChildren(data.items[j]));
								}
							} else {
								shallowItemsClone = extensionCommandUtils._cloneItemWithoutChildren(data.items);
							}
						}
						if(serviceRegistry){
							var progress = serviceRegistry.getService("orion.page.progress");
						}
						if(serviceOrReference.run) {
							if(progress){
								progress.progress(serviceOrReference.run(shallowItemsClone), "Running command: " + commandOptions.name);
							}else {
								serviceOrReference.run(shallowItemsClone);
							}
						} else if (serviceRegistry) {
							if(progress){
								progress.progress(serviceRegistry.getService(serviceOrReference).run(shallowItemsClone), "Running command: " + commandOptions.name);
							} else {
								serviceRegistry.getService(serviceOrReference).run(shallowItemsClone);
							}
						}
					};
				}  // otherwise the caller will make an appropriate callback for the extension
			}
			deferred.resolve(commandOptions);
		}
		
		var commandOptions = {
			name: info.name || info.nameKey || DEFAULT_NAME,
			image: info.image,
			id: getIdFromInfo(info),
			tooltip: info.tooltip || info.tooltipKey || DEFAULT_TOOLTIP,
			isEditor: info.isEditor,
			showGlobally: info.showGlobally
		};
		enhanceCommandOptions(commandOptions, deferred);
		
		return deferred;
	};

	/**
	 * Gets the "open with" command in the given <code>commandRegistry</code> for a given item. If {@link #createFileCommands}, has not been called,
	 * this returns <code>null</code>.
	 * @name orion.extensionCommands.getOpenWithCommand
	 * @function
	 * @param {orion.commandregistry.CommandRegistry} commandRegistry The command registry to consult.
	 * @param {Object} item The item to open.
	 * @param {orion.commands.Command[]} The optional list of commands to search for the appropriate command. If it is not provided, 
	 * 		orion.extensionCommands.getOpenWithCommands() is used.
	 */
	extensionCommandUtils.getOpenWithCommand = function(commandRegistry, item, commands) {
		var openWithCommand;
		var openWithCommands = commands || extensionCommandUtils.getOpenWithCommands(commandRegistry);
		for (var i=0; i < openWithCommands.length; i++) {
			if (openWithCommands[i].visibleWhen(item)) {
				var isDefault = openWithCommands[i].isEditor === "default"; //$NON-NLS-0$
				if (!openWithCommand || isDefault) {
					openWithCommand = openWithCommands[i];
					if (isDefault) {
						break;
					}
				}
			}
		}
		return openWithCommand;
	};

	/**
	 * Gets any "open with" commands in the given <code>commandRegistry</code>. If {@link #createFileCommands}, has not been called,
	 * this returns an empty array.
	 * @name orion.extensionCommands.getOpenWithCommands
	 * @function
	 * @param {orion.commandregistry.CommandRegistry} commandRegistry The command registry to consult.
	 * @returns {orion.commands.Command[]} All the "open with" commands added to the given <code>commandRegistry</code>.
	 */
	extensionCommandUtils.getOpenWithCommands = function(commandRegistry) {
		var openWithCommands = [];
		for (var commandId in commandRegistry._commandList) {
			var command = commandRegistry._commandList[commandId];
			if (command.isEditor) {
				openWithCommands.push(command);
			}
		}
		return openWithCommands;
	};
	
	var contentTypesCache;

	/**
	 * Collects file commands from extensions, turns them into {@link orion.commands.Command}s, and adds the commands with the given <code>commandRegistry</code>.
	 * @name orion.extensionCommands.createAndPlaceFileCommandsExtension
	 * @function
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry
	 * @param {orion.commandregistry.CommandRegistry} commandRegistry
	 * @param {String} toolbarId
	 * @param {Number} position
	 * @param {String} commandGroup
	 * @param {Boolean} isNavigator
	 * @param {Function} visibleWhen
	 * @returns {orion.Promise}
	 */
	extensionCommandUtils.createAndPlaceFileCommandsExtension = function(serviceRegistry, commandRegistry, toolbarId, position, commandGroup, isNavigator) {
		var navCommands = (isNavigator ? "all" : undefined); //$NON-NLS-0$
		var openWithCommands = !!isNavigator;
		return extensionCommandUtils.createFileCommands(serviceRegistry, null, navCommands, openWithCommands, commandRegistry).then(function(fileCommands) {
			if (commandGroup && (0 < fileCommands.length)) {
				commandRegistry.addCommandGroup(toolbarId, "eclipse.openWith", 1000, messages["OpenWith"], commandGroup, null, null, null, "dropdownSelection"); ///$NON-NLS-1$ //$NON-NLS-0$
				commandRegistry.addCommandGroup(toolbarId, "eclipse.fileCommandExtensions", 1000, messages["OpenRelated"], commandGroup); //$NON-NLS-0$
			}
			fileCommands.forEach(function(command) {
				var group = null;	
				if (commandGroup) {
					if (command.isEditor) {
						group = commandGroup + "/eclipse.openWith"; //$NON-NLS-0$
					} else {
						group = commandGroup + "/eclipse.fileCommandExtensions"; //$NON-NLS-0$
					}
				}
				
				commandRegistry.registerCommandContribution(toolbarId, command.id, position, group); //$NON-NLS-0$
			});
			return {};
		});
	};
		
	/**
	 * Reads file commands from extensions (<code>"orion.navigate.command"</code> and <code>"orion.navigate.openWith"</code>), and converts them into
	 * instances of {@link orion.commands.Command}.
	 * @name orion.extensionCommands.createFileCommands
	 * @function
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry
	 * @param {orion.core.ContentTypeRegistry} [contentTypeRegistry] If not provided, will be obtained from the serviceRegistry.
	 * @param {String} [includeNavCommands="global"] What kinds of <code>orion.navigate.command</code> contributions to include in the list of returned file commands.
	 * Allowed values are:
	 * <dl>
	 * <dt><code>"all"</code></dt> <dd>Include all nav commands.</dd>
	 * <dt><code>"global"</code></dt> <dd>Include only nav commands having the <code>forceSingleItem</code> and <code>showGlobally</code> flags.</dd>
	 * <dt><code>"none"</code></dt> <dd>Include no nav commands.</dd>
	 * </dl>
	 * @param {Boolean} [includeOpenWithCommands=true] Whether to include commands derived from <code>orion.navigate.openWith</code> in the list of returned file commands.
	 * @param {orion.commandregistry.CommandRegistry} commandRegistry
	 * @returns {orion.Promise} A promise resolving to an {@link orion.commands.Command[]} giving an array of file commands.
	 */
	extensionCommandUtils.createFileCommands = function(serviceRegistry, contentTypeRegistry, includeFileCommands, includeOpenWithCommands, commandRegistry) {
		includeFileCommands = (includeFileCommands === undefined) ? "global" : includeFileCommands;
		includeOpenWithCommands = (includeOpenWithCommands === undefined) ? true : includeOpenWithCommands;

		// Note that the shape of the "orion.navigate.command" extension is not in any shape or form that could be considered final.
		// We've included it to enable experimentation. Please provide feedback on IRC or bugzilla.
		//
		// The shape of the contributed commands is (for now):
		// info - information about the command (object).
		//		required attribute: name - the name of the command
		//		required attribute: id - the id of the command
		//		optional attribute: tooltip - the tooltip to use for the command
		//      optional attribute: image - a URL to an image for the command
		//      optional attribute: uriTemplate - a URI template that can be expanded to generate a URI appropriate for the item.
		//      optional attribute: forceSingleItem - if true, then the service is only invoked when a single item is selected
		//			and the item parameter to the run method is guaranteed to be a single item vs. an array.  When this is not true, 
		//			the item parameter to the run method may be an array of items.
		//      optional attribute: contentType - an array of content types for which this command is valid
		//      optional attribute: validationProperties - an array of validation properties used to read the resource
		//          metadata to determine whether the command is valid for the given resource.  Regular expression patterns are
		//          supported as values in addition to specific values.
		//          For example the validation property
		//				[{source: "Git"}, {source: "Directory", match:"true"}]
		//              specifies that the property "Git" must be present, and that the property "Directory" must be true.
		// run - the implementation of the command (function).
		//        arguments passed to run: (itemOrItems)
		//          itemOrItems (object or array) - an array of items to which the item applies, or a single item if the info.forceSingleItem is true
		//        the run function is assumed to perform all necessary action and the return is not used.
		var fileCommands = [];
		
		if (!extensionCommandUtils._cachedFileCommands) {
			extensionCommandUtils._createCachedFileCommands(serviceRegistry);
		}
		
		//we have already created the file commands, only select applicable ones based on function parameters	
		if (includeFileCommands === "all" || includeFileCommands === "global") { //$NON-NLS-1$ //$NON-NLS-0$
			extensionCommandUtils._cachedFileCommands.forEach(function(fileCommand) {
				var properties = fileCommand.properties;
				// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=402447
				if ((includeFileCommands === "all") || (properties.forceSingleItem && properties.showGlobally)) { //$NON-NLS-0$
					fileCommands.push(fileCommand);
				}
			});
		}
				
		function getContentTypes() {
			var contentTypes = serviceRegistry.getService("orion.core.contentTypeRegistry") || contentTypeRegistry;
			return contentTypesCache || Deferred.when(contentTypes.getContentTypes(), function(ct) { //$NON-NLS-0$
				contentTypesCache = ct;
				return contentTypesCache;
			});
		}
				
		return Deferred.when(getContentTypes(), function() {
			// If we are processing commands for the navigator, also include the open with command.  If we are not in the navigator, we only want the
			// commands we processed before.
			// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=402447
			if (includeOpenWithCommands) {
				if (!extensionCommandUtils._cachedOpenWithExtensions) {
					extensionCommandUtils._cachedOpenWithExtensions = extensionCommandUtils._getOpenWithNavCommandExtensions(serviceRegistry, contentTypesCache);
				}
				//add _cachedOpenWithExtensions to fileCommands
				fileCommands = fileCommands.concat(extensionCommandUtils._cachedOpenWithExtensions);
				commandRegistry._addedOpenWithCommands = includeOpenWithCommands;
			}
						
			var commandDeferreds = fileCommands.map(function(fileCommand) {
				var commandInfo = fileCommand.properties;
				var service = fileCommand.service;
				var cachedCommand = commandRegistry.findCommand(getIdFromInfo(commandInfo));
				if (cachedCommand) {
					return new Deferred().resolve(cachedCommand);
				} else {
					return extensionCommandUtils._createCommandOptions(commandInfo, service, serviceRegistry, contentTypesCache, true).then(function(commandOptions) {
						var command = new mCommands.Command(commandOptions);
						commandRegistry.addCommand(command);
						return command;
					});
				}
			});
			return Deferred.all(commandDeferreds, function(error) {
				return {_error: error};
			}).then(function(errorOrResultArray) {
				return errorOrResultArray;
			});
		});
	};

	extensionCommandUtils._createCachedFileCommands = function(serviceRegistry) {
		var commandsReferences = serviceRegistry.getServiceReferences("orion.navigate.command"); //$NON-NLS-0$
		extensionCommandUtils._cachedFileCommands = [];

		for (var i=0; i<commandsReferences.length; i++) {
			// Exclude any navigation commands themselves, since we are the navigator.
			var id = commandsReferences[i].getProperty("id"); //$NON-NLS-0$
			if (id !== "orion.navigateFromMetadata") { //$NON-NLS-0$
				var service = serviceRegistry.getService(commandsReferences[i]);
				var properties = _getServiceProperties(commandsReferences[i]);
				extensionCommandUtils._cachedFileCommands.push({properties: properties, service: service});
			}
		}
	};
	
	/**
	 * Reads the cached non-openWith file commands from extensions, and 
	 * returns an array containing their ids. If the cached commands haven't
	 * been created an exception will be thrown.
	 * 
	 * @name orion.extensionCommands.getFileCommandIds
	 * @function
	 * @returns {Array} An array containing the {String} ids of the cached non-openWith file commands
	 */
	extensionCommandUtils.getFileCommandIds = function() {
		var ids = [];
		if (!extensionCommandUtils._cachedFileCommands) {
			throw "extensionCommandUtils._cachedFileCommands is not initialized"; //$NON-NLS-0$
		} else if (extensionCommandUtils._cachedFileCommands.length) {
			ids = extensionCommandUtils._cachedFileCommands.map(function(command){
				return command.properties.id;
			});
		}
		return ids;
	};
	
	/**
	 * Reads <code>"orion.navigate.openWith"</code> extensions, and converts them into instances of {@link orion.commands.Command}.
	 * @name orion.extensionCommands.createOpenWithCommands
	 * @function
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry
	 * @param {orion.commandregistry.CommandRegistry} [commandRegistry]
	 * @param {orion.core.ContentTypeRegistry} [contentTypeRegistry] If not provided, will be obtained from the serviceRegistry.
	 * @returns {orion.Promise} A promise resolving to an {@link orion.commands.Command[]} giving an array of file commands.
	 */
	extensionCommandUtils.createOpenWithCommands = function(serviceRegistry, contentTypeService, commandRegistry) {
		if (commandRegistry && commandRegistry._addedOpenWithCommands) {
			// already processed by #createAndPlaceFileCommandsExtension
			return new Deferred().resolve(extensionCommandUtils.getOpenWithCommands(commandRegistry));
		}
		return extensionCommandUtils.createFileCommands(serviceRegistry, contentTypeService, "none", true, commandRegistry);
	};

	return extensionCommandUtils;
});

/*******************************************************************************
 * @license
 * Copyright (c) 2011, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define('orion/searchUtils',['i18n!orion/nls/messages', 'orion/regex', 'orion/editor/textModel', 'orion/URITemplate'], function(messages, mRegex, mTextModel, URITemplate) {

/**
 * @name orion.searchUtils.SearchParams
 * @class
 * @property {String} fileType
 * @property {String} keyword
 * @property {Boolean} regEx
 * @property {Boolean} nameSearch
 * @property {Boolean} caseSensitive
 */

/**
 * @namespace Search utility functions.
 * @name orion.searchUtils
 */
var searchUtils = {};

searchUtils.ALL_FILE_TYPE = "*.*"; //$NON-NLS-0$

function _generateSearchHelperRegEx(inFileQuery, searchParams, fromStart){
	var prefix = "";
	if(fromStart){
		prefix = "^"; //$NON-NLS-0$
	}
	var regexp = mRegex.parse("/" + prefix + inFileQuery.searchStr + "/"); //$NON-NLS-1$ //$NON-NLS-0$
	if (regexp) {
		var pattern = regexp.pattern;
		var flags = regexp.flags;
		if(flags.indexOf("i") === -1 && !searchParams.caseSensitive){ //$NON-NLS-0$ 
			//If the regEx flag does not include 'i' then we have to add it by searchParams.caseSensitive
			flags = flags + "i";//$NON-NLS-0$
		}
		inFileQuery.regExp = {pattern: pattern, flags: flags};
		inFileQuery.wildCard = true;
	}
}

searchUtils.getSearchParams = function(searcher, searchStr, advOptions){
	if (searcher) {
		var newSearchStr = searchStr, commitSearch = true;
		if(newSearchStr === "*"){ //$NON-NLS-0$
			newSearchStr = "";
		}
		if(newSearchStr === ""){
			commitSearch = advOptions && advOptions.type !== searchUtils.ALL_FILE_TYPE;
		}
		if (commitSearch) {
			var searchParams = searcher.createSearchParams(newSearchStr, false, false, advOptions);
			return searchParams;
		}
	} else {
		window.alert(messages["NoSearchAvailableErr"]);
	}
	
	return null;
};

/**
 * Generate a helper query object used for search result renderer.
 * @param {orion.searchUtils.SearchParams} searchParams The search parameters.
 * @param {Boolean} fromStart True if doing file name search, otherwise false.
 * @returns {Object} An object having the properties:<ul>
 * <li><code>{@link orion.searchUtils.SearchParams}</code> <code>params</code> The search parameters.</li>
 * <li><code>{@link Object}</code> <code>inFileQuery</code> The query object for in file search.</li>
 * <li><code>{@link String}</code> <code>displayedSearchTerm</code> The search term display string.</li>
 * </ul>
 * @name orion.searchUtils.generateSearchHelper
 * @function
 */
searchUtils.generateSearchHelper = function(searchParams, fromStart) {
	var searchStr = searchParams.keyword;
	var displayedSearchTerm = searchStr;
	var inFileQuery = {};
	if(searchParams.fileType && searchParams.fileType !== searchUtils.ALL_FILE_TYPE && searchStr === ""){
		displayedSearchTerm = "*." + searchParams.fileType; //$NON-NLS-0$
	}
	if(!searchParams.regEx){
		var hasStar = (searchStr.indexOf("*") > -1); //$NON-NLS-0$
		var hasQMark = (searchStr.indexOf("?") > -1); //$NON-NLS-0$
		if(hasStar){
			searchStr = searchStr.split("*").join(".*"); //$NON-NLS-1$ //$NON-NLS-0$
		}
		if(hasQMark){
			searchStr = searchStr.split("?").join("."); //$NON-NLS-1$ //$NON-NLS-0$
		}
		if(!hasStar && !hasQMark && !searchParams.nameSearch){
			inFileQuery.searchStr = searchParams.caseSensitive ? searchStr.split("\\").join("") : searchStr.split("\\").join("").toLowerCase(); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			inFileQuery.wildCard = false;
		} else {
			inFileQuery.searchStr = searchParams.caseSensitive ? searchStr : searchStr.toLowerCase();
			_generateSearchHelperRegEx(inFileQuery, searchParams, fromStart);
			inFileQuery.wildCard = true;
		}
	} else {
		inFileQuery.searchStr =searchStr;
		_generateSearchHelperRegEx(inFileQuery, searchParams, fromStart);
	}
	inFileQuery.searchStrLength = inFileQuery.searchStr.length;
	return {params: searchParams, inFileQuery: inFileQuery, displayedSearchTerm: displayedSearchTerm};
};

searchUtils.convertSearchParams = function(searchParams) {
	if(searchParams.rows !== undefined){
		searchParams.rows = parseInt(searchParams.rows, 10);
	}
	if(searchParams.start !== undefined){
		searchParams.start = parseInt(searchParams.start, 10);
	}
	if(typeof searchParams.regEx === "string"){ //$NON-NLS-0$
		searchParams.regEx = (searchParams.regEx.toLowerCase() === "true"); //$NON-NLS-0$
	}
	if(typeof searchParams.caseSensitive === "string"){ //$NON-NLS-0$
		searchParams.caseSensitive = (searchParams.caseSensitive.toLowerCase() === "true"); //$NON-NLS-0$
	}
	if(typeof searchParams.nameSearch === "string"){ //$NON-NLS-0$
		searchParams.nameSearch = (searchParams.nameSearch.toLowerCase() === "true"); //$NON-NLS-0$
	}
	if(searchParams.fileNamePatterns !== undefined){
		searchParams.fileNamePatterns = searchUtils.getFileNamePatternsArray(searchParams.fileNamePatterns);
	}
};

searchUtils.getFileNamePatternsArray = function(patterns) {
	var fileNamePatternArray = undefined;
	
	if (patterns) {
		var fileNamePatterns = patterns.trim();
		
		fileNamePatterns = fileNamePatterns.replace(/^(\s*,\s*)+/g, ""); //get rid of leading commas
		
		fileNamePatterns = fileNamePatterns.replace(/([^\\]),(\s*,\s*)*/g, "$1/"); //replace all unescaped commas with slashes (since slashes aren't legal in file names)
		
		fileNamePatterns = fileNamePatterns.replace(/(\s*\/\s*)/g, "/"); //get rid of spaces before and after delimiter
		fileNamePatterns = fileNamePatterns.replace(/\/\/+/g, "/"); //get rid of empty patterns
		fileNamePatterns = fileNamePatterns.replace(/\/+$/g, ""); //get rid of trailing delimiters
		
		fileNamePatternArray = fileNamePatterns.split("/");
	}
	
	return fileNamePatternArray;
};

searchUtils.copySearchParams = function(searchParams, copyReplace) {
	var result = {};
	for (var prop in searchParams) {
		if(searchParams[prop] !== undefined && searchParams[prop] !== null){
			if(!copyReplace && prop === "replace") { //$NON-NLS-0$
				continue;
			}
			result[prop] = searchParams[prop];
		}
	}
	return result;	
};

searchUtils.generateFindURLBinding = function(searchParams, inFileQuery, lineNumber, replaceStr, paramOnly) {
	var params = {
		find: inFileQuery.searchStr,
		regEx: inFileQuery.wildCard ? true : undefined,
		caseSensitive: searchParams.caseSensitive ? true : undefined,
		replaceWith: typeof(replaceStr) === "string" ? replaceStr : undefined, //$NON-NLS-0$
		atLine: typeof(lineNumber) === "number" ? lineNumber : undefined //$NON-NLS-0$
	};
	if(paramOnly){
		return params;
	}
	var binding = new URITemplate("{,params*}").expand({ //$NON-NLS-0$
		params: params
	});
	return "," + binding; //$NON-NLS-0$
};

searchUtils.convertFindURLBinding = function(findParams) {
	if(typeof findParams.regEx === "string"){ //$NON-NLS-0$
		findParams.regEx = (findParams.regEx.toLowerCase() === "true"); //$NON-NLS-0$
	}
	if(typeof findParams.caseSensitive === "string"){ //$NON-NLS-0$
		findParams.caseSensitive = (findParams.caseSensitive.toLowerCase() === "true"); //$NON-NLS-0$
	}
	if(typeof findParams.atLine === "string"){ //$NON-NLS-0$
		findParams.atLine = parseInt(findParams.atLine, 10);
	}
};

searchUtils.replaceRegEx = function(text, regEx, replacingStr){
	var regexp = new RegExp(regEx.pattern, regEx.flags);
	return text.replace(regexp, replacingStr); 
	
};

searchUtils.replaceStringLiteral = function(text, keyword, replacingStr){
	var regexp = mRegex.parse("/" + keyword + "/gim"); //$NON-NLS-1$ //$NON-NLS-0$
	return searchUtils.replaceRegEx(text,regexp, replacingStr);
};

searchUtils.searchOnelineLiteral =  function(inFileQuery, lineString, onlyOnce, textModel, lineIndex){
	var i,startIndex = 0;
	var found = false;
	var result = [];
	while(true){
		i = lineString.indexOf(inFileQuery.searchStr, startIndex);
		if (i < 0) {
			break;
		} else {
			if(textModel) {
				var start = textModel.getLineStart(lineIndex) + i;
				result.push({startIndex: i, length: inFileQuery.searchStrLength, start: start, end: start + inFileQuery.searchStrLength});
			} else {
				result.push({startIndex: i, length: inFileQuery.searchStrLength});
			}
			found = true;
			if(onlyOnce){
				break;
			}
			startIndex = i + inFileQuery.searchStrLength;
		}
	}
	if(found) {
		return result;
	}
	return null;
	
};

/**
 * Helper for finding regex matches in text contents.
 *
 * @param {String}
 *            text Text to search in.
 * @param {String}
 *            pattern A valid regexp pattern.
 * @param {String}
 *            flags Valid regexp flags. Allowed flags are: <code>"i"</code>, <code>"s"</code>, and any combination thereof.
 * @param {Number}
 *            [startIndex] Index to begin searching from.
 * @return {Object} An object giving the match details, or
 *         <code>null</code> if no match found. The
 *         returned object will have the properties:<br />
 *         <code>{Number} index</code><br />
 *         <code>{Number} length</code>
 * @name orion.searchUtils.findRegExp
 * @function
 */
searchUtils.findRegExp =  function(text, pattern, flags, startIndex) {
	if (!pattern) {
		return null;
	}
	flags = flags || "";
	// 'g' makes exec() iterate all matches, 'm' makes ^$
	// work linewise
	flags += (flags.indexOf("g") === -1 ? "g" : "") + //$NON-NLS-1$ //$NON-NLS-0$
			(flags.indexOf("m") === -1 ? "m" : ""); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
	var regexp = new RegExp(pattern, flags);
	var result = null;
	result = regexp.exec(text.substring(startIndex));
	return result && {
		startIndex: result.index + startIndex,
		length: result[0].length
	};
};

searchUtils.searchOnelineRegEx =  function(inFileQuery, lineString, onlyOnce, textModel, lineIndex){
	var startIndex = 0;
	var found = false;
	var result = [];
	while(true){
		var regExResult = searchUtils.findRegExp(lineString, inFileQuery.regExp.pattern, inFileQuery.regExp.flags, startIndex);
		if(regExResult){
			if(textModel) {
				var start = textModel.getLineStart(lineIndex) + regExResult.startIndex;
				regExResult.start = start;
				regExResult.end = start + regExResult.length;
			}
			result.push(regExResult);
			found = true;
			if(onlyOnce){
				break;
			}
			startIndex = regExResult.startIndex + regExResult.length;
		} else {
			break;
		}
	}
	if(found) {
		return result;
	}
	return null;
};

searchUtils.generateNewContents = function( updating, oldContents, newContents, fileModelNode, replaceStr, searchStrLength){
	if(fileModelNode && oldContents){
		if(!updating){
			newContents.contents = [];
		}
		for(var i = 0; i < oldContents.length ; i++){
			var lineStringOrigin = oldContents[i];
			var changingLine = false;
			var checked = false;
			var checkedMatches = [];
			var originalMatches;
			var k, startNumber = 0;
			for(var j = 0; j < fileModelNode.children.length; j++){
				var lnumber = fileModelNode.children[j].lineNumber - 1;
				if(lnumber === i){
					startNumber = j;
					for(k = 0; k < fileModelNode.children[j].matches.length; k++ ){
						if(fileModelNode.children[j+k].checked !== false){
							checkedMatches.push(k);
						}
					}
					checked = (checkedMatches.length > 0);
					originalMatches = fileModelNode.children[j].matches; 
					changingLine = true;
					break;
				}
			}
			if(changingLine){
				var newStr;
				if(!checked){
					newStr = lineStringOrigin;
					for(k = 0; k < fileModelNode.children[startNumber].matches.length; k++ ){
						fileModelNode.children[startNumber+k].newMatches = fileModelNode.children[startNumber+k].matches;
					}
				} else{
					var result =  searchUtils.replaceCheckedMatches(lineStringOrigin, replaceStr, originalMatches, checkedMatches, searchStrLength);
					newStr = result.replacedStr;
					for(k = 0; k < fileModelNode.children[startNumber].matches.length; k++ ){
						fileModelNode.children[startNumber+k].newMatches = result.newMatches;
					}
				}
				if(updating){
					newContents.contents[i] = newStr;
				} else {
					newContents.contents.push(newStr);
				}
			} else if(!updating){
				newContents.contents.push(lineStringOrigin);
			}
		}
	}
};

searchUtils.generateMatchContext = function(contextAroundLength, fileContents, lineNumber/*zero based*/){
	var context = [];
	var totalContextLength = contextAroundLength*2 + 1;
	var startFrom, endTo;
	if(fileContents.length <= totalContextLength){
		startFrom = 0;
		endTo = fileContents.length -1;
	} else {
		startFrom = lineNumber - contextAroundLength;
		if(startFrom < 0){
			startFrom = 0;
			endTo = startFrom + totalContextLength - 1;
		} else {
			endTo = lineNumber + contextAroundLength;
			if(endTo > (fileContents.length -1)){
				endTo = fileContents.length -1;
				startFrom = endTo - totalContextLength + 1;
			}
			
		}
	}
	for(var i = startFrom; i <= endTo; i++){
		context.push({context: fileContents[i], current: (i === lineNumber)});
	}
	return context;
};

/**
 * Split file contents into lines. It also handles the mixed line endings with "\n", "\r" and "\r\n".
 *
 * @param {String} text The file contents.
 * @returns {String[]} Split file lines. 
 * @name orion.searchUtils.splitFile
 * @function
 */
searchUtils.splitFile = function(text) {
	var cr = 0, lf = 0, index = 0, start = 0;
	var splitLines = [];
	while (true) {
		if (cr !== -1 && cr <= index) { 
			cr = text.indexOf("\r", index);  //$NON-NLS-0$
		}
		if (lf !== -1 && lf <= index) { 
			lf = text.indexOf("\n", index);  //$NON-NLS-0$
		}
		if (lf === -1 && cr === -1) {
			splitLines.push(text.substring(start));
			break; 
		}
		var offset = 1;
		if (cr !== -1 && lf !== -1) {
			if (cr + 1 === lf) {
				offset = 2;
				index = lf + 1;
			} else {
				index = (cr < lf ? cr : lf) + 1;
			}
		} else if (cr !== -1) {
			index = cr + 1;
		} else {
			index = lf + 1;
		}
		splitLines.push(text.substring(start, index - offset));
		start = index;
	}
	return splitLines;
};

searchUtils.searchWithinFile = function( inFileQuery, fileModelNode, fileContentText, replacing, caseSensitive, noContext){
	var textModel;
	if(noContext) {
		textModel = new mTextModel.TextModel(fileContentText);
	}
	var fileContents = searchUtils.splitFile(fileContentText);
	if(replacing || noContext){
		fileModelNode.contents = fileContents;
	}
	if(fileModelNode){
		fileModelNode.children = [];
		var totalMatches = 0;
		for(var i = 0; i < fileContents.length ; i++){
			var lineStringOrigin = fileContents[i];
			if(lineStringOrigin && lineStringOrigin.length > 0){
				var lineString = caseSensitive ? lineStringOrigin : lineStringOrigin.toLowerCase();
				var result;
				if(inFileQuery.wildCard){
					result = searchUtils.searchOnelineRegEx(inFileQuery, lineString, false, textModel, i);
				} else {
					result = searchUtils.searchOnelineLiteral(inFileQuery, lineString, false, textModel, i);
				}
				if(result){
					var detailNode, lineNumber = i+1;
					if(!replacing){
						if(noContext) {
							detailNode = {lineNumber: lineNumber, matches: result, name: lineStringOrigin};
						} else {
							detailNode = {parent: fileModelNode, context: searchUtils.generateMatchContext(2, fileContents, i), checked: fileModelNode.checked, 
											  type: "detail", matches: result, lineNumber: lineNumber, name: lineStringOrigin, //$NON-NLS-0$ 
											  location: fileModelNode.location}; //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
						}
						fileModelNode.children.push(detailNode);
					} else {
						for(var j = 0; j < result.length; j++){
							var matchNumber = j+1;
							detailNode = {parent: fileModelNode, checked: fileModelNode.checked, type: "detail", matches: result, lineNumber: lineNumber, matchNumber: matchNumber, name: lineStringOrigin, location: fileModelNode.location}; //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
							fileModelNode.children.push(detailNode);
						}
					}
					totalMatches += result.length;
				}
			}
		}
		fileModelNode.totalMatches = totalMatches;
	}
};

searchUtils.replaceCheckedMatches = function(text, replacingStr, originalMatches, checkedMatches, defaultMatchLength){
	var gap = defaultMatchLength;
	var startIndex = 0;
	var replacedStr = "";
	var newMatches = [];
	for(var i = 0; i < originalMatches.length; i++){
		if(startIndex !== originalMatches[i].startIndex){
			replacedStr = replacedStr + text.substring(startIndex, originalMatches[i].startIndex);
		}
		if(originalMatches[i].length){
			gap = originalMatches[i].length;
		}
		var needReplace = false;
		for (var j = 0; j < checkedMatches.length; j++){
			if(checkedMatches[j] === i){
				needReplace = true;
				break;
			}
		}
		if(needReplace){
			newMatches.push({startIndex: replacedStr.length, length: replacingStr.length});
			replacedStr = replacedStr + replacingStr;
		} else {
			newMatches.push({startIndex: replacedStr.length, length: gap});
			replacedStr = replacedStr + text.substring(originalMatches[i].startIndex, originalMatches[i].startIndex + gap);
		}
		startIndex = originalMatches[i].startIndex + gap;
	}
	if(startIndex < text.length){
		replacedStr = replacedStr + text.substring(startIndex);
	}
	return {replacedStr: replacedStr, newMatches: newMatches};
};

searchUtils.fullPathNameByMeta = function(parents){
	var parentIndex = parents.length;
	var fullPath = "";
	//add parents chain top down if needed
	if(parentIndex > 0){
		for(var j = parentIndex - 1; j > -1; j--){
			var separator = (fullPath === "") ? "" : "/"; //$NON-NLS-1$ //$NON-NLS-0$
			fullPath = fullPath + separator + parents[j].Name;
		}
	}
	return fullPath;
};

return searchUtils;
});

/*******************************************************************************
 * @license
 * Copyright (c) 2011, 2015 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
/*global console prompt */

define('orion/editorCommands',[
	'i18n!orion/edit/nls/messages',
	'orion/i18nUtil',
	'orion/webui/littlelib',
	'orion/widgets/input/DropDownMenu',
	'orion/Deferred',
	'orion/URITemplate',
	'orion/commands',
	'orion/keyBinding',
	'orion/commandRegistry',
	'orion/extensionCommands',
	'orion/contentTypes',
	'orion/searchUtils',
	'orion/objects',
	'orion/PageUtil',
	'orion/PageLinks',
	'orion/editor/annotations',
	'orion/regex',
	'orion/uiUtils',
	'orion/util'
], function(messages, i18nUtil, lib, DropDownMenu, Deferred, URITemplate, mCommands, mKeyBinding, mCommandRegistry, mExtensionCommands, mContentTypes, mSearchUtils, objects, mPageUtil, PageLinks, mAnnotations, regex, mUIUtils, util) {

	var exports = {};

	var contentTypesCache = null;

	function createDelegatedUI(options) {
		var uriTemplate = new URITemplate(options.uriTemplate);
		var params = options.params || {};
		params.OrionHome = params.OrionHome || PageLinks.getOrionHome();
		var href = uriTemplate.expand(params);
		var delegatedParent = document.createElement("div"); //$NON-NLS-0$
		var iframe = document.createElement("iframe"); //$NON-NLS-0$
		iframe.id = options.id;
		iframe.name = options.id;
		iframe.type = "text/html"; //$NON-NLS-0$
		iframe.sandbox = "allow-scripts allow-same-origin allow-forms allow-popups"; //$NON-NLS-0$
		iframe.frameborder = options.border !== undefined ? options.border : 1;
		iframe.src = href;
		iframe.className = "delegatedUI"; //$NON-NLS-0$
		if (options.width) {
			delegatedParent.style.width = options.width;
			iframe.style.width = options.width;
		}
		if (options.height) {
			delegatedParent.style.height = options.height;
			iframe.style.height = options.height;
		}
		iframe.style.visibility = 'hidden'; //$NON-NLS-0$
		if (options.parent !== null) {
			(options.parent || window.document.body).appendChild(delegatedParent);
		}
		delegatedParent.appendChild(iframe);
		iframe.style.left = options.left || (window.innerWidth - parseInt(iframe.clientWidth, 10))/2 + "px"; //$NON-NLS-0$
		iframe.style.top = options.top || (window.innerHeight - parseInt(iframe.clientHeight, 10))/2 + "px"; //$NON-NLS-0$
		iframe.style.visibility = '';
		// Listen for notification from the iframe.  We expect either a "result" or a "cancelled" property.
		window.addEventListener("message", function _messageHandler(event) { //$NON-NLS-0$
			if (event.source !== iframe.contentWindow) {
				return;
			}
			if (typeof event.data === "string") { //$NON-NLS-0$
				var data = JSON.parse(event.data);
				if (data.pageService === "orion.page.delegatedUI" && data.source === options.id) { //$NON-NLS-0$
					if (data.cancelled) {
						// console.log("Delegated UI Cancelled");
						if (options.cancelled) {
							options.cancelled();
						}
					} else if (data.result) {
						if (options.done) {
							options.done(data.result);
						}
					} else if (data.Status || data.status) {
						if (options.status) {
							options.status(data.Status || data.status);
						}
					}
					window.removeEventListener("message", _messageHandler, false); //$NON-NLS-0$
					if (delegatedParent.parentNode) {
						delegatedParent.parentNode.removeChild(delegatedParent);
					}
				}
			}
		}, false);

		return delegatedParent;
	}
	exports.createDelegatedUI = createDelegatedUI;

	/**
	 * Handles a status message from a service by forwarding to the <tt>orion.page.message</tt> service
	 * and stripping HTML.
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry
	 * @param {Object|string} status
	 * @returns {orion.Promise}
	 */
	function handleStatusMessage(serviceRegistry, status) {
		if (status && typeof status.HTML !== "undefined") { //$NON-NLS-0$
			delete status.HTML;
		}
		var statusService = serviceRegistry.getService("orion.page.message"); //$NON-NLS-0$
		if (statusService) {
			return statusService.setProgressResult(status).then(null, function(e) {
				console.log(e);
				throw e; // reject
			});
		} else {
			console.log(status);
			return new Deferred().resolve(status);
		}
	}

	function errorTransformer(error) {
		return {_error: error};
	}

	function EditorCommandFactory (options) {
		this.serviceRegistry = options.serviceRegistry;
		this.commandService = options.commandRegistry;
		this.fileClient = options.fileClient;
		this.inputManager = options.inputManager;
		this.renderToolbars = options.renderToolbars;
		this.toolbarId = options.toolbarId;
		this.saveToolbarId = options.saveToolbarId;
		this.editToolbarId = options.editToolbarId;
		this.pageNavId = options.navToolbarId;
		this.editorContextMenuId = options.editorContextMenuId;
		this.isReadOnly = options.readonly;
		this.textSearcher = options.textSearcher;
		this.searcher = options.searcher;
		this.localSettings = options.localSettings;
		this.editorPreferences = options.editorPreferences;
		this.differ = options.differ;
		this.blamer = options.blamer;
		var that = this;
		this.listener = {
			onServiceAdded: function(event) {
				that._onServiceAdded(event.serviceReference);
			},
			onServiceRemoved: function(event) {
				that._onServiceRemoved(event.serviceReference);
			}
		};
		this.serviceRegistry.addEventListener("registered", this.listener.onServiceAdded); //$NON-NLS-0$
		this.serviceRegistry.addEventListener("unregistering", this.listener.onServiceRemoved); //$NON-NLS-0$
	}
	EditorCommandFactory.prototype = {
		/**
		 * Creates the common text editing commands. Also generates commands for any installed plug-ins that
		 * contribute editor actions.
		 */
		createCommands: function() {
			this._createSettingsCommand();
			this._createGotoLineCommnand();
			this._createFindCommnand();
			this._createBlameCommand();
			this._createDiffCommand();
			this._createShowTooltipCommand();
			this._createUndoStackCommands();
			this._createClipboardCommands();
			this._createSaveCommand();
			return this._createEditCommands();
		},
		//TODO: We need a better way invoke side bar action 
		setSideBar: function(sideBar) {
			this.sideBar = sideBar;
		},
		getEditCommands: function() {
			var commands = [];
			var commandRegistry = this.commandService;
			for (var commandId in commandRegistry._commandList) {
				var command = commandRegistry._commandList[commandId];
				if (command.editInfo) {
					commands.push(command);
				}
			}
			return commands;
		},
		updateCommands: function(target) {
			target = target || {};
			this.editor = target.editor;
			this.inputManager = target.inputManager;
			this.localSettings = target.localSettings;
			this.differ = target.differ;
			this.blamer = target.blamer;
			this.textSearcher = target.textSearcher;
			
			if (this._recreateEditCommands) {
				this._createEditCommands().then(function() {
					this.registerCommands();
					this.registerContextMenuCommands();
					if (this.renderToolbars) {
						this.renderToolbars();
					}
				}.bind(this));
			}
		},
		_registerCommandGroups: function(scopeId, rootPath) {
			var commandRegistry = this.commandService;
			var serviceRegistry = this.serviceRegistry, parentPath;
			var groupReferences = serviceRegistry.getServiceReferences("orion.edit.command.category"); //$NON-NLS-0$
			groupReferences.forEach(function(serviceReference) {
				var groupInfo = {};
				var propertyNames = serviceReference.getPropertyKeys();
				for (var j = 0; j < propertyNames.length; j++) {
					groupInfo[propertyNames[j]] = serviceReference.getProperty(propertyNames[j]);
				}
				parentPath = rootPath;
				if (groupInfo.parentPath) {
					parentPath += "/" + groupInfo.parentPath;
				}
				commandRegistry.addCommandGroup(scopeId, groupInfo.id, groupInfo.position || 101, groupInfo.name, parentPath, null, groupInfo.imageClass);
			}.bind(this));			
		},
		registerCommands: function() {
			var commandRegistry = this.commandService;
			
			commandRegistry.registerCommandContribution("settingsActions", "orion.edit.settings", 1, null, false, new mKeyBinding.KeyBinding("s", true, true), null, this); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			commandRegistry.registerCommandContribution(this.editToolbarId || this.toolbarId, "orion.edit.undo", 400, this.editToolbarId ? "orion.menuBarEditGroup/orion.edit.undoGroup" : null, !this.editToolbarId, new mKeyBinding.KeyBinding('z', true), null, this); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-3$
			commandRegistry.registerCommandContribution(this.editToolbarId || this.toolbarId, "orion.edit.redo", 401, this.editToolbarId ? "orion.menuBarEditGroup/orion.edit.undoGroup" : null, !this.editToolbarId, util.isMac ? new mKeyBinding.KeyBinding('z', true, true) : new mKeyBinding.KeyBinding('y', true), null, this); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-4$
			commandRegistry.registerCommandContribution(this.saveToolbarId || this.toolbarId, "orion.openResource", 1, this.saveToolbarId ? "orion.menuBarFileGroup/orion.edit.saveGroup" : null, false, new mKeyBinding.KeyBinding('f', true, true)); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			commandRegistry.registerCommandContribution(this.saveToolbarId || this.toolbarId, "orion.edit.save", 2, this.saveToolbarId ? "orion.menuBarFileGroup/orion.edit.saveGroup" : null, false, new mKeyBinding.KeyBinding('s', true), null, this); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-3$
			commandRegistry.registerCommandContribution(this.editToolbarId || this.pageNavId, "orion.edit.gotoLine", 3, this.editToolbarId ? "orion.menuBarEditGroup/orion.findGroup" : null, !this.editToolbarId, new mKeyBinding.KeyBinding('l', !util.isMac, false, false, util.isMac), new mCommandRegistry.URLBinding("gotoLine", "line"), this); //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-5$
			commandRegistry.registerCommandContribution(this.editToolbarId || this.pageNavId, "orion.edit.find", 0, this.editToolbarId ? "orion.menuBarEditGroup/orion.findGroup" : null, !this.editToolbarId, new mKeyBinding.KeyBinding('f', true), new mCommandRegistry.URLBinding("find", "find"), this); //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-5$
			commandRegistry.registerCommandContribution(this.toolbarId, "orion.keyAssist", 0, "orion.menuBarToolsGroup", false, new mKeyBinding.KeyBinding(191, false, true, true)); //$NON-NLS-1$ //$NON-NLS-0$ //$NON-NLS-2$
			commandRegistry.registerCommandContribution(this.toolbarId , "orion.edit.showTooltip", 1, "orion.menuBarToolsGroup", false, new mKeyBinding.KeyBinding(113), null, this);//$NON-NLS-1$ //$NON-NLS-2$ 
			commandRegistry.registerCommandContribution(this.toolbarId , "orion.edit.blame", 2, "orion.menuBarToolsGroup", false, new mKeyBinding.KeyBinding('b', true, true), new mCommandRegistry.URLBinding("blame", "blame"), this); //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-5$
			commandRegistry.registerCommandContribution(this.toolbarId , "orion.edit.diff", 3, "orion.menuBarToolsGroup", false, new mKeyBinding.KeyBinding('d', true, true), new mCommandRegistry.URLBinding("diff", "diff"), this); //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-5$
			
			this._registerCommandGroups(this.toolbarId, "orion.menuBarToolsGroup"); //$NON-NLS-1$

			// KB exists so that we can pass an array (from info.key) rather than actual arguments
			function createKeyBinding(args) {
				if (!args) { return null; }
				var keyBinding = new mKeyBinding.KeyBinding();
				mKeyBinding.KeyBinding.apply(keyBinding, args);
				return keyBinding;
			}
			var commands = this.getEditCommands();
			for (var i = 0, position = 100; i < commands.length; i++, position++) {
				var command = commands[i], info = command.editInfo;
				
				// Handle quick fixes
				if (info.scopeId) {
					commandRegistry.registerCommandContribution(info.scopeId, command.id, position, info.scopeId + "Group", info.bindingOnly, createKeyBinding(info.key), null, this); //$NON-NLS-0$
				} else {
					var parentPath = "orion.menuBarToolsGroup"; //$NON-NLS-1$
					if (info.parentPath) {
						parentPath += "/" + info.parentPath;
					}
					commandRegistry.registerCommandContribution(this.toolbarId, command.id, position, parentPath, info.bindingOnly, createKeyBinding(info.key), null, this);
				}
			}
		},
		registerContextMenuCommands: function() {
			var commandRegistry = this.commandService;
			// main context menu
			commandRegistry.addCommandGroup(this.editorContextMenuId, "orion.editorContextMenuGroup", 100, null, null, null, null, null, "dropdownSelection"); //$NON-NLS-1$ //$NON-NLS-2$
			
			var index = 1;
			//TODO - non-nls is wrong, check accelerator conflicts etc.
			commandRegistry.registerCommandContribution(this.editorContextMenuId, "orion.edit.copy", index++, "orion.editorContextMenuGroup/orion.edit.copyGroup", false, new mKeyBinding.KeyBinding('c', true)); //$NON-NLS-1$ //$NON-NLS-2$
			commandRegistry.registerCommandContribution(this.editorContextMenuId, "orion.edit.cut", index++, "orion.editorContextMenuGroup/orion.edit.copyGroup", false, new mKeyBinding.KeyBinding('x', true)); //$NON-NLS-1$ //$NON-NLS-2$
			commandRegistry.registerCommandContribution(this.editorContextMenuId, "orion.edit.paste", index++, "orion.editorContextMenuGroup/orion.edit.copyGroup", false, new mKeyBinding.KeyBinding('v', true)); //$NON-NLS-1$ //$NON-NLS-2$
			commandRegistry.registerCommandContribution(this.editorContextMenuId, "orion.edit.undo", index++, "orion.editorContextMenuGroup/orion.edit.undoGroup", false); //$NON-NLS-1$ //$NON-NLS-2$
			commandRegistry.registerCommandContribution(this.editorContextMenuId, "orion.edit.redo", index++, "orion.editorContextMenuGroup/orion.edit.undoGroup", false); //$NON-NLS-1$ //$NON-NLS-2$
			commandRegistry.registerCommandContribution(this.editorContextMenuId, "orion.edit.find", index++,"orion.editorContextMenuGroup/orion.findGroup", false); //$NON-NLS-1$ //$NON-NLS-2$
			commandRegistry.registerCommandContribution(this.editorContextMenuId, "orion.edit.gotoLine", index++, "orion.editorContextMenuGroup/orion.findGroup", false); //$NON-NLS-1$ //$NON-NLS-2$
			commandRegistry.registerCommandContribution(this.editorContextMenuId, "orion.quickSearch", index++, "orion.editorContextMenuGroup/orion.findGroup", false); //$NON-NLS-1$ //$NON-NLS-2$
			commandRegistry.registerCommandContribution(this.editorContextMenuId, "orion.openSearch", index++, "orion.editorContextMenuGroup/orion.findGroup", false); //$NON-NLS-1$ //$NON-NLS-2$

			// 'Tools' cascade
			commandRegistry.addCommandGroup(this.editorContextMenuId, "orion.editorContextMenuToolsGroup", 400, messages["Tools"], "orion.editorContextMenuGroup"); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			commandRegistry.registerCommandContribution(this.editorContextMenuId , "orion.edit.blame", 1, "orion.editorContextMenuGroup/orion.editorContextMenuToolsGroup", false); //$NON-NLS-1$ //$NON-NLS-2$
			commandRegistry.registerCommandContribution(this.editorContextMenuId , "orion.edit.diff", 2, "orion.editorContextMenuGroup/orion.editorContextMenuToolsGroup", false); //$NON-NLS-1$ //$NON-NLS-2$

			this._registerCommandGroups(this.editorContextMenuId, "orion.editorContextMenuGroup/orion.editorContextMenuToolsGroup"); //$NON-NLS-1$

			// Register extra tools commands
			var commands = this.getEditCommands();
			for (var i = 0, position = 100; i < commands.length; i++, position++) {
				var command = commands[i], info = command.editInfo;
				var parentPath = "orion.editorContextMenuGroup/orion.editorContextMenuToolsGroup"; //$NON-NLS-1$
				if (info.parentPath) {
					parentPath += "/" + info.parentPath;
				}
				this.commandService.registerCommandContribution(this.editorContextMenuId, command.id, position, parentPath);
			}
		},
		overwriteKeyBindings: function(editor) {
			var that = this;
			this.editor = editor;
			if (editor.getTextView && editor.getTextView()) {
				var textView = editor.getTextView();
				textView.setKeyBinding(new mKeyBinding.KeyBinding('s', true), "save"); //$NON-NLS-1$ //$NON-NLS-2$
				var saveCommand = that.commandService.findCommand("orion.edit.save"); //$NON-NLS-0$
				textView.setAction("save", function() { //$NON-NLS-0$
					saveCommand.callback.call({inputManager: that.inputManager});
					return true;
				}, saveCommand); //$NON-NLS-0$
			
				textView.setAction("gotoLine", function (data) { //$NON-NLS-0$
					if (data) {
						editor.onGotoLine(data.line - 1, 0, undefined, data.callback);
						return true;
					}
					that.commandService.runCommand("orion.edit.gotoLine"); //$NON-NLS-0$
					return true;
				}, that.commandService.findCommand("orion.edit.gotoLine")); //$NON-NLS-0$
				
				if(this.textSearcher) {
					textView.setAction("find", function (data) { //$NON-NLS-0$
						if (data) {
							that.textSearcher.show(data);
							return true;
						}
						that.commandService.runCommand("orion.edit.find", null, null, new mCommandRegistry.ParametersDescription( //$NON-NLS-0$
							[new mCommandRegistry.CommandParameter('useEditorSelection', 'text', '', "true")], //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-3$
							{clientCollect: true}));
						return true;
					}, that.commandService.findCommand("orion.edit.find")); //$NON-NLS-0$
				}
				
				var keyAssistCommand = that.commandService.findCommand("orion.keyAssist"); //$NON-NLS-0$
				if (keyAssistCommand) {
					textView.setKeyBinding(new mKeyBinding.KeyStroke(191, false, true, !util.isMac, util.isMac), keyAssistCommand.id);
					textView.setAction(keyAssistCommand.id, keyAssistCommand.callback, keyAssistCommand);
				}
				
				// Support future key binding changes
				this.bindingChangeListener = function(args) {
					this._handleBindingChanges(textView, args);
				}.bind(this);
				this.commandService.addEventListener("bindingChanged", this.bindingChangeListener);
			}
		},
		_handleBindingChanges: function(textView, args) {
			// Only execute if this is a textView action
			var actions = textView.getActions(true);
			if (actions.indexOf(args.id) !== -1) {
				if (args.prevBinding) {
					textView.setKeyBinding(args.prevBinding, null);
				}
				textView.setKeyBinding(args.newBinding, args.id);
			}
		},
		showKeyBindings: function(keyAssist) {
			var editor = this.editor;
			if (editor && editor.getTextView && editor.getTextView()) {
				var textView = editor.getTextView();
				// Remove actions without descriptions
				var editorActions = textView.getActions(true).filter(function (element) {
					var desc = textView.getActionDescription(element);
					return desc && desc.name;
				});
				editorActions.sort(function (a, b) {
					return textView.getActionDescription(a).name.localeCompare(textView.getActionDescription(b).name);
				});
				keyAssist.createHeader(messages["Editor"]);
				var execute = function (actionID) {
					return function () {
						textView.focus();
						return textView.invokeAction(actionID);
					};
				};
				var scopes = {}, binding;
				for (var i = 0; i < editorActions.length; i++) {
					var actionID = editorActions[i];
					var actionDescription = textView.getActionDescription(actionID);
					var bindings = textView.getKeyBindings(actionID);
					if (bindings.length > 0) {
						for (var j = 0; j < bindings.length; j++) {
							binding = bindings[j];
							var bindingString = mUIUtils.getUserKeyString(binding);
							if (binding.scopeName) {
								if (!scopes[binding.scopeName]) {
									scopes[binding.scopeName] = [];
								}
								scopes[binding.scopeName].push({bindingString: bindingString, name: actionDescription.name, execute: execute(actionID)});
							} else {
								keyAssist.createItem(binding, actionDescription.name, actionID, execute(actionID));
							}
						}
					} else {
						keyAssist.createItem(null, actionDescription.name, actionID, execute(actionID)); //$NON-NLS-1$
					}
				}
				for (var scopedBinding in scopes) {
					if (scopes[scopedBinding].length) {
						keyAssist.createHeader(scopedBinding);
						for (var k = 0; k < scopes[scopedBinding].length; k++) {
							binding = scopes[scopedBinding][k];
							keyAssist.createItem(binding, binding.name, binding.name, binding.execute);
						}
					}
				}
			}
		},
		_createSettingsCommand: function() {
			var that = this;
			var settingsCommand = new mCommands.Command({
				imageClass: "core-sprite-wrench", //$NON-NLS-0$
				tooltip: messages.LocalEditorSettings,
				id: "orion.edit.settings", //$NON-NLS-0$
				visibleWhen: /** @callback */ function(items, data) {
					var editor = data.handler.editor || that.editor;
					return editor && editor.installed && data.handler.localSettings;
				},
				callback: function(data) {
					var localSettings = this.localSettings || that.localSettings;
					var dropDown = settingsCommand.settingsDropDown;
					if (!dropDown || dropDown.isDestroyed()) {
						dropDown = settingsCommand.settingsDropDown = new DropDownMenu(data.domNode.parentNode, data.domNode, {
							noClick: true,
							selectionClass: 'dropdownSelection', //$NON-NLS-0$
							onShow: function() {
								dropDown.focus();
							},
							onHide: function() {
								that.editor.focus();
							}
						});
						dropDown.updateContent = localSettings.show.bind(localSettings);
						var menu = dropDown.getContentNode();
						menu.tabIndex = menu.style.marginTop = 0;
					}
					dropDown.click();
				}
			});
			this.commandService.addCommand(settingsCommand);
		},
		_createClipboardCommands: function() {
			
			//TODO - test to see whether copy/cut/paste is supported instead of IE
			if (util.isIE) {
				var that = this;
				
				var copyCommand = new mCommands.Command({
					name: messages.Copy,
					id: "orion.edit.copy", //$NON-NLS-0$
					visibleWhen: /** @callback */ function(items, data) {
						var editor = data.handler.editor || that.editor;
						return editor && editor.installed;
					},
					callback: function() {
						var editor = this.editor || that.editor;
						if (editor && editor.getTextView && editor.getTextView()) {
							var textView = editor.getTextView();
							textView.copy();
							editor.focus();
						}
					}
				});
				this.commandService.addCommand(copyCommand);
				
				var cutCommand = new mCommands.Command({
					name: messages.Cut,
					id: "orion.edit.cut", //$NON-NLS-0$
					visibleWhen: /** @callback */ function(items, data) {
						var editor = data.handler.editor || that.editor;
						return editor && editor.installed;
					},
					callback: function() {
						var editor = this.editor || that.editor;
						if (editor && editor.getTextView && editor.getTextView()) {
							var textView = editor.getTextView();
							textView.cut();
							editor.focus();
						}
					}
				});
				this.commandService.addCommand(cutCommand);
				
				var pasteCommand = new mCommands.Command({
					name: messages.Paste,
					id: "orion.edit.paste", //$NON-NLS-0$
					visibleWhen: /** @callback */ function(items, data) {
						var editor = data.handler.editor || that.editor;
						return editor && editor.installed;
					},
					callback: function() {
						var editor = this.editor || that.editor;
						if (editor && editor.getTextView && editor.getTextView()) {
							var textView = editor.getTextView();
							textView.paste();
							editor.focus();
						}
					}
				});
				this.commandService.addCommand(pasteCommand);
			}
		},
		_createUndoStackCommands: function() {
			var that = this;
			var undoCommand = new mCommands.Command({
				name: messages.Undo,
				id: "orion.edit.undo", //$NON-NLS-0$
				visibleWhen: /** @callback */ function(items, data) {
					var editor = data.handler.editor || that.editor;
					return editor && editor.installed;
				},
				callback: function() {
					var editor = this.editor || that.editor;
					editor.getUndoStack().undo();
				}
			});
			this.commandService.addCommand(undoCommand);

			var redoCommand = new mCommands.Command({
				name: messages.Redo,
				id: "orion.edit.redo", //$NON-NLS-0$
				visibleWhen: /** @callback */ function(items, data) {
					var editor = data.handler.editor || that.editor;
					return editor && editor.installed;
				},
				callback: function() {
					var editor = this.editor || that.editor;
					editor.getUndoStack().redo();
				}
			});
			this.commandService.addCommand(redoCommand);
		},
		_createSaveCommand: function() {
			var that = this;
			var saveCommand = new mCommands.Command({
				name: messages.Save,
				tooltip: messages.saveFile,
				imageClass : "core-sprite-save", //$NON-NLS-0$
				id: "orion.edit.save", //$NON-NLS-0$
				visibleWhen: /** @callback */ function(items, data) {
					var inputManager = data.handler.inputManager || that.inputManager;
					var editor = data.handler.editor || that.editor || (inputManager && inputManager.getEditor());
					if (!editor || !editor.installed || !inputManager || !inputManager.isSaveEnabled()) {
						return false;
					}
					return true;
				},
				callback: function() {
					var inputManager = this.inputManager || that.inputManager;
					inputManager.save();
				}
			});
			this.commandService.addCommand(saveCommand);
		},
		_createGotoLineCommnand: function() {
			var that = this;

			// page navigation commands (go to line)
			var lineParameter = new mCommandRegistry.ParametersDescription(
				[new mCommandRegistry.CommandParameter('line', 'number', messages.gotoLinePrompt)], //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				{hasOptionalParameters: false},
				function(data) {
					var editor = data.handler.editor || that.editor;
					var line = editor.getModel().getLineAtOffset(editor.getCaretOffset()) + 1;
					return [new mCommandRegistry.CommandParameter('line', 'number', messages.gotoLinePrompt, line.toString())]; //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				}
			);

			var gotoLineCommand =  new mCommands.Command({
				name: messages.gotoLine,
				tooltip: messages.gotoLineTooltip,
				id: "orion.edit.gotoLine", //$NON-NLS-0$
				visibleWhen: /** @callback */ function(items, data) {
					var editor = data.handler.editor || that.editor;
					return editor && editor.installed;
				},
				parameters: lineParameter,
				callback: function(data) {
					var line;
					var editor = this.editor || that.editor;
					var model = editor.getModel();
					if (data.parameters && data.parameters.valueFor('line')) { //$NON-NLS-0$
						line = data.parameters.valueFor('line'); //$NON-NLS-0$
					} else {
						line = model.getLineAtOffset(editor.getCaretOffset());
						line = prompt(messages.gotoLinePrompt, line + 1);
						if (line) {
							line = parseInt(line, 10);
						}
					}
					if (line) {
						editor.onGotoLine(line - 1, 0);
					}
				}
			});
			this.commandService.addCommand(gotoLineCommand);
		},
		_createFindCommnand: function() {
			var that = this;

			// find&&replace commands (find)
			var findParameter = new mCommandRegistry.ParametersDescription(
				[new mCommandRegistry.CommandParameter('find', 'text', 'Find:')], //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				{clientCollect: true},
				function(data) {
					var editor = data.handler.editor || that.editor;
					var textSearcher = data.handler.textSearcher || that.textSearcher;
					var selection = editor.getSelection();
					var searchString = "";
					if (selection.end > selection.start) {
						var model = editor.getModel();
						searchString = model.getText(selection.start, selection.end);
						if (textSearcher && textSearcher.getOptions().regex) {
							searchString = regex.escape(searchString);
						}
					}
					return [new mCommandRegistry.CommandParameter('find', 'text', 'Find:', searchString)]; //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				}
			);
			var findCommand =  new mCommands.Command({
				name: messages.Find,
				tooltip: messages.Find,
				id: "orion.edit.find", //$NON-NLS-0$
				visibleWhen: /** @callback */ function(items, data) {
					var editor = data.handler.editor || that.editor;
					var textSearcher = data.handler.textSearcher || that.textSearcher;
					return editor && editor.installed && textSearcher;
				},
				parameters: findParameter,
				callback: function(data) {
					var node = lib.node("replaceCompareDiv"); //$NON-NLS-0$
					if (node && node.classList.contains("replaceCompareDivVisible")) { //$NON-NLS-0$
						return false; //TODO is there a better way of preventing the command from being executed?
					}
					var editor = this.editor || that.editor;
					var textSearcher = this.textSearcher || that.textSearcher;
					if (findCommand.textSearcher && findCommand.textSearcher !== textSearcher) {
						findCommand.textSearcher.hide();
					}
					findCommand.textSearcher = textSearcher;
					var searchString = "";
					var parsedParam = null;
					var selection = editor.getSelection();
					if (selection.end > selection.start && data.parameters.valueFor('useEditorSelection')) {//$NON-NLS-0$ If there is selection from editor, we want to use it as the default keyword
						var model = editor.getModel();
						searchString = model.getText(selection.start, selection.end);
						if (textSearcher.getOptions().regex) {
							searchString = regex.escape(searchString);
						}
					} else {//If there is no selection from editor, we want to parse the parameter from URL binding
						if (data.parameters && data.parameters.valueFor('find')) { //$NON-NLS-0$
							searchString = data.parameters.valueFor('find'); //$NON-NLS-0$
							parsedParam = mPageUtil.matchResourceParameters();
							mSearchUtils.convertFindURLBinding(parsedParam);
						}
					}
					if(parsedParam){
						textSearcher.setOptions({regex: parsedParam.regEx, caseInsensitive: !parsedParam.caseSensitive});
						var tempOptions = {};
						if(parsedParam.atLine){
							tempOptions.start = editor.getModel().getLineStart(parsedParam.atLine-1);
						}
						textSearcher.show({findString: searchString, replaceString: parsedParam.replaceWith});
						textSearcher.find(true, tempOptions);
						that.commandService.closeParameterCollector(); //TODO is there a better way of hiding the parameter collector?
					} else {
						textSearcher.show({findString: searchString});
					}
				}
			});
			this.commandService.addCommand(findCommand);
		},

		_createBlameCommand: function(){
			var that = this;
			var blameCommand = new mCommands.Command({
				name: messages.Blame,
				tooltip: messages.BlameTooltip,
				id: "orion.edit.blame", //$NON-NLS-0$
				parameters: new mCommandRegistry.ParametersDescription([new mCommandRegistry.CommandParameter('blame', 'boolean')], {clientCollect: true}), //$NON-NLS-1$ //$NON-NLS-0$
				visibleWhen: /** @callback */ function(items, data) {
					var editor = data.handler.editor || that.editor;
					var blamer = data.handler.blamer || that.blamer;
					return editor && editor.installed && blamer && blamer.isVisible();
				},
				callback: function(data) {
					var visible = false;
					var editor = this.editor || that.editor;
					var blamer = this.blamer || that.blamer;
					var annotations = editor.getAnnotationModel().getAnnotations();
					while (annotations.hasNext()) {
						var annotation = annotations.next();
						if (annotation.type === mAnnotations.AnnotationType.ANNOTATION_BLAME) {
							visible = true;
							break;
						}
					}
					visible = !visible;
					if (data.parameters && data.parameters.valueFor('blame')) { //$NON-NLS-0$
						visible = data.parameters.valueFor('blame') === "true"; //$NON-NLS-1$ //$NON-NLS-0$
					}
					if (visible) {
						blamer.doBlame();
					} else{
						editor.showBlame([]);
					}
					editor.focus();
				}
			});
			this.commandService.addCommand(blameCommand);
		},

		_createDiffCommand: function(){
			var that = this;
			var diffCommand = new mCommands.Command({
				name: messages.Diff,
				tooltip: messages.DiffTooltip,
				id: "orion.edit.diff", //$NON-NLS-0$
				visibleWhen: /** @callback */ function(items, data) {
					var editor = data.handler.editor || that.editor;
					var differ = data.handler.differ || that.differ;
					return editor && editor.installed && differ && differ.isVisible();
				},
				callback: function() {
					var editor = this.editor || that.editor;
					var differ = this.differ || that.differ;
					differ.toggleEnabled();
					var editorPreferences = this.editorPreferences;
					editorPreferences.getPrefs(function(pref){
						pref.diffService = differ.isEnabled();
						editorPreferences.setPrefs(pref);
					});
					editor.focus();
				}
			});
			this.commandService.addCommand(diffCommand);
		},

		_createShowTooltipCommand: function(){
			var that = this;
			var showTooltipCommand = new mCommands.Command({
				name: messages.showTooltip,
				tooltip: messages.showTooltipTooltip,
				id: "orion.edit.showTooltip", //$NON-NLS-0$
				visibleWhen: /** @callback */ function(items, data) {
					var editor = data.handler.editor || that.editor;
					return editor && editor.installed;
				},
				callback: function() {
					var editor = this.editor || that.editor;
					var tooltip = editor.getTooltip();
					var tv = editor.getTextView();
					var offset = tv.getCaretOffset();
					var pos = tv.getLocationAtOffset(offset);
					tooltip.show({
						x: pos.x,
						y: pos.y,
						getTooltipInfo: function() {
							return editor._getTooltipInfo(this.x, this.y);
						}
					}, false, true);
				}
			});
			this.commandService.addCommand(showTooltipCommand);
		},
		_onServiceRemoved: function(serviceReference) {
			if (serviceReference.getProperty("objectClass").indexOf("orion.edit.command") !== -1) { //$NON-NLS-1$ //$NON-NLS-2$
				this._recreateEditCommands = true;
			}
		},
		_onServiceAdded: function(serviceReference) {
			if (serviceReference.getProperty("objectClass").indexOf("orion.edit.command") !== -1) { //$NON-NLS-1$ //$NON-NLS-2$
				this._recreateEditCommands = true;
			}
		},
		_createEditCommands: function() {
			var that = this;
			this._recreateEditCommands = false;

			function getContentTypes(serviceRegistry) {
				if (contentTypesCache) {
					return contentTypesCache;
				}
				var contentTypeService = serviceRegistry.getService("orion.core.contentTypeRegistry"); //$NON-NLS-0$
				//TODO Shouldn't really be making service selection decisions at this level. See bug 337740
				if (!contentTypeService) {
					contentTypeService = new mContentTypes.ContentTypeRegistry(serviceRegistry);
					contentTypeService = serviceRegistry.getService("orion.core.contentTypeRegistry"); //$NON-NLS-0$
				}
				return contentTypeService.getContentTypes().then(function(ct) {
					contentTypesCache = ct;
					return contentTypesCache;
				});
			}

			// add the commands generated by plug-ins who implement the "orion.edit.command" extension.
			//
			// Note that the shape of the "orion.edit.command" extension is not in any shape or form that could be considered final.
			// We've included it to enable experimentation. Please provide feedback in the following bug:
			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=337766
			//
			// iterate through the extension points and generate commands for each one.
			var serviceRegistry = this.serviceRegistry;
			var commandRegistry = this.commandService;
			var actionReferences = serviceRegistry.getServiceReferences("orion.edit.command"); //$NON-NLS-0$
			var progress = serviceRegistry.getService("orion.page.progress"); //$NON-NLS-0$
			var handleStatus = handleStatusMessage.bind(null, serviceRegistry);
			var makeCommand = function(info, service, options) {
				var commandVisibleWhen = options.visibleWhen;
				options.visibleWhen = function(items, data) {
					var editor = data.handler.editor || that.editor;
					var inputManager = data.handler.inputManager || that.inputManager;
					if (!editor || !editor.installed || !inputManager) {
						return false;
					}
					if (info.editor && editor.id && info.editor !== editor.id) {
						return false;
					}
					if (that.inputManager.getReadOnly()) {
						return false;
					}
					return !commandVisibleWhen || commandVisibleWhen(items);
				};
				options.callback = function(data) {
					var editor = this.editor || that.editor;
					var inputManager = this.inputManager || that.inputManager;
					//TODO should all text editors have selection?
					var selection = editor.getSelection ? editor.getSelection() : {start: 0, end: 0};
					var model = editor.getModel();

					/*
					 * Processes the result object from old run() API.
					 * @deprecated: command services should implement execute() instead.
					 */
					var processEditorResult = function(result) {
						if (typeof result === "object" && result) { //$NON-NLS-0$
							if (result.text) {
								editor.setText(result.text);
							}
							if (result.selection) {
								if (editor.setSelection) {
									editor.setSelection(result.selection.start, result.selection.end, true /*scroll to*/);
								}
								editor.focus();
							}
						} else if (typeof result === 'string') { //$NON-NLS-0$
							editor.setText(result, selection.start, selection.end, true /*scroll to*/);
							if (editor.setSelection) {
								editor.setSelection(selection.start, selection.start + result.length);
							}
							editor.focus();
						}
					};

					var serviceCall, handleResult;
					if (service.execute) {
						var context = {
							contentType: inputManager.getContentType(),
							input: inputManager.getInput(),
							offset: editor.getCaretOffset()
						};
						
						// TODO: Make this more generic
						if (info.scopeId === "orion.edit.quickfix") { //$NON-NLS-0$
							context.annotation = {
								start: data.userData.start,
								end: data.userData.end,
								title: data.userData.title,
								id: data.userData.id,
								data: data.userData.data
							};
						}
						var editorContext = editor.getEditorContext();
						// Hook up delegated UI and Status handling
						editorContext.openDelegatedUI = function(/*options, ..*/) {
							var options1 = arguments[0];
							options1 = options1 || {};
							options1.done = processEditorResult;
							options1.status = handleStatus;
							options1.params = options1.params || {};
							objects.mixin(options1.params, inputManager.getFileMetadata());
							createDelegatedUI.apply(null, Array.prototype.slice.call(arguments));
						};
						editorContext.setStatus = handleStatus;
						
						editor.focus();
						serviceCall = service.execute(editorContext, context); 
						handleResult = function(result){
							if (result && result.searchParams && result.refResult) {
								if(that.sideBar) {
									that.sideBar.fillSearchPane(result.searchParams.keyword, {Location: result.searchParams.resource}, result);
								}
							}
							var statusService = serviceRegistry.getService("orion.page.message"); //$NON-NLS-0$
							if (statusService) {
								statusService.setProgressMessage("");
							}
						};
					} else {
						serviceCall = service.run(model.getText(selection.start,selection.end), model.getText(), selection, inputManager.getInput());
						handleResult = function(result){
							if (result && result.uriTemplate) {
								var options1 = {};
								options1.uriTemplate = result.uriTemplate;
								options1.params = inputManager.getFileMetadata();
								options1.id = info.id;
								options1.width = result.width;
								options1.height = result.height;
								options1.done = processEditorResult;
								options1.status = handleStatus;
								createDelegatedUI(options1);
							} else if (result && (result.Status || result.status)) {
								handleStatus(result.Status || result.status);
							} else {
								processEditorResult(result);
							}
						};
					}
					progress.showWhile(serviceCall, i18nUtil.formatMessage(messages.running, options.name)).then(handleResult);
					return true;
				};
				return new mCommands.Command(options);
			};
			return Deferred.when(getContentTypes(this.serviceRegistry), function() {
				// Create Commands
				var commandPromises = [];
				actionReferences.forEach(function(serviceReference) {
					var service = that.serviceRegistry.getService(serviceReference);
					var info = {};
					var propertyNames = serviceReference.getPropertyKeys();
					for (var j = 0; j < propertyNames.length; j++) {
						info[propertyNames[j]] = serviceReference.getProperty(propertyNames[j]);
					}
					info.forceSingleItem = true;  // for compatibility with mExtensionCommands._createCommandOptions

					var deferred = mExtensionCommands._createCommandOptions(info, serviceReference, that.serviceRegistry, contentTypesCache, false, /* @callback */ function(items) {
						// items is the editor and we care about the file metadata for validation
						return that.inputManager.getFileMetadata();
					}).then(function(commandOptions){
						var command = makeCommand(info, service, commandOptions);
						command.editInfo = info;
						commandRegistry.addCommand(command);
						return command;
					});
					commandPromises.push(deferred);
				});
				return Deferred.all(commandPromises, errorTransformer);
			});
		}
	};
	exports.EditorCommandFactory = EditorCommandFactory;
	exports.handleStatusMessage = handleStatusMessage;

	return exports;
});

/*******************************************************************************
 * @license
 * Copyright (c) 2014 IBM Corporation and others. All rights reserved. This
 * program and the accompanying materials are made available under the terms of
 * the Eclipse Public License v1.0 (http://www.eclipse.org/legal/epl-v10.html),
 * and the Eclipse Distribution License v1.0
 * (http://www.eclipse.org/org/documents/edl-v10.html).
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/globalCommands',[], function() {
	return {
	};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2011, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/edit/dispatcher',['orion/edit/dispatcher'], function() {
	/**
	 * @name orion.edit.Dispatcher
	 * @class Forwards events from an {@link orion.editor.Editor} to interested services.
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry
	 * @param {orion.core.ContentTypeRegistry}
	 * @param {orion.editor.Editor} editor
	 * @param {orion.editor.InputManger} inputManager
	 */
	function Dispatcher(serviceRegistry, contentTypeRegistry, editor, inputManager) {
		this.serviceRegistry = serviceRegistry;
		this.editor = editor;
		this.inputManager = inputManager;
		this.ctRegistry = contentTypeRegistry;
		this.serviceReferences = {};

		var that = this;
		this.inputManager.addEventListener("InputChanged", function(e) { //$NON-NLS-0$
			that.contentType = e.contentType;
			that.updateListeners();
		}.bind(this));
		this.listener = {
			onServiceAdded: function(event) {
				that._onServiceAdded(event.serviceReference);
			},
			onServiceRemoved: function(event) {
				that._onServiceRemoved(event.serviceReference);
			}
		};
		this.serviceRegistry.addEventListener("registered", this.listener.onServiceAdded); //$NON-NLS-0$
		this.serviceRegistry.addEventListener("unregistering", this.listener.onServiceRemoved); //$NON-NLS-0$
	}
	Dispatcher.prototype = /** @lends orion.edit.Dispatcher.prototype */ {
		updateListeners: function() {
			this._removeAllListeners();

			var serviceRegistry = this.serviceRegistry,
			    serviceRefs = serviceRegistry.getServiceReferences("orion.edit.model"); //$NON-NLS-0$
			for (var i=0; i < serviceRefs.length; i++) {
				this._wireServiceReference(serviceRefs[i]);
			}
		},
		_wireServiceReference: function(serviceRef) {
			var refContentType = serviceRef.getProperty("contentType"); //$NON-NLS-0$
			if (typeof refContentType !== "undefined" && refContentType !== null) { //$NON-NLS-0$
				// See if the registered service is interested in the current ContentType.
				var self = this;
				var inputContentType = this.contentType;
				if (this.ctRegistry.isSomeExtensionOf(inputContentType, refContentType)) {
					self._wireService(serviceRef, self.serviceRegistry.getService(serviceRef));
				}
			}
		},
		_wireService: function(serviceReference, service) {
			var textView = this.editor.getTextView();
			if (!textView)
				throw new Error("TextView not installed");
			var keys = Object.keys(service);
			for (var i=0; i < keys.length; i++) {
				var key = keys[i], method = service[key];
				if (key.substr(0, 2) !== "on" || typeof method !== "function") {//$NON-NLS-1$ //$NON-NLS-0$
					continue;
				}
				var type = key.substr(2);
				this._wireServiceMethod(serviceReference, service, method, textView, type);
			}
		},
		_wireServiceMethod: function(serviceReference, service, serviceMethod, textView, type) {
//			console.log("  Add listener " + type + " for " + serviceReference.getProperty('service.id'));
			var _self = this;
			var listener = function(event) {
				// Inject metadata about the file being edited into the event.
				event.file = _self.getServiceFileObject();
				serviceMethod(event).then(/*No return value*/);
			};
			var serviceId = serviceReference.getProperty('service.id'); //$NON-NLS-0$
			this.serviceReferences[serviceId] = this.serviceReferences[serviceId] || [];
			this.serviceReferences[serviceId].push([textView, type, listener]);
			textView.addEventListener(type, listener);
		},
		_onServiceRemoved: function(serviceReference) {
			var serviceId = serviceReference.getProperty('service.id');
			this._removeListeners(serviceId);
		},
		_onServiceAdded: function(serviceReference) {
			if (serviceReference.getProperty("objectClass").indexOf("orion.edit.model") !== -1) { //$NON-NLS-0$
				this._wireServiceReference(serviceReference);
			}
		},
		_removeListeners: function(serviceId) {
			var serviceReferences = this.serviceReferences[serviceId];
			if (serviceReferences) {
				for (var i=0; i < serviceReferences.length; i++) {
					var listener = serviceReferences[i];
					var textView = listener[0], type = listener[1], func = listener[2];
//					console.log("  Remove listener " + type + " for " + serviceId);
					textView.removeEventListener(type, func);
				}
				delete this.serviceReferences[serviceId];
			}
		},
		_removeAllListeners: function() {
			var keys = Object.keys(this.serviceReferences);
			for (var i=0; i < keys.length; i++) {
				this._removeListeners(keys[i]);
			}
		},
		/**
		 * @since 8.0
		 */
		getServiceFileObject: function() {
			var metadata = this.inputManager.getFileMetadata();
			if (!metadata) {
				return null;
			}
			return Dispatcher.toServiceFileObject(metadata, this.inputManager.getContentType());
		},
	};

	/**
	 * @since 8.0
	 */
	Dispatcher.toServiceFileObject = function(metadata, contentType) {
		var data = Object.create(null);
		data.name = metadata.Name;
		data.location = metadata.Location;
		data.parents = metadata.Parents;

		if (contentType) {
			data.contentType = Object.create(null);
			data.contentType.id = contentType.id;
			data.contentType.name = contentType.name;
			data.contentType.imageClass = contentType.imageClass;
			data.contentType.extension = contentType.extension;
		}
		return data;
	};

	return {Dispatcher: Dispatcher};
});
/******************************************************************************* 
 * @license
 * Copyright (c) 2013, 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation 
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/edit/editorContext',[
], function() {

	/**
	 * @name orion.edit.EditorContext
	 * @class Encapsulates service methods related to an editor.
	 * @classdesc Encapsulates service methods related to an editor.
	 * @description 
	 */

	/**
	 * Gets the editor context service.
	 * @name orion.edit.EditorContext.getEditorContext
	 * @function
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry The service registry to consult.
	 * @returns {orion.edit.EditorContext}
	 */
	function getEditorContext(serviceRegistry, serviceID) {
		var editorContext = Object.create(null);
		getReferences(serviceRegistry, serviceID).forEach(function(serviceRef) { 
			var service = serviceRegistry.getService(serviceRef);
			Object.keys(service).forEach(function(key) {
				if (typeof service[key] === "function") { //$NON-NLS-0$
					editorContext[key] = service[key].bind(service);
				}
			});
		});
		return editorContext;
	}

	/**
	 * Gets the editor context "options" object.
	 * @name orion.edit.EditorContext.getOptions
	 * @function
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry The service registry to consult.
	 * @returns {Object}
	 */
	function getOptions(serviceRegistry, serviceID) {
		var options = Object.create(null);
		getReferences(serviceRegistry, serviceID).forEach(function(serviceRef) {
			serviceRef.getPropertyKeys().forEach(function(key) {
				if (key !== "service.id" && key !== "service.names" && key !== "objectClass") //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
					options[key] = serviceRef.getProperty(key);
			});
		});
		return options;
	}

	function getReferences(serviceRegistry, serviceID) {
		return serviceRegistry.getServiceReferences(serviceID || "orion.edit.context"); //$NON-NLS-0$
	}

	return {
		getEditorContext: getEditorContext,
		getOptions: getOptions
	};
});
/******************************************************************************* 
 * @license
 * Copyright (c) 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation 
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/edit/typedefs',[
	"orion/objects",
	"orion/serviceTracker",
	"orion/i18nUtil"
], function(objects, ServiceTracker, i18nUtil) {

	function err(msg, serviceRef) {
		throw new Error(i18nUtil.formatMessage("${0} [service id: ${1}]", msg, serviceRef.getProperty("service.id"))); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$ 
	}

	/**
	 * @name orion.core.TypeDef
	 * @class
	 * @description Represents a Type Definition. The shape of a Type Definition is not defined by Orion.
	 */

	/**
	 * @name orion.core.TypeDefProperties
	 * @class
	 * @description Represents the properties associated with a Type Definition. Minimally an {@link #id} property is defined.
	 */
	/**
	 * @name orion.core.TypeDefProperties#id
	 * @property
	 * @type String
	 * @description The ID of the TypeDef this properties object is for.
	 */

	/**
	 * @name orion.core.TypeDefRegistry
	 * @class
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry
	 * @description Tracks type definitions registered with a Service Registry.
	 */
	function TypeDefRegistry(serviceRegistry) {
		this.props = Object.create(null); // String -> Object, maps id to properties
		this.defs = Object.create(null);  // String -> TypeDef, maps id to type def
		this.registration = null;

		var tracker = new ServiceTracker(serviceRegistry, "orion.core.typedef"); //$NON-NLS-0$
		var _self = this;
		tracker.onServiceAdded = function(serviceRef) {
			var id = serviceRef.getProperty("id"), defs = serviceRef.getProperty("defs"); //$NON-NLS-1$ //$NON-NLS-0$
			if (!id || !defs)
				err("TypeDef registration missing required 'id' or 'defs'", serviceRef); //$NON-NLS-0$

			var properties = {};
			serviceRef.getPropertyKeys().forEach(function(key) {
				if (key !== "defs" && key !== "service.id" && key !== "service.names" && key !== "objectClass") //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$
					properties[key] = serviceRef.getProperty(key);
			});
			_self.props[id] = properties;
			_self.defs[id] = defs;
			_self._updateRegistration();
		};
		tracker.removedService = function(serviceRef) {
			var id = serviceRef.getProperty("id"); //$NON-NLS-0$
			delete _self.props[id];
			delete _self.defs[id];
			_self._updateRegistration();
		};
		tracker.open();

		// Register accessor service that exposes TypeDef info to editorContext
		this.registration = serviceRegistry.registerService("orion.edit.context", { //$NON-NLS-0$
			getTypeDef: this.getTypeDef.bind(this)
		}, null);
		this._updateRegistration();
	}
	objects.mixin(TypeDefRegistry.prototype, /** @lends orion.core.TypeDefRegistry.prototype */ {
		_updateRegistration: function() {
			if (this.registration) {
				this.registration.setProperties({ typeDefs: this.getAllProperties() });
			}
		},
		/**
		 * Gets a TypeDef.
		 * @param {String} id The ID of the desired TypeDef.
		 * @returns {orion.core.TypeDef|null} The TypeDef object.
		 */
		getTypeDef: function(id) {
			return this.defs[id] || null;
		},
		/**
		 * Gets the properties for a TypeDef.
		 * @param {String} id The ID of the TypeDef to get properties for.
		 * @returns {orion.core.TypeDefProperties|null} The properties object.
		 */
		getProperties: function(id) {
			return this.props[id] || null;
		},
		/**
		 * Gets the properties of all TypeDefs in the registry.
		 * @returns {Object} A map of all properties. The keys are TypeDef IDs and the values are 
		 * the {@link orion.core.TypeDefProperties} returned by {@link #getProperties(id)}.
		 */
		getAllProperties: function() {
			return this.props;
		}
	});

	return TypeDefRegistry;
});
/*******************************************************************************
 * @license
 * Copyright (c) 2014 IBM Corporation and others. All rights reserved. This
 * program and the accompanying materials are made available under the terms of
 * the Eclipse Public License v1.0 (http://www.eclipse.org/legal/epl-v10.html),
 * and the Eclipse Distribution License v1.0
 * (http://www.eclipse.org/org/documents/edl-v10.html).
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('examples/editor/textStyler',[], function() {
	return {
	};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors: IBM Corporation - initial API and implementation
 *               Alex Lakatos - fix for bug#369781
 ******************************************************************************/

/*eslint-env browser, amd*/
define("orion/editor/textStyler", ['orion/editor/annotations', 'orion/editor/eventTarget', 'orion/metrics'], //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
	function(mAnnotations, mEventTarget, mMetrics) {

	/*
	 * Throughout textStyler "block" refers to a potentially multi-line token.
	 * Typical examples are multi-line comments and multi-line strings.
	 */

	var binarySearch = function(array, offset, inclusive, low, high) {
		var index;
		if (low === undefined) { low = -1; }
		if (high === undefined) { high = array.length; }
		while (high - low > 1) {
			index = Math.floor((high + low) / 2);
			if (offset <= array[index].start) {
				high = index;
			} else if (inclusive && offset < array[index].end) {
				high = index;
				break;
			} else {
				low = index;
			}
		}
		return high;
	};
	
	function copy(object) {
		return JSON.parse(JSON.stringify(object));
	}

	var createPatternBasedAdapter = function(grammars, rootId, contentType) {
		return new PatternBasedAdapter(grammars, rootId, contentType);
	};

	function PatternBasedAdapter(grammars, rootId, contentType) {
		this._patternManager = new PatternManager(grammars, rootId);
		this._contentType = contentType;
	}
	PatternBasedAdapter.prototype = {
		blockSpansBeyondEnd: function(block) {
			return block.pattern.regexEnd === this._eolRegex;
		},
		computeBlocks: function(model, text, block, offset, startIndex, endIndex, maxBlockCount) {
			if (!text) {
				return [];
			}

			var results = [];
			var matches = [];
			var result;
			startIndex = startIndex || 0;
			endIndex = endIndex || Infinity;
			maxBlockCount = maxBlockCount || Infinity;
			block.blockPatterns.forEach(function(current) {
				result = this._findMatch(current.regexBegin || current.regex, text, startIndex);
				if (result) {
					matches.push({result: result, pattern: current});
				}
			}.bind(this));
			if (!matches.length) {
				return results;
			}
			matches.sort(function(a,b) {
				if (a.result.index < b.result.index) {
					return -1;
				}
				if (a.result.index > b.result.index) {
					return 1;
				}
				return a.pattern.pattern.index < b.pattern.pattern.index ? -1 : 1;
			});

			var index = 0;
			while (matches.length > 0) {
				var current = matches[0];
				matches.splice(0,1);

				if (endIndex < current.result.index) {
					break;
				}

				if (current.result.index < index) {
					/* processing of another match has moved index beyond this match */
					this._updateMatch(current, text, matches, index, endIndex);
					continue;
				}

				var start = offset + current.result.index;
				var contentStart = current.result.index;
				var resultEnd = null;

				var endRegex = current.pattern.regexEnd;
				if (!endRegex) {
					resultEnd = this.createBlock(
						{
							start: start,
							end: start + current.result[0].length,
							contentStart: start,
							contentEnd: start + current.result[0].length
						},
						block.styler,
						model,
						block,
						current.pattern);
				} else {
					contentStart += current.result[0].length;
					var testPattern = current.pattern;
					/*
					 * If the end regex contains a capture reference (eg.- "\1") then substitute
					 * the resolved capture values from the begin match.
					 */
					var resolvedEndRegex = this._substituteCaptureValues(endRegex, current.result);
					if (resolvedEndRegex !== endRegex) {
						/*
						 * A substitution was made, so make a copy of the test pattern and set its
						 * end regex to the resolved one.  This will cause end-match detection to be
						 * performed with this concrete end regex value, but the original pattern
						 * definition containing the capture reference will not be affected.
						 */
						testPattern = {
							pattern: testPattern.pattern,
							regexBegin: testPattern.regexBegin,
							regexEnd: resolvedEndRegex
						};
						endRegex = resolvedEndRegex;
					}

					var lastIndex = contentStart;
					while (!resultEnd) {
						result = this._findMatch(endRegex, text, lastIndex);
						if (!result) {
							this._eolRegex.lastIndex = 0;
							result = this._eolRegex.exec(text);
							testPattern = {
								pattern: testPattern.pattern,
								regexBegin: testPattern.regexBegin,
								regexEnd: this._eolRegex
							};
						}
						var testBlock = this.createBlock(
							{
								start: start,
								end: offset + result.index + result[0].length,
								contentStart: offset + contentStart,
								contentEnd: offset + result.index
							},
							block.styler,
							model,
							block,
							testPattern);
						var subBlocks = testBlock.getBlocks();
						if (!subBlocks.length || subBlocks[subBlocks.length - 1].end <= (result.index + offset)) {
							resultEnd = testBlock;
						}
						lastIndex = result.index + result[0].length;
					}
				}
				results.push(resultEnd);
				if (results.length === maxBlockCount || endIndex <= resultEnd.end) {
					break;
				}
				index = resultEnd.end - offset;
				this._updateMatch(current, text, matches, index, endIndex);
			}
			return results;
		},
		computeStyle: function(block, model, offset) {
			if (!block.pattern) {
				return null;
			}

			var fullBlock = {
				start: block.start,
				end: block.end,
				style: block.pattern.pattern.name
			};
			if (block.contentStart <= offset && offset < block.contentEnd) {
				if (block.pattern.pattern.contentName) {
					return {
						start: block.contentStart,
						end: block.contentEnd,
						style: block.pattern.pattern.contentName
					};
				}
				return fullBlock;
			}

			var regex, captures, testString, index;
			if (offset < block.contentStart) {
				captures = block.pattern.pattern.beginCaptures || block.pattern.pattern.captures;
				if (!captures) {
					return fullBlock;
				}
				regex = block.pattern.regexBegin;
				testString = model.getText(block.start, block.contentStart);
				index = block.start;
			} else {
				captures = block.pattern.pattern.endCaptures || block.pattern.pattern.captures;
				if (!captures) {
					return fullBlock;
				}
				regex = block.pattern.regexEnd;
				testString = model.getText(block.contentEnd, block.end);
				index = block.contentEnd;
			}

			regex.lastIndex = 0;
			var result = regex.exec(testString);
			if (result) {
				var styles = [];
				this._getCaptureStyles(result, captures, index, styles);
				var style = styles[binarySearch(styles, offset, true)];
				if (style && style.start <= offset && offset < style.end) {
					return style;
				}
			}
			return fullBlock;
		},
		createBlock: function(bounds, styler, model, parent, data) {
			/* for pattern-based matching data is a pattern */
			return new Block(
				bounds,
				data ? data.pattern.name : null,
				data ? data.pattern.id : null,
				styler,
				model,
				parent,
				function(newBlock) {
					newBlock.pattern = data;
					newBlock.linePatterns = [];
					newBlock.blockPatterns = [];
					newBlock.enclosurePatterns = {};
					this._initPatterns(this._patternManager, newBlock);
				}.bind(this));
		},
		getBlockContentStyleName: function(block) {
			return block.pattern.pattern.contentName || block.pattern.pattern.name;
		},
		getBlockEndStyle: function(block, text, endIndex, _styles) {
			/* pattern-defined blocks specify an end style by either a capture or name */
			var result;
			if (block.pattern.regexEnd) {
				result = this._findMatch(block.pattern.regexEnd, text, 0);
				if (result) {
					/* the end match is still valid */
					var captures = block.pattern.pattern.endCaptures || block.pattern.pattern.captures;
					if (captures) {
						this._getCaptureStyles(result, captures, endIndex - result[0].length, _styles);
					} else if (block.pattern.pattern.name) {
						_styles.push({start: endIndex - result[0].length, end: endIndex, style: block.pattern.pattern.name});
					}
				}
			}
			return result ? result[0] : null;
		},
		getBlockFoldBounds: function(block, model) {
			return {start: block.start, end: block.end};
		},
		getBlockStartStyle: function(block, text, index, _styles) {
			/* pattern-defined blocks specify a start style by either a capture or name */
			var result;
			if (block.pattern.regexBegin) {
				result = this._findMatch(block.pattern.regexBegin, text, 0);
				if (result) {
					/* the begin match is still valid */
					var captures = block.pattern.pattern.beginCaptures || block.pattern.pattern.captures;
					if (captures) {
						this._getCaptureStyles(result, captures, index, _styles);
					} else {
						_styles.push({start: index, end: index + result[0].length, style: block.pattern.pattern.name});
					}
				}
			}
			return result ? result[0] : null;
		},
		getBracketMatch: function(block, text) {
			var match;
			var keys = Object.keys(block.enclosurePatterns);
			for (var i = 0; i < keys.length; i++) {
				var current = block.enclosurePatterns[keys[i]];
				var result = this._findMatch(current.regex, text, 0);
				if (result && result.index === 0) {
					match = current;
					break;
				}
			}
			if (!match) { return null; }

			var closingName;
			var atStart = false;
			if (match.pattern.name.indexOf(this._PUNCTUATION_SECTION_BEGIN) !== -1) {
				atStart = true;
				closingName = match.pattern.name.replace(this._PUNCTUATION_SECTION_BEGIN, this._PUNCTUATION_SECTION_END);
			} else {
				closingName = match.pattern.name.replace(this._PUNCTUATION_SECTION_END, this._PUNCTUATION_SECTION_BEGIN);
			}
			var closingBracket = block.enclosurePatterns[closingName];
			if (!closingBracket) { return null; }

			return {
				beginName: match.pattern.name,
				endName: closingName,
				atStart: atStart
			};
		},
		getContentType: function() {
			return this._contentType;
		},
		parse: function(text, offset, startIndex, block, _styles, ignoreCaptures) {
			if (!text) {
				return;
			}
			var patterns = block.linePatterns;
			if (!patterns) {
				return;
			}

			var matches = [];
			var result;
			patterns.forEach(function(current) {
				var regex = current.regex || current.regexBegin;
				regex.oldLastIndex = regex.lastIndex;
				result = this._findMatch(regex, text, startIndex);
				if (result) {
					matches.push({result: result, pattern: current});
				}
			}.bind(this));
			matches.sort(function(a,b) {
				if (a.result.index < b.result.index) {
					return -1;
				}
				if (a.result.index > b.result.index) {
					return 1;
				}
				return a.pattern.pattern.index < b.pattern.pattern.index ? -1 : 1;
			});

			var index = startIndex;
			while (matches.length > 0) {
				var current = matches[0];
				matches.splice(0,1);

				if (current.result.index < index) {
					/* processing of another match has moved index beyond this match */
					this._updateMatch(current, text, matches, index);
					continue;
				}

				/* apply the style */
				var start = current.result.index;
				var end;
				var substyles = [];
				if (current.pattern.regex) {	/* line pattern defined by a "match" */
					result = current.result;
					end = start + result[0].length;
					var tokenStyle = {start: offset + start, end: offset + end, style: current.pattern.pattern.name};
					if (!ignoreCaptures) {
						if (current.pattern.pattern.captures) {
							this._getCaptureStyles(result, current.pattern.pattern.captures, offset + start, substyles);
						}
						substyles.sort(function(a,b) {
							if (a.start < b.start) {
								return -1;
							}
							if (a.start > b.start) {
								return 1;
							}
							return 0;
						});
						for (var j = 0; j < substyles.length - 1; j++) {
							if (substyles[j + 1].start < substyles[j].end) {
								var newStyle = {start: substyles[j + 1].end, end: substyles[j].end, style: substyles[j].style};
								substyles[j].end = substyles[j + 1].start;
								substyles.splice(j + 2, 0, newStyle);
							}
						}
					}
					this._mergeStyles(tokenStyle, substyles, _styles);
				} else {	/* pattern defined by a "begin/end" pair */
					/*
					 * If the end match contains a capture reference (eg.- "\1") then update
					 * its regex with the resolved capture values from the begin match.
					 */
					var endRegex = current.pattern.regexEnd;
					endRegex = this._substituteCaptureValues(endRegex, current.result);

					result = this._findMatch(endRegex, text, current.result.index + current.result[0].length);
					if (!result) {
						this._eolRegex.lastIndex = 0;
						result = this._eolRegex.exec(text);
					}
					end = result.index + result[0].length;
					_styles.push({start: offset + start, end: offset + end, style: current.pattern.pattern.name});
				}
				index = result.index + result[0].length;
				this._updateMatch(current, text, matches, index);
			}
			patterns.forEach(function(current) {
				var regex = current.regex || current.regexBegin;
				regex.lastIndex = regex.oldLastIndex;
			});
		},
		/** @callback */
		setStyler: function(styler) {
		},
		verifyBlock: function(baseModel, text, ancestorBlock, changeCount) {
			var result = null;
			var matches = [];
			var match;
			var parentBlock = ancestorBlock.parent;
			parentBlock.blockPatterns.forEach(function(current) {
				match = this._findMatch(current.regexBegin || current.regex, text, 0);
				if (match) {
					matches.push({result: match, pattern: current});
				}
			}.bind(this));
			matches.sort(function(a,b) {
				/* ensure that matches at index 0 make it to the front, other matches do not matter */
				if (!a.result.index && b.result.index) {
					return -1;
				}
				if (a.result.index && !b.result.index) {
					return 1;
				}
				if (!a.result.index && !b.result.index) {
					return a.pattern.pattern.index < b.pattern.pattern.index ? -1 : 1;
				}
				return 0;
			});
			if (!matches.length || matches[0].result.index !== 0 || matches[0].pattern.pattern.id !== ancestorBlock.pattern.pattern.id) {
				result = false;
			} else {
				/* the block start appears to be unchanged, now verify that the block end is unchanged */
				match = matches[0];
				var endRegex = match.pattern.regexEnd;
				if (!endRegex) {
					/* single-match block, just verify its length */
					result = ancestorBlock.start + match.result[0].length === ancestorBlock.end + changeCount;
				} else {
					/* begin/end-match block */

					 /*
					  * Determine whether an earlier match of the block's end pattern has been introduced.
					  * Verifying that this has NOT happened (the most typical case) can be quickly done by
					  * verifying that the first occurrence of its end pattern is still at its former location.
					  * However if a match is found prior to this then the blocks preceding it must be computed
					  * to verify that it is a valid end match (ie.- it is not contained within another block).
				 	  */

					/*
					 * If the end regex contains a capture reference (eg.- "\1") then substitute
					 * the resolved capture values from the begin match.
					 */
					endRegex = this._substituteCaptureValues(endRegex, match.result);

					var searchStartIndex = match.result[0].length;
					var currentMatch = this._findMatch(endRegex, text, searchStartIndex);
					while (result === null && currentMatch && ancestorBlock.start + currentMatch.index !== ancestorBlock.contentEnd + changeCount) {
						/*
						 * A match was found preceeding the former end match, so now compute
						 * blocks to determine whether it is in fact a valid new end match.
						 */
						var blocks = this.computeBlocks(baseModel, text, ancestorBlock, ancestorBlock.start, searchStartIndex, currentMatch.index + 1, null);
						if (!blocks.length || blocks[blocks.length - 1].end <= ancestorBlock.start + currentMatch.index) {
							/* the match is valid, so the attempt to use ancestorBlock as-is fails */
							result = false;
						} else {
							/* the match is not valid, so search for the next potential end match */
							if (!blocks.length) {
								currentMatch = null;
							} else {
								searchStartIndex = blocks[blocks.length - 1].end - ancestorBlock.start;
								currentMatch = this._findMatch(endRegex, text, searchStartIndex);
							}
						}
					}
					if (!currentMatch) {
						this._eolRegex.lastIndex = 0;
						currentMatch = this._eolRegex.exec(text);
						result = ancestorBlock.start + currentMatch.index === ancestorBlock.end + changeCount;
					}
				}
			}
			return result !== null ? result : true;
		},

		/** @private */

		_findMatch: function(regex, text, startIndex, testBeforeMatch) {
			/*
			 * testBeforeMatch provides a potential optimization for callers that do not strongly expect to find
			 * a match.  If this argument is defined then test() is initially called on the regex, which executes
			 * significantly faster than exec().  If a match is found then the regex's lastIndex is reverted to
			 * its pre-test() value, and exec() is then invoked on it in order to get the match details.
			 */

			var index = startIndex;
			var initialLastIndex = regex.lastIndex;
			this._linebreakRegex.lastIndex = startIndex;

			var currentLine = this._linebreakRegex.exec(text);
			/*
			 * Processing of the first line is treated specially, as it may not start at the beginning of a logical line, but
			 * regex's may be dependent on matching '^'.  To resolve this, compute the full line corresponding to the start
			 * of the text, even if it begins prior to startIndex, and adjust the regex's lastIndex accordingly to begin searching
			 * for matches at the correct location.
			 */
			var lineString, indexAdjustment;
			regex.lastIndex = 0;
			if (currentLine) {
				var lineStart = currentLine.index;
				while (0 <= --lineStart) {
					var char = text.charAt(lineStart);
					if (char === this._NEWLINE || char === this._CR) {
						break;
					}
				}
				lineString = text.substring(lineStart + 1, currentLine.index + currentLine[1].length);
				regex.lastIndex = indexAdjustment = currentLine.index - lineStart - 1;
			}
			while (currentLine && currentLine.index < text.length) {
				var result;
				if (testBeforeMatch) {
					var revertIndex = regex.lastIndex;
					if (regex.test(lineString)) {
						regex.lastIndex = revertIndex;
						result = regex.exec(lineString);
					}
				} else {
					result = regex.exec(lineString);
				}
				if (result) {
					result.index += index;
					result.index -= indexAdjustment;
					regex.lastIndex = initialLastIndex;
					return result;
				}
				indexAdjustment = 0;
				index += currentLine[0].length;
				currentLine = this._linebreakRegex.exec(text);
				if (currentLine) {
					lineString = currentLine[1];
					regex.lastIndex = 0;
				}
			}
			regex.lastIndex = initialLastIndex;
			return null;
		},
		_getCaptureStyles: function(result, captures, offset, _styles) {
			if (captures[0]) {
				/* capture index 0 is the full result */
				_styles.push({start: offset, end: offset + result[0].length, style: captures[0].name});
				return;
			}

			var stringIndex = 0;
			for (var i = 1; i < result.length; i++) {
				if (result[i]) {
					var capture = captures[i];
					if (capture) {
						var styleStart = offset + stringIndex;
						_styles.push({start: styleStart, end: styleStart + result[i].length, style: capture.name});
					}
					stringIndex += result[i].length;
				}
			}
		},
		_initPatterns: function(patternManager, block) {
			if (block.pattern && block.pattern.pattern.linePatterns) {
				block.linePatterns = block.pattern.pattern.linePatterns;
				block.blockPatterns = block.pattern.pattern.blockPatterns;
				block.enclosurePatterns = block.pattern.pattern.enclosurePatterns;
				return;
			}
			var patterns = patternManager.getPatterns(block.pattern ? block.pattern.pattern : null);
			var initRegex = function(match) {
				var matchString = typeof(match) === "string" ? match : match.match;
				var result = this._ignoreCaseRegex.exec(matchString);
				var flags = this._FLAGS;
				if (result) {
					matchString = matchString.substring(result[0].length);
					flags += "i";
				}
				return new RegExp(matchString, flags);
			}.bind(this);
			var lastBlock = -1;
			var index = 0;
			patterns.forEach(function(current) {
				var pattern;
				if (current.match && !current.begin && !current.end) {
					pattern = {regex: initRegex(current.match), pattern: current};
					block.linePatterns.push(pattern);
					if (current.name && current.name.indexOf("punctuation.section") === 0 && (current.name.indexOf(this._PUNCTUATION_SECTION_BEGIN) !== -1 || current.name.indexOf(this._PUNCTUATION_SECTION_END) !== -1)) { //$NON-NLS-0$
						block.enclosurePatterns[current.name] = pattern;
					}
				} else if (!current.match && current.begin && current.end) {
					lastBlock = index;
					pattern = {regexBegin: initRegex(current.begin), regexEnd: initRegex(current.end), pattern: current};
					block.linePatterns.push(pattern);
				}
				index++;
			}.bind(this));
			block.blockPatterns = block.linePatterns.slice(0, lastBlock + 1);
			if (block.pattern) {
				block.pattern.pattern.enclosurePatterns = block.enclosurePatterns;
				block.pattern.pattern.linePatterns = block.linePatterns;
				block.pattern.pattern.blockPatterns = block.blockPatterns;
			}
		},
		_mergeStyles: function(fullStyle, substyles, resultStyles) {
			var i = fullStyle.start;
			substyles.forEach(function(current) {
				if (i <= current.start) {
					resultStyles.push({start: i, end: current.start, style: fullStyle.style});
				}
				resultStyles.push(current);
				i = current.end;
			});
			if (i < fullStyle.end) {
				resultStyles.push({start: i, end: fullStyle.end, style: fullStyle.style});
			}
		},
		_substituteCaptureValues: function(regex, resolvedResult) {
			var regexString = regex.toString();
			this._captureReferenceRegex.lastIndex = 0;
			if (!this._captureReferenceRegex.test(regexString)) {
				/* nothing to do */
				return regex;
			}

			this._captureReferenceRegex.lastIndex = 0;
			var result = this._captureReferenceRegex.exec(regexString);
			while (result) {
				regexString = regexString.replace(result[0], resolvedResult[result[1]] || "");
				this._captureReferenceRegex.lastIndex = 0;
				result = this._captureReferenceRegex.exec(regexString);
			}
			/* return an updated regex, remove the leading '/' and trailing /FLAGS */
			return new RegExp(regexString.substring(1, regexString.length - 1 - this._FLAGS.length), this._FLAGS);
		},
		_updateMatch: function(match, text, matches, minimumIndex, endIndex) {
			var regEx = match.pattern.regex ? match.pattern.regex : match.pattern.regexBegin;
			endIndex = endIndex || Infinity;
			var result = this._findMatch(regEx, text, minimumIndex, true);
			if (result && result.index < endIndex) {
				match.result = result;
				for (var i = 0; i < matches.length; i++) {
					if (result.index < matches[i].result.index || (result.index === matches[i].result.index && match.pattern.pattern.index < matches[i].pattern.pattern.index)) {
						matches.splice(i, 0, match);
						return;
					}
				}
				matches.push(match);
			}
		},
		_captureReferenceRegex: /\\(\d)/g,
		_eolRegex: /$/,
		_ignoreCaseRegex: /^\(\?i\)\s*/,
		_linebreakRegex: /(.*)(?:[\r\n]|$)/g,
		_CR: "\r", //$NON-NLS-0$
		_FLAGS: "g", //$NON-NLS-0$
		_NEWLINE: "\n", //$NON-NLS-0$
		_PUNCTUATION_SECTION_BEGIN: ".begin", //$NON-NLS-0$
		_PUNCTUATION_SECTION_END: ".end" //$NON-NLS-0$
	};

	function PatternManager(grammars, rootId) {
		this._unnamedCounter = 0;
		this._patterns = [];
		this._rootId = rootId;
		grammars.forEach(function(grammar) {
			this._addRepositoryPatterns(grammar.repository || {}, grammar.id);
			this._addPatterns(grammar.patterns || [], grammar.id);
		}.bind(this));
	}
	PatternManager.prototype = {
		getPatterns: function(pattern) {
			var parentId;
			if (!pattern) {
				parentId = this._rootId + "#" + this._NO_ID;
			} else {
				if (typeof(pattern) === "string") { //$NON-NLS-0$
					parentId = pattern;
				} else {
					parentId = pattern.qualifiedId;
				}
				parentId += "#";
			}
			/* indexes on patterns are used to break ties when multiple patterns match the same start text */
			var indexCounter = [0];
			var resultObject = {};
			var regEx = new RegExp("^" + parentId + "[^#]+$"); //$NON-NLS-0$
			this._patterns.forEach(function(current) {
				if (regEx.test(current.qualifiedId)) {
					if (current.include) {
						this._processInclude(current, indexCounter, resultObject);
					} else {
						var newPattern = copy(current);
						newPattern.index = indexCounter[0]++;
						resultObject[current.qualifiedId] = newPattern;
					}
				}
			}.bind(this));

			var result = [];
			var keys = Object.keys(resultObject);
			keys.forEach(function(current) {
				result.push(resultObject[current]);
			});
			return result;
		},

		/** @private */

		_addPattern: function(pattern, patternId, parentId) {
			pattern.parentId = parentId;
			pattern.id = patternId;
			pattern.qualifiedId = pattern.parentId + "#" + pattern.id;
			this._patterns.push(pattern);
			if (pattern.patterns && !pattern.include) {
				this._addPatterns(pattern.patterns, pattern.qualifiedId);
			}
		},
		_addPatterns: function(patterns, parentId) {
			patterns.forEach(function(pattern) {
				this._addPattern(pattern, this._NO_ID + this._unnamedCounter++, parentId);
			}.bind(this));
		},
		_addRepositoryPatterns: function(repository, parentId) {
			var keys = Object.keys(repository);
			keys.forEach(function(key) {
				this._addPattern(repository[key], key, parentId);
			}.bind(this));
		},
		_processInclude: function(pattern, indexCounter, resultObject) {
			var searchExp;
			var index = pattern.include.indexOf("#");
			if (index === 0) {
				/* inclusion of pattern from same grammar */
				searchExp = new RegExp("^" + pattern.qualifiedId.substring(0, pattern.qualifiedId.indexOf("#")) + pattern.include + "$");
			} else if (index === -1) {
				/* inclusion of whole grammar */
				searchExp = new RegExp("^" + pattern.include + "#" + this._NO_ID + "[^#]+$");
			} else {
				/* inclusion of specific pattern from another grammar */
				searchExp = new RegExp("^" + pattern.include + "$");
			}
			this._patterns.forEach(function(current) {
				if (searchExp.test(current.qualifiedId)) {
					if (current.include) {
						this._processInclude(current, indexCounter, resultObject);
					} else if (!resultObject[current.qualifiedId]) {
						var newPattern = copy(current);
						newPattern.index = indexCounter[0]++;
						resultObject[current.qualifiedId] = newPattern;
					}
				}
			}.bind(this));
		},
		_NO_ID: "NoID"	//$NON-NLS-0$
	};

	function Block(bounds, name, typeId, styler, model, parent, initFn) {
		this.start = bounds.start;
		this.end = bounds.end;
		this.contentStart = bounds.contentStart;
		this.contentEnd = bounds.contentEnd;
		this.name = name;
		this.typeId = typeId;
		this.styler = styler;
		this.parent = parent;
		if (initFn) {
			initFn(this);
		}
		this._subBlocks = styler.computeBlocks(model, model.getText(this.contentStart, this.end), this, this.contentStart, null, null, null);
	}
	Block.prototype = {
		adjustBounds: function(index, value) {
			if (index < this.start) {
				this.start += value;
			}
			if (index < this.contentStart) {
				this.contentStart += value;
			}
			if (index <= this.end) {
				this.end += value;
			}
			if (index <= this.contentEnd) {
				this.contentEnd += value;
			}
			this._subBlocks.forEach(function(current) {
				if (index <= current.end) {
					current.adjustBounds(index, value);
				}
			});
		},
		getBlocks: function() {
			return this._subBlocks;
		},
		getBlockAtIndex: function(index) {
			return binarySearch(this.getBlocks(), index, true);
		},
		isRenderingWhitespace: function() {
			return this.styler._isRenderingWhitespace();
		}
	};

	function TextStylerAccessor(styler) {
		this._styler = styler;
	}
	TextStylerAccessor.prototype = {
		getStyles: function(offset) {
			return this._styler.getStyles(offset);
		}
	};

	function TextStyler(view, annotationModel, stylerAdapter) {		
		this._whitespacesVisible = false;
		this._highlightCaretLine = false;
		this._foldingEnabled = true;
		this._detectTasks = true;
		this._annotationProviders = [];
		this._view = view;
		this._annotationModel = annotationModel;
		this._stylerAdapter = stylerAdapter;
		this._stylerAdapter.setStyler(this);
		this._accessor = new TextStylerAccessor(this);
		this._bracketAnnotations;

		var self = this;
		this._listener = {
			onChanged: function(e) {
				self._onModelChanged(e);
			},
			onDestroy: function(e) {
				self._onDestroy(e);
			},
			onLineStyle: function(e) {
				self._onLineStyle(e);
			},
			onMouseDown: function(e) {
				self._onMouseDown(e);
			},
			onSelection: function(e) {
				self._onSelection(e);
			}
		};
		var model = view.getModel();
		if (model.getBaseModel) {
			model = model.getBaseModel();
		}
		model.addEventListener("Changed", this._listener.onChanged); //$NON-NLS-0$
		view.addEventListener("MouseDown", this._listener.onMouseDown); //$NON-NLS-0$
		view.addEventListener("Selection", this._listener.onSelection); //$NON-NLS-0$
		view.addEventListener("Destroy", this._listener.onDestroy); //$NON-NLS-0$
		view.addEventListener("LineStyle", this._listener.onLineStyle); //$NON-NLS-0$

		var charCount = model.getCharCount();
		var rootBounds = {start: 0, contentStart: 0, end: charCount, contentEnd: charCount};
		if (charCount >= 50000) {
			var startTime = new Date().getTime();
		}
		this._rootBlock = this._stylerAdapter.createBlock(rootBounds, this, model, null);
		if (startTime) {
			var interval = new Date().getTime() - startTime;
			if (interval > 10) {
				mMetrics.logTiming(
					"editor", //$NON-NLS-0$
					"styler compute blocks (ms/50000 chars)", //$NON-NLS-0$
					interval * 50000 / charCount,
					stylerAdapter.getContentType());
			}
		}
		if (annotationModel) {
			var add = [];
			annotationModel.removeAnnotations(mAnnotations.AnnotationType.ANNOTATION_FOLDING);
			this._computeFolding(this._rootBlock.getBlocks(), view.getModel(), add);
			if (this._detectTasks) {
				annotationModel.removeAnnotations(mAnnotations.AnnotationType.ANNOTATION_TASK);
				this._computeTasks(this._rootBlock, model, add);
			}
			annotationModel.replaceAnnotations([], add);
		}
		view.redrawLines();
	}
	TextStyler.prototype = {
		addAnnotationProvider: function(value) {
			if (typeof value === "function") { //$NON-NLS-0$
				this._annotationProviders.push(value);
			}
		},
		computeBlocks: function(model, text, block, offset, startIndex, endIndex, maxBlockCount) {
			return this._stylerAdapter.computeBlocks(model, text, block, offset, startIndex, endIndex, maxBlockCount);
		},
		destroy: function() {
			if (this._view) {
				var model = this._view.getModel();
				if (model.getBaseModel) {
					model = model.getBaseModel();
				}
				model.removeEventListener("Changed", this._listener.onChanged); //$NON-NLS-0$
				this._view.removeEventListener("MouseDown", this._listener.onMouseDown); //$NON-NLS-0$
				this._view.removeEventListener("Selection", this._listener.onSelection); //$NON-NLS-0$
				this._view.removeEventListener("Destroy", this._listener.onDestroy); //$NON-NLS-0$
				this._view.removeEventListener("LineStyle", this._listener.onLineStyle); //$NON-NLS-0$
				this._view = null;
			}
		},
		getAnnotationModel: function() {
			return this._annotationModel;
		},
		getBlockAtIndex: function(index) {
			return this._findBlock(this._rootBlock, index);
		},
		getRootBlock: function() {
			return this._rootBlock;
		},
		getStyleAccessor: function() {
			return this._accessor;
		},
		getStyles: function(offset) {
			var result = [];
			var model = this._view.getModel();
			if (model.getBaseModel) {
				model = model.getBaseModel();
			}
			var block = this._findBlock(this._rootBlock, offset);
			var lineIndex = model.getLineAtOffset(offset);
			var lineText = model.getLine(lineIndex);
			var styles = [];
			this._stylerAdapter.parse(lineText, model.getLineStart(lineIndex), 0, block, styles);
			var style = styles[binarySearch(styles, offset, true)];
			if (style && style.start <= offset && offset < style.end) {
				result.push(style);
			}
			while (block) {
				style = this._stylerAdapter.computeStyle(block, model, offset);
				if (style) {
					result.splice(0, 0, style);
				}
				block = block.parent;
			}
			return result;
		},
		getTextModel: function() {
			var model = this._view.getModel();
			if (model.getBaseModel) {
				model = model.getBaseModel();
			}
			return model;
		},
		removeAnnotationProvider: function(value) {
			if (typeof value !== "function") { //$NON-NLS-0$
				return;
			}
			var index = this._annotationProviders.indexOf(value);
			if (index !== -1) {
				this._annotationProviders.splice(index, 1);
			}
		},
		setDetectHyperlinks: function() {
		},
		setDetectTasks: function(enabled) {
			this._detectTasks = enabled;
		},
		setFoldingEnabled: function(enabled) {
			this._foldingEnabled = enabled;
		},
		setHighlightCaretLine: function(highlight) {
			this._highlightCaretLine = highlight;
		},
		setWhitespacesVisible: function(visible, redraw) {
			if (this._whitespacesVisible === visible) { return; }
			this._whitespacesVisible = visible;
			if (redraw) {
				this._view.redraw();
			}
		},

		/** @private */

		_computeFolding: function(blocks, viewModel, _add) {
			if (!viewModel.getBaseModel) { return; }
			var baseModel = viewModel.getBaseModel();
			blocks.forEach(function(block) {
				var foldBounds = this._stylerAdapter.getBlockFoldBounds(block, baseModel);
				var annotation = this._createFoldingAnnotation(viewModel, baseModel, foldBounds.start, foldBounds.end);
				if (annotation) {
					_add.push(annotation);
				}
				this._computeFolding(block.getBlocks(), viewModel, _add);
			}.bind(this));
		},
		_computeTasks: function(block, baseModel, annotations, start, end) {
			start = start || block.start;
			end = end || block.end;
			if (block.start <= end && start <= block.end) {
				if (!this._annotationModel) { return; }

				var annotationType = mAnnotations.AnnotationType.ANNOTATION_TASK;
				if (block.name && block.name.indexOf("comment") === 0) {
					var substyles = [];
					var lineIndex = baseModel.getLineAtOffset(block.contentStart);
					var lineStart = baseModel.getLineStart(lineIndex);
					this._stylerAdapter.parse(baseModel.getText(lineStart, block.end), lineStart, block.contentStart - lineStart, block, substyles, true);
					for (var i = 0; i < substyles.length; i++) {
						if (substyles[i].style === "meta.annotation.task.todo" && start <= substyles[i].start && substyles[i].end <= end) {
							annotations.push(mAnnotations.AnnotationType.createAnnotation(annotationType, substyles[i].start, substyles[i].end, baseModel.getText(substyles[i].start, substyles[i].end)));
						}
					}
				}

				block.getBlocks().forEach(function(current) {
					this._computeTasks(current, baseModel, annotations, start, end);
				}.bind(this));
			}
		},
		_createFoldingAnnotation: function(viewModel, baseModel, start, end) {
			var startLine = baseModel.getLineAtOffset(start);
			var endLine = baseModel.getLineAtOffset(end);
			if (startLine === endLine) {
				return null;
			}
			if (startLine + 1 === endLine && baseModel.getLineStart(endLine) === baseModel.getLineEnd(endLine)) {
				return null;
			}
			return new (mAnnotations.AnnotationType.getType(mAnnotations.AnnotationType.ANNOTATION_FOLDING))(start, end, viewModel);
		},
		_findBlock: function(parentBlock, offset) {
			var blocks = parentBlock.getBlocks();
			if (!blocks.length) {
				return parentBlock;
			}

			var index = binarySearch(blocks, offset, true);
			if (index < blocks.length && blocks[index].start <= offset && offset < blocks[index].end) {
				return this._findBlock(blocks[index], offset);
			}
			return parentBlock;
		},
		_findBrackets: function(model, bracketMatch, block, text, offset, end) {
			var result = [], styles = [];
			var start = offset, blocks = block.getBlocks();
			var startIndex = binarySearch(blocks, offset, true);
			for (var i = startIndex; i < blocks.length; i++) {
				if (blocks[i].start >= end) { break; }
				var blockStart = blocks[i].start;
				var blockEnd = blocks[i].end;
				if (start < blockStart) {
					var lineIndex = model.getLineAtOffset(start);
					var lineStart = model.getLineStart(lineIndex);
					this._stylerAdapter.parse(text.substring(lineStart - offset, blockStart - offset), lineStart, start - lineStart, block, styles);
					styles.forEach(function(current) {
						if (current.style) {
							if (current.style.indexOf(bracketMatch.beginName) === 0) {
								result.push(current.start + 1);
							} else if (current.style.indexOf(bracketMatch.endName) === 0) {
								result.push(-(current.start + 1));
							}
						}
					});
					styles = [];
				}
				start = blockEnd;
			}
			if (start < end) {
				lineIndex = model.getLineAtOffset(start);
				lineStart = model.getLineStart(lineIndex);
				this._stylerAdapter.parse(text.substring(lineStart - offset, end - offset), lineStart, start - lineStart, block, styles);
				styles.forEach(function(current) {
					if (current.style) {
						if (current.style.indexOf(bracketMatch.beginName) === 0) {
							result.push(current.start + 1);
						} else if (current.style.indexOf(bracketMatch.endName) === 0) {
							result.push(-(current.start + 1));
						}
					}
				});
			}
			return result;
		},
		_findMatchingBracket: function(model, block, offset) {
			var lineIndex = model.getLineAtOffset(offset);
			var lineEnd = model.getLineEnd(lineIndex);
			var text = model.getText(offset, lineEnd);

			var bracketMatch = this._stylerAdapter.getBracketMatch(block, text);
			if (!bracketMatch) { return -1; }

			var lineText = model.getLine(lineIndex);
			var lineStart = model.getLineStart(lineIndex);
			var brackets = this._findBrackets(model, bracketMatch, block, lineText, lineStart, lineEnd);
			for (var i = 0; i < brackets.length; i++) {
				var sign = brackets[i] >= 0 ? 1 : -1;
				if (brackets[i] * sign - 1 === offset) {
					var level = 1;
					if (!bracketMatch.atStart) {
						i--;
						for (; i>=0; i--) {
							sign = brackets[i] >= 0 ? 1 : -1;
							level += sign;
							if (level === 0) {
								return brackets[i] * sign - 1;
							}
						}
						lineIndex -= 1;
						while (lineIndex >= 0) {
							lineText = model.getLine(lineIndex);
							lineStart = model.getLineStart(lineIndex);
							lineEnd = model.getLineEnd(lineIndex);
							brackets = this._findBrackets(model, bracketMatch, block, lineText, lineStart, lineEnd);
							for (var j = brackets.length - 1; j >= 0; j--) {
								sign = brackets[j] >= 0 ? 1 : -1;
								level += sign;
								if (level === 0) {
									return brackets[j] * sign - 1;
								}
							}
							lineIndex--;
						}
					} else {
						i++;
						for (; i<brackets.length; i++) {
							sign = brackets[i] >= 0 ? 1 : -1;
							level += sign;
							if (level === 0) {
								return brackets[i] * sign - 1;
							}
						}
						lineIndex += 1;
						var lineCount = model.getLineCount();
						while (lineIndex < lineCount) {
							lineText = model.getLine(lineIndex);
							lineStart = model.getLineStart(lineIndex);
							lineEnd = model.getLineEnd(lineIndex);
							brackets = this._findBrackets(model, bracketMatch, block, lineText, lineStart, lineEnd);
							for (var k=0; k<brackets.length; k++) {
								sign = brackets[k] >= 0 ? 1 : -1;
								level += sign;
								if (level === 0) {
									return brackets[k] * sign - 1;
								}
							}
							lineIndex++;
						}
					}
					break;
				}
			}
			return -1;
		},
		_getLineStyle: function(lineIndex) {
			if (this._highlightCaretLine) {
				var view = this._view;
				var model = view.getModel();
				var selections = view.getSelections();
				var hasCaret = false;
				if (!selections.some(function(selection) {
					if (selection.start === selection.end) {
						hasCaret = hasCaret || model.getLineAtOffset(selection.start) === lineIndex;
						return false;
					}
					return true;
				}) && hasCaret) return this._caretLineStyle;
			}
			return null;
		},
		_getStyles: function(block, model, text, offset, startIndex) {
			var end = offset + text.length;

			var styles = [];
			var start = offset + startIndex, blocks = block.getBlocks();
			var startBlockIndex = binarySearch(blocks, start, true);
			for (var i = startBlockIndex; i < blocks.length; i++) {
				if (blocks[i].start >= end) { break; }
				var blockStart = blocks[i].start;
				var blockEnd = blocks[i].end;
				if (start < blockStart) {
					/* content on that line that preceeds the start of the block */
					var lineIndex = model.getLineAtOffset(start);
					var lineStart = model.getLineStart(lineIndex);
					this._stylerAdapter.parse(text.substring(lineStart - offset, blockStart - offset), lineStart, start - lineStart, block, styles);
				}
				var s = Math.max(start, blockStart);
				if (s === blockStart) {
					/* currently in the block's "start" segment */
					var startString = this._stylerAdapter.getBlockStartStyle(blocks[i], text.substring(s - offset), s, styles);
					if (startString) {
						s += startString.length;
					}
				}

				/*
				 * Compute the block end now in order to determine the end-bound of the contained content, but do not add
				 * its styles to the styles array until content styles have been computed, so that ordering is preserved.
				 */
				var e = Math.min(end, blockEnd);
				var endStyles = [];
				if (e === blockEnd) {
					/* currently in the block's "end" segment */
					var testString = text.substring(e - start - (blocks[i].end - blocks[i].contentEnd));
					var endString = this._stylerAdapter.getBlockEndStyle(blocks[i], testString, e, endStyles);
					if (endString) {
						e -= endString.length;
					}
				}

				lineIndex = model.getLineAtOffset(s);
				lineStart = model.getLineStart(lineIndex);
				var blockSubstyles = this._getStyles(blocks[i], model, text.substring(lineStart - offset, e - offset), lineStart, s - lineStart);
				var blockStyleName = this._stylerAdapter.getBlockContentStyleName(blocks[i]);
				if (blockStyleName) {
					/*
					 * If a name was specified for the current block then apply its style throughout its
					 * content wherever a style is not provided by a sub-element.
					 */
					var index = s;
					blockSubstyles.forEach(function(current) {
						if (current.start - index) {
							styles.push({start: index, end: current.start, style: blockStyleName});
						}
						if (current.mergeable) {
							current.style += "," + blockStyleName;
						}
						styles.push(current);
						index = current.end;
					});
					if (e - index) {
						styles.push({start: index, end: e, style: blockStyleName});
					}
				} else {
					styles = styles.concat(blockSubstyles);
				}
				styles = styles.concat(endStyles);
				start = blockEnd;
			}
			if (start < end) {
				/* content on that line that follows the end of the block */
				lineIndex = model.getLineAtOffset(start);
				lineStart = model.getLineStart(lineIndex);
				this._stylerAdapter.parse(text.substring(lineStart - offset, end - offset), lineStart, start - lineStart, block, styles);
			}

			return styles;
		},
		_isRenderingWhitespace: function() {
			return this._whitespacesVisible;
		},
		_onDestroy: function() {
			this.destroy();
		},
		_onLineStyle: function(e) {
			if (e.textView === this._view) {
				e.style = this._getLineStyle(e.lineIndex);
			}

			var offset = e.lineStart;
			var model = e.textView.getModel();
			if (model.getBaseModel) {
				offset = model.mapOffset(offset);
				var baseModel = model.getBaseModel();
			}

			e.ranges = this._getStyles(this._rootBlock, baseModel || model, e.lineText, offset, 0);
			for (var i = e.ranges.length - 1; i >= 0; i--) {
				var current = e.ranges[i];
				if (current.style) {
					current.style = {styleClass: current.style.replace(/\./g, " ")};
					if (baseModel) {
						var length = current.end - current.start;
						current.start = model.mapOffset(current.start, true);
						current.end = current.start + length;
					}
				} else {
					e.ranges.splice(i, 1);
				}
			};
			if (this._isRenderingWhitespace()) {
				this._spliceStyles(this._spacePattern, e.ranges, e.lineText, e.lineStart);
				this._spliceStyles(this._tabPattern, e.ranges, e.lineText, e.lineStart);
			}
		},
		_onModelChanged: function(e) {
			var start = e.start;
			var removedCharCount = e.removedCharCount;
			var addedCharCount = e.addedCharCount;
			var changeCount = addedCharCount - removedCharCount;
			var viewModel = this._view.getModel();
			var baseModel = viewModel.getBaseModel ? viewModel.getBaseModel() : viewModel;
			var end = start + removedCharCount;
			var charCount = baseModel.getCharCount();

			/* compute the nearest ancestor block to the start and end indices */
			var lineStart = baseModel.getLineStart(baseModel.getLineAtOffset(start));
			var ancestorBlock = this._findBlock(this._rootBlock, start);

			var blockExtended, blocks, parentBlock, redraw, text, te, ts;
			do {
				parentBlock = ancestorBlock.parent;

				/*
				 * Determine whether ancestorBlock contains the full range of
				 * text whose styling may be affected by this model change.
				 */
				if (!blockExtended && parentBlock) {
					/* verify that ancestorBlock's start and end bounds are not affected by this change */
					if (changeCount < 0 && ancestorBlock.end - start <= -changeCount) {
						/* the end bound has definitely been affected, no verifyBlock() required, move up to the parent */
						ancestorBlock = parentBlock;
						continue;
					}
					text = baseModel.getText(ancestorBlock.start, Math.min(charCount, ancestorBlock.end + changeCount + 1));
					if (!this._stylerAdapter.verifyBlock(baseModel, text, ancestorBlock, changeCount)) {
						ancestorBlock = parentBlock;
						continue;
					}
				}

				/*
				 * The change has not directly changed ancestorBlock's start/end strings, now verify that its end
				 * bound is still valid (ie.- ensure that a new block is not extending beyond the end bound).
				 */

				blocks = ancestorBlock.getBlocks();
				var blockCount = blocks.length;
				var blockStart = binarySearch(blocks, lineStart, true);
				var blockEnd = binarySearch(blocks, end, false, blockStart - 1, blockCount);

				/*
				 * If the change immediately follows the preceding block then test whether
				 * the block should be extended.
				 */
				blockExtended = false;
				if (blockStart && blocks.length && blocks[blockStart - 1].end === start) {
					text = baseModel.getText(blocks[blockStart - 1].start, Math.min(charCount, start + 1));
					var tempBlocks = this.computeBlocks(baseModel, text, ancestorBlock, blocks[blockStart - 1].start, null, null, null);
					if (tempBlocks.length && tempBlocks[0].end !== blocks[blockStart - 1].end) {
						/* the change has affected the preceding block's end, so include this block */
						blockStart--;
						blockExtended = true;
					}
				}

				if (blockStart < blockCount && blocks[blockStart].start <= lineStart && (lineStart < blocks[blockStart].end || blockExtended)) {
					ts = blocks[blockStart].start;
					if (ts > start) { ts += changeCount; }
				} else if (blockStart === blockCount && blockCount > 0 && ancestorBlock.end - changeCount === blocks[blockCount - 1].end) {
					ts = blocks[--blockStart].start;
					if (ts > start) { ts += changeCount; }
				} else {
					ts = Math.max(lineStart, ancestorBlock.contentStart);
				}

				if (blockEnd < blockCount) {
					te = blocks[blockEnd].end;
				} else {
					te = ancestorBlock.contentEnd;
				}
				if (start <= te) { te += changeCount; }
				te = Math.min(te, charCount - 1);
				text = baseModel.getText(ts, te + 1);
				var newBlocks = this.computeBlocks(baseModel, text, ancestorBlock, ts, null, null, null);

				if (blockEnd < blockCount) {
					/* ensure that blockEnd's end is preserved */
					if (newBlocks.length && newBlocks[newBlocks.length - 1].end === te && newBlocks[newBlocks.length - 1].typeId === blocks[blockEnd].typeId) {
						break;
					}

					/*
					 * ancestorBlock's end match is no longer valid because it is being spanned by a block from
					 * within.  Attempt to find a subsequent sibling block with the same type, as its end match
					 * will serve as the end match for this spanning block as well.
					 */
					if (newBlocks.length && this._stylerAdapter.blockSpansBeyondEnd(newBlocks[newBlocks.length - 1])) {
						blockEnd++;
						var subBlocks = newBlocks[newBlocks.length - 1].getBlocks();
						var spanningTypeId = (subBlocks.length ? subBlocks[subBlocks.length - 1] : newBlocks[newBlocks.length - 1]).typeId;
						while (blockEnd < blockCount) {
							if (blocks[blockEnd].typeId === spanningTypeId) {
								/* found a potential end block, must verify it */
								var tempTe = blocks[blockEnd].end + changeCount;
								tempTe = Math.min(tempTe, charCount - 1);
								text = baseModel.getText(ts, tempTe + 1);
								var tempNewBlocks = this.computeBlocks(baseModel, text, ancestorBlock, ts, null, null, null);
								if (tempNewBlocks.length && tempNewBlocks[tempNewBlocks.length - 1].end === tempTe) {
									/* verified, can now stop looking */
									te = tempTe;
									newBlocks = tempNewBlocks;
									break;
								}
							}
							blockEnd++;
						}
						if (blockEnd < blockCount) {
							break;
						}
					}
				} else {
					/* ensure that ancestorBlock's end is preserved */
					if (!newBlocks.length || newBlocks[newBlocks.length - 1].end <= ancestorBlock.contentEnd + changeCount) {
						break;
					}
				}

				/*
				 * The end block's end bound is spanned by a block from within, so move up to the ancestor
				 * block, or extend end to the end of the content if already at the root-level block.
				 */

				if (!parentBlock) {
					te = charCount;
					blockEnd = blockCount;
					text = baseModel.getText(ts, te);
					newBlocks = this.computeBlocks(baseModel, text, ancestorBlock, ts, null, null, null);
					break;
				}

				ancestorBlock = parentBlock;
				redraw = true; /* blocks may not appear to be changed in the context of the parent block */
			} while (true);

			this._rootBlock.adjustBounds(start, changeCount);
			blockEnd = Math.min(blockEnd + 1, blockCount);

			var block;
			if (!redraw) {
				redraw = (blockEnd - blockStart) !== newBlocks.length;
			}
			if (!redraw) {
				for (var i = 0; i < newBlocks.length; i++) {
					block = blocks[blockStart + i];
					var newBlock = newBlocks[i];
					if (block.start !== newBlock.start || block.end !== newBlock.end || block.typeId !== newBlock.typeId) {
						redraw = true;
						break;
					}
				}
			}

			if (!blocks.length && !newBlocks.length) {
				var eventOldBlocks = [ancestorBlock];
				var eventNewBlocks = [ancestorBlock];
				this.dispatchEvent({
					type: "BlocksChanged", //$NON-NLS-0$
					oldBlocks: eventOldBlocks,
					newBlocks: eventNewBlocks
				});
				ancestorBlock = eventNewBlocks[0]; /* enables the markdown styler to return a modified ancestorBlock */
			} else {
				this.dispatchEvent({
					type: "BlocksChanged", //$NON-NLS-0$
					oldBlocks: blocks.slice(blockStart, blockEnd),
					newBlocks: newBlocks
				});
			}

			var args = [blockStart, blockEnd - blockStart].concat(newBlocks);
			Array.prototype.splice.apply(blocks, args);
			if (redraw) {
				var redrawStart = ts;
				var redrawEnd = te;
				if (viewModel !== baseModel) {
					redrawStart = viewModel.mapOffset(redrawStart, true);
					redrawEnd = viewModel.mapOffset(redrawEnd, true);
				}
				this._view.redrawRange(redrawStart, redrawEnd);
			}

			if (this._annotationModel) {
				var remove = [], add = [];
				var allFolding = [];
				var iter = this._annotationModel.getAnnotations(ts, te);
				var doFolding = this._foldingEnabled && baseModel !== viewModel;
				var parent = ancestorBlock.parent || ancestorBlock;
				while (iter.hasNext()) {
					var annotation = iter.next();
					if (doFolding && annotation.type === mAnnotations.AnnotationType.ANNOTATION_FOLDING) {
						allFolding.push(annotation);
						block = this._findBlock(parent, annotation.start);
						while (block) {
							var foldBounds = this._stylerAdapter.getBlockFoldBounds(block, baseModel);
							if (annotation.start === foldBounds.start && annotation.end === foldBounds.end) {
								break;
							}
							block = block.parent;
						}
						if (block && annotation.start === foldBounds.start && annotation.end === foldBounds.end) {
							var annotationStart = annotation.start;
							var annotationEnd = annotation.end;
							if (annotationStart > start) {
								annotationStart -= changeCount;
							}
							if (annotationEnd > start) {
								annotationEnd -= changeCount;
							}
							if (annotationStart <= start && start < annotationEnd && annotationStart <= end && end < annotationEnd) {
								var startLine = baseModel.getLineAtOffset(annotation.start);
								var endLine = baseModel.getLineAtOffset(annotation.end);
								if (startLine !== endLine) {
									if (!annotation.expanded) {
										annotation.expand();
									}
								} else {
									remove.push(annotation);
								}
							}
						} else {
							remove.push(annotation);
							annotation.expand();
						}
					} else if (annotation.type === mAnnotations.AnnotationType.ANNOTATION_TASK) {
						if (ancestorBlock.start <= annotation.start && annotation.end <= ancestorBlock.end) {
							remove.push(annotation);
						}
					}
				}
				if (doFolding) {
					parent.getBlocks().forEach(function(block) {
						this._updateFolding(block, baseModel, viewModel, allFolding, add, ts, te);
					}.bind(this));
				}
				if (this._detectTasks) {
					this._computeTasks(ancestorBlock, baseModel, add, ts, te);
				}
				this._annotationProviders.forEach(function(current) {
					var providerRemove = [];
					var providerAdd = [];
					current(this._annotationModel, baseModel, ancestorBlock, ts, te, providerRemove, providerAdd);
					remove = remove.concat(providerRemove);
					add = add.concat(providerAdd);
				}.bind(this));
				this._annotationModel.replaceAnnotations(remove, add);
			}
		},
		_onMouseDown: function(e) {
			if (e.clickCount !== 2) { return; }
			var model = this._view.getModel();
			var offset = this._view.getOffsetAtLocation(e.x, e.y);
			if (offset > 0) {
				var mapOffset = offset - 1;
				var baseModel = model;
				if (model.getBaseModel) {
					mapOffset = model.mapOffset(mapOffset);
					baseModel = model.getBaseModel();
				}
				var block = this._findBlock(this._rootBlock, mapOffset);
				var bracket = this._findMatchingBracket(baseModel, block, mapOffset);
				if (bracket !== -1) {
					e.preventDefault();
					var mapBracket = bracket;
					if (model.getBaseModel) {
						mapBracket = model.mapOffset(mapBracket, true);
					}
					if (offset > mapBracket) {
						offset--;
						mapBracket++;
					}
					this._view.setSelection(mapBracket, offset);
				}
			}
		},
		_onSelection: function(e) {
			var oldSelections = Array.isArray(e.oldValue) ? e.oldValue : [e.oldValue];
			var newSelections = Array.isArray(e.newValue) ? e.newValue : [e.newValue];
			var view = this._view;
			var model = view.getModel();
			var lineIndex;
			if (this._highlightCaretLine) {
				function getHighlightLines(selections) {
					var lines = {};
					if (selections.some(function(selection) {
						if (selection.isEmpty()) {
							lines[model.getLineAtOffset(selection.start).toString()] = true;
						} else {
							return true;
						}
						return false;
					})) return {};
					return lines;
				}
				var oldLines = getHighlightLines(oldSelections);
				var newLines = getHighlightLines(newSelections);
				function redraw(o, n) {
					for (var p in o) {
						if (!n[p]) {
							lineIndex = p >> 0;
							view.redrawLines(lineIndex, lineIndex + 1);
						}
					}
				}
				redraw(oldLines, newLines);
				redraw(newLines, oldLines);
			}
			if (!this._annotationModel) { return; }

			var remove = this._bracketAnnotations, add, caret;
			if (newSelections.length === 1 && newSelections[0].isEmpty() && (caret = newSelections[0].getCaret()) > 0) {
				var mapCaret = caret - 1;
				if (model.getBaseModel) {
					mapCaret = model.mapOffset(mapCaret);
					model = model.getBaseModel();
				}
				var block = this._findBlock(this._rootBlock, mapCaret);
				var bracket = this._findMatchingBracket(model, block, mapCaret);
				if (bracket !== -1) {
					add = [
						mAnnotations.AnnotationType.createAnnotation(mAnnotations.AnnotationType.ANNOTATION_MATCHING_BRACKET, bracket, bracket + 1),
						mAnnotations.AnnotationType.createAnnotation(mAnnotations.AnnotationType.ANNOTATION_CURRENT_BRACKET, mapCaret, mapCaret + 1)
					];
				}
			}
			this._bracketAnnotations = add;
			this._annotationModel.replaceAnnotations(remove, add);
		},
		_spliceStyles: function(whitespacePattern, ranges, text, offset) {
			var regex = whitespacePattern.regex;
			regex.lastIndex = 0;
			var rangeIndex = 0;
			var result = regex.exec(text);
			while (result) {
				var charIndex = offset + result.index;
				while (rangeIndex < ranges.length) {
					if (charIndex < ranges[rangeIndex].end) {
						break;
					}
					rangeIndex++;
				}
				var newStyle = {
					start: charIndex,
					end: charIndex + 1,
					style: copy(whitespacePattern.style)
				};
				if (rangeIndex < ranges.length && ranges[rangeIndex].start <= charIndex) {
					var endStyle = {start: charIndex + 1, end: ranges[rangeIndex].end, style: ranges[rangeIndex].style};
					ranges[rangeIndex].end = charIndex;
					ranges.splice(rangeIndex + 1, 0, endStyle);
					ranges.splice(rangeIndex + 1, 0, newStyle);
					rangeIndex += 2;
					newStyle.style.styleClass += " " + ranges[rangeIndex].style.styleClass; //$NON-NLS-0$
				} else {
					ranges.splice(rangeIndex, 0, newStyle);
					rangeIndex++;
				}
				result = regex.exec(text);
			}
		},
		_updateFolding: function(block, baseModel, viewModel, allFolding, _add, start, end) {
			start = start || block.start;
			end = end || block.end;
			var foldBounds = this._stylerAdapter.getBlockFoldBounds(block, baseModel);
			if (!block.doNotFold && foldBounds.start <= end && start <= foldBounds.end) {
				var index = binarySearch(allFolding, foldBounds.start, true);
				if (!(index < allFolding.length && allFolding[index].start === foldBounds.start && allFolding[index].end === foldBounds.end)) {
					var annotation = this._createFoldingAnnotation(viewModel, baseModel, foldBounds.start, foldBounds.end);
					if (annotation) {
						_add.push(annotation);
					}
				}
				block.getBlocks().forEach(function(current) {
					this._updateFolding(current, baseModel, viewModel, allFolding, _add, start, end);
				}.bind(this));
			}
		},
		_caretLineStyle: {styleClass: "meta annotation currentLine"}, //$NON-NLS-0$
		_spacePattern: {regex: /[ ]/g, style: {styleClass: "punctuation separator space", unmergeable: true}}, //$NON-NLS-0$
		_tabPattern: {regex: /\t/g, style: {styleClass: "punctuation separator tab", unmergeable: true}} //$NON-NLS-0$
	};

	mEventTarget.EventTarget.addMixin(TextStyler.prototype);

	return {
		TextStyler: TextStyler,
		Block: Block,
		createPatternBasedAdapter: createPatternBasedAdapter
	};
});

/******************************************************************************* 
 * @license
 * Copyright (c) 2011, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation 
 ******************************************************************************/

/*eslint-env browser, amd*/
define("orion/editor/textMateStyler", ['orion/regex' ], function(mRegex) {

var RegexUtil = {
	// Rules to detect some unsupported Oniguruma features
	unsupported: [
		{regex: /\(\?[ims\-]:/, func: function(match) { return "option on/off for subexp"; }},
		{regex: /\(\?<([=!])/, func: function(match) { return (match[1] === "=") ? "lookbehind" : "negative lookbehind"; }},
		{regex: /\(\?>/, func: function(match) { return "atomic group"; }}
	],
	
	/**
	 * @param {String} str String giving a regular expression pattern from a TextMate grammar.
	 * @param {String} [flags] [ismg]+
	 * @returns {RegExp}
	 */
	toRegExp: function(str) {
		function fail(feature, match) {
			throw new Error("Unsupported regex feature \"" + feature + "\": \"" + match[0] + "\" at index: "
					+ match.index + " in " + match.input);
		}
		// Turns an extended regex pattern into a normal one
		function normalize(/**String*/ str) {
			var result = "";
			var insideCharacterClass = false;
			var len = str.length;
			for (var i=0; i < len; ) {
				var chr = str.charAt(i);
				if (!insideCharacterClass && chr === "#") {
					// skip to eol
					while (i < len && chr !== "\r" && chr !== "\n") {
						chr = str.charAt(++i);
					}
				} else if (!insideCharacterClass && /\s/.test(chr)) {
					// skip whitespace
					while (i < len && /\s/.test(chr)) { 
						chr = str.charAt(++i);
					}
				} else if (chr === "\\") {
					result += chr;
					if (!/\s/.test(str.charAt(i+1))) {
						result += str.charAt(i+1);
						i += 1;
					}
					i += 1;
				} else if (chr === "[") {
					insideCharacterClass = true;
					result += chr;
					i += 1;
				} else if (chr === "]") {
					insideCharacterClass = false;
					result += chr;
					i += 1;
				} else {
					result += chr;
					i += 1;
				}
			}
			return result;
		}
		
		var flags = "";
		var i;
		
		// Handle global "x" flag (whitespace/comments)
		str = RegexUtil.processGlobalFlag("x", str, function(subexp) {
				return normalize(subexp);
			});
		
		// Handle global "i" flag (case-insensitive)
		str = RegexUtil.processGlobalFlag("i", str, function(subexp) {
				flags += "i";
				return subexp;
			});
		
		// Check for remaining unsupported syntax
		for (i=0; i < this.unsupported.length; i++) {
			var match;
			if ((match = this.unsupported[i].regex.exec(str))) {
				fail(this.unsupported[i].func(match), match);
			}
		}
		
		return new RegExp(str, flags);
	},
	
	/**
	 * Checks if flag applies to entire pattern. If so, obtains replacement string by calling processor
	 * on the unwrapped pattern. Handles 2 possible syntaxes: (?f)pat and (?f:pat)
	 * @param {String} flag
	 * @param {String} str
	 * @param {Function} processor
	 */
	processGlobalFlag: function(flag, str, processor) {
		function getMatchingCloseParen(/*String*/pat, /*Number*/start) {
			var depth = 0,
			    len = pat.length,
			    flagStop = -1;
			for (var i=start; i < len && flagStop === -1; i++) {
				switch (pat.charAt(i)) {
					case "\\":
						i++; // escape: skip next char
						break;
					case "(":
						depth++;
						break;
					case ")":
						depth--;
						if (depth === 0) {
							flagStop = i;
						}
						break;
				}
			}
			return flagStop;
		}
		var flag1 = "(?" + flag + ")",
		    flag2 = "(?" + flag + ":";
		if (str.substring(0, flag1.length) === flag1) {
			return processor(str.substring(flag1.length));
		} else if (str.substring(0, flag2.length) === flag2) {
			var flagStop = getMatchingCloseParen(str, 0);
			if (flagStop < str.length-1) {
				throw new Error("Only a " + flag2 + ") group that encloses the entire regex is supported in: " + str);
			}
			return processor(str.substring(flag2.length, flagStop));
		}
		return str;
	},
	
	hasBackReference: function(/**RegExp*/ regex) {
		return (/\\\d+/).test(regex.source);
	},
	
	/** @returns {RegExp} A regex made by substituting any backreferences in <code>regex</code> for the value of the property
	 * in <code>sub</code> with the same name as the backreferenced group number. */
	getSubstitutedRegex: function(/**RegExp*/ regex, /**Object*/ sub, /**Boolean*/ escape) {
		escape = (typeof escape === "undefined") ? true : false;
		var exploded = regex.source.split(/(\\\d+)/g);
		var array = [];
		for (var i=0; i < exploded.length; i++) {
			var term = exploded[i];
			var backrefMatch = /\\(\d+)/.exec(term);
			if (backrefMatch) {
				var text = sub[backrefMatch[1]] || "";
				array.push(escape ? mRegex.escape(text) : text);
			} else {
				array.push(term);
			}
		}
		return new RegExp(array.join(""));
	},
	
	/**
	 * Builds a version of <code>regex</code> with every non-capturing term converted into a capturing group. This is a workaround
	 * for JavaScript's lack of API to get the index at which a matched group begins in the input string.<p>
	 * Using the "groupified" regex, we can sum the lengths of matches from <i>consuming groups</i> 1..n-1 to obtain the 
	 * starting index of group n. (A consuming group is a capturing group that is not inside a lookahead assertion).</p>
	 * Example: groupify(/(a+)x+(b+)/) === /(a+)(x+)(b+)/<br />
	 * Example: groupify(/(?:x+(a+))b+/) === /(?:(x+)(a+))(b+)/
	 * @param {RegExp} regex The regex to groupify.
	 * @param {Object} [backRefOld2NewMap] Optional. If provided, the backreference numbers in regex will be updated using the 
	 * properties of this object rather than the new group numbers of regex itself.
	 * <ul><li>[0] {RegExp} The groupified version of the input regex.</li>
	 * <li>[1] {Object} A map containing old-group to new-group info. Each property is a capturing group number of <code>regex</code>
	 * and its value is the corresponding capturing group number of [0].</li>
	 * <li>[2] {Object} A map indicating which capturing groups of [0] are also consuming groups. If a group number is found
	 * as a property in this object, then it's a consuming group.</li></ul>
	 */
	groupify: function(regex, backRefOld2NewMap) {
		var NON_CAPTURING = 1,
		    CAPTURING = 2,
		    LOOKAHEAD = 3,
		    NEW_CAPTURING = 4;
		var src = regex.source,
		    len = src.length;
		var groups = [],
		    lookaheadDepth = 0,
		    newGroups = [],
		    oldGroupNumber = 1,
		    newGroupNumber = 1;
		var result = [],
		    old2New = {},
		    consuming = {};
		for (var i=0; i < len; i++) {
			var curGroup = groups[groups.length-1];
			var chr = src.charAt(i);
			switch (chr) {
				case "(":
					// If we're in new capturing group, close it since ( signals end-of-term
					if (curGroup === NEW_CAPTURING) {
						groups.pop();
						result.push(")");
						newGroups[newGroups.length-1].end = i;
					}
					var peek2 = (i + 2 < len) ? (src.charAt(i+1) + "" + src.charAt(i+2)) : null;
					if (peek2 === "?:" || peek2 === "?=" || peek2 === "?!") {
						// Found non-capturing group or lookahead assertion. Note that we preserve non-capturing groups
						// as such, but any term inside them will become a new capturing group (unless it happens to
						// also be inside a lookahead).
						var groupType;
						if (peek2 === "?:") {
							groupType = NON_CAPTURING;
						} else {
							groupType = LOOKAHEAD;
							lookaheadDepth++;
						}
						groups.push(groupType);
						newGroups.push({ start: i, end: -1, type: groupType /*non capturing*/ });
						result.push(chr);
						result.push(peek2);
						i += peek2.length;
					} else {
						groups.push(CAPTURING);
						newGroups.push({ start: i, end: -1, type: CAPTURING, oldNum: oldGroupNumber, num: newGroupNumber });
						result.push(chr);
						if (lookaheadDepth === 0) {
							consuming[newGroupNumber] = null;
						}
						old2New[oldGroupNumber] = newGroupNumber;
						oldGroupNumber++;
						newGroupNumber++;
					}
					break;
				case ")":
					var group = groups.pop();
					if (group === LOOKAHEAD) { lookaheadDepth--; }
					newGroups[newGroups.length-1].end = i;
					result.push(chr);
					break;
				case "*":
				case "+":
				case "?":
				case "}":
					// Unary operator. If it's being applied to a capturing group, we need to add a new capturing group
					// enclosing the pair
					var op = chr;
					var prev = src.charAt(i-1),
					    prevIndex = i-1;
					if (chr === "}") {
						for (var j=i-1; src.charAt(j) !== "{" && j >= 0; j--) {}
						prev = src.charAt(j-1);
						prevIndex = j-1;
						op = src.substring(j, i+1);
					}
					var lastGroup = newGroups[newGroups.length-1];
					if (prev === ")" && (lastGroup.type === CAPTURING || lastGroup.type === NEW_CAPTURING)) {
						// Shove in the new group's (, increment num/start in from [lastGroup.start .. end]
						result.splice(lastGroup.start, 0, "(");
						result.push(op);
						result.push(")");
						var newGroup = { start: lastGroup.start, end: result.length-1, type: NEW_CAPTURING, num: lastGroup.num };
						for (var k=0; k < newGroups.length; k++) {
							group = newGroups[k];
							if (group.type === CAPTURING || group.type === NEW_CAPTURING) {
								if (group.start >= lastGroup.start && group.end <= prevIndex) {
									group.start += 1;
									group.end += 1;
									group.num = group.num + 1;
									if (group.type === CAPTURING) {
										old2New[group.oldNum] = group.num;
									}
								}
							}
						}
						newGroups.push(newGroup);
						newGroupNumber++;
						break;
					} else {
						// Fallthrough to default
					}
				default:
					if (chr !== "|" && curGroup !== CAPTURING && curGroup !== NEW_CAPTURING) {
						// Not in a capturing group, so make a new one to hold this term.
						// Perf improvement: don't create the new group if we're inside a lookahead, since we don't 
						// care about them (nothing inside a lookahead actually consumes input so we don't need it)
						if (lookaheadDepth === 0) {
							groups.push(NEW_CAPTURING);
							newGroups.push({ start: i, end: -1, type: NEW_CAPTURING, num: newGroupNumber });
							result.push("(");
							consuming[newGroupNumber] = null;
							newGroupNumber++;
						}
					}
					result.push(chr);
					if (chr === "\\") {
						var peek = src.charAt(i+1);
						// Eat next so following iteration doesn't think it's a real special character
						result.push(peek);
						i += 1;
					}
					break;
			}
		}
		while (groups.length) {	
			// Close any remaining new capturing groups
			groups.pop();
			result.push(")");
		}
		var newRegex = new RegExp(result.join(""));
		
		// Update backreferences so they refer to the new group numbers. Use backRefOld2NewMap if provided
		var subst = {};
		backRefOld2NewMap = backRefOld2NewMap || old2New;
		for (var prop in backRefOld2NewMap) {
			if (backRefOld2NewMap.hasOwnProperty(prop)) {
				subst[prop] = "\\" + backRefOld2NewMap[prop];
			}
		}
		newRegex = this.getSubstitutedRegex(newRegex, subst, false);
		
		return [newRegex, old2New, consuming];
	},
	
	/** @returns {Boolean} True if the captures object assigns scope to a matching group other than "0". */
	complexCaptures: function(capturesObj) {
		if (!capturesObj) { return false; }
		for (var prop in capturesObj) {
			if (capturesObj.hasOwnProperty(prop)) {
				if (prop !== "0") {
					return true;
				}
			}
		}
		return false;
	}
};

	/**
	 * @private
	 * @param obj {Object} A JSON-ish object.
	 * @returns {Object} Deep copy of <code>obj</code>. Does not work on properties that are functions or RegExp instances.
	 */
	function clone(obj) {
		var c;
		if (obj instanceof Array) {
			c = new Array(obj.length);
			for (var i=0; i < obj.length; i++) {
				c[i] = clone(obj[i]);
			}
		} else {
			c = {};
			for (var prop in obj) {
				if (Object.prototype.hasOwnProperty.call(obj, prop)) {
					var value = obj[prop];
					if (typeof value === "object" && value !== null) {
						c[prop] = clone(value);
					} else {
						c[prop] = value;
					}
				}
			}
		}
		return c;
	}

	/**
	 * @name orion.editor.TextMateStyler
	 * @class A styler that knows how to apply a subset of the TextMate grammar format to style a line.
	 *
	 * <h4>Styling from a grammar:</h4>
	 * <p>Each scope name given in the grammar is converted to an array of CSS class names. For example 
	 * a region of text with scope <code>keyword.control.php</code> will be assigned the CSS classes<br />
	 * <code>keyword, keyword-control, keyword-control-php</code></p>
	 *
	 * <p>A CSS file can give rules matching any of these class names to provide generic or more specific styling.
	 * For example,</p>
	 * <p><code>.keyword { font-color: blue; }</code></p>
	 * <p>colors all keywords blue, while</p>
	 * <p><code>.keyword-control-php { font-weight: bold; }</code></p>
	 * <p>bolds only PHP control keywords.</p>
	 *
	 * <p>This is useful when using grammars that adhere to TextMate's
	 * <a href="http://manual.macromates.com/en/language_grammars.html#naming_conventions">scope name conventions</a>,
	 * as a single CSS rule can provide consistent styling to similar constructs across different languages.</p>
	 * 
	 * <h4>Top-level grammar constructs:</h4>
	 * <ul><li><code>patterns, repository</code> (with limitations, see "Other Features") are supported.</li>
	 * <li><code>scopeName, firstLineMatch, foldingStartMarker, foldingStopMarker</code> are <b>not</b> supported.</li>
	 * <li><code>fileTypes</code> is <b>not</b> supported. When using the Orion service registry, the "orion.edit.highlighter"
	 * service serves a similar purpose.</li>
	 * </ul>
	 *
	 * <h4>Regular expression constructs:</h4>
	 * <ul>
	 * <li><code>match</code> patterns are supported.</li>
	 * <li><code>begin .. end</code> patterns are supported.</li>
	 * <li>The "extended" regex forms <code>(?x)</code> and <code>(?x:...)</code> are supported, but <b>only</b> when they 
	 * apply to the entire regex pattern.</li>
	 * <li>Matching is done using native JavaScript <code>RegExp</code>s. As a result, many features of the Oniguruma regex
	 * engine used by TextMate are <b>not</b> supported.
	 * Unsupported features include:
	 *   <ul><li>Named captures</li>
	 *   <li>Setting flags inside subgroups (eg. <code>(?i:a)b</code>)</li>
	 *   <li>Lookbehind and negative lookbehind</li>
	 *   <li>Subexpression call</li>
	 *   <li>etc.</li>
	 *   </ul>
	 * </li>
	 * </ul>
	 * 
	 * <h4>Scope-assignment constructs:</h4>
	 * <ul>
	 * <li><code>captures, beginCaptures, endCaptures</code> are supported.</li>
	 * <li><code>name</code> and <code>contentName</code> are supported.</li>
	 * </ul>
	 * 
	 * <h4>Other features:</h4>
	 * <ul>
	 * <li><code>applyEndPatternLast</code> is supported.</li>
	 * <li><code>include</code> is supported, but only when it references a rule in the current grammar's <code>repository</code>.
	 * Including <code>$self</code>, <code>$base</code>, or <code>rule.from.another.grammar</code> is <b>not</b> supported.</li>
	 * </ul>
	 * 
	 * @description Creates a new TextMateStyler.
	 * @extends orion.editor.AbstractStyler
	 * @param {orion.editor.TextView} textView The <code>TextView</code> to provide styling for.
	 * @param {Object} grammar The TextMate grammar to use for styling the <code>TextView</code>, as a JavaScript object. You can
	 * produce this object by running a PList-to-JavaScript conversion tool on a TextMate <code>.tmLanguage</code> file.
	 * @param {Object[]} [externalGrammars] Additional grammar objects that will be used to resolve named rule references.
	 */
	function TextMateStyler(textView, grammar, externalGrammars) {
		this.initialize(textView);
		// Copy grammar object(s) since we will mutate them
		this.grammar = clone(grammar);
		this.externalGrammars = externalGrammars ? clone(externalGrammars) : [];
		
		this._styles = {}; /* key: {String} scopeName, value: {String[]} cssClassNames */
		this._tree = null;
		this._allGrammars = {}; /* key: {String} scopeName of grammar, value: {Object} grammar */
		this.preprocess(this.grammar);
	}
	TextMateStyler.prototype = /** @lends orion.editor.TextMateStyler.prototype */ {
		initialize: function(textView) {
			this.textView = textView;
			this.textView.stylerOptions = this;
			var self = this;
			
			this._listener = {
				onModelChanged: function(e) {
					self.onModelChanged(e);
				},
				onDestroy: function(e) {
					self.onDestroy(e);
				},
				onLineStyle: function(e) {
					self.onLineStyle(e);
				},
				onStorage: function(e){
					self.onStorage(e);
				}
			};
			textView.addEventListener("ModelChanged", this._listener.onModelChanged);
			textView.addEventListener("Destroy", this._listener.onDestroy);
			textView.addEventListener("LineStyle", this._listener.onLineStyle);
			textView.redrawLines();
		},
		onDestroy: function(/**eclipse.DestroyEvent*/ e) {
			this.destroy();
		},
		destroy: function() {
			if (this.textView) {
				this.textView.removeEventListener("ModelChanged", this._listener.onModelChanged);
				this.textView.removeEventListener("Destroy", this._listener.onDestroy);
				this.textView.removeEventListener("LineStyle", this._listener.onLineStyle);
				this.textView = null;
			}
			this.grammar = null;
			this._styles = null;
			this._tree = null;
			this._listener = null;
		},
		/** @private */
		preprocess: function(grammar) {
			var stack = [grammar];
			for (; stack.length !== 0; ) {
				var rule = stack.pop();
				if (rule._resolvedRule && rule._typedRule) {
					continue;
				}
//					console.debug("Process " + (rule.include || rule.name));
				
				// Look up include'd rule, create typed *Rule instance
				rule._resolvedRule = this._resolve(rule);
				rule._typedRule = this._createTypedRule(rule);
				
				// Convert the scope names to styles and cache them for later
				this.addStyles(rule.name);
				this.addStyles(rule.contentName);
				this.addStylesForCaptures(rule.captures);
				this.addStylesForCaptures(rule.beginCaptures);
				this.addStylesForCaptures(rule.endCaptures);
				
				if (rule._resolvedRule !== rule) {
					// Add include target
					stack.push(rule._resolvedRule);
				}
				if (rule.patterns) {
					// Add subrules
					for (var i=0; i < rule.patterns.length; i++) {
						stack.push(rule.patterns[i]);
					}
				}
			}
		},
		
		/**
		 * Adds eclipse.Style objects for scope to our _styles cache.
		 * @private
		 * @param {String} scope A scope name, like "constant.character.php".
		 */
		addStyles: function(scope) {
			if (scope && !this._styles[scope]) {
				this._styles[scope] = [];
				var scopeArray = scope.split(".");
				for (var i = 0; i < scopeArray.length; i++) {
					this._styles[scope].push(scopeArray.slice(0, i + 1).join("-"));
				}
			}
		},
		/** @private */
		addStylesForCaptures: function(/**Object*/ captures) {
			for (var prop in captures) {
				if (captures.hasOwnProperty(prop)) {
					var scope = captures[prop].name;
					this.addStyles(scope);
				}
			}
		},
		/**
		 * A rule that contains subrules ("patterns" in TextMate parlance) but has no "begin" or "end".
		 * Also handles top level of grammar.
		 * @private
		 */
		ContainerRule: (function() {
			function ContainerRule(/**Object*/ rule) {
				this.rule = rule;
				this.subrules = rule.patterns;
			}
			ContainerRule.prototype.valueOf = function() { return "aa"; };
			return ContainerRule;
		}()),
		/**
		 * A rule that is delimited by "begin" and "end" matches, which may be separated by any number of
		 * lines. This type of rule may contain subrules, which apply only inside the begin .. end region.
		 * @private
		 */
		BeginEndRule: (function() {
			function BeginEndRule(/**Object*/ rule) {
				this.rule = rule;
				// TODO: the TextMate blog claims that "end" is optional.
				this.beginRegex = RegexUtil.toRegExp(rule.begin);
				this.endRegex = RegexUtil.toRegExp(rule.end);
				this.subrules = rule.patterns || [];
				
				this.endRegexHasBackRef = RegexUtil.hasBackReference(this.endRegex);
				
				// Deal with non-0 captures
				var complexCaptures = RegexUtil.complexCaptures(rule.captures);
				var complexBeginEnd = RegexUtil.complexCaptures(rule.beginCaptures) || RegexUtil.complexCaptures(rule.endCaptures);
				this.isComplex = complexCaptures || complexBeginEnd;
				if (this.isComplex) {
					var bg = RegexUtil.groupify(this.beginRegex);
					this.beginRegex = bg[0];
					this.beginOld2New = bg[1];
					this.beginConsuming = bg[2];
					
					var eg = RegexUtil.groupify(this.endRegex, this.beginOld2New /*Update end's backrefs to begin's new group #s*/);
					this.endRegex = eg[0];
					this.endOld2New = eg[1];
					this.endConsuming = eg[2];
				}
			}
			BeginEndRule.prototype.valueOf = function() { return this.beginRegex; };
			return BeginEndRule;
		}()),
		/**
		 * A rule with a "match" pattern.
		 * @private
		 */
		MatchRule: (function() {
			function MatchRule(/**Object*/ rule) {
				this.rule = rule;
				this.matchRegex = RegexUtil.toRegExp(rule.match);
				this.isComplex = RegexUtil.complexCaptures(rule.captures);
				if (this.isComplex) {
					var mg = RegexUtil.groupify(this.matchRegex);
					this.matchRegex = mg[0];
					this.matchOld2New = mg[1];
					this.matchConsuming = mg[2];
				}
			}
			MatchRule.prototype.valueOf = function() { return this.matchRegex; };
			return MatchRule;
		}()),
		/**
		 * @param {Object} rule A rule from the grammar.
		 * @returns {MatchRule|BeginEndRule|ContainerRule}
		 * @private
		 */
		_createTypedRule: function(rule) {
			if (rule.match) {
				return new this.MatchRule(rule);
			} else if (rule.begin) {
				return new this.BeginEndRule(rule);
			} else {
				return new this.ContainerRule(rule);
			}
		},
		/**
		 * Resolves a rule from the grammar (which may be an include) into the real rule that it points to.
		 * @private
		 */
		_resolve: function(rule) {
			var resolved = rule;
			if (rule.include) {
				if (rule.begin || rule.end || rule.match) {
					throw new Error("Unexpected regex pattern in \"include\" rule " + rule.include);
				}
				var name = rule.include;
				if (name.charAt(0) === "#") {
					resolved = this.grammar.repository && this.grammar.repository[name.substring(1)];
					if (!resolved) { throw new Error("Couldn't find included rule " + name + " in grammar repository"); }
				} else if (name === "$self") {
					resolved = this.grammar;
				} else if (name === "$base") {
					// $base is only relevant when including rules from foreign grammars
					throw new Error("Include \"$base\" is not supported"); 
				} else {
					resolved = this._allGrammars[name];
					if (!resolved) {
						for (var i=0; i < this.externalGrammars.length; i++) {
							var grammar = this.externalGrammars[i];
							if (grammar.scopeName === name) {
								this.preprocess(grammar);
								this._allGrammars[name] = grammar;
								resolved = grammar;
								break;
							}
						}
					}
				}
			}
			return resolved;
		},

		/** @private */
		ContainerNode: (function() {
			function ContainerNode(parent, rule) {
				this.parent = parent;
				this.rule = rule;
				this.children = [];
				
				this.start = null;
				this.end = null;
			}
			ContainerNode.prototype.addChild = function(child) {
				this.children.push(child);
			};
			ContainerNode.prototype.valueOf = function() {
				var r = this.rule;
				return "ContainerNode { " + (r.include || "") + " " + (r.name || "") + (r.comment || "") + "}";
			};
			return ContainerNode;
		}()),
		/** @private */
		BeginEndNode: (function() {
			function BeginEndNode(parent, rule, beginMatch) {
				this.parent = parent;
				this.rule = rule;
				this.children = [];
				
				this.setStart(beginMatch);
				this.end = null; // will be set eventually during parsing (may be EOF)
				this.endMatch = null; // may remain null if we never match our "end" pattern
				
				// Build a new regex if the "end" regex has backrefs since they refer to matched groups of beginMatch
				if (rule.endRegexHasBackRef) {
					this.endRegexSubstituted = RegexUtil.getSubstitutedRegex(rule.endRegex, beginMatch);
				} else {
					this.endRegexSubstituted = null;
				}
			}
			BeginEndNode.prototype.addChild = function(child) {
				this.children.push(child);
			};
			/** @return {Number} This node's index in its parent's "children" list */
			BeginEndNode.prototype.getIndexInParent = function(node) {
				return this.parent ? this.parent.children.indexOf(this) : -1;
			};
			/** @param {RegExp.match} beginMatch */
			BeginEndNode.prototype.setStart = function(beginMatch) {
				this.start = beginMatch.index;
				this.beginMatch = beginMatch;
			};
			/** @param {RegExp.match|Number} endMatchOrLastChar */
			BeginEndNode.prototype.setEnd = function(endMatchOrLastChar) {
				if (endMatchOrLastChar && typeof(endMatchOrLastChar) === "object") {
					var endMatch = endMatchOrLastChar;
					this.endMatch = endMatch;
					this.end = endMatch.index + endMatch[0].length;
				} else {
					var lastChar = endMatchOrLastChar;
					this.endMatch = null;
					this.end = lastChar;
				}
			};
			BeginEndNode.prototype.shiftStart = function(amount) {
				this.start += amount;
				this.beginMatch.index += amount;
			};
			BeginEndNode.prototype.shiftEnd = function(amount) {
				this.end += amount;
				if (this.endMatch) { this.endMatch.index += amount; }
			};
			BeginEndNode.prototype.valueOf = function() {
				return "{" + this.rule.beginRegex + " range=" + this.start + ".." + this.end + "}";
			};
			return BeginEndNode;
		}()),
		/** Pushes rules onto stack such that rules[startFrom] is on top
		 * @private
		 */
		push: function(/**Array*/ stack, /**Array*/ rules) {
			if (!rules) { return; }
			for (var i = rules.length; i > 0; ) {
				stack.push(rules[--i]);
			}
		},
		/** Executes <code>regex</code> on <code>text</code>, and returns the match object with its index 
		 * offset by the given amount.
		 * @returns {RegExp.match}
		 * @private
		 */
		exec: function(/**RegExp*/ regex, /**String*/ text, /**Number*/ offset) {
			var match = regex.exec(text);
			if (match) { match.index += offset; }
			regex.lastIndex = 0; // Just in case
			return match;
		},
		/** @returns {Number} The position immediately following the match.
		 * @private
		 */
		afterMatch: function(/**RegExp.match*/ match) {
			return match.index + match[0].length;
		},
		/**
		 * @returns {RegExp.match} If node is a BeginEndNode and its rule's "end" pattern matches the text.
		 * @private
		 */
		getEndMatch: function(/**Node*/ node, /**String*/ text, /**Number*/ offset) {
			if (node instanceof this.BeginEndNode) {
				var rule = node.rule;
				var endRegex = node.endRegexSubstituted || rule.endRegex;
				if (!endRegex) { return null; }
				return this.exec(endRegex, text, offset);
			}
			return null;
		},
		/** Called once when file is first loaded to build the parse tree. Tree is updated incrementally thereafter 
		 * as buffer is modified.
		 * @private
		 */
		initialParse: function() {
			var last = this.textView.getModel().getCharCount();
			// First time; make parse tree for whole buffer
			var root = new this.ContainerNode(null, this.grammar._typedRule);
			this._tree = root;
			this.parse(this._tree, false, 0);
		},
		onModelChanged: function(/**eclipse.ModelChangedEvent*/ e) {
			var addedCharCount = e.addedCharCount,
			    addedLineCount = e.addedLineCount,
			    removedCharCount = e.removedCharCount,
			    removedLineCount = e.removedLineCount,
			    start = e.start;
			if (!this._tree) {
				this.initialParse();
			} else {
				var model = this.textView.getModel();
				var charCount = model.getCharCount();
				
				// For rs, we must rewind to the line preceding the line 'start' is on. We can't rely on start's
				// line since it may've been changed in a way that would cause a new beginMatch at its lineStart.
				var rs = model.getLineEnd(model.getLineAtOffset(start) - 1); // may be < 0
				var fd = this.getFirstDamaged(rs, rs);
				rs = rs === -1 ? 0 : rs;
				var stoppedAt;
				if (fd) {
					// [rs, re] is the region we need to verify. If we find the structure of the tree
					// has changed in that area, then we may need to reparse the rest of the file.
					stoppedAt = this.parse(fd, true, rs, start, addedCharCount, removedCharCount);
				} else {
					// FIXME: fd == null ?
					stoppedAt = charCount;
				}
				this.textView.redrawRange(rs, stoppedAt);
			}
		},
		/** @returns {BeginEndNode|ContainerNode} The result of taking the first (smallest "start" value) 
		 * node overlapping [start,end] and drilling down to get its deepest damaged descendant (if any).
		 * @private
		 */
		getFirstDamaged: function(start, end) {
			// If start === 0 we actually have to start from the root because there is no position
			// we can rely on. (First index is damaged)
			if (start < 0) {
				return this._tree;
			}
			
			var nodes = [this._tree];
			var result = null;
			while (nodes.length) {
				var n = nodes.pop();
				if (!n.parent /*n is root*/ || this.isDamaged(n, start, end)) {
					// n is damaged by the edit, so go into its children
					// Note: If a node is damaged, then some of its descendents MAY be damaged
					// If a node is undamaged, then ALL of its descendents are undamaged
					if (n instanceof this.BeginEndNode) {
						result = n;
					}
					// Examine children[0] last
					for (var i=0; i < n.children.length; i++) {
						nodes.push(n.children[i]);
					}
				}
			}
			return result || this._tree;
		},
		/** @returns true If <code>n</code> overlaps the interval [start,end].
		 * @private
		 */
		isDamaged: function(/**BeginEndNode*/ n, start, end) {
			// Note strict > since [2,5] doesn't overlap [5,7]
			return (n.start <= end && n.end > start);
		},
		/**
		 * Builds tree from some of the buffer content
		 *
		 * TODO cleanup params
		 * @param {BeginEndNode|ContainerNode} origNode The deepest node that overlaps [rs,rs], or the root.
		 * @param {Boolean} repairing 
		 * @param {Number} rs See _onModelChanged()
		 * @param {Number} [editStart] Only used for repairing === true
		 * @param {Number} [addedCharCount] Only used for repairing === true
		 * @param {Number} [removedCharCount] Only used for repairing === true
		 * @returns {Number} The end position that redrawRange should be called for.
		 * @private
		 */
		parse: function(origNode, repairing, rs, editStart, addedCharCount, removedCharCount) {
			var model = this.textView.getModel();
			var lastLineStart = model.getLineStart(model.getLineCount() - 1);
			var eof = model.getCharCount();
			var initialExpected = this.getInitialExpected(origNode, rs);
			
			// re is best-case stopping point; if we detect change to tree, we must continue past it
			var re = -1;
			if (repairing) {
				origNode.repaired = true;
				origNode.endNeedsUpdate = true;
				var lastChild = origNode.children[origNode.children.length-1];
				var delta = addedCharCount - removedCharCount;
				var lastChildLineEnd = lastChild ? model.getLineEnd(model.getLineAtOffset(lastChild.end + delta)) : -1;
				var editLineEnd = model.getLineEnd(model.getLineAtOffset(editStart + removedCharCount));
				re = Math.max(lastChildLineEnd, editLineEnd);
			}
			re = (re === -1) ? eof : re;
			
			var expected = initialExpected;
			var node = origNode;
			var matchedChildOrEnd = false;
			var pos = rs;
			var redrawEnd = -1;
			while (node && (!repairing || (pos < re))) {
				var matchInfo = this.getNextMatch(model, node, pos);
				if (!matchInfo) {
					// Go to next line, if any
					pos = (pos >= lastLineStart) ? eof : model.getLineStart(model.getLineAtOffset(pos) + 1);
				}
				var match = matchInfo && matchInfo.match,
				    rule = matchInfo && matchInfo.rule,
				    isSub = matchInfo && matchInfo.isSub,
				    isEnd = matchInfo && matchInfo.isEnd;
				if (isSub) {
					pos = this.afterMatch(match);
					if (rule instanceof this.BeginEndRule) {
						matchedChildOrEnd = true;
						// Matched a child. Did we expect that?
						if (repairing && rule === expected.rule && node === expected.parent) {
							// Yes: matched expected child
							var foundChild = expected;
							foundChild.setStart(match);
							// Note: the 'end' position for this node will either be matched, or fixed up by us post-loop
							foundChild.repaired = true;
							foundChild.endNeedsUpdate = true;
							node = foundChild; // descend
							expected = this.getNextExpected(expected, "begin");
						} else {
							if (repairing) {
								// No: matched unexpected child.
								this.prune(node, expected);
								repairing = false;
							}
							
							// Add the new child (will replace 'expected' in node's children list)
							var subNode = new this.BeginEndNode(node, rule, match);
							node.addChild(subNode);
							node = subNode; // descend
						}
					} else {
						// Matched a MatchRule; no changes to tree required
					}
				} else if (isEnd || pos === eof) {
					if (node instanceof this.BeginEndNode) {
						if (match) {
							matchedChildOrEnd = true;
							redrawEnd = Math.max(redrawEnd, node.end); // if end moved up, must still redraw to its old value
							node.setEnd(match);
							pos = this.afterMatch(match);
							// Matched node's end. Did we expect that?
							if (repairing && node === expected && node.parent === expected.parent) {
								// Yes: found the expected end of node
								node.repaired = true;
								delete node.endNeedsUpdate;
								expected = this.getNextExpected(expected, "end");
							} else {
								if (repairing) {
									// No: found an unexpected end
									this.prune(node, expected);
									repairing = false;
								}
							}
						} else {
							// Force-ending a BeginEndNode that runs until eof
							node.setEnd(eof);
							delete node.endNeedsUpdate;
						}
					}
					node = node.parent; // ascend
				}
				
				if (repairing && pos >= re && !matchedChildOrEnd) {
					// Reached re without matching any begin/end => initialExpected itself was removed => repair fail
					this.prune(origNode, initialExpected);
					repairing = false;
				}
			} // end loop
			// TODO: do this for every node we end?
			this.removeUnrepairedChildren(origNode, repairing, rs);
			
			//console.debug("parsed " + (pos - rs) + " of " + model.getCharCount + "buf");
			this.cleanup(repairing, origNode, rs, re, eof, addedCharCount, removedCharCount);
			if (repairing) {
				return Math.max(redrawEnd, pos);
			} else {
				return pos; // where we stopped reparsing
			}
		},
		/** Helper for parse() in the repair case. To be called when ending a node, as any children that
		 * lie in [rs,node.end] and were not repaired must've been deleted.
		 * @private
		 */
		removeUnrepairedChildren: function(node, repairing, start) {
			if (repairing) {
				var children = node.children;
				var removeFrom = -1;
				for (var i=0; i < children.length; i++) {
					var child = children[i];
					if (!child.repaired && this.isDamaged(child, start, Number.MAX_VALUE /*end doesn't matter*/)) {
						removeFrom = i;
						break;
					}
				}
				if (removeFrom !== -1) {
					node.children.length = removeFrom;
				}
			}
		},
		/** Helper for parse() in the repair case
		 * @private
		 */
		cleanup: function(repairing, origNode, rs, re, eof, addedCharCount, removedCharCount) {
			var i, node, maybeRepairedNodes;
			if (repairing) {
				// The repair succeeded, so update stale begin/end indices by simple translation.
				var delta = addedCharCount - removedCharCount;
				// A repaired node's end can't exceed re, but it may exceed re-delta+1.
				// TODO: find a way to guarantee disjoint intervals for repaired vs unrepaired, then stop using flag
				var maybeUnrepairedNodes = this.getIntersecting(re-delta+1, eof);
				maybeRepairedNodes = this.getIntersecting(rs, re);
				// Handle unrepaired nodes. They are those intersecting [re-delta+1, eof] that don't have the flag
				for (i=0; i < maybeUnrepairedNodes.length; i++) {
					node = maybeUnrepairedNodes[i];
					if (!node.repaired && node instanceof this.BeginEndNode) {
						node.shiftEnd(delta);
						node.shiftStart(delta);
					}
				}
				// Translate 'end' index of repaired node whose 'end' was not matched in loop (>= re)
				for (i=0; i < maybeRepairedNodes.length; i++) {
					node = maybeRepairedNodes[i];
					if (node.repaired && node.endNeedsUpdate) {
						node.shiftEnd(delta);
					}
					delete node.endNeedsUpdate;
					delete node.repaired;
				}
			} else {
				// Clean up after ourself
				maybeRepairedNodes = this.getIntersecting(rs, re);
				for (i=0; i < maybeRepairedNodes.length; i++) {
					delete maybeRepairedNodes[i].repaired;
				}
			}
		},
		/**
		 * @param model {orion.editor.TextModel}
		 * @param {Node} node
		 * @param {Number} pos
		 * @param {Boolean} [matchRulesOnly] Optional, if true only "match" subrules will be considered.
		 * @returns {Object} A match info object with properties:
		 * {Boolean} isEnd
		 * {Boolean} isSub
		 * {RegExp.match} match
		 * {(Match|BeginEnd)Rule} rule
		 * @private
		 */
		getNextMatch: function(model, node, pos, matchRulesOnly) {
			var lineIndex = model.getLineAtOffset(pos);
			var lineEnd = model.getLineEnd(lineIndex);
			var line = model.getText(pos, lineEnd);

			var stack = [],
			    expandedContainers = [],
			    subMatches = [],
			    subrules = [];
			this.push(stack, node.rule.subrules);
			while (stack.length) {
				var next = stack.length ? stack.pop() : null;
				var subrule = next && next._resolvedRule._typedRule;
				if (subrule instanceof this.ContainerRule && expandedContainers.indexOf(subrule) === -1) {
					// Expand ContainerRule by pushing its subrules on
					expandedContainers.push(subrule);
					this.push(stack, subrule.subrules);
					continue;
				}
				if (subrule && matchRulesOnly && !(subrule.matchRegex)) {
					continue;
				}
				var subMatch = subrule && this.exec(subrule.matchRegex || subrule.beginRegex, line, pos);
				if (subMatch) {
					subMatches.push(subMatch);
					subrules.push(subrule);
				}
			}

			var bestSub = Number.MAX_VALUE,
			    bestSubIndex = -1;
			for (var i=0; i < subMatches.length; i++) {
				var match = subMatches[i];
				if (match.index < bestSub) {
					bestSub = match.index;
					bestSubIndex = i;
				}
			}
			
			if (!matchRulesOnly) {
				// See if the "end" pattern of the active begin/end node matches.
				// TODO: The active begin/end node may not be the same as the node that holds the subrules
				var activeBENode = node;
				var endMatch = this.getEndMatch(node, line, pos);
				if (endMatch) {
					var doEndLast = activeBENode.rule.applyEndPatternLast;
					var endWins = bestSubIndex === -1 || (endMatch.index < bestSub) || (!doEndLast && endMatch.index === bestSub);
					if (endWins) {
						return {isEnd: true, rule: activeBENode.rule, match: endMatch};
					}
				}
			}
			return bestSubIndex === -1 ? null : {isSub: true, rule: subrules[bestSubIndex], match: subMatches[bestSubIndex]};
		},
		/**
		 * Gets the node corresponding to the first match we expect to see in the repair.
		 * @param {BeginEndNode|ContainerNode} node The node returned via getFirstDamaged(rs,rs) -- may be the root.
		 * @param {Number} rs See _onModelChanged()
		 * Note that because rs is a line end (or 0, a line start), it will intersect a beginMatch or 
		 * endMatch either at their 0th character, or not at all. (begin/endMatches can't cross lines).
		 * This is the only time we rely on the start/end values from the pre-change tree. After this 
		 * we only look at node ordering, never use the old indices.
		 * @returns {Node}
		 * @private
		 */
		getInitialExpected: function(node, rs) {
			// TODO: Kind of weird.. maybe ContainerNodes should have start & end set, like BeginEndNodes
			var i, child;
			if (node === this._tree) {
				// get whichever of our children comes after rs
				for (i=0; i < node.children.length; i++) {
					child = node.children[i]; // BeginEndNode
					if (child.start >= rs) {
						return child;
					}
				}
			} else if (node instanceof this.BeginEndNode) {
				if (node.endMatch) {
					// Which comes next after rs: our nodeEnd or one of our children?
					var nodeEnd = node.endMatch.index;
					for (i=0; i < node.children.length; i++) {
						child = node.children[i]; // BeginEndNode
						if (child.start >= rs) {
							break;
						}
					}
					if (child && child.start < nodeEnd) {
						return child; // Expect child as the next match
					}
				} else {
					// No endMatch => node goes until eof => it end should be the next match
				}
			}
			return node; // We expect node to end, so it should be the next match
		},
		/**
		 * Helper for repair() to tell us what kind of event we expect next.
		 * @param {Node} expected Last value returned by this method.
		 * @param {String} event "begin" if the last value of expected was matched as "begin",
		 *  or "end" if it was matched as an end.
		 * @returns {Node} The next expected node to match, or null.
		 * @private
		 */
		getNextExpected: function(/**Node*/ expected, event) {
			var node = expected;
			if (event === "begin") {
				var child = node.children[0];
				if (child) {
					return child;
				} else {
					return node;
				}
			} else if (event === "end") {
				var parent = node.parent;
				if (parent) {
					var nextSibling = parent.children[parent.children.indexOf(node) + 1];
					if (nextSibling) {
						return nextSibling;
					} else {
						return parent;
					}
				}
			}
			return null;
		},
		/** Helper for parse() when repairing. Prunes out the unmatched nodes from the tree so we can continue parsing.
		 * @private
		 */
		prune: function(/**BeginEndNode|ContainerNode*/ node, /**Node*/ expected) {
			var expectedAChild = expected.parent === node;
			if (expectedAChild) {
				// Expected child wasn't matched; prune it and all siblings after it
				node.children.length = expected.getIndexInParent();
			} else if (node instanceof this.BeginEndNode) {
				// Expected node to end but it didn't; set its end unknown and we'll match it eventually
				node.endMatch = null;
				node.end = null;
			}
			// Reparsing from node, so prune the successors outside of node's subtree
			if (node.parent) {
				node.parent.children.length = node.getIndexInParent() + 1;
			}
		},
		onLineStyle: function(/**eclipse.LineStyleEvent*/ e) {
			function byStart(r1, r2) {
				return r1.start - r2.start;
			}
			
			if (!this._tree) {
				// In some cases it seems onLineStyle is called before onModelChanged, so we need to parse here
				this.initialParse();
			}
			var lineStart = e.lineStart,
			    model = this.textView.getModel(),
			    lineEnd = model.getLineEnd(e.lineIndex);
			
			var rs = model.getLineEnd(model.getLineAtOffset(lineStart) - 1); // may be < 0
			var node = this.getFirstDamaged(rs, rs);
			
			var scopes = this.getLineScope(model, node, lineStart, lineEnd);
			e.ranges = this.toStyleRanges(scopes);
			// Editor requires StyleRanges must be in ascending order by 'start', or else some will be ignored
			e.ranges.sort(byStart);
		},
		/** Runs parse algorithm on [start, end] in the context of node, assigning scope as we find matches.
		 * @private
		 */
		getLineScope: function(model, node, start, end) {
			var pos = start;
			var expected = this.getInitialExpected(node, start);
			var scopes = [],
			    gaps = [];
			while (node && (pos < end)) {
				var matchInfo = this.getNextMatch(model, node, pos);
				if (!matchInfo) { 
					break; // line is over
				}
				var match = matchInfo && matchInfo.match,
				    rule = matchInfo && matchInfo.rule,
				    isSub = matchInfo && matchInfo.isSub,
				    isEnd = matchInfo && matchInfo.isEnd;
				if (match.index !== pos) {
					// gap [pos..match.index]
					gaps.push({ start: pos, end: match.index, node: node});
				}
				if (isSub) {
					pos = this.afterMatch(match);
					if (rule instanceof this.BeginEndRule) {
						// Matched a "begin", assign its scope and descend into it
						this.addBeginScope(scopes, match, rule);
						node = expected; // descend
						expected = this.getNextExpected(expected, "begin");
					} else {
						// Matched a child MatchRule;
						this.addMatchScope(scopes, match, rule);
					}
				} else if (isEnd) {
					pos = this.afterMatch(match);
					// Matched and "end", assign its end scope and go up
					this.addEndScope(scopes, match, rule);
					expected = this.getNextExpected(expected, "end");
					node = node.parent; // ascend
				}
			}
			if (pos < end) {
				gaps.push({ start: pos, end: end, node: node });
			}
			var inherited = this.getInheritedLineScope(gaps, start, end);
			return scopes.concat(inherited);
		},
		/** @private */
		getInheritedLineScope: function(gaps, start, end) {
			var scopes = [];
			for (var i=0; i < gaps.length; i++) {
				var gap = gaps[i];
				var node = gap.node;
				while (node) {
					// if node defines a contentName or name, apply it
					var rule = node.rule.rule;
					var name = rule.name,
					    contentName = rule.contentName;
					// TODO: if both are given, we don't resolve the conflict. contentName always wins
					var scope = contentName || name;
					if (scope) {
						this.addScopeRange(scopes, gap.start, gap.end, scope);
						break;
					}
					node = node.parent;
				}
			}
			return scopes;
		},
		/** @private */
		addBeginScope: function(scopes, match, typedRule) {
			var rule = typedRule.rule;
			this.addCapturesScope(scopes, match, (rule.beginCaptures || rule.captures), typedRule.isComplex, typedRule.beginOld2New, typedRule.beginConsuming);
		},
		/** @private */
		addEndScope: function(scopes, match, typedRule) {
			var rule = typedRule.rule;
			this.addCapturesScope(scopes, match, (rule.endCaptures || rule.captures), typedRule.isComplex, typedRule.endOld2New, typedRule.endConsuming);
		},
		/** @private */
		addMatchScope: function(scopes, match, typedRule) {
			var rule = typedRule.rule,
			    name = rule.name,
			    captures = rule.captures;
			if (captures) {	
				// captures takes priority over name
				this.addCapturesScope(scopes, match, captures, typedRule.isComplex, typedRule.matchOld2New, typedRule.matchConsuming);
			} else {
				this.addScope(scopes, match, name);
			}
		},
		/** @private */
		addScope: function(scopes, match, name) {
			if (!name) { return; }
			scopes.push({start: match.index, end: this.afterMatch(match), scope: name });
		},
		/** @private */
		addScopeRange: function(scopes, start, end, name) {
			if (!name) { return; }
			scopes.push({start: start, end: end, scope: name });
		},
		/** @private */
		addCapturesScope: function(/**Array*/scopes, /*RegExp.match*/ match, /**Object*/captures, /**Boolean*/isComplex, /**Object*/old2New, /**Object*/consuming) {
			if (!captures) { return; }
			if (!isComplex) {
				this.addScope(scopes, match, captures[0] && captures[0].name);
			} else {
				// apply scopes captures[1..n] to matching groups [1]..[n] of match
				
				// Sum up the lengths of preceding consuming groups to get the start offset for each matched group.
				var newGroupStarts = {1: 0};
				var sum = 0;
				for (var num = 1; match[num] !== undefined; num++) {
					if (consuming[num] !== undefined) {
						sum += match[num].length;
					}
					if (match[num+1] !== undefined) {
						newGroupStarts[num + 1] = sum;
					}
				}
				// Map the group numbers referred to in captures object to the new group numbers, and get the actual matched range.
				var start = match.index;
				for (var oldGroupNum = 1; captures[oldGroupNum]; oldGroupNum++) {
					var scope = captures[oldGroupNum].name;
					var newGroupNum = old2New[oldGroupNum];
					var groupStart = start + newGroupStarts[newGroupNum];
					// Not every capturing group defined in regex need match every time the regex is run.
					// eg. (a)|b matches "b" but group 1 is undefined
					if (typeof match[newGroupNum] !== "undefined") {
						var groupEnd = groupStart + match[newGroupNum].length;
						this.addScopeRange(scopes, groupStart, groupEnd, scope);
					}
				}
			}
		},
		/** @returns {Node[]} In depth-first order
		 * @private
		 */
		getIntersecting: function(start, end) {
			var result = [];
			var nodes = this._tree ? [this._tree] : [];
			while (nodes.length) {
				var n = nodes.pop();
				var visitChildren = false;
				if (n instanceof this.ContainerNode) {
					visitChildren = true;
				} else if (this.isDamaged(n, start, end)) {
					visitChildren = true;
					result.push(n);
				}
				if (visitChildren) {
					var len = n.children.length;
//					for (var i=len-1; i >= 0; i--) {
//						nodes.push(n.children[i]);
//					}
					for (var i=0; i < len; i++) {
						nodes.push(n.children[i]);
					}
				}
			}
			return result.reverse();
		},
		/**
		 * Applies the grammar to obtain the {@link eclipse.StyleRange[]} for the given line.
		 * @returns {eclipse.StyleRange[]}
		 * @private
		 */
		toStyleRanges: function(/**ScopeRange[]*/ scopeRanges) {
			var styleRanges = [];
			for (var i=0; i < scopeRanges.length; i++) {
				var scopeRange = scopeRanges[i];
				var classNames = this._styles[scopeRange.scope];
				if (!classNames) { throw new Error("styles not found for " + scopeRange.scope); }
				var classNamesString = classNames.join(" ");
				styleRanges.push({start: scopeRange.start, end: scopeRange.end, style: {styleClass: classNamesString}});
//				console.debug("{start " + styleRanges[i].start + ", end " + styleRanges[i].end + ", style: " + styleRanges[i].style.styleClass + "}");
			}
			return styleRanges;
		}
	};
	
	return {
		RegexUtil: RegexUtil,
		TextMateStyler: TextMateStyler
	};
});

/******************************************************************************* 
 * @license
 * Copyright (c) 2011, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation 
 ******************************************************************************/
/*eslint-env browser, amd*/
define("orion/editor/AsyncStyler", ['i18n!orion/editor/nls/messages', 'orion/editor/annotations'], function(messages, mAnnotations) {
	var SERVICE_NAME = "orion.edit.highlighter";
	var HIGHLIGHT_ERROR_ANNOTATION = "orion.annotation.highlightError";
	var badServiceError = SERVICE_NAME + " service must be an event emitter";
	mAnnotations.AnnotationType.registerType(HIGHLIGHT_ERROR_ANNOTATION, {
		title: messages.syntaxError,
		html: "<div class='annotationHTML error'></div>",
		rangeStyle: {styleClass: "annotationRange error"}
	});

	function isRelevant(serviceReference) {
		return serviceReference.getProperty("objectClass").indexOf(SERVICE_NAME) !== -1 &&
				serviceReference.getProperty("type") === "highlighter";
	}

	/**
	 * @name orion.editor.AsyncStyler
	 * @class Provides asynchronous styling for a TextView using registered "highlighter" services.
	 * @description Creates an <code>AsyncStyler</code>. An AsyncStyler allows style information to be sent to a 
	 * <code>TextView</code> asynchronously through the service segistry. Style is generated by <em>style providers</em>, which are services
	 * having the <code>'orion.edit.highlighter'</code> service name and a <code>type</code> === <code>'highlighter'</code> service property.
	 *
	 * <p>A style provider monitors changes to the TextView (typically using an <code>orion.edit.model</code> service) and 
	 * dispatches a service event of type <code>'orion.edit.highlighter.styleReady'</code> when it has style information to send.
	 * The event carries a payload of style information for one or more lines in the TextView. The AsyncStyler then applies
	 * the style information fron the event to the TextView using the {@link orion.editor.TextView#event:onLineStyle} API.
	 * </p>
	 *
	 * <p>Applying style information may cause the TextView to be redrawn, which is potentially expensive. To minimize the 
	 * number of redraws, a provider should provide style for many lines in a single StyleReadyEvent.
	 * </p>
	 *
	 * @param {orion.editor.TextView} textView The TextView to style.
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry The ServiceRegistry to monitor for highlighter services.
	 * @param {orion.editor.AnnotationModel} [annotationModel] The Annotation Model to use for creating error and warning annotations.
	 * @see orion.editor.StyleReadyEvent
	 */
	function AsyncStyler(textView, serviceRegistry, annotationModel) {
		this.initialize(textView, serviceRegistry, annotationModel);
		this.lineStyles = [];
	}
	AsyncStyler.prototype = /** @lends orion.editor.AsyncStyler.prototype*/ {
		/** @private */
		initialize: function(textView, serviceRegistry, annotationModel) {
			this.textView = textView;
			this.serviceRegistry = serviceRegistry;
			this.annotationModel = annotationModel; 
			this.services = [];

			var self = this;
			this.listener = {
				onModelChanging: function(e) {
					self.onModelChanging(e);
				},
				onModelChanged: function(e) {
					self.onModelChanged(e);
				},
				onDestroy: function(e) {
					self.onDestroy(e);
				},
				onLineStyle: function(e) {
					self.onLineStyle(e);
				},
				onStyleReady: function(e) {
					self.onStyleReady(e);
				},
				onServiceAdded: function(serviceEvent) {
					self.onServiceAdded(serviceEvent.serviceReference, self.serviceRegistry.getService(serviceEvent.serviceReference));
				},
				onServiceRemoved: function(serviceEvent) {
					self.onServiceRemoved(serviceEvent.serviceReference, self.serviceRegistry.getService(serviceEvent.serviceReference));
				}
			};
			textView.addEventListener("ModelChanging", this.listener.onModelChanging);
			textView.addEventListener("ModelChanged", this.listener.onModelChanged);
			textView.addEventListener("Destroy", this.listener.onDestroy);
			textView.addEventListener("LineStyle", this.listener.onLineStyle);
			serviceRegistry.addEventListener("registered", this.listener.onServiceAdded);
			serviceRegistry.addEventListener("unregistering", this.listener.onServiceRemoved);

			var serviceRefs = serviceRegistry.getServiceReferences(SERVICE_NAME);
			for (var i = 0; i < serviceRefs.length; i++) {
				var serviceRef = serviceRefs[i];
				if (isRelevant(serviceRef)) {
					this.addServiceListener(serviceRegistry.getService(serviceRef));
				}
			}
		},
		/** @private */
		onDestroy: function(e) {
			this.destroy();
		},
		/** Deactivates this styler and removes any listeners it registered. */
		destroy: function() {
			if (this.textView) {
				this.textView.removeEventListener("ModelChanging", this.listener.onModelChanging);
				this.textView.removeEventListener("ModelChanged", this.listener.onModelChanged);
				this.textView.removeEventListener("Destroy", this.listener.onDestroy);
				this.textView.removeEventListener("LineStyle", this.listener.onLineStyle);
				this.textView = null;
			}
			if (this.services) {
				for (var i = 0; i < this.services.length; i++) {
					this.removeServiceListener(this.services[i]);
				}
				this.services = null;
			}
			if (this.serviceRegistry) {
				this.serviceRegistry.removeEventListener("registered", this.listener.onServiceAdded);
				this.serviceRegistry.removeEventListener("unregistering", this.listener.onServiceRemoved);
				this.serviceRegistry = null;
			}
			this.listener = null;
			this.lineStyles = null;
		},
		/** @private */
		onModelChanging: function(e) {
			this.startLine = this.textView.getModel().getLineAtOffset(e.start);
		},
		/** @private */
		onModelChanged: function(e) {
			var startLine = this.startLine;
			if (e.addedLineCount || e.removedLineCount) {
				Array.prototype.splice.apply(this.lineStyles, [startLine, e.removedLineCount].concat(this._getEmptyStyle(e.addedLineCount)));
			}
		},
		/** @private */
		onStyleReady: function(e) {
			var style = e.lineStyles || e.style;
			var min = Number.MAX_VALUE, max = -1;
			var model = this.textView.getModel();
			for (var lineIndex in style) {
				if (Object.prototype.hasOwnProperty.call(style, lineIndex)) {
					this.lineStyles[lineIndex] = style[lineIndex];
					min = Math.min(min, lineIndex);
					max = Math.max(max, lineIndex);
				}
			}
//			console.debug("Got style for lines " + (min+1) + " to " + (max+1));
			min = Math.max(min, 0);
			max = Math.min(max, model.getLineCount());
			
			var annotationModel = this.annotationModel;
			if (annotationModel) {
				var annos = annotationModel.getAnnotations(model.getLineStart(min), model.getLineEnd(max));
				var toRemove = [];
				while (annos.hasNext()) {
					var anno = annos.next();
					if (anno.type === HIGHLIGHT_ERROR_ANNOTATION) {
						toRemove.push(anno);
					}
				}
				var toAdd = [];
				for (var i = min; i <= max; i++) {
					lineIndex = i;
					var lineStyle = this.lineStyles[lineIndex], errors = lineStyle && lineStyle.errors;
					var lineStart = model.getLineStart(lineIndex);
					if (errors) {
						for (var j=0; j < errors.length; j++) {
							var err = errors[j];
							toAdd.push(mAnnotations.AnnotationType.createAnnotation(HIGHLIGHT_ERROR_ANNOTATION, err.start + lineStart, err.end + lineStart));
						}
					}
				}
				annotationModel.replaceAnnotations(toRemove, toAdd);
			}
			this.textView.redrawLines(min, max + 1);
		},
		/** @private */
		onLineStyle: function(e) {
			function _toDocumentOffset(ranges, lineStart) {
				var len = ranges.length, result = [];
				for (var i=0; i < len; i++) {
					var r = ranges[i];
					result.push({
						start: r.start + lineStart,
						end: r.end + lineStart,
						style: r.style
					});
				}
				return result;
			}
			var style = this.lineStyles[e.lineIndex];
			if (style) {
				// The 'ranges', 'errors' are of type {@link orion.editor.LineStyleEvent#ranges}, except the 
				// start and end indices are line-relative offsets, not document-relative.
				if (style.ranges) { e.ranges = _toDocumentOffset(style.ranges, e.lineStart); }
				else if (style.style) { e.style = style.style; }
			}
		},
		/** @private */
		_getEmptyStyle: function(n) {
			var result = [];
			for (var i=0; i < n; i++) {
				result.push(null);
			}
			return result;
		},
		/**
		 * Sets the content type ID for which style information will be provided. The ID will be passed to all style provider 
		 * services monitored by this AsyncStyler by calling the provider's own <code>setContentType(contentTypeId)</code> method.
		 *
		 * <p>In this way, a single provider service can be registered for several content types, and select different logic for 
		 * each type.</p>
		 * @param {String} contentTypeId The Content Type ID describing the kind of file currently being edited in the TextView.
		 * @see orion.core.ContentType
		 */
		setContentType: function(contentTypeId) {
			this.contentType = contentTypeId;
			if (this.services) {
				for (var i = 0; i < this.services.length; i++) {
					var service = this.services[i];
					if (service.setContentType) {
						var progress = this.serviceRegistry.getService("orion.page.progress");
						if(progress){
							progress.progress(service.setContentType(this.contentType), "Styling content type: " + this.contentType.id ? this.contentType.id: this.contentType);
						} else {
							service.setContentType(this.contentType);
						}
					}
				}
			}
		},
		/** @private */
		onServiceAdded: function(serviceRef, service) {
			if (isRelevant(serviceRef)) {
				this.addServiceListener(service);
			}
		},
		/** @private */
		onServiceRemoved: function(serviceRef, service) {
			if (this.services.indexOf(service) !== -1) {
				this.removeServiceListener(service);
			}
		},
		/** @private */
		addServiceListener: function(service) {
			if (typeof service.addEventListener === "function") {
				service.addEventListener("orion.edit.highlighter.styleReady", this.listener.onStyleReady);
				this.services.push(service);
				if (service.setContentType && this.contentType) {
					var progress = this.serviceRegistry.getService("orion.page.progress");
					if(progress){
						progress.progress(service.setContentType(this.contentType), "Styling content type: " + this.contentType.id ? this.contentType.id: this.contentType);
					} else {
						service.setContentType(this.contentType);
					}
				}
			} else {
				if (typeof console !== "undefined") {
					console.log(new Error(badServiceError));
				}
			}
		},
		/** @private */
		removeServiceListener: function(service) {
			if (typeof service.removeEventListener === "function") {
				service.removeEventListener("orion.edit.highlighter.styleReady", this.listener.onStyleReady);
				var serviceIndex = this.services.indexOf(service);
				if (serviceIndex !== -1) {
					this.services.splice(serviceIndex, 1);
				}
			} else {
				if (typeof console !== "undefined") {
					console.log(new Error(badServiceError));
				}
			}
		}
	};

	/**
	 * @name orion.editor.StyleReadyEvent
	 * @class Represents the styling for a range of lines, as provided by a service.
	 * @description Represents the styling for a range of lines, as provided by a service.
	 * @property {Object} lineStyles A map of style information. Each key of the map is a line index, and the value 
	 * is a {@link orion.editor.StyleReadyEvent#LineStyle} giving the style information for the line.
	 */
	/**
	 * @name orion.editor.StyleReadyEvent#LineStyle
	 * @class Represents style information for a line.
	 * @description Represents style information for a line.
	 * <p>Note that the offsets given in the {@link #ranges} and {@link #errors} properties are relative to the start of the
	 * line that this LineStyle is associated with, not the start of the document.</p>
	 * @property {orion.editor.StyleRange[]} ranges Optional; Gives the styles for this line.
	 * @property {orion.editor.StyleRange[]} errors Optional; Gives the error styles for this line. Error styles will be 
	 * presented as annotations in the UI.
	 */

	return AsyncStyler;
});
/******************************************************************************* 
 * @license
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/

/*eslint-env browser, amd*/
define('orion/highlight',['examples/editor/textStyler', 'orion/editor/textStyler', 'orion/editor/textMateStyler', 'orion/editor/AsyncStyler', 'orion/Deferred'], 
		function(mTextStyler, mTextStyler2, mTextMateStyler, AsyncStyler, Deferred) {

	var NEW = 1;

	/**
	 * Returns a promise that will provide a styler for the given content type.
	 * @static
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry
	 * @param {orion.core.ContentTypeRegistry} contentTypeService
	 * @param {orion.core.ContentType} contentType
	 * @param {orion.editor.TextView} textView
	 * @param {orion.editor.AnnotationModel} annotationModel
	 * @param {String} [fileName] Deprecated.
	 * @param {Boolean} [allowAsync=true]
	 * @returns {orion.Deferred}
	 */
	function createStyler(serviceRegistry, contentTypeService, contentType, textView, annotationModel, fileName, allowAsync) {
		// Returns a promise (if provider matches) or null (if it doesn't match).
		function getPromise(provider, extension) {
			var contentTypeIds = provider.getProperty("contentType") || provider.getProperty("contentTypes"), //$NON-NLS-1$ //$NON-NLS-0$
			    fileTypes = provider.getProperty("fileTypes"); // backwards compatibility //$NON-NLS-0$
			if (contentTypeIds) {
				return Deferred.when(contentTypeService.isSomeExtensionOf(contentType, contentTypeIds)).then(
					function (isMatch) {
						return isMatch ? provider : null;
					});
			} else if (extension && fileTypes && fileTypes.indexOf(extension) !== -1) {
				var d = new Deferred();
				d.resolve(provider);
				return d;
			}
			return null;
		}
		function isAllowed(provider) {
			return allowAsync || provider.getProperty("type") !== "highlighter"; //$NON-NLS-1$ //$NON-NLS-0$
		}
		function createDefaultStyler(contentType) {
			var styler = null;
			switch (contentType && contentType.id) {
				case "application/javascript": //$NON-NLS-0$
				case "application/json": //$NON-NLS-0$
					styler = new mTextStyler.TextStyler(textView, "js", annotationModel); //$NON-NLS-0$
					break;
				case "text/x-java-source": //$NON-NLS-0$
					styler = new mTextStyler.TextStyler(textView, "java", annotationModel); //$NON-NLS-0$
					break;
				case "text/css": //$NON-NLS-0$
					styler = new mTextStyler.TextStyler(textView, "css", annotationModel); //$NON-NLS-0$
					break;
			}
			return styler;
		}
		// Check default styler
		if (!NEW) {
			var styler = createDefaultStyler(contentType);
			if (styler) {
				var result = new Deferred();
				result.resolve(styler);
				return result;
			}
		}

		if (NEW && !this.orionGrammars) {
			this.orionGrammars = {};
		}

		// Check services
		var extension = fileName && fileName.split(".").pop().toLowerCase(); //$NON-NLS-0$
		var serviceRefs = serviceRegistry.getServiceReferences("orion.edit.highlighter"); //$NON-NLS-0$
		var textmateGrammars = [], promises = [];
		for (var i = 0; i < serviceRefs.length; i++) {
			var serviceRef = serviceRefs[i];
			var type = serviceRef.getProperty("type");
			if (type === "grammar" || (!NEW && typeof type === "undefined")) { //$NON-NLS-1$ //$NON-NLS-0$
				textmateGrammars.push(serviceRef.getProperty("grammar")); //$NON-NLS-0$
			} else if (type !== "highlighter") {
				var id = serviceRef.getProperty("id");
				if (id && !this.orionGrammars[id]) {
					this.orionGrammars[id] = {
						id: id,
						contentTypes: serviceRef.getProperty("contentTypes"),
						patterns: serviceRef.getProperty("patterns"),
						repository: serviceRef.getProperty("repository")
					};
				}
			}
			var promise = getPromise(serviceRef, extension);
			if (promise) {
				promises.push(promise);
			}
		}
		return Deferred.all(promises, function(error) {return {_error: error};}).then(function(promises) {
			// Take the last matching provider. Could consider ranking them here.
			var provider;
			for (var i = promises.length - 1; i >= 0; i--) {
				var promise = promises[i];
				if (promise && !promise._error && isAllowed(promise)) {
					provider = promise;
					break;
				}
			}
			var styler;
			if (provider) {
				var type = provider.getProperty("type"); //$NON-NLS-0$
				if (type === "highlighter") { //$NON-NLS-0$
					styler = new AsyncStyler(textView, serviceRegistry, annotationModel);
					styler.setContentType(contentType);
				} else if (type === "grammar" || (!NEW && typeof type === "undefined")) { //$NON-NLS-1$ //$NON-NLS-0$
					var textmateGrammar = provider.getProperty("grammar"); //$NON-NLS-0$
					styler = new mTextMateStyler.TextMateStyler(textView, textmateGrammar, textmateGrammars);
				} else {
					if (NEW) {
						var grammars = [];
						for(var key in this.orionGrammars) {
						    grammars.push(this.orionGrammars[key]);
						}
						var stylerAdapter = new mTextStyler2.createPatternBasedAdapter(grammars, provider.getProperty("id"), contentType.id);  //$NON-NLS-0$
						styler = new mTextStyler2.TextStyler(textView, annotationModel, stylerAdapter);
					}
				}
			} else if (NEW && (!contentType || contentType.id !== "text/x-markdown")) { //$NON-NLS-0$
				/* even if a grammar was not found create a default pattern adapter so that whitespace will be shown if appropriate */
				stylerAdapter = new mTextStyler2.createPatternBasedAdapter([], "");
				styler = new mTextStyler2.TextStyler(textView, annotationModel, stylerAdapter);
			}
			return styler;
		});
	}

	/**
	 * @name orion.highlight.SyntaxHighlighter
	 * @class
	 * @description 
	 * <p>Requires service {@link orion.core.ContentTypeRegistry}</p>
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry Registry to look up highlight providers from.
	 * @param {orion.core.ContentType} [contentTypeService=null] A stand alone content type service that is not registered in theservice registry.
	 */
	function SyntaxHighlighter(serviceRegistry, contentTypeService) {
		this.serviceRegistry = serviceRegistry;
		this.contentTypeService = contentTypeService;
		this.styler = null;
	}
	SyntaxHighlighter.prototype = /** @lends orion.highlight.SyntaxHighlighter.prototype */ {
		/**
		 * @param {orion.core.ContentType} contentType
		 * @param {orion.editor.TextView} textView
		 * @param {orion.editor.AnnotationModel} annotationModel
		 * @param {String} [fileName] <i>Deprecated.</i> For backwards compatibility only, service-contributed highlighters
		 * will be checked against the file extension instead of contentType.
		 * @param {Boolean} [allowAsync=true] If true, plugin-contributed asynchronous highlighters (i.e. <code>type == "highlighter"</code>
		 * will be consulted. If false, only rule-based highlighters will be consulted.
		 * @returns {orion.Deferred} A promise that is resolved when this highlighter has been set up.
		 */
		setup: function(fileContentType, textView, annotationModel, fileName, allowAsync) {
			allowAsync = typeof allowAsync === "undefined" ? true : allowAsync; //$NON-NLS-0$
			if (this.styler) {
				if (this.styler.destroy) {
					this.styler.destroy();
				}
				this.styler = null;
			}
			var self = this;
			return createStyler(this.serviceRegistry, this.contentTypeService ? this.contentTypeService : this.serviceRegistry.getService("orion.core.contentTypeRegistry"), //$NON-NLS-0$
				fileContentType, textView, annotationModel, fileName, allowAsync).then(
					function(styler) {
						self.styler = styler;
						return styler;
					});
		},
		getStyler: function() {
			return this.styler;
		}
	};
	return {
		createStyler: createStyler,
		SyntaxHighlighter: SyntaxHighlighter
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2013, 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/

/*eslint-env browser, amd*/
define('orion/markOccurrences',[
	'orion/Deferred'
], function(Deferred) {

	function MarkOccurrences(serviceRegistry, inputManager, editor) {
		this.registry = serviceRegistry;
		this.inputManager = inputManager;
		this.editor = editor;
	}
	MarkOccurrences.prototype = {
		/**
		 * Sets whether mark occurrences are shown.
		 * 
		 * @param visible whether mark occurrences are shown
		 */
		setOccurrencesVisible: function(visible) {
			if (this.occurrencesVisible === visible) {
				return;
			}
			this.occurrencesVisible = visible;
			if (!visible) {
				this.editor.showOccurrences([]);
			}
		},
		/* Looks up applicable references of occurrence service, calls references, calls the editor to show the occurrences. */
		findOccurrences: function() {
			function getServiceRefs(registry, contentType, title) {
				var contentTypeService = registry.getService("orion.core.contentTypeRegistry"); //$NON-NLS-0$
				function getFilteredServiceRef(registry, sReference, contentType) {
					var contentTypeIds = sReference.getProperty("contentType"); //$NON-NLS-0$
					return contentTypeService.isSomeExtensionOf(contentType, contentTypeIds).then(function(result) {
						return result ? sReference : null;
					});
				}
				var serviceRefs = registry.getServiceReferences("orion.edit.occurrences"); //$NON-NLS-0$
				var filteredServiceRefs = [];
				for (var i=0; i < serviceRefs.length; i++) {
					var serviceRef = serviceRefs[i];
					var pattern = serviceRef.getProperty("pattern"); // backwards compatibility //$NON-NLS-0$
					if (serviceRef.getProperty("contentType")) { //$NON-NLS-0$
						filteredServiceRefs.push(getFilteredServiceRef(registry, serviceRef, contentType));
					} else if (pattern && new RegExp(pattern).test(title)) {
						var d = new Deferred();
						d.resolve(serviceRef);
						filteredServiceRefs.push(d);
					}
				}
				
				// Return a promise that gives the service references that aren't null
				return Deferred.all(filteredServiceRefs, function(error) {return {_error: error}; }).then(
					function(serviceRefs) {
						var capableServiceRefs = [];
						for (var i=0; i < serviceRefs.length; i++) {
							var serviceRef = serviceRefs[i];
							if (serviceRef && !serviceRef._error) {
								capableServiceRefs.push(serviceRef);
							}
						}
						return capableServiceRefs;
					});
			}
			
			var occurrenceTimer;
			var self = this;
			var occurrencesService = self.registry.getService("orion.edit.occurrences"); //$NON-NLS-0$
			var selectionListener = function(e) {
				if (occurrenceTimer) {
					window.clearTimeout(occurrenceTimer);
				}
				if (!self.occurrencesVisible) { return; }
				occurrenceTimer = window.setTimeout(function() {
					occurrenceTimer = null;
					var editor = self.editor;
					var selections = editor.getSelections();
					if (selections.length > 1) {
						self.editor.showOccurrences([]);
						return;
					}
					var context = {
						selection: selections[0],
						contentType: self.inputManager.getContentType().id
					};
					occurrencesService.computeOccurrences(editor.getEditorContext(), context).then(function (occurrences) {
						self.editor.showOccurrences(occurrences);
					});	
				}, 500);
			};
						
			self.inputManager.addEventListener("InputChanged", function(event) {//$NON-NLS-0$
				var textView = self.editor.getTextView();
				if (textView) {
					textView.removeEventListener("Selection", selectionListener); //$NON-NLS-0$
					getServiceRefs(self.registry, event.contentType, event.title).then(function(serviceRefs) {
						if (!serviceRefs || serviceRefs.length === 0) {
							if (occurrenceTimer) {
								window.clearTimeout(occurrenceTimer);
							}
						} else {
							textView.addEventListener("Selection", selectionListener); //$NON-NLS-0$
						}
					});
				}
			});
		}
	};
	return {MarkOccurrences: MarkOccurrences};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/syntaxchecker',[
	'orion/Deferred',
	'orion/edit/editorContext',
	'orion/i18nUtil',
], function(Deferred, EditorContext, i18nUtil) {

    function getValidators(registry, contentType, title) {
		var contentTypeService = registry.getService("orion.core.contentTypeRegistry"); //$NON-NLS-0$
		function getFilteredValidator(validator, contentType) {
			var contentTypeIds = validator.getProperty("contentType"); //$NON-NLS-0$
			return contentTypeService.isSomeExtensionOf(contentType, contentTypeIds).then(function(result) {
				return result ? validator : null;
			});
		}
		var validators = registry.getServiceReferences("orion.edit.validator"); //$NON-NLS-0$
		var filteredValidators = [];
		for (var i=0; i < validators.length; i++) {
			var serviceReference = validators[i];
			var pattern = serviceReference.getProperty("pattern"); // backwards compatibility //$NON-NLS-0$
			if (serviceReference.getProperty("contentType")) { //$NON-NLS-0$
				filteredValidators.push(getFilteredValidator(serviceReference, contentType));
			} else if (pattern && new RegExp(pattern).test(title)) {
				var d = new Deferred();
				d.resolve(serviceReference);
				filteredValidators.push(d);
			}
		}
		// Return a promise that gives the validators that aren't null
		return Deferred.all(filteredValidators, function(error) {return {_error: error}; }).then(
			function(validators) {
				var capableValidators = [];
				for (var i=0; i < validators.length; i++) {
					var validator = validators[i];
					if (validator && !validator._error) {
						capableValidators.push(validator);
					}
				}
				return capableValidators;
			});
	}
			
var SyntaxChecker = (function () {
	/**
	 * @name orion.SyntaxChecker
	 * @class Provides access to validation services registered with the service registry.
	 * @description Provides access to validation services registered with the service registry.
	 */
	function SyntaxChecker(serviceRegistry, model) {
		this.registry = serviceRegistry;
		this.textModel = model;
	}

	function clamp(n, min, max) {
		n = Math.max(n, min);
		n = Math.min(n, max);
		return n;
	}

    

    function extractProblems(data) {
		data = data || {};
		var problems = data.problems || data.errors || data;
		return Array.isArray(problems) ? problems : [];
	}

	SyntaxChecker.prototype = /** @lends orion.SyntaxChecker.prototype */ {
		/**
		 * Looks up applicable validators, calls them to obtain problems, passes problems to the marker service.
		 */
		checkSyntax: function (contentType, title, message, contents, editorContext) {
			if (!contentType || message) {
				return new Deferred().resolve([]);
			}
			if (!message) {
				var serviceRegistry = this.registry;
				var self = this;
				return getValidators(serviceRegistry, contentType, title).then(function(validators) {
					var progress = serviceRegistry.getService("orion.page.progress");
					var problemPromises = validators.map(function(validator) {
						var service = serviceRegistry.getService(validator);
						var promise;
						if (service.computeProblems) {
							var context = {
								contentType: contentType.id,
								title: title
							};
							promise = service.computeProblems(editorContext ? editorContext : EditorContext.getEditorContext(serviceRegistry), context);
						} else if (service.checkSyntax) {
							// Old API
							promise = service.checkSyntax(title, contents);
						}
						return progress.progress(promise, "Validating " + title).then(extractProblems);
					});
					
					return Deferred.all(problemPromises, function(error) {return {_error: error}; })
						.then(function(results) {
							var problems = [];
							for (var i=0; i < results.length; i++) {
								var probs = results[i];
								if (!probs._error) {
									self._fixup(probs);
									problems = problems.concat(probs);
								}
							}
							return new Deferred().resolve(problems);
							//serviceRegistry.getService("orion.core.marker")._setProblems(problems); //$NON-NLS-0$
						});
				});
			}
		},
		
		setTextModel: function(model) {
			this.textModel = model;
		},
		
		_fixup: function(problems) {
    		var model = this.textModel;
    		for (var i=0; i < problems.length; i++) {
    			var problem = problems[i];
    			
    			problem.description = problem.description || problem.reason;
    			problem.severity = problem.severity || "error"; //$NON-NLS-0$
    			problem.start = (typeof problem.start === "number") ? problem.start : problem.character; //$NON-NLS-0$
    
    			// Range check
    			if (typeof problem.line === "number") {//$NON-NLS-0$
    				// start, end are line offsets: 1-based in range [1 .. length+1]
    				var lineLength = model.getLine(problem.line - 1, false).length;
    				problem.start = clamp(problem.start, 1, lineLength);
    				problem.end = (typeof problem.end === "number") ? problem.end : -1; //$NON-NLS-0$
    				problem.end = clamp(problem.end, problem.start + 1, lineLength + 1);
    
    				// TODO probably need similar workaround for bug 423482 here
    			} else {
    				// start, end are document offsets (0-based)
    				var charCount = model.getCharCount();
    				problem.start = clamp(problem.start, 0, charCount); // leave room for end
    				problem.end = (typeof problem.end === "number") ? problem.end : -1; //$NON-NLS-0$
    				problem.end = clamp(problem.end, problem.start, charCount);
    
    				// Workaround: if problem falls on the empty, last line in the buffer, move it to a valid line.
    				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=423482
    				if (problem.end === charCount && model.getLineCount() > 1 && charCount === model.getLineStart(model.getLineCount() - 1)) {
    					var prevLine = model.getLineCount() - 2, prevLineStart = model.getLineStart(prevLine), prevLineEnd = model.getLineEnd(prevLine);
    					if (prevLineStart === prevLineEnd) {
    						// Empty range on an empty line seems to be OK, if not at EOF
    						problem.start = problem.end = prevLineEnd;
    					} else {
    						problem.start = prevLineEnd - 1;
    						problem.end = prevLineEnd;
    					}
    				}
    			}
    		}
    	}
	};
	return SyntaxChecker;
}());
return {SyntaxChecker: SyntaxChecker,
		getValidators: getValidators};
});

/******************************************************************************* 
 * @license
 * Copyright (c) 2014 Pivotal Software Inc. and others
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: Pivotal Software Inc. - initial API and implementation 
 ******************************************************************************/
/*eslint-env amd, browser*/
define('orion/liveEditSession',[
	'orion/Deferred'
], function(Deferred) {

	var resource;
	var services = [];
//	var previousEditSession;

	function handleError(err) {
		return err instanceof Error ? err : new Error(err);
	}

	/**
	 * @name orion.LiveEditSession
	 * @class Provides access to validation services registered with the service registry.
	 * @description Provides access to validation services registered with the service registry.
	 */
	function LiveEditSession(serviceRegistry, editor) {
		this.registry = serviceRegistry;
		this.editor = editor;
	}

	LiveEditSession.prototype = /** @lends orion.LiveEditSession.prototype */ {
		/**
		 * Looks up applicable live edit sessions, calls them to pass editor context to services.
		 */
		start: function (contentType, title) {
			function getLiveEditors(registry, contentType) {
				var contentTypeService = registry.getService("orion.core.contentTypeRegistry"); //$NON-NLS-0$
				function getFilteredLiveEditSession(liveEditSession, contentType) {
					var contentTypeIds = liveEditSession.getProperty("contentType"); //$NON-NLS-0$
					return contentTypeService.isSomeExtensionOf(contentType, contentTypeIds).then(function(result) {
						return result ? liveEditSession : null;
					});
				}
				var liveEditSessions = registry.getServiceReferences("orion.edit.live"); //$NON-NLS-0$
				var filteredLiveEditSessions = [];
				for (var i=0; i < liveEditSessions.length; i++) {
					var serviceReference = liveEditSessions[i];
					if (serviceReference.getProperty("contentType")) { //$NON-NLS-0$
						filteredLiveEditSessions.push(getFilteredLiveEditSession(serviceReference, contentType));
					}
				}
				// Return a promise that gives the live edit sessions that aren't null
				return Deferred.all(filteredLiveEditSessions, handleError).then(function(liveEditSessions) {
					var capableLiveEditSessions = [];
					for (var i=0; i < liveEditSessions.length; i++) {
						var liveEdit = liveEditSessions[i];
						if (liveEdit && !(liveEdit instanceof Error)) {
							capableLiveEditSessions.push(liveEdit);
						}
					}
					return capableLiveEditSessions;
				});
			}

			if (resource !== title) {
				if (services) {
					services.forEach(function(service) {
						service.endEdit(resource);
					});
				}
				services = [];
				resource = title;
				if (!contentType) {
					return;
				}
				var editor = this.editor;
				var serviceRegistry = this.registry;
				getLiveEditors(serviceRegistry, contentType).then(function(liveEditors) {
					var editSessionPromises = liveEditors.map(function(liveEditor) {
						var service = serviceRegistry.getService(liveEditor);
						if (service.startEdit) {
							var context = {
								contentType: contentType.id,
								title: title
							};
							services.push(service);
							var editorContext = editor.getEditorContext();
							return service.startEdit(editorContext, context);
						}
					});

					/*previousEditSession =*/ Deferred.all(editSessionPromises, handleError);
				});
			}
		}
	};

	return LiveEditSession;
});
/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/

/*eslint-env browser, amd*/
define('orion/problems',["orion/EventTarget"], function(EventTarget) {

	/**
	 * Creates a new problem service instance. Client should obtain the service
	 * <tt>orion.core.marker</tt> from the service registry rather than instantiate
	 * this service directly.
	 * @class The problem service tracks markers and sends notification of marker changes.
	 * @name orion.problems.ProblemService
	 */
	function ProblemService(serviceRegistry, serviceID) {
		this._serviceRegistry = serviceRegistry;
		EventTarget.attach(this);
		this._serviceRegistration = serviceRegistry.registerService(serviceID || "orion.core.marker", this); //$NON-NLS-0$
	}

	ProblemService.prototype = /** @lends orion.problems.ProblemService.prototype */ {
		// provider
		_setProblems: function(problems) {
			this.problems = problems;
			this.dispatchEvent({type:"problemsChanged", problems:problems}); //$NON-NLS-0$
		}	    
	};
	ProblemService.prototype.constructor = ProblemService;
	
	//return the module exports
	return {ProblemService: ProblemService};
});


/*******************************************************************************
 * @license
 * Copyright (c) 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/

define ('orion/blamer',[
	'orion/extensionCommands', //$NON-NLS-0$
	'orion/PageLinks', //$NON-NLS-0$
	'orion/URITemplate' //$NON-NLS-0$
], function(mExtensionCommands, PageLinks, URITemplate) {
	
	function Blamer(serviceRegistry, inputManager, editor) {
		this.serviceRegistry = serviceRegistry;
		this.inputManager = inputManager;
		this.editor = editor;
	}
	
	Blamer.prototype = {
		getBlamer: function() {
			var metadata = this.inputManager.getFileMetadata();
			var blamers = this.serviceRegistry.getServiceReferences("orion.edit.blamer"); //$NON-NLS-0$
			for (var i=0; i < blamers.length; i++) {
				var serviceReference = blamers[i];
				var info = {};
				info.validationProperties = serviceReference.getProperty("validationProperties"); //$NON-NLS-0$
				info.forceSingleItem = true;
				var validator = mExtensionCommands._makeValidator(info, this.serviceRegistry);
				if (validator.validationFunction.bind(validator)(metadata)) {
					return this.serviceRegistry.getService(serviceReference);
				}
			}
			return null;
		},

		isVisible: function() {
			return !!this.getBlamer();
		},
		
		doBlame: function() {
			var service = this.getBlamer();
			if (service) {
				var handleResult = function(results) {
					var orionHome = PageLinks.getOrionHome();
					for (var i=0; i<results.length; i++) {
						var range = results[i];
						var uriTemplate = new URITemplate(range.CommitLink);
						var params = {};
						params.OrionHome = orionHome;
						range.CommitLink = uriTemplate.expand(params);
					}
					this.editor.showBlame(results);
				}.bind(this);
				var inputManager = this.inputManager;
				var context = {metadata: inputManager.getFileMetadata()};
				service.computeBlame(this.editor.getEditorContext(), context).then(handleResult);
			}
		}
	};
	return {Blamer: Blamer}; 
});



/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/
define('orion/compare/diffParser',[], function() {

var orion = orion || {};

orion.DiffParser = (function() {
	var isWindows = window.navigator.platform.indexOf("Win") !== -1; //$NON-NLS-0$
	var NO_NEW_LINE = "\\ No newline at end of file"; //$NON-NLS-0$
	/** @private */
	function DiffParser(lineDelimiter, diffLineDilemeter) {
		this._lineDelimiter = lineDelimiter ? lineDelimiter : (isWindows ? "\r\n" : "\n");  //$NON-NLS-1$ //$NON-NLS-0$
		this._diffLineDelimiter = diffLineDilemeter ? diffLineDilemeter : this._lineDelimiter;
		this._DEBUG = false;
	}
	DiffParser.prototype = {
		_init: function(){
			//Input
			this._oFileContents = [];
			this._diffContents = [];

			//All the middle result when computing
			this._oBlocks = []; //each item inside original block will be formatted as [startAtLine , linesInBlock]
			this._nBlocks = []; //each item inside new block will be formatted as [startAtLine , linesInBlock , indexInDiffLines]
			this._hunkRanges = [];
			this._lastToken = " "; //$NON-NLS-0$

			//Final result as out put
			this._deltaMap = [];
			this._nFileContents = [];

			//Flag of new line at file end
			this._oNewLineAtEnd = true;
			this._nNewLineAtEnd = true;
			this._diffContentIndex = 1;
		},

		/**
		 * Returns an array of arrays of the parsed original blocks of the file.
		 * This is formatted as [[startAtLine, linesInBlock]] where startAtLine is
		 * the start line of the new diff block, and linesInBlock is the number of
		 * lines that were affected
		 */
		getOriginalBlocks: function() {
			return this._oBlocks;
		},

		/**
		 * Returns an array of arrays of the parsed diffs of the new blocks in the file.
		 * This is formatted as [[startAtLine, linesInBlock, indexInDiffLines]]
		 * where startAtLine is the start line of the new diff block, linesInBlock is
		 * the number of lines that were affected, and indexInDiffLines is the starting
		 * index of the diff in the line block.
		 */
		getNewBlocks: function() {
			return this._nBlocks;
		},

		setLineDelim: function(lineDelimiter){
			this._lineDelimiter = lineDelimiter;
			this._diffLineDelimiter = lineDelimiter;
		},

		getDiffArray: function(){
			return {array:this._diffContents , index:this._diffContentIndex};
		},

		parse: function(oFileString , diffString , detectConflicts ,doNotBuildNewFile){
			this._init();
			if(diffString === ""){
				return {outPutFile: oFileString, mapper: []};
			}
			this._oFileContents = oFileString === "" ? []:oFileString.split(this._lineDelimiter);
			this._diffContents = diffString.split(this._diffLineDelimiter);
			var returnObj = {};
			returnObj.mapper = [];

			var totalLines = this._diffContents.length;
			this._hunkRanges = [];
			for(var i = 0; i <totalLines ; i++){
				this._addFileModeProperties(this._diffContents[i], returnObj);
				var hunkRange = this._parseHunkRange(i);
				if (hunkRange) {
				    this._hunkRanges.push(hunkRange);
				}
		    }
			if(0 === this._hunkRanges.length){
				returnObj.outPutFile = oFileString;
				return returnObj;
			}

			if(this._DEBUG){
				console.log("***Diff contents: \n"); //$NON-NLS-0$
				this._diffContents.forEach(function(singleLine) {
					console.log(singleLine);
				});
				console.log("***Hunk ranges: \n"); //$NON-NLS-0$
				console.log(JSON.stringify(this._hunkRanges));
			}
			for(var j = 0; j <this._hunkRanges.length ; j++){
				this._parseHunkBlock(j);
			}
			if(this._DEBUG){
				console.log("***Original Hunk range blocks: \n"); //$NON-NLS-0$
				console.log(JSON.stringify(this._oBlocks));
				console.log("***New Hunk range blocks: \n"); //$NON-NLS-0$
				console.log(JSON.stringify(this._nBlocks));
			}
			this._buildMap(detectConflicts);
			if(this._DEBUG){
				console.log("***New Line at end of file(original): \n"); //$NON-NLS-0$
				console.log(JSON.stringify(this._oNewLineAtEnd));
				console.log("***New Line at end of file(new): \n"); //$NON-NLS-0$
				console.log(JSON.stringify(this._nNewLineAtEnd));
				console.log("***Mapper: \n"); //$NON-NLS-0$
				this._logMap();
				console.log("***Total line number in original file: " + this._oFileContents.length); //$NON-NLS-0$
			}
			if(!doNotBuildNewFile){
				this._buildNewFile();
			}
			if(this._DEBUG){
				//console.log("***New File: \n");
				//this._logNewFile();
				//console.log("***Total line number in new file: " + this._nFileContents.length);
			}
			returnObj.outPutFile = this._nFileContents.join(this._diffLineDelimiter);
			returnObj.mapper = this._deltaMap;
			return returnObj;
		},
		
		_addFileModeProperties: function(line, returnObj){
			function startWith(line, str){
				return line.lastIndexOf(str, 0) === 0;				
			}
			
			if(startWith(line,"-")||startWith(line,"+")||startWith(line,"@")){
				return;
			}
			if(startWith(line,"deleted file mode")){
				returnObj.deletedFileMode = line.split("deleted file mode")[1].match(/[^ ]+/g)[0];
			}
			if(startWith(line,"new file mode")){
				returnObj.newFileMode = line.split("new file mode")[1].match(/[^ ]+/g)[0];
			}
			if(startWith(line,"old mode")){
				returnObj.oldMode = line.split("old mode")[1].match(/[^ ]+/g)[0];
			}
			if(startWith(line,"new mode")){
				returnObj.newMode = line.split("new mode")[1].match(/[^ ]+/g)[0];
			}
		},


		_logMap: function(){
			for(var i = 0;i < this._deltaMap.length ; i++){
				console.log(JSON.stringify(this._deltaMap[i]));
				if(this._deltaMap[i][2] > 0){
					console.log("    **Diff content on change/add: \n"); //$NON-NLS-0$
					for(var j = 0;j < this._deltaMap[i][0] ; j++){
						console.log("    " + this._diffContents[this._deltaMap[i][2]+j-1]); //$NON-NLS-0$
					}
				}
			}
		},

		_logNewFile: function(){
			for(var i = 0;i < this._nFileContents.length ; i++){
				console.log(this._nFileContents[i]);
			}
		},

		_createBlock: function(token , blocks , startAtLine , endAtLine){
			if(endAtLine === startAtLine && token === " "){ //$NON-NLS-0$
				return;
			}
			var block = [startAtLine , endAtLine - startAtLine ,"s" ]; //$NON-NLS-0$
			if(token === "-"){ //$NON-NLS-0$
				block[2] = "r"; //$NON-NLS-0$
			} else if(token === "+"){ //$NON-NLS-0$
				block[2] = "a"; //$NON-NLS-0$
			} else if(token === "c"){ //$NON-NLS-0$
				block[2] = "c"; //$NON-NLS-0$
			}
			blocks.push(block);
		},

		_createMinusBlock: function(oBlkStart , nBlkStart , oBlockLength, lastMinusPos){
			var len = this._oBlocks.length;
			if(len === 0 || oBlkStart !== this._oBlocks[len-1][0]){
				this._oBlocks.push([oBlkStart === 0 ? 1 : oBlkStart , oBlockLength, lastMinusPos]);
				this._nBlocks.push([nBlkStart , 0 , -2]);
			} else {
				this._oBlocks[len-1][1] = this._oBlocks[len-1][1] + oBlockLength;
				this._oBlocks[len-1][2] = lastMinusPos;
			}
		},

		_createPlusBlock: function(oBlkStart , nBlkStart , nBlockLength , lastPlusPos ){
			var len = this._nBlocks.length;
			if(len === 0 || nBlkStart !== this._nBlocks[len-1][0]){
				this._oBlocks.push([oBlkStart === 0 ? 1 : oBlkStart , 0]);
				this._nBlocks.push([nBlkStart , nBlockLength , lastPlusPos]);
			} else {
				this._nBlocks[len-1][1] = this._nBlocks[len-1][1] + nBlockLength;
				this._nBlocks[len-1][2] = lastPlusPos;
			}
		},

		//read line by line in a hunk range
		_parseHunkBlock: function(hunkRangeNo ){
			var lastToken = " "; //$NON-NLS-0$
			var startNo = this._hunkRanges[hunkRangeNo][0] + 1;
			var endNo = (hunkRangeNo === (this._hunkRanges.length - 1) ) ? this._diffContents.length : this._hunkRanges[hunkRangeNo+1][0];

			var oCursor = 0;
			var nCursor = 0;
			var oBlkStart = this._hunkRanges[hunkRangeNo][1];
			var nBlkStart = this._hunkRanges[hunkRangeNo][3];
			var lastPlusPos = startNo;
			var lastMinusPos = startNo;
			for (var i = startNo ; i< endNo ; i++){
				if( 0 === this._diffContents[i].length){
					continue;
				}
				var curToken = this._diffContents[i][0];
				if(curToken === "\\"){ //$NON-NLS-0$
					if( NO_NEW_LINE === this._diffContents[i].substring(0 , this._diffContents[i].length-1) ||
						NO_NEW_LINE === this._diffContents[i]){
						if(lastToken === "-"){ //$NON-NLS-0$
							this._oNewLineAtEnd = false;
						} else if(lastToken === " "){ //$NON-NLS-0$
							this._nNewLineAtEnd = false;
							this._oNewLineAtEnd = false;
						} else {
							this._nNewLineAtEnd = false;
						}
						if(i > startNo && this._diffContents[i-1][this._diffContents[i-1].length-1] === "\r"){ //$NON-NLS-0$
							this._diffContents[i-1] = this._diffContents[i-1].substring(0 , this._diffContents[i-1].length-1);
						}
						continue;
					}
				}
				switch(curToken){
				case "-": //$NON-NLS-0$
				case "+": //$NON-NLS-0$
				case " ": //$NON-NLS-0$
					break;
				default:
					continue;
				}

				if(lastToken !== curToken){
					if(curToken === "+"){ //$NON-NLS-0$
						lastPlusPos = i;
					}
					if(curToken === "-"){ //$NON-NLS-0$
						lastMinusPos = i;
					}
					switch(lastToken){
					case " ": //$NON-NLS-0$
						oBlkStart = this._hunkRanges[hunkRangeNo][1] + oCursor;
						nBlkStart = this._hunkRanges[hunkRangeNo][3] + nCursor;
						break;
					case "-": //$NON-NLS-0$
						this._createMinusBlock(oBlkStart , nBlkStart ,this._hunkRanges[hunkRangeNo][1] + oCursor - oBlkStart, lastMinusPos);
						break;
					case "+": //$NON-NLS-0$
						this._createPlusBlock(oBlkStart , nBlkStart ,this._hunkRanges[hunkRangeNo][3] + nCursor - nBlkStart , lastPlusPos);
						break;
					default:
					}
					lastToken = curToken;
				}

				switch(curToken){
				case "-": //$NON-NLS-0$
					oCursor++;
					break;
				case "+": //$NON-NLS-0$
					nCursor++;
					break;
				case " ": //$NON-NLS-0$
					oCursor++;
					nCursor++;
					break;
				}
			}
			switch(lastToken){
			case "-": //$NON-NLS-0$
				this._createMinusBlock(oBlkStart , nBlkStart ,this._hunkRanges[hunkRangeNo][1] + oCursor - oBlkStart, lastMinusPos);
				break;
			case "+": //$NON-NLS-0$
				this._createPlusBlock(oBlkStart , nBlkStart ,this._hunkRanges[hunkRangeNo][3] + nCursor - nBlkStart , lastPlusPos);
				break;
			}
		},

		_detectConflictes: function(startIndexInDiff , lines){
			if(startIndexInDiff < 0){
				return false;
			}
			var endIndex = startIndexInDiff + lines;
			for(var i = startIndexInDiff ; i < endIndex ; i++){
				var line = this._diffContents[i];
				if(line.indexOf("<<<<<") > -1 || line.indexOf(">>>>>") > -1){ //$NON-NLS-1$ //$NON-NLS-0$
					return true;
				}
			}
			return false;
		},

		_buildMap: function(detectConflicts){
			var  blockLen = this._oBlocks.length;
			var oFileLen = this._oFileContents.length;
			var oFileLineCounter = 0;
			var delta, lastSamePos = 1;
			for(var i = 0 ; i < blockLen ; i++){
				delta =  this._oBlocks[i][0] - lastSamePos;
				//Create the "same on both" delta
				if(delta > 0){
					this._deltaMap.push([delta , delta , 0]);
					oFileLineCounter += delta;
				}
				if(detectConflicts && this._detectConflictes(this._nBlocks[i][2] , this._nBlocks[i][1])){
					this._deltaMap.push([this._nBlocks[i][1] , this._oBlocks[i][1] , this._nBlocks[i][2]+1 , 1]);
				} else {
					this._deltaMap.push([this._nBlocks[i][1] , this._oBlocks[i][1] , this._nBlocks[i][2]+1]);
				}
				oFileLineCounter += this._oBlocks[i][1];
				lastSamePos = this._oBlocks[i][0] + this._oBlocks[i][1];
			}
			if(0 < (oFileLen - lastSamePos)){
				this._deltaMap.push([oFileLen - lastSamePos+1 , oFileLen - lastSamePos+1 , 0]);
				oFileLineCounter += (oFileLen - lastSamePos+1);
			}
			if(oFileLineCounter < oFileLen){
				delta = oFileLen - oFileLineCounter;
				var lastMapItem = this._deltaMap[this._deltaMap.length-1];
				if(lastMapItem[2] === 0){
					lastMapItem[0] += delta;
					lastMapItem[1] += delta;
				} else if (lastMapItem[2] === -1){
					this._deltaMap.push([delta , delta , 0]);
				} else if(this._nNewLineAtEnd === this._oNewLineAtEnd){
					this._deltaMap.push([delta , delta , 0]);
				} else {
					if(this._nNewLineAtEnd){
						lastMapItem[0] += delta;
					}
					if(this._oNewLineAtEnd){
						lastMapItem[1] += delta;
					}
				}
			}
		},

		_buildNewFile: function(){
			var oFileCursor = 1;
			var lastUpdateBySameBlk = false;
			var len = this._deltaMap.length;
			var i, j;
			for(i = 0;i < len ; i++){
				lastUpdateBySameBlk = false;
				if(this._deltaMap[i][2] === 0){
					for(j = 0;j < this._deltaMap[i][0] ; j++){
						this._nFileContents.push(this._oFileContents[oFileCursor+j-1]);
					}
					lastUpdateBySameBlk = true;
				} else if(this._deltaMap[i][2] > 0){
					for(j = 0;j < this._deltaMap[i][0] ; j++){
						this._nFileContents.push(this._diffContents[this._deltaMap[i][2]+j-1].substring(1));
					}
				}
				oFileCursor = oFileCursor + this._deltaMap[i][1];
			}
			if(this._nNewLineAtEnd && !lastUpdateBySameBlk){
				this._nFileContents.push("");
				//this._deltaMap[len-1][0] = this._deltaMap[len-1][0] + 1;
			}
		},

		//In many versions of GNU diff, each range can omit the comma and trailing value s, in which case s defaults to 1.
		_converHRangeBody: function(body , retVal){
			if( body ){
				var number =  parseInt(body, 10);
				retVal.push( number >= 0 ? number : 1);
			} else {
				retVal.push(1);
			}
		},

		//The hunk range information contains two hunk ranges.
		//The range for the hunk of the original file is preceded by a minus symbol, and the range for the new file is preceded by a plus symbol.
		//Each hunk range is of the format l,s where l is the starting line number and s is the number of lines the change hunk applies to for each respective file.
		//In many versions of GNU diff, each range can omit the comma and trailing value s, in which case s defaults to 1.
		//Note that the only really interesting value is the l line number of the first range; all the other values can be computed from the diff.
		/*
		 * return value :
		 * [lineNumberInDiff , OriginalL , OriginalS , NewL ,NewS] , no matter "-l,s +l,s" or "+l,s -l,s"
		 */
		_parseHunkRange: function(lineNumber){
			var oneLine = this._diffContents[lineNumber];
			var regex = /^@@\s*-([\+|\-]*[\d]+)\s*,*\s*([\d]*)\s*\+([\+|\-]*[\d]+)\s*,*\s*([\d]*)\s*@@+/;// Pattern : "@@-l,s +l,s@@" or "@@-l,s +l@@" or "@@-l +l,s@@" or even "@@--l,s +-l,s@@"
			var regexReverse = /^@@\s*\+([\+|\-]*[\d]+)\s*,*\s*([\d]*)\s*-([\+|\-]*[\d]+)\s*,*\s*([\d]*)\s*@@+/;// Pattern : "@@+l,s -l,s@@" or "@@+l,s -l@@" or "@@+l -l,s@@" or even "@@+-l,s --l,s@@"
			var match = regex.exec(oneLine);
			var retVal = null;
			if(match && match.length === 5) {
				retVal = [lineNumber];
				this._converHRangeBody(match[1], retVal);
				this._converHRangeBody(match[2], retVal);
				this._converHRangeBody(match[3], retVal);
				this._converHRangeBody(match[4], retVal);
			} else {
				match = regexReverse.exec(oneLine);
				if(match && match.length === 5) {
					retVal = [lineNumber];
					this._converHRangeBody(match[3], retVal);
					this._converHRangeBody(match[4], retVal);
					this._converHRangeBody(match[1], retVal);
					this._converHRangeBody(match[2], retVal);
				}
			}
			return retVal;
		}

	};
	return DiffParser;
}());
return orion;
});

/*******************************************************************************
 * @license
 * Copyright (c) 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/*eslint-env browser, amd*/

define('orion/differ',[
	'orion/extensionCommands', //$NON-NLS-0$
	'orion/compare/diffParser' //$NON-NLS-0$
], function(mExtensionCommands, mDiffParser) {

	function Differ(serviceRegistry, inputManager, editor) {
		this._serviceRegistry = serviceRegistry;
		this._inputManager = inputManager;
		this._editor = editor;
		this._enabled = false;
		this.init();
	}

	Differ.prototype = {
		init: function(){
			var that = this;
			this._changeListener = function() {
				if (that._enabled) {
					if (that.occurrenceTimer) {
						window.clearTimeout(that.occurrenceTimer);
					}
					that.occurrenceTimer = window.setTimeout(function() {
						that.occurrenceTimer = null;
						that.doDiff();
					}, 500);
				}
			};
			this._inputManager.addEventListener("InputChanged", function() { //$NON-NLS-0$
				var textView = that._editor.getTextView();
				if (textView) {
					textView.removeEventListener("ModelChanged", that._changeListener); //$NON-NLS-0$
				}
				var service = that.service = that.getDiffer();
				if (service) {
					if (textView) {
						textView.addEventListener("ModelChanged", that._changeListener); //$NON-NLS-0$
					}
				}
			});
		},
		
		isVisible: function() {
			return !!this.getDiffer();
		},

		getDiffer: function(){
			var metadata = this._inputManager.getFileMetadata();
			var diffServices = this._serviceRegistry.getServiceReferences("orion.edit.diff"); //$NON-NLS-0$
			for (var i = 0; i < diffServices.length; i++) {
				var serviceReference = diffServices[i];
				var info = {};
				info.validationProperties = serviceReference.getProperty("validationProperties"); //$NON-NLS-0$
				info.forceSingleItem = true;
				var validator = mExtensionCommands._makeValidator(info, this._serviceRegistry);
				if (validator.validationFunction.bind(validator)(metadata)) {
					return this._serviceRegistry.getService(serviceReference);
				}
			}
			return null;
		},

		showAnnotations: function(service){
			var that = this;
			var diffParser = new mDiffParser.DiffParser();
			var context = {
				metadata: this._inputManager.getFileMetadata()
			};
			service.computeDiff(this._editor.getEditorContext(), context).then(function(diffContent) {
				diffParser.setLineDelim("\n"); //$NON-NLS-0$
				diffParser.parse("", diffContent);
				var oBlocks = diffParser.getOriginalBlocks();
				var nBlocks = diffParser.getNewBlocks();
				var diffArray = diffParser.getDiffArray();
				var diffRanges = that.getDiffRanges(oBlocks, nBlocks, diffArray);
				if(that._enabled){//for cases when user turns off diffService computing diff
					that._editor.showDiffAnnotations(diffRanges);
				}
			});
		},
		
		getDiffRanges: function(originalHunkBlocks, newHunkBlocks){
			var diffResult = [];
			for (var i = 0; i < newHunkBlocks.length; i++) {
				var lineStart = newHunkBlocks[i][0];
				var oldSize = originalHunkBlocks[i][1];
				var newSize = newHunkBlocks[i][1];
				if (newSize === 0) {
					diffResult.push({
						lineStart: lineStart - 1,
						lineEnd: lineStart,
						type:"deleted" //$NON-NLS-0$
					});
				} else if (oldSize === 0) {
					diffResult.push({
						lineStart: lineStart - 1,
						lineEnd: lineStart + newSize - 1,
						type:"added" //$NON-NLS-0$
					});
				} else {
					diffResult.push({
						lineStart: lineStart - 1,
						lineEnd: lineStart + newSize - 1,
						type:"modified" //$NON-NLS-0$
					});
				}
			}
			return diffResult;
		},

		toggleEnabled: function(){
			this.setEnabled(!this.isEnabled());
		},

		setEnabled: function(state){
			this._enabled = state;
			this.doDiff();
		},

		isEnabled: function(){
			return this._enabled;
		},

		doDiff: function() {
			if (!this._enabled) {
				this._editor.showDiffAnnotations([]);
			} else {
				if (this.service) {
					var that = this;
					this._inputManager.save().then(function() {
						that.showAnnotations(that.service);
					});
				}
			}
		}

	};

	Differ.prototype.constructor = Differ;

	//return the module exports
	return {Differ: Differ};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('orion/webui/contextmenu',[
	'orion/webui/littlelib', 
	'orion/webui/dropdown', 
	'orion/objects'
], function(lib, mDropdown, objects) {

	var Dropdown = mDropdown.Dropdown;
	
	/**
	 * @class orion.webui.ContextMenu
	 * @extends orion.webui.Dropdown
	 * 
	 * Attaches context menu behavior to a given node.  
	 *
	 * @see orion.webui.Dropdown for more documentation
	 *
	 * @name orion.webui.contextmenu.ContextMenu
	 *
	 */
	function ContextMenu(options) {
		options.skipTriggerEventListeners = true; //we want different event listeners on the trigger node
		Dropdown.call(this, options); //invoke super constructor
		this._initialize();
	}
	
	ContextMenu.prototype = Object.create(Dropdown.prototype);
	ContextMenu.prototype.constructor = ContextMenu;
	
	objects.mixin(ContextMenu.prototype, /** @lends orion.webui.contextmenu.ContextMenu.prototype */ {
			
		_initialize: function() {
			if (!this._dropdownNode.dropdown) {
				//used by commandRegistry to set the parentNode of a child dropdown menu
				this._dropdownNode.dropdown = this;
			}
			
			//add context menu event handlers
			this._boundcontextmenuEventHandler = this._contextmenuEventHandler.bind(this);
			this._boundContextMenuCloser = this._contextMenuCloser.bind(this);
			this._triggerNode.addEventListener("contextmenu", this._boundcontextmenuEventHandler, true); //$NON-NLS-0$
			window.addEventListener("contextmenu", this._boundContextMenuCloser, false); //$NON-NLS-0$
		},
		
		 _contextMenuCloser: function(event){
			this.close(event);
		},
		
		_contextmenuEventHandler: function(event){
			if (this.open(event)) {
				lib.stop(event);
			} else {
				this.close();
			}
		},		
	});
	
	// overrides Dropdown.protoype.open
	ContextMenu.prototype.open = function(event) {
		//clicking on the trigger node should close the context menu
		this._triggerNode.addEventListener("click",  this._boundContextMenuCloser, false);//$NON-NLS-0$
		
		return Dropdown.prototype.open.call(this, event); //call function in super class
	};
	
	// overrides Dropdown.protoype.close
	ContextMenu.prototype.close = function(restoreFocus) {
		this._triggerNode.removeEventListener("click",  this._boundContextMenuCloser, false); //$NON-NLS-0$
		
		return Dropdown.prototype.close.call(this, restoreFocus); //call function in super class
	};
	
	// overrides Dropdown.protoype._positionDropdown
	ContextMenu.prototype._positionDropdown = function(mouseEvent) {
		if (mouseEvent) {
			var mouseLeft = mouseEvent.clientX;
			var mouseTop = mouseEvent.clientY;
			
			// we want the position to be relative to the mouse event
			this._dropdownNode.style.position = "fixed"; //$NON-NLS-0$
			
			// set the initial position
			this._dropdownNode.style.left = (mouseLeft + 1) + "px"; //$NON-NLS-0$
			this._dropdownNode.style.top = mouseTop +  "px"; //$NON-NLS-0$
					
			// ensure that the menu fits on the page...
			var bounds = lib.bounds(this._dropdownNode);
			var width = bounds.width;
			var height = bounds.height;
			var bodyBounds = lib.bounds(document.body);
			
			//ensure menu fits on page horizontally
			var overflowX = (mouseLeft + width) - (bodyBounds.left + bodyBounds.width);
			if (0 < overflowX) {
				this._dropdownNode.style.left = Math.floor(mouseLeft - overflowX) + "px"; //$NON-NLS-0$	
			}
			
			//ensure menu fits on page vertically
			var overflowY = (mouseTop + height) - (bodyBounds.top + bodyBounds.height);
			if (0 < overflowY) {
				this._dropdownNode.style.top = Math.floor(mouseTop - overflowY) + "px";	//$NON-NLS-0$
			}
		} else {
			Dropdown.prototype._positionDropdown.call(this); //call function in super class
		}
	};
	
	// overrides Dropdown.protoype.destroy
	ContextMenu.prototype.destroy = function() {
		this._triggerNode.removeEventListener("contextmenu", this._boundcontextmenuEventHandler, true); //$NON-NLS-0$
		this._triggerNode.removeEventListener("click",  this._boundContextMenuCloser, false); //$NON-NLS-0$
		window.removeEventListener("contextmenu", this._boundContextMenuCloser, false); //$NON-NLS-0$
		this._dropdownNode.dropdown = null;
		Dropdown.prototype.destroy.call(this); //call function in super class
	};
	
	//return the module exports
	return {ContextMenu: ContextMenu};
});
/*******************************************************************************
 * @license
 * Copyright (c) 2010, 2014 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html).
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/*eslint-env browser, amd*/
define('orion/editorView',[
	'i18n!orion/edit/nls/messages',
	'orion/editor/editor',
	'orion/editor/eventTarget',
	'orion/editor/textView',
	'orion/editor/textModel',
	'orion/editor/projectionTextModel',
	'orion/editor/editorFeatures',
	'orion/hover',
	'orion/editor/contentAssist',
	'orion/editor/emacs',
	'orion/editor/vi',
	'orion/editorPreferences',
	'orion/widgets/themes/ThemePreferences',
	'orion/widgets/themes/editor/ThemeData',
	'orion/widgets/settings/EditorSettings',
	'orion/searchAndReplace/textSearcher',
	'orion/editorCommands',
	'orion/globalCommands',
	'orion/edit/dispatcher',
	'orion/edit/editorContext',
	'orion/edit/typedefs',
	'orion/highlight',
	'orion/markOccurrences',
	'orion/syntaxchecker',
	'orion/liveEditSession',
	'orion/problems',
	'orion/blamer',
	'orion/differ',
	'orion/keyBinding',
	'orion/util',
	'orion/Deferred',
	'orion/webui/contextmenu',
	'orion/metrics',
	'orion/objects'
], function(
	messages,
	mEditor, mEventTarget, mTextView, mTextModel, mProjectionTextModel, mEditorFeatures, mHoverFactory, mContentAssist,
	mEmacs, mVI, mEditorPreferences, mThemePreferences, mThemeData, EditorSettings,
	mSearcher, mEditorCommands, mGlobalCommands,
	mDispatcher, EditorContext, TypeDefRegistry, Highlight,
	mMarkOccurrences, mSyntaxchecker, LiveEditSession,
	mProblems, mBlamer, mDiffer,
	mKeyBinding, util, Deferred, mContextMenu, mMetrics, objects
) {
	var fPattern = "/__embed/";
	var Dispatcher = mDispatcher.Dispatcher;

	function parseNumericParams(input, params) {
		for (var i = 0; i < params.length; i++) {
			var param = params[i];
			if (input[param]) {
				input[param] = parseInt(input[param], 10);
			}
		}
	}

	/**
	 * Constructs a new EditorView object.
	 *
	 * @class
	 * @name orion.EditorView
	 * @borrows orion.editor.EventTarget#addEventListener as #addEventListener
	 * @borrows orion.editor.EventTarget#removeEventListener as #removeEventListener
	 * @borrows orion.editor.EventTarget#dispatchEvent as #dispatchEvent
	 */
	function EditorView(options) {
		this._parent = options.parent;
		if(typeof this._parent === "string") {
			this._parent = document.getElementById(options.parent);
		}
		this.id = options.id || "";
		this.activateContext = options.activateContext;
		this.renderToolbars = options.renderToolbars;
		this.serviceRegistry = options.serviceRegistry;
		this.contentTypeRegistry = options.contentTypeRegistry;
		this.commandRegistry = options.commandRegistry;
		this.progress = options.progress;
		this.statusService = options.statusService;
		this.editorCommands = options.editorCommands;
		this.fileClient = options.fileService;
		this.inputManager = options.inputManager;
		this.preferences = options.preferences;
		this.readonly = options.readonly;
		this.searcher = options.searcher;
		this.statusReporter = options.statusReporter;
		this.model = options.model;
		this.undoStack = options.undoStack;
		this.problemsServiceID = options.problemsServiceID || "orion.core.marker"; //$NON-NLS-0$
		this.editContextServiceID = options.editContextServiceID || "orion.edit.context"; //$NON-NLS-0$
		this.syntaxHighlighter = new Highlight.SyntaxHighlighter(this.serviceRegistry);
		this.typeDefRegistry = new TypeDefRegistry(this.serviceRegistry);
		var keyAssist = mGlobalCommands.getKeyAssist ? mGlobalCommands.getKeyAssist() : null;
		if(keyAssist) {
			keyAssist.addProvider(this.editorCommands);
		}
		var mainSplitter = mGlobalCommands.getMainSplitter ? mGlobalCommands.getMainSplitter() : null;
		if(mainSplitter) {
			mainSplitter.splitter.addEventListener("resize", function (evt) { //$NON-NLS-0$
				if (this.editor && evt.node === mainSplitter.main) {
					this.editor.resize();
				}
			}.bind(this));
		}
		if(mGlobalCommands.getGlobalEventTarget) {
			mGlobalCommands.getGlobalEventTarget().addEventListener("toggleTrim", function() { //$NON-NLS-0$
				if (this.editor) {
					this.editor.resize();
				}
			}.bind(this));
		}
		this.settings = {};
		this._init();
	}
	EditorView.prototype = /** @lends orion.EditorView.prototype */ {
		updateKeyMode: function(prefs, textView) {
			if (this.emacs) {
				textView.removeKeyMode(this.emacs);
			}
			if (this.vi) {
				textView.removeKeyMode(this.vi);
			}
			if (prefs.keyBindings === "Emacs") { //$NON-NLS-0$
				if (!this.emacs) {
					this.emacs = new mEmacs.EmacsMode(textView);
				}
				textView.addKeyMode(this.emacs);
			} else if (prefs.keyBindings === "vi") { //$NON-NLS-0$
				if (!this.vi) {
					this.vi = new mVI.VIMode(textView, this.statusReporter);
				}
				textView.addKeyMode(this.vi);
			}
		},
		setContents: function(contents, contentType) {
			var cType = this.contentTypeRegistry.getContentType(contentType);
			var fileExt = "txt";
			if(cType && cType.extension && cType.extension.length > 0) {
				fileExt = cType.extension[0];
			}
			var currentLocation = fPattern + this.id + "/foo." + fileExt;
			var def;
			var sameFile = currentLocation === this.lastFileLocation;
			if(sameFile || !this.lastFileLocation) {
				def = new Deferred().resolve();
			} else {
				def = this.fileClient.deleteFile(this.lastFileLocation);
			}
			return def.then(function() {
				return this.fileClient.write(currentLocation, contents).then(function(){
					this.lastFileLocation = currentLocation;
					if (sameFile) {
						this.inputManager.load();
					} else {
						this.inputManager.setInput(currentLocation);
					}
				}.bind(this));
			}.bind(this));
		},
		getParent: function() {
			return this._parent;
		},
		getSettings: function() {
			return this.settings;
		},
		setParent: function(p) {
			this._parent = p;
		},
		updateSourceCodeActions: function(prefs, sourceCodeActions) {
			if (sourceCodeActions) {
				sourceCodeActions.setAutoPairParentheses(prefs.autoPairParentheses);
				sourceCodeActions.setAutoPairBraces(prefs.autoPairBraces);
				sourceCodeActions.setAutoPairSquareBrackets(prefs.autoPairSquareBrackets);
				sourceCodeActions.setAutoPairAngleBrackets(prefs.autoPairAngleBrackets);
				sourceCodeActions.setAutoPairQuotations(prefs.autoPairQuotations);
				sourceCodeActions.setAutoCompleteComments(prefs.autoCompleteComments);
				sourceCodeActions.setSmartIndentation(prefs.smartIndentation);
			}
		},
		updateViewOptions: function(prefs) {
			var marginOffset = 0;
			if (prefs.showMargin) {
				marginOffset = prefs.marginOffset;
				if (typeof marginOffset !== "number") { //$NON-NLS-0$
					marginOffset = prefs.marginOffset = parseInt(marginOffset, 10);
				}
			}
			var wrapOffset = 0;
			if (prefs.wordWrap) {
				wrapOffset = marginOffset;
			}
			return {
				readonly: this.readonly || this.inputManager.getReadOnly(),
				tabSize: prefs.tabSize || 4,
				expandTab: prefs.expandTab,
				wrapMode: prefs.wordWrap,
				wrapOffset: wrapOffset,
				marginOffset: marginOffset,
				scrollAnimation: prefs.scrollAnimation ? prefs.scrollAnimationTimeout : 0
			};
		},
		updateSettings: function(prefs) {
			this.settings = prefs;
			var editor = this.editor;
			var inputManager = this.inputManager;
			inputManager.setAutoLoadEnabled(prefs.autoLoad);
			inputManager.setAutoSaveTimeout(prefs.autoSave ? prefs.autoSaveTimeout : -1);
			inputManager.setSaveDiffsEnabled(prefs.saveDiffs);
			inputManager.setEncodingCharset(prefs.encodingCharset);
			this.differ.setEnabled(this.settings.diffService);
			this.updateStyler(prefs);
			var textView = editor.getTextView();
			if (textView) {
				this.updateKeyMode(prefs, textView);
				textView.setOptions(this.updateViewOptions(prefs));
			}
			this.updateSourceCodeActions(prefs, editor.getSourceCodeActions());
			editor.setAnnotationRulerVisible(prefs.annotationRuler);
			editor.setLineNumberRulerVisible(prefs.lineNumberRuler);
			editor.setFoldingRulerVisible(prefs.foldingRuler);
			editor.setOverviewRulerVisible(prefs.overviewRuler);
			editor.setZoomRulerVisible(prefs.zoomRuler);
			if (this.renderToolbars) {
				this.renderToolbars(inputManager.getFileMetadata());
			}
			this.markOccurrences.setOccurrencesVisible(prefs.showOccurrences);
			if (editor.getContentAssist()) {
				editor.getContentAssist().setAutoTriggerEnabled(prefs.contentAssistAutoTrigger);
			}

			this.dispatchEvent({
				type: "Settings", //$NON-NLS-0$
				newSettings: this.settings
			});
		},
		updateStyler: function(prefs) {
			var styler = this.syntaxHighlighter.getStyler();
			if (styler) {
				if (styler.setWhitespacesVisible) {
					styler.setWhitespacesVisible(prefs.showWhitespaces, true);
				}
			}
		},
		createSession: function(evt) {
			var editor = this.editor;
			var textView = editor.getTextView();
			var inputManager = this.inputManager;
			if (textView && inputManager) {
				var metadata = inputManager.getFileMetadata();
				if (metadata) {
					evt.session = {
						get: function() {
							return sessionStorage.editorViewSection ? JSON.parse(sessionStorage.editorViewSection) : {}; 
						},
						apply: function(animate) {
							var session = this.get();
							var locationSession = session[metadata.Location];
							if (locationSession && locationSession.ETag === metadata.ETag) {
								editor.setSelections(locationSession.selections);
								textView.setTopIndex(locationSession.topIndex, animate ? function() {} : undefined);
							}
						},
						save: function() {
							var session = this.get();
							session[metadata.Location] = {
								ETag: metadata.ETag,
								topIndex: textView.getTopIndex(),
								selections: editor.getSelections().map(function(s) { return s.getOrientedSelection(); })
							};
							sessionStorage.editorViewSection = JSON.stringify(session);
						}
					};
				}
			}
		},
		_init: function() {
			if (this.preferences) {
				// There should be only one editor preferences
				this.editorCommands.editorPreferences = this.editorPreferences = this.editorCommands.editorPreferences || new mEditorPreferences.EditorPreferences(this.preferences);
				this.editorPreferences.addEventListener("Changed", function (evt) { //$NON-NLS-0$
					var prefs = evt.preferences;
					if (!prefs) {
						this.editorPreferences.getPrefs(this.updateSettings.bind(this));
					} else {
						this.updateSettings(prefs);
					}
				}.bind(this));
				// There should be only one theme preferences
				this.editorCommands.themePreferences = this.themePreferences = this.editorCommands.themePreferences || new mThemePreferences.ThemePreferences(this.preferences, new mThemeData.ThemeData());
				this.themePreferences.apply();
			}

			var that = this;

			var readonly = this.readonly;
			var commandRegistry = this.commandRegistry;
			var serviceRegistry = this.serviceRegistry;
			var activateContext = this.activateContext;
			var inputManager = this.inputManager;
			var progress = this.progress;
			var contentTypeRegistry = this.contentTypeRegistry;
			var editorCommands = this.editorCommands;

			var textViewFactory = function() {
				var options = that.updateViewOptions(that.settings);
				objects.mixin(options, {
					parent: that._parent,
					model: new mProjectionTextModel.ProjectionTextModel(that.model || new mTextModel.TextModel()),
					wrappable: true
				});
				var textView = new mTextView.TextView(options);
				return textView;
			};

			var keyBindingFactory = function(editor, keyModeStack, undoStack, contentAssist) {

				var localSearcher = that.textSearcher = (mSearcher.TextSearcher ? new mSearcher.TextSearcher(editor, serviceRegistry, commandRegistry, undoStack) : null);

				var keyBindings = new mEditorFeatures.KeyBindingsFactory().createKeyBindings(editor, undoStack, contentAssist, localSearcher);
				that.updateSourceCodeActions(that.settings, keyBindings.sourceCodeActions);

				var textView = editor.getTextView();
				textView.setAction("toggleWrapMode", function() { //$NON-NLS-0$
					textView.invokeAction("toggleWrapMode", true); //$NON-NLS-0$
					var wordWrap = textView.getOptions("wrapMode"); //$NON-NLS-0$
					that.settings.wordWrap = wordWrap;
					if (that.editorPreferences) {
						that.editorPreferences.setPrefs(that.settings);
					}
					return true;
				});

				textView.setKeyBinding(new mKeyBinding.KeyStroke('z', true, false, true), "toggleZoomRuler"); //$NON-NLS-1$ //$NON-NLS-2$
				textView.setAction("toggleZoomRuler", function() { //$NON-NLS-0$
					if (!that.settings.zoomRulerVisible) return false;
					that.settings.zoomRuler = !that.settings.zoomRuler;
					if (that.editorPreferences) {
						that.editorPreferences.setPrefs(that.settings);
					}
					return true;
				}, {name: messages.toggleZoomRuler});

				that.vi = that.emacs = null;
				that.updateKeyMode(that.settings, textView);

				editorCommands.overwriteKeyBindings(editor);

				return keyBindings;
			};

			// Content Assist
			var setContentAssistProviders = function(editor, contentAssist, event) {
				// Content assist is about to be activated; set its providers.
				var fileContentType = inputManager.getContentType();
				var fileName = editor.getTitle();
				var serviceRefs = serviceRegistry.getServiceReferences("orion.edit.contentAssist").concat(serviceRegistry.getServiceReferences("orion.edit.contentassist")); //$NON-NLS-1$ //$NON-NLS-2$
				var providerInfoArray = event && event.providers;
				if (!providerInfoArray) {
					providerInfoArray = serviceRefs.map(function(serviceRef) {
						var contentTypeIds = serviceRef.getProperty("contentType"), //$NON-NLS-0$
						    pattern = serviceRef.getProperty("pattern"); // backwards compatibility //$NON-NLS-0$
						if ((contentTypeIds && contentTypeRegistry.isSomeExtensionOf(fileContentType, contentTypeIds)) ||
								(pattern && new RegExp(pattern).test(fileName))) {
							var service = serviceRegistry.getService(serviceRef);
							var id = serviceRef.getProperty("service.id").toString();  //$NON-NLS-0$
							var charTriggers = serviceRef.getProperty("charTriggers"); //$NON-NLS-0$
							var excludedStyles = serviceRef.getProperty("excludedStyles");  //$NON-NLS-0$

							if (charTriggers) {
								charTriggers = new RegExp(charTriggers);
							}

							if (excludedStyles) {
								excludedStyles = new RegExp(excludedStyles);
							}

							return {provider: service, id: id, charTriggers: charTriggers, excludedStyles: excludedStyles};
						}
						return null;
					}).filter(function(providerInfo) {
						return !!providerInfo;
					});
				}

				// Produce a bound EditorContext that contentAssist can invoke with no knowledge of ServiceRegistry.
				var boundEditorContext = {};
				Object.keys(EditorContext).forEach(function(key) {
					if (typeof EditorContext[key] === "function") { //$NON-NLS-0$
						boundEditorContext[key] = EditorContext[key].bind(null, serviceRegistry, that.editContextServiceID);
					}
				});
				contentAssist.setEditorContextProvider(boundEditorContext);
				contentAssist.setProviders(providerInfoArray);
				contentAssist.setAutoTriggerEnabled(that.settings.contentAssistAutoTrigger);
				contentAssist.setProgress(progress);
				contentAssist.setStyleAccessor(that.getStyleAccessor());
			};

			var contentAssistFactory = readonly ? null : {
				createContentAssistMode: function(editor) {
					var contentAssist = new mContentAssist.ContentAssist(editor.getTextView(), serviceRegistry);

					contentAssist.addEventListener("Activating", setContentAssistProviders.bind(null, editor, contentAssist)); //$NON-NLS-0$
					var widget = new mContentAssist.ContentAssistWidget(contentAssist, "contentassist"); //$NON-NLS-0$
					var result = new mContentAssist.ContentAssistMode(contentAssist, widget);
					contentAssist.setMode(result);

					// preload content assist plugins to reduce the delay
					// that happens when a user first triggers content assist
					setContentAssistProviders(editor, contentAssist);
					contentAssist.initialize();
					return result;
				}
			};

			var editor = this.editor = new mEditor.Editor({
				textViewFactory: textViewFactory,
				undoStackFactory: that.undoStack ? {createUndoStack: function(editor) {
					that.undoStack.setView(editor.getTextView());
					return that.undoStack;
				}}: new mEditorFeatures.UndoFactory(),
				textDNDFactory: new mEditorFeatures.TextDNDFactory(),
				annotationFactory: new mEditorFeatures.AnnotationFactory(),
				foldingRulerFactory: new mEditorFeatures.FoldingRulerFactory(),
				zoomRulerFactory: new mEditorFeatures.ZoomRulerFactory(),
				lineNumberRulerFactory: new mEditorFeatures.LineNumberRulerFactory(),
				hoverFactory: new mHoverFactory.HoverFactory(serviceRegistry, inputManager, commandRegistry),
				contentAssistFactory: contentAssistFactory,
				keyBindingFactory: keyBindingFactory,
				statusReporter: this.statusReporter,
				domNode: this._parent
			});
			editor.id = "orion.editor"; //$NON-NLS-0$
			editor.processParameters = function(params) {
				parseNumericParams(params, ["start", "end", "line", "offset", "length"]); //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-5$
				return this.showSelection(params.start, params.end, params.line, params.offset, params.length);
			};
			editor.getEditorContext = function() {
				return EditorContext.getEditorContext(serviceRegistry, that.editContextServiceID);
			};

			this.dispatcher = new Dispatcher(this.serviceRegistry, this.contentTypeRegistry, editor, inputManager);
			if(this.themePreferences && this.editorPreferences){
				this.localSettings = EditorSettings ? new EditorSettings({local: true, editor: editor, themePreferences: this.themePreferences, preferences: this.editorPreferences}) : null;
			}
			var liveEditSession = new LiveEditSession(serviceRegistry, editor);
			inputManager.addEventListener("InputChanging", function(evt) { //$NON-NLS-0$
				that.createSession(evt);
			});
			window.addEventListener("beforeunload", function(evt) { //$NON-NLS-0$
				that.createSession(evt);
				if (evt.session) {
					evt.session.save();
				}
			});
			inputManager.addEventListener("InputChanged", function(event) { //$NON-NLS-0$
				that.createSession(event);
				var textView = editor.getTextView();
				if (textView) {
					liveEditSession.start(inputManager.getContentType(), event.title);
					textView.setOptions(this.updateViewOptions(this.settings));
					this.syntaxHighlighter.setup(event.contentType, editor.getTextView(), editor.getAnnotationModel(), event.title, true).then(function() {
						this.updateStyler(this.settings);
						if (editor.getContentAssist()) {
							// the file changed, we need to figure out the correct auto triggers to use
							setContentAssistProviders(editor, editor.getContentAssist());
						}
					}.bind(this));
					if(textView.onInputChanged) {
						textView.onInputChanged({type:event.type});
					}
				} else {
					liveEditSession.start();
				}
			}.bind(this));
			inputManager.addEventListener("Saving", function(event) { //$NON-NLS-0$
				if (that.settings.trimTrailingWhiteSpace) {
					editor.getTextView().invokeAction("trimTrailingWhitespaces"); //$NON-NLS-0$
				}
				var textView = editor.getTextView();
				if(textView && textView.onSaving) {
					textView.onSaving({type:event.type});
				}
			});

			this.blamer = new mBlamer.Blamer(serviceRegistry, inputManager, editor);
			this.differ = new mDiffer.Differ(serviceRegistry, inputManager, editor);

			this.problemService = new mProblems.ProblemService(serviceRegistry, this.problemsServiceID);
			var markerService = serviceRegistry.getService(this.problemsServiceID);
			if(markerService) {
				markerService.addEventListener("problemsChanged", function(event) { //$NON-NLS-0$
					editor.showProblems(event.problems);
				});
			}

			var markOccurrences = this.markOccurrences = new mMarkOccurrences.MarkOccurrences(serviceRegistry, inputManager, editor);
			markOccurrences.setOccurrencesVisible(this.settings.occurrencesVisible);
			markOccurrences.findOccurrences();
			
			var syntaxChecker = new mSyntaxchecker.SyntaxChecker(serviceRegistry, editor.getModel());
			editor.addEventListener("InputChanged", function(evt) { //$NON-NLS-0$
				syntaxChecker.setTextModel(editor.getModel());
				var input = inputManager.getInput();
				syntaxChecker.checkSyntax(inputManager.getContentType(), evt.title, evt.message, evt.contents, editor.getEditorContext()).then(function(problems) {
					if (input === inputManager.getInput()) {
						serviceRegistry.getService(that.problemsServiceID)._setProblems(problems);
					}
				});
				if (inputManager.getReadOnly()) {
					editor.reportStatus(messages.readonly, "error"); //$NON-NLS-0$
				}
			});

			var contextImpl = Object.create(null);
			[
				"getCaretOffset", "setCaretOffset", //$NON-NLS-1$ //$NON-NLS-2$
				"getSelection", "getSelectionText", "setSelection", //$NON-NLS-1$ //$NON-NLS-2$
				"getSelections", "setSelections", //$NON-NLS-1$ //$NON-NLS-2$
				"getText", "setText", //$NON-NLS-1$ //$NON-NLS-2$
				"getLineAtOffset", //$NON-NLS-0$
				"getLineStart", //$NON-NLS-0$
				"isDirty", //$NON-NLS-0$.
				"markClean", //$NON-NLS-0$.
			].forEach(function(method) {
				contextImpl[method] = editor[method].bind(editor);
			});
			contextImpl.showMarkers = function(markers) {
				serviceRegistry.getService(that.problemsServiceID)._setProblems(markers);
			};
			contextImpl.enterLinkedMode = function(linkedModeModel) {
				editor.getLinkedMode().enterLinkedMode(linkedModeModel);
			};
			contextImpl.exitLinkedMode = function(escapePosition) {
				editor.getLinkedMode().exitLinkedMode(escapePosition);
			};
			/**
			 * @description Opens the given location
			 * @function
			 * @param {String} fileurl The URL to open
			 * @param {Object} options The map of options. 
			 * 
			 * Current set of understood options include:
			 *   start - (number) The start range to select when opening an editor
			 *   end - (number) The end range to select when opening an editor
			 *   newwindow - (boolean) If we should open the URL in a new tab
			 * 
			 * @since 9.0
			 */
			contextImpl.openEditor = function(fileurl, options) {
				activateContext.openEditor(fileurl, options);
			};
			
			/**
			 * @since 7.0
			 */
			contextImpl.getFileMetadata = function() {
				return that.dispatcher.getServiceFileObject();
			};
			// Forward status from plugin to orion.page.message
			contextImpl.setStatus = mEditorCommands.handleStatusMessage.bind(null, serviceRegistry);
			serviceRegistry.registerService(this.editContextServiceID, contextImpl, null);
		},
		create: function() {
			this.editor.install();
			if(this.editorPreferences) {
				this.editorPreferences.getPrefs(this.updateSettings.bind(this));
			}
			
			// Create a context menu...
			this._createContextMenu();
		},
		destroy: function() {
			if(this.lastFileLocation) {
				this.fileClient.deleteFile(this.lastFileLocation);
			}
			this.editor.uninstall();
		},
		getStyleAccessor: function() {
			var styleAccessor = null;
			var styler = this.syntaxHighlighter.getStyler();
			if (styler && styler.getStyleAccessor) {
				styleAccessor = styler.getStyleAccessor();
			}
			return styleAccessor;
		},
		_createContextMenu: function() {			
			// Create the context menu element (TBD: re0use a single Node for all context Menus ??)
			this._editorContextMenuNode = document.createElement("ul"); //$NON-NLS-0$
			this._editorContextMenuNode.className = "dropdownMenu"; //$NON-NLS-0$
			this._editorContextMenuNode.setAttribute("role", "menu"); //$NON-NLS-1$ //$NON-NLS-2$
			this._parent.parentNode.appendChild(this._editorContextMenuNode);
			
			// Hook the context menu to the textView's content node
			var tv = this.editor.getTextView();
			var contextMenu = new mContextMenu.ContextMenu({
				dropdown: this._editorContextMenuNode,
				triggerNode: tv._clientDiv
			});
						
			//function called when the context menu is triggered to set the nav selection properly
			var contextMenuTriggered = function(wrapper) {
				var re = wrapper.event;
				if (re.target) {
					this.commandRegistry.destroy(this._editorContextMenuNode); // remove previous content
					this.commandRegistry.renderCommands("editorContextMenuActions", this._editorContextMenuNode, null, this, "menu"); //$NON-NLS-1$ //$NON-NLS-2$
					mMetrics.logEvent("contextMenu", "opened", "editor"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				}
			}.bind(this);
			contextMenu.addEventListener("triggered", contextMenuTriggered); //$NON-NLS-0$
		}
	};
	mEventTarget.EventTarget.addMixin(EditorView.prototype);

	return {EditorView: EditorView};
});

/******************************************************************************* 
 * @license
 * Copyright (c) 2011, 2013 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('embeddedEditor/helper/editorSetup',[
	'orion/editor/textModel',
	'orion/editor/undoStack',
	'orion/commandRegistry',
	'orion/inputManager',
	'orion/fileClient',
	'orion/contentTypes',
	'orion/editorView',
	'orion/editorCommands',
	'orion/objects'
], function(
	mTextModel,
	mUndoStack,
	mCommandRegistry,
	mInputManager,
	mFileClient,
	mContentTypes,
	mEditorView,
	mEditorCommands,
	objects
) {
	var idCounter = 0;
	function EditorSetupHelper(options) {
		this._serviceRegistry = options.serviceRegistry;
		this._pluginRegistry = options.pluginRegistry;
		this._commandRegistry = new mCommandRegistry.CommandRegistry({});
		this._fileClient = new mFileClient.FileClient(this._serviceRegistry);
		this._contentTypeRegistry = new mContentTypes.ContentTypeRegistry(this._serviceRegistry);
		this._editorCommands = new mEditorCommands.EditorCommandFactory({
			serviceRegistry: this._serviceRegistry,
			commandRegistry: this._commandRegistry,
			fileClient: this._fileClient,
			toolbarId: "_orion_hidden_actions",
			navToolbarId: "_orion_hidden_actions"
			/*
			renderToolbars: this.renderToolbars.bind(this),
			searcher: this.searcher,
			readonly: this.readonly,
			toolbarId: "toolsActions", //$NON-NLS-0$
			saveToolbarId: "fileActions", //$NON-NLS-0$
			editToolbarId: "editActions", //$NON-NLS-0$
			navToolbarId: "pageNavigationActions", //$NON-NLS-0$
			*/
		});
		this._progressService = {
			progress: function(deferred, operationName, progressMonitor){
				return deferred;
			},
			showWhile: function(deferred, message, avoidDisplayError){
				return deferred;
			}
		};			
		this._serviceRegistry.registerService("orion.page.progress", this._progressService);
	}
	
	objects.mixin(EditorSetupHelper.prototype, /** @lends orion.editor.EditorSetupHelper.prototype */ {
		destroy: function() {
		},
		createInputManager: function() {
			var inputManager = this._inputManager = new mInputManager.InputManager({
				serviceRegistry: this._serviceRegistry,
				fileClient: this._fileClient,
				progressService: this._progressService,
				//statusReporter: this.statusReporter.bind(this),
				selection: this.selection,
				contentTypeRegistry: this._contentTypeRegistry
			});
			inputManager.addEventListener("InputChanged", function(evt) { //$NON-NLS-0$
				evt.editor = this.editorView.editor;
				this.pageActionsScope = "_orion_hidden_actions";
				this._commandRegistry.destroy(this.pageActionsScope);
				this._commandRegistry.renderCommands(this.pageActionsScope, this.pageActionsScope, evt.metadata, evt.editor, "tool"); //$NON-NLS-0$
			}.bind(this));
			inputManager.addEventListener("InputChanging", function(e) { //$NON-NLS-0$
				e.editor = this.editorView.editor;
			}.bind(this));
		},
		defaultOptions: function(parentId) {
			var model = new mTextModel.TextModel();
			var id = idCounter.toString();
			var context = Object.create(null);
			context.openEditor = function(fileurl, options){this.editorView.editor.setSelection(options.start, options.end);}.bind(this);
			return {
				activateContext: context,
				id: id,
				parent: parentId,
				model: model,
				undoStack: new mUndoStack.UndoStack(model, 500),
				serviceRegistry: this._serviceRegistry,
				pluginRegistry: this._pluginRegistry,
				commandRegistry: this._commandRegistry,
				contentTypeRegistry: this._contentTypeRegistry,
				editorCommands: this._editorCommands,
				progressService: this._progressService,
				inputManager: this._inputManager, // fake it
				fileService: this._fileClient, // fake it
				problemsServiceID: "orion.core.marker" + id, //$NON-NLS-0$
				editContextServiceID: "orion.edit.context" + id, //$NON-NLS-0$
				editModelContextServiceID: "orion.edit.model.context" + id, //$NON-NLS-0$
				readonly: false
			};
		},
		createEditor: function(options) {
			return this._editorCommands.createCommands().then(function() {
				this._editorCommands.registerCommands();
				this.createInputManager();
				this.editorView = new mEditorView.EditorView(this.defaultOptions(options.parent));
				idCounter++;
				this.editorView.create();
				this._inputManager.editor = this.editorView.editor;
				this._inputManager.setAutoSaveTimeout(300);
				this._editorCommands.inputManager = this._inputManager;
				if(options.contentType && typeof options.contents === "string") {
					this.editorView.setContents(options.contents, options.contentType);
				}
				return this.editorView;
			}.bind(this));
		}
	});
	
	return {
		EditorSetupHelper: EditorSetupHelper
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2011, 2012 IBM Corporation and others. All rights reserved. This
 * program and the accompanying materials are made available under the terms of
 * the Eclipse Public License v1.0 (http://www.eclipse.org/legal/epl-v10.html),
 * and the Eclipse Distribution License v1.0
 * (http://www.eclipse.org/org/documents/edl-v10.html).
 * 
 * Contributors: IBM Corporation - initial API and implementation
 ******************************************************************************/
/*eslint-env browser, amd*/
define('embeddedEditor/builder/embeddedEditor',[
	'embeddedEditor/helper/bootstrap',
	'embeddedEditor/helper/editorSetup',
	'orion/objects'
], function(
	mBootstrap,
	mEditorSetup,
	objects
) {
	function CodeEdit() {
	}

	objects.mixin(CodeEdit.prototype, {
		/**
		 * @class This object describes the options for <code>create</code>.
		 * @name orion.editor.EditOptions
		 *
		 * @property {String|DOMElement} parent the parent element for the view, it can be either a DOM element or an ID for a DOM element.
		 * @property {String} [contents=""] the editor contents.
		 * @property {String} [contentType] the type of the content (eg.- application/javascript, text/html, etc.)
		 */
		/**
		 * Creates an editorview instance configured with the given options.
		 * 
		 * @param {orion.editor.EditOptions} options the editor options.
		 */
		create: function(options) {
			return mBootstrap.startup(options).then(function(core) {
				var serviceRegistry = core.serviceRegistry;
				var pluginRegistry = core.pluginRegistry;
				var editorHelper = new mEditorSetup.EditorSetupHelper({
					serviceRegistry: serviceRegistry,
					pluginRegistry: pluginRegistry
				});
				return editorHelper.createEditor(options);
			});
		}
	});
	return CodeEdit;
});
return require('embeddedEditor/builder/embeddedEditor');}));