/* orion browse */ 
/**
 * 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
 *******************************************************************************/
/*global define console window document*/

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.2 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 () {
    

    //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') {
                mixin(target[prop], source[prop], force);
            }
        }
    }

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

        return {
            version: '2.0.1+',
            /**
             * 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);
                    }

                    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) 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
 ******************************************************************************/

/*global define */
define('orion/i18n',{
	load: function(name, parentRequire, onLoad, config) {
		if (parentRequire.specified && parentRequire.specified("orion/bootstrap")) { //$NON-NLS-0$
			parentRequire(["orion/i18n!" + name], function(languages) { //$NON-NLS-0$
				onLoad(languages);
			});
		} else {
			onLoad({});
		}
	}
});

/*******************************************************************************
 * @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).
 *
 ******************************************************************************/

/*global define */

//NLS_CHARSET=UTF-8

define('orion/edit/nls/root/messages',{
	"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$
	"searchFiles": "Search Files...", //$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$
	"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": "Can not 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$
	"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$
	"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$
	"SidePanel": "Side Panel", //$NON-NLS-1$ //$NON-NLS-0$
	"SidePanelTooltip": "Choose what to show in the side panel.", //$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": "(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$
});

/*******************************************************************************
 * @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). 
 * 
 ******************************************************************************/
define('orion/edit/nls/messages',['orion/i18n!orion/edit/nls/messages', 'orion/edit/nls/root/messages'], function(bundle, root) {
	var result = {
			root:root
	};
	Object.keys(bundle).forEach(function(key) {
		if (typeof result[key] === 'undefined') {
			result[key] = bundle[key];
		}
	});
	return result;
});

/*******************************************************************************
 * @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). 
 * 
 ******************************************************************************/

//NLS_CHARSET=UTF-8
 
define('orion/navigate/nls/root/messages',{
	"Click one of the tasks below to create an Orion folder.  You can upload, import, or generate files.": "Click one of the tasks below to create an Orion folder.  You can upload, import, or generate files.",
	"Toggle Left Pane": "Toggle Left Pane",
	"Open or close the left pane": "Open or close the left pane",
	"Navigator": "Navigator",
	"Strings Xtrnalizr": "Strings Xtrnalizr",
	"Externalize Strings from JavaScript files in this folder": "Externalize strings from JavaScript files in this folder.",
	"Choose a Folder": "Choose a Folder",
	"Copy of ${0}": "Copy of ${0}",
	"Enter a new name for '${0}'": "Enter a new name for '${0}'",
	"Choose folder...": "Choose folder...",
	"Rename": "Rename",
	"Rename the selected files or folders": "Rename the selected files or folders",
	"Compare with each other": "Compare with each other",
	"Compare the selected 2 files with each other": "Compare the selected 2 files with each other",
	"Compare with...": "Compare with...",
	"Compare the selected folder with a specified folder": "Compare the selected folder with a specified folder",
	"Delete": "Delete",
	"Unknown item": "Unknown item",
	"Are you sure you want to delete these ${0} items?": "Are you sure you want to delete these ${0} items?",
	"Are you sure you want to delete '${0}'?": "Are you sure you want to delete '${0}'?",
	"Export as zip": "Zip",
	"Create a zip file of the folder contents and download it": "Create a zip file of the folder contents and download it",
	"New File": "File",
	"Create a new file": "Create a new file",
	"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",
	"Create an empty folder on the Orion server.  You can import, upload, or create content in the editor.": "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",
	"Import content from an SFTP location into an Orion folder.  You will be prompted for the SFTP location and credentials.": "Import content from an SFTP location into an Orion folder.  You will be prompted for the SFTP location and credentials.",
	"Imported Content": "Imported Content",
	"Upload a Zip": "Upload a Zip",
	"Upload content from a local zip file": "Upload content from a local zip file",
	"Upload content from a local zip file into an Orion folder.  You will be prompted for the local zip file.": "Upload content from a local zip file into an Orion folder.  You will be prompted for the local zip file.",
	"Uploaded Content": "Uploaded Content",
	"Creating an upload folder for ": "Creating an upload folder for ",
	"Clone Git Repository": "Clone Git Repository",
	"Clone a git repository": "Clone a git repository",
	"Go to the Orion repositories page to provide a git repository URL.  Once the repository is created, it will appear in the Navigator.": "Go to the Orion repositories page to provide a git repository URL.  Once the repository is created, it will appear in the Navigator.",
	"Link to Server": "Link to Server",
	"Link to existing content on the server": "Link to existing content on the server",
	"Create a folder that links to an existing folder on the server.": "Create a folder that links to an existing folder on the server.",
	"Server path:": "Server path:",
	"The name and server location were not specified.": "The name and server location were not specified.",
	"Go Up": "Go Up",
	"Move up to the parent folder": "Move up to the parent folder",
	"Go Into": "Go Into",
	"Move into the selected folder": "Move into the selected folder",
	"File or zip archive": "File or zip archive",
	"Import a file or zip archive from your local file system": "Import a file or zip archive from your local file system",
	"SFTP from...": "SFTP",
	"Copy files and folders from a specified SFTP connection": "Copy files and folders from a specified SFTP connection",
	"Importing from ${0}": "Importing from ${0}",
	"SFTP to...": "SFTP",
	"Copy files and folders to a specified SFTP location": "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}",
	"Move files and folders to a new location": "Move files and folders to a new location",
	"Cut": "Cut",
	"Copy": "Copy",
	"Fetching children of ": "Fetching children of ",
	"Paste": "Paste",
	"Cannot paste into the Workspace root": "Cannot paste into the Workspace root",
	"Open With": "Open With",
	"Loading ": "Loading ",
	"New": "New",
	"File": "File",
	"Actions": "Actions",
	"Orion Content": "Orion Content",
	" is not supported in this file system": " is not supported in this file system",
	"Create new content" : "Create new content",
	"Import from HTTP..." : "HTTP",
	"File URL:" : "File URL:",
	"Import a file from a URL and optionally unzip it" : "Import a file from a URL and optionally unzip it",
	"Unzip *.zip files:" : "Unzip *.zip files:",
	"Extracted from:" : "Extracted from:",
	"Did not drop ${0}.  Folder drop is not supported in this browser.": "Did not drop ${0}.  Folder drop is not supported in this browser.",
	"You cannot copy files directly into the workspace.  Create a folder first.": "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",
	"Uploading the following file failed: ": "Uploading the following file failed: ",
	"Enter project name:": "Enter project name:",
	"Creating project ": "Creating project ",
	"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."
});

/*******************************************************************************
 * @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). 
 * 
 ******************************************************************************/
define('orion/navigate/nls/messages',['orion/i18n!orion/navigate/nls/messages', 'orion/navigate/nls/root/messages'], function(bundle, root) {
	var result = {
			root:root
	};
	Object.keys(bundle).forEach(function(key) {
		if (typeof result[key] === 'undefined') {
			result[key] = bundle[key];
		}
	});
	return result;
});

/*******************************************************************************
 * @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
 ******************************************************************************/
/*global exports module define setTimeout process document MutationObserver*/

(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) 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).
 *
 ******************************************************************************/

//NLS_CHARSET=UTF-8

define('orion/nls/root/messages',{
	"Navigator": "Navigator",
	"Sites": "Sites",
	"Shell": "Shell",
	"ShellLinkWorkspace": "Workspace Shell",
	"Get Plugins": "Get Plugins",
	"Global": "Global",
	"Editor": "Editor",
	"EditorRelatedLink": "Show Current Folder",
	"EditorRelatedLinkParent": "Show Enclosing Folder",
	"EditorLinkWorkspace": "Show Workspace",
	"EditorRelatedLinkProj": "Show Project",
	"Filter bindings": "Filter bindings",
	"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.",
	"Show a list of all the keybindings on this page": "Show a list of all the keybindings on this page",
	"Show Keys": "Show Keys",
	"Hide or show the page banner and footer": "Hide or show the page banner and footer",
	"Toggle banner and footer": "Toggle banner and footer",
	"Choose a file by name and open an editor on it": "Choose a file by name and open an editor on it",
	"Find File Named...": "Find File Named...",
	"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",
	"Can't search: no search service is available": "Can't search: no search service is available",
	"could not find banner parent, id was ": "could not find banner parent, id was ",
	"Related": "Related",
	"Options": "Options",
	"FAQ": "FAQ",
	"Report a Bug": "Bugs",
	"Privacy Policy": "Privacy",
	"Terms of Use": "Terms",
	"Copyright Agent": "Copyright",
	"Orion Logo": "Orion Logo",
	"Orion is in Beta. Please try it out but BEWARE your data may be lost.": "2014-06-28_22-01-29",
	"LOG: ": "LOG: ",
	"View": "View",
	"no parent": "no parent",
	"no tree model": "no tree model",
	"no renderer": "no renderer",
	"could not find table row ": "could not find table row ",
	"Resource is out of sync with the server. Do you want to save it anyway?": "Resource is out of sync with the server. Do you want to save it anyway?",
	"Operations": "Operations",
	"Operations running": "Operations running",
	"Some operations finished with warning": "Some operations finished with warning",
	"Some operations finished with error": "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",
	"Operations - Press spacebar to show current operations" : "Operations - Press spacebar to show current operations",
	"Type a keyword or wild card to search in root" : "Type a keyword or wild card to search in root",
	"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 Plugin",
	"Generate a sample plugin for integrating with Orion.": "Generate a sample plugin for integrating with Orion.",
	"Browser": "Web Browser",
	"OutlineProgress": "Getting outline for ${0} from ${1}",
	"UnknownError": "An unknown error occurred.",
	"UnknownWarning": "An unknown warning occurred.",
	"Filter": "Filter (* = any string, ? = any character)",
	"To view the browser's context menu, trigger the context menu again.": "To view the browser's context menu, trigger the context menu again.",
	"Edit": "Edit",
	"CentralNavTooltip": "Toggle Navigation Menu"
});

/*******************************************************************************
 * @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). 
 * 
 ******************************************************************************/
define('orion/nls/messages',['orion/i18n!orion/nls/messages', 'orion/nls/root/messages'], function(bundle, root) {
	var result = {
			root:root
	};
	Object.keys(bundle).forEach(function(key) {
		if (typeof result[key] === 'undefined') {
			result[key] = bundle[key];
		}
	});
	return result;
});

/*******************************************************************************
 * @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
 *******************************************************************************/

/*global define navigator document*/
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) 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
 ******************************************************************************/
/*global console window define document*/
/*jslint regexp:false*/

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();
		return { 
			left: clientRect.left + document.documentElement.scrollLeft,
			top: clientRect.top + document.documentElement.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);
			}
		}
	}

	/**
	 * 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;
		});
	}
	
	/**
	 * 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,
		ESCAPE: 27,
		SPACE: 32,
		PAGEUP: 33,
		PAGEDOWN: 34,
		END: 35,
		HOME: 36,
		LEFT: 37,
		UP: 38,
		RIGHT: 39,
		DOWN: 40,
		INSERT: 45,
		DEL: 46
	};
		
	//return module exports
	return {
		$: $,
		$$: $$,
		$$array: $$array,
		node: node,
		contains: contains,
		bounds: bounds,
		empty: empty,
		firstTabbable: firstTabbable,
		lastTabbable: lastTabbable,
		stop: stop,
		processTextNodes: processTextNodes,
		processDOMNodes: processDOMNodes,
		addAutoDismiss: addAutoDismiss,
		setFramesEnabled: setFramesEnabled,
		removeAutoDismiss: removeAutoDismiss,
		KEY: KEY
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*jslint amd:true forin:true devel:true*/
/*global console document*/

define('orion/webui/treetable',['i18n!orion/nls/messages', 'orion/webui/littlelib'], function(messages, lib) {

	/**
	 * Constructs a new TableTree with the given options.
	 * 
	 * @param options 
	 * @name orion.treetable.TableTree 
	 * @class Generates an HTML table where one of the columns is indented according to depth of children.
	 * <p>Clients must supply a model that generates children items, and a renderer can be supplied which
	 * generates the HTML table row for each child. Custom rendering allows clients to use checkboxes,
	 * images, links, etc. to describe each  element in the tree.  Renderers handle all clicks and other
	 * behavior via their supplied row content.</p>
	 * 
	 * <p>The table tree parent can be specified by id or DOM node.</p>
	 * 
	 * <p>The tree provides API for the client to programmatically expand and collapse
	 * nodes, based on the client renderer's definition of how that is done (click on icon, etc.).
	 * The tree will manage the hiding and showing of child DOM elements and proper indent</p>
	 * 
	 * The model must implement:
	 * <ul>
	 *   <li>getRoot(onItem)</li>
	 *   <li>getChildren(parentItem, onComplete)</li>
	 *   <li>getId(item)  // must be a valid DOM id</li>
	 * </ul>
	 * 
	 * Renderers must implement:
	 * <ul>
	 *   <li>initTable(tableNode) // set up table attributes and a header if desired</li>
	 *   <li>render(item, tr) // generate tds for the row</li>
	 *   <li>labelColumnIndex() // 0 based index of which td contains the primary label which will be indented</li>
	 *   <li>rowsChanged // optional, perform any work (such as styling) that should happen after the row content changes</li>
	 *   <li>updateExpandVisuals(row, isExpanded) // update any expand/collapse visuals for the row based on the specified state</li>
	 * </ul>
	 *   TODO DOC
	 *   wrapperCallback
	 *   tableCallback
	 *   bodyCallback
	 *   rowCallback
	 */
	function TableTree (options) {
		this._init(options);
	}
	TableTree.prototype = /** @lends orion.treetable.TableTree.prototype */ {
		_init: function(options) {
			var parent = options.parent;
			var tree = this;
			parent = lib.node(parent);
			if (!parent) { throw messages["no parent"]; }
			if (!options.model) { throw messages["no tree model"]; }
			if (!options.renderer) { throw messages["no renderer"]; }
			this._parent = parent;
			this._treeModel = options.model;
			this._onComplete = options.onComplete;
			this._renderer = options.renderer;
			this._showRoot = options.showRoot === undefined ? false : options.showRoot;
			this._indent = options.indent === undefined ? 16 : options.indent;
			this._onCollapse = options.onCollapse;
			this._labelColumnIndex = options.labelColumnIndex === undefined ? 0 : options.labelColumnIndex;
			this._id = options.id === undefined ? "treetable" : options.id; //$NON-NLS-0$
			this._tableStyle = options.tableStyle;
			this._tableElement = options.tableElement || "table"; //$NON-NLS-0$
			this._tableBodyElement = options.tableBodyElement || "tbody"; //$NON-NLS-0$
			this._tableRowElement = options.tableRowElement || "tr"; //$NON-NLS-0$
			
			// Generate the table
			this._treeModel.getRoot(function (root) {
				tree._root = root;
				if (tree._showRoot) {
					root._depth = 0;
					tree._generate([root], 0);
				}
				else {
					tree._treeModel.getChildren(root, function(children) {
						if (tree.destroyed) { return; }
						tree._generate(children, 0);
					});
				}
			});
		},
		
		destroy: function() {
			this.destroyed = true;
		},
		
		_generate: function(children, indentLevel) {
			lib.empty(this._parent);
			var wrapper = document.createElement("div"); //$NON-NLS-0$
			if (this._renderer.wrapperCallback) {
				this._renderer.wrapperCallback(wrapper);
			}
			var table = document.createElement(this._tableElement); //$NON-NLS-0$
			if (this._renderer.tableCallback) {
				this._renderer.tableCallback(table);
			}
			table.id = this._id;
			if (this._tableStyle) {
				table.classList.add(this._tableStyle);
			}
			this._renderer.initTable(table, this);
			this._bodyElement = document.createElement(this._tableBodyElement); //$NON-NLS-0$
			if (this._renderer.bodyCallback) {
				this._renderer.bodyCallback(this._bodyElement);
			}
			this._bodyElement.id = this._id+"tbody"; //$NON-NLS-0$
			if (children.length === 0) {
				if (this._renderer.emptyCallback) {
					this._renderer.emptyCallback(this._bodyElement);
				}
			} else {
				this._generateChildren(children, indentLevel); //$NON-NLS-0$
			}
			table.appendChild(this._bodyElement);
			wrapper.appendChild(table);
			this._parent.appendChild(wrapper);
			this._rowsChanged();
			if (this._onComplete) {
				this._onComplete(this);
			}
		},
		
		_generateChildren: function(children, indentLevel, referenceNode) {
			for (var i=0; i<children.length; i++) {
				var row = document.createElement(this._tableRowElement); //$NON-NLS-0$
				row.id = this._treeModel.getId(children[i]);
				row._depth = indentLevel;
				// This is a perf problem and potential leak because we're bashing a dom node with
				// a javascript object.  (Whereas above we are using simple numbers/strings). 
				// We should consider an item map.
				row._item = children[i];
				this._renderer.render(children[i], row);
				// generate an indent
				var indent = this._indent * indentLevel;
				row.childNodes[Math.min(row.childNodes.length - 1, this._labelColumnIndex)].style.paddingLeft = indent +"px";  //$NON-NLS-0$
				
				if (this._renderer.rowCallback) {
					this._renderer.rowCallback(row, children[i]);
				}
				
				if (referenceNode) {
					this._bodyElement.insertBefore(row, referenceNode.nextSibling);
					if (referenceNode) { //$NON-NLS-0$
						referenceNode = row;
					}
				} else {
					this._bodyElement.appendChild(row);
				}
			}
		},
		
		_rowsChanged: function() {
			// notify the renderer if it has implemented the function
			if (this._renderer.rowsChanged) {
				this._renderer.rowsChanged();
			}
		},
		
		getSelected: function() {
			return this._renderer.getSelected();
		},
		
		refresh: function(item, children, /* optional */ forceExpand) {
			var parentId = this._treeModel.getId(item);
			var tree;
			if (parentId === this._id) {  // root of tree
				this._removeChildRows(parentId);
				this._generateChildren(children, 0);
				this._rowsChanged();
			} else if (parentId === this._treeModel.getId(this._root) && item.removeAll) {
				this._removeAllRows();
				this._generateChildren(children, 0);
				this._rowsChanged();
			} else {  // node in the tree
				var row = lib.node(parentId);
				if (row) {
					// if it is showing children, refresh what is showing
					row._item = item;
					// If the row should be expanded
					if (row && (forceExpand || row._expanded)) {
						this._removeChildRows(parentId);
						if(children){
							row._expanded = true;
							this._renderer.updateExpandVisuals(row, true);
							this._generateChildren(children, row._depth+1, row); //$NON-NLS-0$
							this._rowsChanged();
						} else {
							tree = this;
							this._renderer.updateExpandVisuals(row, "progress"); //$NON-NLS-0$
							children = this._treeModel.getChildren(row._item, function(children) {
								if (tree.destroyed) { return; }
								tree._renderer.updateExpandVisuals(row, true);
								if (!row._expanded) {
									row._expanded = true;
									tree._generateChildren(children, row._depth+1, row); //$NON-NLS-0$
									tree._rowsChanged();
								}
							});
						}
					} else {
						this._renderer.updateExpandVisuals(row, false);
					}
				} else {
					// the item wasn't found.  We could refresh the root here, but for now
					// let's log it to figure out why.
					console.log(messages["could not find table row "] + parentId);
				}
			}
		},
		
		getItem: function(itemOrId) {  // a dom node, a dom id, or the item
			var node = lib.node(itemOrId);
			if (node && node._item) {
				return node._item;
			}
			return itemOrId;  // return what we were given
		},
		
		toggle: function(id) {
			var row = lib.node(id);
			if (row) {
				if (row._expanded) {
					this.collapse(id);
				}
				else {
					this.expand(id);
				}
			}
		},
		
		isExpanded: function(itemOrId) {
			var id = typeof(itemOrId) === "string" ? itemOrId : this._treeModel.getId(itemOrId); //$NON-NLS-0$
			var row =lib.node(id);
			if (row) {
				return row._expanded;
			}
			return false;
		},
		
		expand: function(itemOrId , postExpandFunc , args) {
			var id = typeof(itemOrId) === "string" ? itemOrId : this._treeModel.getId(itemOrId); //$NON-NLS-0$
			var row = lib.node(id);
			if (row) {
				var tree = this;
				if (row._expanded) {
					if (postExpandFunc) {
						postExpandFunc.apply(tree, args);
					}
					return;
				}
				this._renderer.updateExpandVisuals(row, "progress"); //$NON-NLS-0$
				this._treeModel.getChildren(row._item, function(children) {
					if (tree.destroyed) { return; }
					tree._renderer.updateExpandVisuals(row, true);
					if (!row._expanded) {
						row._expanded = true;
						tree._generateChildren(children, row._depth+1, row); //$NON-NLS-0$
						tree._rowsChanged();
					}
					if (postExpandFunc) {
						postExpandFunc.apply(tree, args);
					}
				});
			}
		}, 
		
		_removeChildRows: function(parentId) {
			// true if we are removing directly from table
			var foundParent = parentId === this._id;
			var stop = false;
			var parentDepth = -1;
			var toRemove = [];
			var rows = lib.$$array(".treeTableRow", this._parent); //$NON-NLS-0$
			for (var i=0; i < rows.length; i++) {
				var row = rows[i];
				if (stop) {
					break;
				}
				if (foundParent) {
					if (row._depth > parentDepth) {
						toRemove.push(row);
					}
					else {
						stop = true;  // we reached a sibling to our parent
					}
				} else {
					if (row.id === parentId) {
						foundParent = true;
						parentDepth = row._depth;
					}
				}
			}
			for (var j=0; j<toRemove.length; j++) {
				var child = toRemove[j];
				child.parentNode.removeChild(child);
			}
		},
		
		_removeAllRows: function() {
			var rows = lib.$$array(".treeTableRow", this._parent); //$NON-NLS-0$
			for (var j=0; j<rows.length; j++) {
				rows[j].parentNode.removeChild(rows[j]);
			}
		},
		
		collapse: function(itemOrId) {
			var id = typeof(itemOrId) === "string" ? itemOrId : this._treeModel.getId(itemOrId); //$NON-NLS-0$
			var row = lib.node(id);
			if (row) {
				if (!row._expanded) {
					return;
				}
				row._expanded = false;
				this._renderer.updateExpandVisuals(row, false);
				this._removeChildRows(id);
				this._rowsChanged();
			}
			if(this._onCollapse){
				this._onCollapse(row._item);
			}
		},
		
		/**
		 * Returns this tree's indentation increment
		 */
		getIndent: function() {
			return this._indent;
		}
	};  // end prototype
	TableTree.prototype.constructor = TableTree;
	//return module exports
	return {TableTree: TableTree};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define window */
/*jslint regexp:false browser:true forin:true*/

define('orion/treeModelIterator',[], function(){

var exports = {};

exports.TreeModelIterator = (function() {
	/**
	 * Creates a new tree iterator.
	 *
	 * @name orion.TreeModelIterator.TreeModelIterator
	 * @class A tree model based iterator component.
	 * @param {list} firstLevelChildren The first level children of the tree root, each item has children and parent property recursively.
	 * @param {Object} options The options object which provides iterate patterns and all call back functions when iteration happens.
	 */
	function TreeModelIterator(firstLevelChildren, options) {
		this.firstLevelChildren = firstLevelChildren;
		this.reset();
		this._init(options);
	}
	TreeModelIterator.prototype = /** @lends orion.TreeModelIterator.TreeModelIterator.prototype */ {
		
		_init: function(options){
			if(!options){
				return;
			}
			this.isExpanded = options.isExpanded;//optional callback providing that if a model item is expanded even if it has children. Default is true if it has children.
			this.isExpandable = options.isExpandable;//optional  callback providing that if a model item is expandable.Default is true .
			this.forceExpandFunc = options.forceExpandFunc;//optional  callback providing the expansion on the caller side.
			this.getChildrenFunc = options.getChildrenFunc;//optional  callback providing the of a parent, instead of using the .children property.
		},
			
		topLevel: function(modelItem) {
			return modelItem.parent ? (modelItem.parent === this.root) : true;
		},
		
		_getChildren: function(model){
			if(typeof this.getChildrenFunc === "function") {
				return this.getChildrenFunc(model);
			}
			return model ? model.children : null;
		},
		
		_expanded: function(model){
			if(!model){
				return true;//root is always expanded
			}
			var children = this._getChildren(model);
			var expanded = (children && children.length > 0);
			if(this.isExpanded && expanded){
				expanded = this.isExpanded(model);
			}
			return expanded;
		},
		
		//This is for the force expand
		_expandable: function(model){
			if(!model){
				return true;//root is always expandable
			}
			if(this.isExpandable){
				return this.isExpandable(model);
			}
			return false;//If there is no isExpandable provided, we assume nothing is expandable
		},
		
		_diveIn: function(model){
			if( this._expanded(model)){
				var children = this._getChildren(model);
				this.setCursor(children[0]);
				return this.cursor();
			}
			return null;
		},
		
		_drillToLast: function(model){
			if( this._expanded(model)){
				var children = this._getChildren(model);
				return this._drillToLast(children[children.length-1]);
			}
			return model;
		},
		
		_forward: function(forceExpand){
			//first we will try to dive into the current cursor
			if(!this._cursor){
				return null;
			}
			var next = this._diveIn(this._cursor);
			if(!next){
				if(forceExpand && this._expandable(this._cursor) && this.forceExpandFunc){
					var that = this;
					return this.forceExpandFunc(this._cursor, "first", function(model){if(model){that.setCursor(model);}}); //$NON-NLS-0$
				}
				next = this._findSibling(this._cursor, true);
				if(next){
					this.setCursor(next);
				} 
			}
			return next;
		},
		
		_backward: function(forceExpand){
			if(!this._cursor){
				return null;
			}
			var previous = this._findSibling(this._cursor, false);
			if(previous && previous !== this._cursor.parent){
				previous = this._drillToLast(previous);
			}
			if(forceExpand && previous && this._expandable(previous) && this.forceExpandFunc && previous !== this._cursor.parent){
				var that = this;
				return this.forceExpandFunc(previous, "last", function(model){if(model){that.setCursor(model);}}); //$NON-NLS-0$
			}
			if(previous){
				this.setCursor(previous);
			} 
			return previous;
		},
		
		_findSibling: function(current, forward){
			var isTopLevel = this.topLevel(current);
			var children = this._getChildren(current.parent);
			var siblings = isTopLevel ? this.firstLevelChildren: children;
			for(var i = 0; i < siblings.length; i++){
				if(siblings[i] === current){
					if((i === 0 && !forward) ){
						return isTopLevel ? null : current.parent;
					} else if (i === (siblings.length-1) && forward) {
						return isTopLevel ? null : this._findSibling(current.parent, forward);
					} else {
						return forward ? siblings[i+1] : siblings[i-1];
					}
				}
			}
			return null;
		},
		
		_inParentChain: function(model, compareTo){
			var parent = model.parent;
			while(parent){
				if(parent === compareTo){
					return true;
				}
				parent = parent.parent;
			}
			return false;
		},
		
		_getTopLevelParent: function(model){
			if(this.topLevel(model)){
				return model;
			}
			var parent = model.parent;
			while(parent){
				if(this.topLevel(parent)){
					return parent;
				}
				parent = parent.parent;
			}
			return null;
		},
		
		_onCollapse: function(model){
			if(this._expanded(model.parent)){
				return model;
			}
			return this._onCollapse(model.parent);
		},
		
		_scan: function(forward, from, to){
			this.setCursor(from);
			var selection = [];
			selection.push(from);
			while(true){
				if(this.iterate(forward)){
					selection.push(this.cursor());
				} else {
					break;
				}
				if(to === this.cursor()){
					return selection;
				}
			}
			selection = [];
			return null;
		},
		
		/**
		 * Set the cursor to the given model
		 * @param {Object} the given model
		 */
		setCursor: function(modelItem) {
			this._prevCursor = this._cursor;
			this._cursor = modelItem;
		},
		
		/**
		 * Set the the first level children
		 * @param {list} the first level children
		 */
		setTree: function(firstLevelChildren) {
			this.firstLevelChildren = firstLevelChildren;
			if(this.firstLevelChildren.length > 0){
				this.root = this.firstLevelChildren[0].parent;
			}
		},
		
		/**
		 * Iterate from the current cursor
		 * @param {object} from the model object that the selection range starts from. Will be included in the return array.
		 * @param {object} to the model object that the selection range ends at. Will be included in the return array.
		 * @returns {Array} The selection of models in the array.
		 */
		scan: function(from, to) {
			var currentCursor = this.cursor();
			var selection = this._scan(true, from, to);
			if(!selection){
				selection = this._scan(false, from, to);
			}
			this.setCursor(currentCursor);
			return selection;
		},
		
		/**
		 * scan a selection range 
		 * @param {boolean} forward the iteration direction. If true then iterate to next, otherwise previous.
		 * @param {boolean} forceExpand optional. the flag for the current cursor to dive into its children. 
		 *                  If the cursor has no children yet or its children are not expanded, this method will call forceExpandFunc.
		 *                  If there is no forceExpandFunc defined it will not expand.
		 */
		iterate: function(forward, forceExpand) {
			return forward ? this._forward(forceExpand) : this._backward(forceExpand);
		},
		
		/**
		 * Iterate from the current cursor only on the top level children
		 * @param {boolean} forward the iteration direction. If true then iterate to next, otherwise previous.
		 * @param {boolean} roundTrip the round trip flag. If true then iterate to the beginning at bottom or end at beginning.
		 */
		iterateOnTop: function(forward, roundTrip) {
			var topSibling = this._findSibling(this._getTopLevelParent(this.cursor()), forward);
			if(topSibling){
				this.setCursor(topSibling);
			} else if(roundTrip && this.firstLevelChildren.length > 0) {
				this.setCursor(forward ? this.firstLevelChildren[0] : this.firstLevelChildren[this.firstLevelChildren.length - 1]);
			}
		},
		
		/**
		 * When the parent model containing the cursor is collapsed, the cursor has to be surfaced to the parent
		 */
		collapse: function(collapsedModel) {
			if(!this._cursor){
				return null;
			}
			if(this._inParentChain(this._cursor, collapsedModel)){
				this.setCursor(collapsedModel);
				return this._cursor;
			}
			return null;
		},
		
		/**
		 * Reset cursor and previous cursor
		 */
		reset: function(){
			this._cursor = null;
			this._prevCursor = null;
			this.root = null;
			//By default the cursor is pointed to the first child 
			if(this.firstLevelChildren.length > 0){
				this._cursor = this.firstLevelChildren[0];
				this.root = this.firstLevelChildren[0].parent;
			}
		},
		
		/**
		 * Convenient method to see if last iterate action moved the cursor
		 */
		cursorMoved: function(){
			return this._cursor !== this._prevCursor;
		},
		
		/**
		 * Get current selected model by the iteration
		 */
		cursor: function(){
			return this._cursor;
		},
		
		/**
		 * Get previously selected model by the iteration
		 */
		prevCursor: function(){
			return this._prevCursor;
		}
	};
	return TreeModelIterator;
}());

return exports;
});

/*******************************************************************************
 * @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
 *******************************************************************************/
/*global define window document navigator*/

define('orion/uiUtils',['orion/webui/littlelib'], function(lib) {
                
	/**
	 * This class contains static utility methods. It is not intended to be instantiated.
	 * @class This class contains static utility methods.
	 * @name orion.uiUtils
	 */
	
	function getUserKeyStrokeString(binding) {
		var userString = "";
		var isMac = navigator.platform.indexOf("Mac") !== -1; //$NON-NLS-0$
	
		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 {
			if (binding.mod1) {
				userString+= "Ctrl+"; //$NON-NLS-0$
			}
			if (binding.mod2) {
				userString+= "Shift+"; //$NON-NLS-0$
			}
			if (binding.mod3) {
				userString+= "Alt+"; //$NON-NLS-0$
			}
		}
		
		if (binding.alphaKey) {
			return userString+binding.alphaKey;
		}
		if (binding.type === "keypress") {
			return userString+binding.keyCode; 
		}
		for (var keyName in lib.KEY) {
			if (typeof(lib.KEY[keyName] === "number")) { //$NON-NLS-0$
				if (lib.KEY[keyName] === binding.keyCode) {
					if (isMac) {
						switch (lib.KEY[keyName]) {
							case lib.KEY.BKSPC:
								keyName = "\u232b";	//$NON-NLS-0$
								break;
							case lib.KEY.TAB:
								keyName = "\u21e5";	//$NON-NLS-0$
								break;
							case lib.KEY.ENTER:
								keyName = "\u23ce";	//$NON-NLS-0$
								break;
							case lib.KEY.ESCAPE:
								keyName = "\u238b";	//$NON-NLS-0$
								break;
							case lib.KEY.SPACE:
								keyName = "\u2423";	//$NON-NLS-0$
								break;
							case lib.KEY.PAGEUP:
								keyName = "\u21de";	//$NON-NLS-0$
								break;
							case lib.KEY.PAGEDOWN:
								keyName = "\u21df";	//$NON-NLS-0$
								break;
							case lib.KEY.END:
								keyName = "\u21f2";	//$NON-NLS-0$
								break;
							case lib.KEY.HOME:
								keyName = "\u21f1";	//$NON-NLS-0$
								break;
							case lib.KEY.DEL:
								keyName = "\u2326";	//$NON-NLS-0$
								break;
							default: //do nothing
						}
					}
					
					switch (lib.KEY[keyName]) {				
						case lib.KEY.DOWN:
							keyName = "\u2193";	//$NON-NLS-0$
							break;
						case lib.KEY.UP:
							keyName = "\u2191"; //$NON-NLS-0$
							break;
						case lib.KEY.RIGHT:
							keyName = "\u2192"; //$NON-NLS-0$
							break;
						case lib.KEY.LEFT:
							keyName = "\u2190"; //$NON-NLS-0$
							break;
						
						default: //do nothing
					}
						
					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> is an HTML5 form element.
	 * @param {Element} element
	 * @function
	 * @returns {Boolean}
	 */
	function isFormElement(element) {
		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;
		}
		return false;
	}

	//return module exports
	return {
		getUserKeyString: getUserKeyString,
		getUserText: getUserText,
		openInNewWindow: openInNewWindow,
		followLink: followLink,
		createButton: createButton,
		createDropdownButton: createDropdownButton,
		isFormElement: isFormElement
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define window */
/*jslint regexp:false browser:true forin:true*/

define('orion/explorers/explorerNavHandler',[
	'orion/webui/littlelib',
	'orion/treeModelIterator',
	'orion/uiUtils'
], function(lib, mTreeModelIterator, UiUtils){

var exports = {};
var userAgent = navigator.userAgent;
var isPad = userAgent.indexOf("iPad") !== -1; //$NON-NLS-0$
var isMac = window.navigator.platform.indexOf("Mac") !== -1; //$NON-NLS-0$

exports.ExplorerNavHandler = (function() {

	/**
	 * Creates a new tree iteration handler
	 * 
	 * @name orion.explorerNavHandler.ExplorerNavHandler
	 * @class A tree iteration handler based on an explorer.
	 * @param {Object} explorer The {@link orion.explorer.Explorer} instance.
	 * @param {Object} options The options object which provides iterate patterns and all call back functions when iteration happens.
	 * @param {String} [options.gridClickSelectionPolicy="none"] Controls how clicking on a grid model item -- for example, a link or a button -- affects
	 * the selection (or how it affects the cursor, if the <code>selectionPolicy</code> is <code>"cursorOnly"</code>). Allowed values are:
	 * <ul>
	 * <li><code>"none"</code>: Clicking on a grid item will not change the selection (or cursor). This is the default.</li>
	 * <li><code>"active"</code>: Clicking on a grid item will change the selection (or cursor).</li>
	 * </ul>
	 * @param {String} [options.selectionPolicy=null] Selection policy for this explorer. Allowed values are:
	 * <ul>
	 * <li><code>"cursorOnly"</code>: No selection of model items is allowed.</li>
	 * <li><code>"singleSelection"</code>: Up to 1 model item can be selected.</li>
	 * <li><code>"readonlySelection"</code>: Selection cannot be changed while this selection policy is set.</li>
	 * <li><code>null</code>: Zero or more model items can be selected. This is the default.</li>
	 * </ul>
	 * @param {Function} [options.postDefaultFunc] If this function provides addtional behaviors after the default behavior. Some explorers may want to do something else when the cursor is changed, etc.
	 * @param {Function} [options.preventDefaultFunc] If this function returns true then the default behavior of all key press will stop at this time.
	 * The key event is passed to preventDefaultFunc. It can implement its own behavior based on the key event.
	 */
	function ExplorerNavHandler(explorer, navDict, options) {
		this.explorer = explorer;
		this.model = this.explorer.model;
		this._navDict = navDict;
		
	    this._listeners = [];
	    this._selections = [];
	    
	    this._currentColumn = 0;
	    var parentDiv = this._getEventListeningDiv();
	    parentDiv.tabIndex = 0;
		parentDiv.classList.add("selectionModelContainer"); //$NON-NLS-0$
		var self = this;
		this._modelIterator = new mTreeModelIterator.TreeModelIterator([], {
			isExpanded: this.isExpanded.bind(this),		
			getChildrenFunc: options.getChildrenFunc,
			isExpandable: this.explorer.renderer.isExpandable ? 
				function(model) { return self.explorer.renderer.isExpandable(model); } : 
				function(model) { return self.isExpandable(model); },
			forceExpandFunc: this.explorer.forceExpandFunc ? 
				function(modelToExpand, childPosition, callback) {
					return self.explorer.forceExpandFunc(modelToExpand, childPosition, callback);
				} : undefined
		});
		this._init(options);
		
	    if(!options || options.setFocus !== false){
			parentDiv.focus();
	    }
	    var keyListener = function (e) { 
			if(UiUtils.isFormElement(e.target)) {
				// Not for us
				return true;
			}
			if(self.explorer.preventDefaultFunc && self.explorer.preventDefaultFunc(e, self._modelIterator.cursor())){
				return true;
			}
			if(e.keyCode === lib.KEY.DOWN) {
				return self.onDownArrow(e);
			} else if(e.keyCode === lib.KEY.UP) {
				return self.onUpArrow(e);
			} else if(e.keyCode === lib.KEY.RIGHT) {
				return self.onRightArrow(e);
			} else if(e.keyCode === lib.KEY.LEFT) {
				return self.onLeftArrow(e);
			} else if(e.keyCode === lib.KEY.SPACE) {
				return self.onSpace(e);
			} else if(e.keyCode === lib.KEY.ENTER) {
				return self.onEnter(e);
			}
		};
		parentDiv.addEventListener("keydown", keyListener, false); //$NON-NLS-0$
		this._listeners.push({type: "keydown", listener: keyListener}); //$NON-NLS-0$
		var l1 = function (e) { 
			if(self.explorer.onFocus){
				self.explorer.onFocus(false);
			} else {
				self.toggleCursor(null, false);
			}
		};
		parentDiv.addEventListener("blur", l1, false); //$NON-NLS-0$
		this._listeners.push({type: "blur", listener: l1}); //$NON-NLS-0$
		var l2 = function (e) { 
			if(self.explorer.onFocus){
				self.explorer.onFocus(true);
			} else {
				self.toggleCursor(null, true);
			}
		};
		parentDiv.addEventListener("focus", l2, false); //$NON-NLS-0$
		this._listeners.push({type: "focus", listener: l2}); //$NON-NLS-0$
		this._parentDiv = parentDiv;
	}
	
	ExplorerNavHandler.prototype = /** @lends orion.explorerNavHandler.ExplorerNavHandler.prototype */ {
		
		destroy: function() {
			this._parentDiv.classList.remove("selectionModelContainer"); //$NON-NLS-0$
			this.removeListeners();	
		},
		
		_init: function(options){
			this._linearGridMove = false;//temporary. If true right key on the last grid will go to first grid of next row
			                            // Left key on the first grid will go to the last line grid of the previous line
			if(!options){
				return;
			}
			this._selectionPolicy = options.selectionPolicy;
			this.gridClickSelectionPolicy = options.gridClickSelectionPolicy || "none"; //$NON-NLS-0$
			this.preventDefaultFunc = options.preventDefaultFunc;
			this.postDefaultFunc = options.postDefaultFunc;
		},
		
		_ctrlKeyOn: function(e){
			return isMac ? e.metaKey : e.ctrlKey;
		},
		
		removeListeners: function(){
			if(this._listeners){
				for (var i=0; i < this._listeners.length; i++) {
					this._parentDiv.removeEventListener(this._listeners[i].type, this._listeners[i].listener, false);
				}
			}
		},
		
		focus: function(){
		    var parentDiv = this._getEventListeningDiv();
		    if(parentDiv){
				parentDiv.focus();
		    }
		},
		
		_getEventListeningDiv: function(secondLevel){
			if(this.explorer.keyEventListeningDiv && typeof this.explorer.keyEventListeningDiv === "function"){ //$NON-NLS-0$
				return this.explorer.keyEventListeningDiv(secondLevel);
			}
			return lib.node(this.explorer._parentId);
		},
		
		isExpandable: function(model){
			if(!model){
				return false;
			}
			var expandImage = lib.node(this.explorer.renderer.expandCollapseImageId(this.model.getId(model)));
			return expandImage ? true: false;
		},
		
		isExpanded: function(model){
			if(!model){
				return false;
			}
			return this.explorer.myTree.isExpanded(this.model.getId(model));
		},
		
		refreshSelection: function(noScroll, visually){
			var that = this;
			if(this.explorer.selection){
				this.explorer.selection.getSelections(function(selections) {
					that._clearSelection(visually);
					for (var i = 0; i < selections.length; i++){
						that._selections.push(selections[i]);
					}
					if(that._selections.length > 0){
						that.cursorOn(that._selections[0], true, false, noScroll);
					} else {//If there is no selection, we should just the first item as the cursored items.  
						that.cursorOn(null, false, false, noScroll);
					}
					//If shift selection anchor exists and in the refreshed selection range, we just keep it otherwise clear the anchor
					//See https://bugs.eclipse.org/bugs/show_bug.cgi?id=419170
					if(!(that._shiftSelectionAnchor && that._inSelection(that._shiftSelectionAnchor) >= 0)){
						that._shiftSelectionAnchor = null;
					}
				});
			}
		},
		
		refreshModel: function(navDict, model, topIterationNodes, noReset){
		    this._currentColumn = 0;
			this.topIterationNodes = [];
			this.model = model;
			this._navDict = navDict;
			if(this.model.getTopIterationNodes){
				this.topIterationNodes = this.model.getTopIterationNodes();
			} else if(topIterationNodes){
				this.topIterationNodes = topIterationNodes;
			}
			this._modelIterator.setTree(this.topIterationNodes);
			if(!noReset && this.explorer.selection){
				//refresh the current cursor visual, otherwise the next cursorOn() call will not remove the previoous cursor visual properly.
				this.toggleCursor(this._modelIterator.cursor(), false);
				this._modelIterator.reset();
			}
			this.refreshSelection(true);
		},
		
		getTopLevelNodes: function(){
			return this._modelIterator.firstLevelChildren;
		},
		
		_inSelection: function(model){
			var modelId = this.model.getId(model);
			for(var i = 0; i < this._selections.length; i++){
				if(modelId === this.model.getId(this._selections[i])){
					return i;
				}
			}
			return -1;
		},
		
		
		_clearSelection: function(visually){
			if(visually){
				for(var i = 0; i < this._selections.length; i++){
					this._checkRow(this._selections[i], true);
				}
			}
			this._selections = [];
		},
		
		getSelectionPolicy: function() {
			return this._selectionPolicy;
		},
		
		setSelectionPolicy: function(policy) {
			if (this._selectionPolicy === policy) {
				return;
			}
			if(this._selectionPolicy === "readonlySelection" || policy === "readonlySelection"){
				this._toggleSelectionClass("disabledRow", policy==="readonlySelection");
			}
			this._selectionPolicy = policy;
			if(this._selectionPolicy === "cursorOnly"){ //$NON-NLS-0$
				this._clearSelection(true);
			}
		},
		
		setSelection: function(model, toggling, shiftSelectionAnchor){
			if(this._selectionPolicy === "readonlySelection"){
				return;
			}
			if(this._selectionPolicy === "cursorOnly"){ //$NON-NLS-0$
				if(toggling && this.explorer.renderer._useCheckboxSelection){
					this._checkRow(model,true);
				}
				return;
			}
			if(!this._isRowSelectable(model)){
				return;
			}
			if(!toggling || this._selectionPolicy === "singleSelection"){//$NON-NLS-0$
				this._clearSelection(true);
				this._checkRow(model,false);		
				this._selections.push(model);
				this._lastSelection = model;
			} else{
				var index = this._inSelection(model);
				if(index >= 0){
					this._checkRow(model, true);
					this._selections.splice(index, 1);
				} else {
					this._checkRow(model,false);		
					this._selections.push(model);
					this._lastSelection = model;
				}
			}
			if(shiftSelectionAnchor){
				this._shiftSelectionAnchor = this._lastSelection;
			}
			if (this.explorer.selection) {
				this.explorer.renderer.storeSelections();
				this.explorer.selection.setSelections(this._selections);		
			}
		},
		
		moveColumn: function(model, offset){
			if(!model){
				model = this.currentModel();
			}
			var gridChildren = this._getGridChildren(model);
			if((gridChildren && gridChildren.length > 1) || (offset === 0 && gridChildren)){
				if(offset !== 0){
					this.toggleCursor(model, false);
				}
				var column = this._currentColumn;
				var rowChanged= true;
				column = column + offset;
				if(column < 0){
					if(this._linearGridMove && offset !== 0){
						if(this._modelIterator.iterate(false)){
							model = this.currentModel();
						} else {
							rowChanged = false;
						}
					}
					column = rowChanged ? gridChildren.length - 1 : this._currentColumn;
				} else if(column >= gridChildren.length){
					if(this._linearGridMove && offset !== 0){
						if(this._modelIterator.iterate(true)){
							model = this.currentModel();
						} else {
							rowChanged = false;
						}
					}
					column = rowChanged ? 0 : this._currentColumn;
				}
				this._currentColumn = column;
				if(offset !== 0){
					this.toggleCursor(model, true);
				}
				return true;
			}
			return false;
		},
		
		_getGridChildren: function(model){
			if(this._navDict){
				return this._navDict.getGridNavHolder(model);
			}
			return null;
		},
		
		getCurrentGrid:  function(model){
			if(!model){
				model = this.currentModel();
			}
			var gridChildren = this._getGridChildren(model);
			if(gridChildren && gridChildren.length > 0){
				return gridChildren[this._currentColumn];
			}
			return null;
		},

		/**
		 * @returns {Element} The ancestor element of <code>node</code> that provides grid/tree/treegrid behavior,
		 * or <code>null</code> if no such node was found.
		 */
		getAriaContainerElement: function(node) {
			var stop = this._parentDiv, role;
			while (node && node !== stop &&
					(role = node.getAttribute("role")) !== "grid" && role !== "tree" && role !== "treegrid") {//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$//$NON-NLS-0$
				node = node.parentNode;
			}
			return node === stop ? null : node;
		},

		toggleCursor:  function(model, on){
			var currentRow = this.getRowDiv(model);
			var currentgrid = this.getCurrentGrid(model);
			if(currentgrid) {
				if(currentRow){
					if (on) {
						currentRow.classList.add("treeIterationCursorRow"); //$NON-NLS-0$
					} else {
						currentRow.classList.remove("treeIterationCursorRow"); //$NON-NLS-0$
					}
				}
				if(currentgrid.domNode){
					var ariaElement = this.getAriaContainerElement(currentgrid.domNode);
					if (on) {
						currentgrid.domNode.classList.add("treeIterationCursor"); //$NON-NLS-0$
						if (ariaElement) {
							var activeDescendantId = currentgrid.domNode.id;
							ariaElement.setAttribute("aria-activedescendant", activeDescendantId); //$NON-NLS-0$
						}
					} else {
						currentgrid.domNode.classList.remove("treeIterationCursor"); //$NON-NLS-0$
					}
				}
			} else {
				if(currentRow){
					if (on) {
						currentRow.classList.add("treeIterationCursorRow_Dotted"); //$NON-NLS-0$
					} else {
						currentRow.classList.remove("treeIterationCursorRow_Dotted"); //$NON-NLS-0$
					}
				}
			}
		},
		
		currentModel: function(){
			return this._modelIterator.cursor();
		},
		
		cursorOn: function(model, force, next, noScroll){
			var previousModel, currentModel;
			if(model || force){
				if(currentModel === this._modelIterator.cursor()){
					return;
				}
				previousModel = this._modelIterator.cursor();
				currentModel = model;
				this._modelIterator.setCursor(currentModel);
			} else {
				previousModel = this._modelIterator.prevCursor();
				currentModel = this._modelIterator.cursor();
			}
			if(previousModel === currentModel && !force){
				return;
			}
			this.toggleCursor(previousModel, false);
			if(force && !currentModel){
				return;
			}
			this.moveColumn(null, 0);
			this.toggleCursor(currentModel, true);
			var currentRowDiv = this.getRowDiv();
			if(currentRowDiv && !noScroll) {
				var offsetParent = currentRowDiv.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;
				}
				if (offsetParent) {
					var visible = true;
					if(currentRowDiv.offsetTop <= offsetParent.scrollTop){
						visible = false;
						if(next === undefined){
							next = false;
						}
					}else if((currentRowDiv.offsetTop + currentRowDiv.offsetHeight) >= (offsetParent.scrollTop + offsetParent.clientHeight)){
						visible = false;
						if(next === undefined){
							next = true;
						}
					}
					if(!visible){
						currentRowDiv.scrollIntoView(!next);
					}
				}
			}
			if(this.explorer.onCursorChanged){
				this.explorer.onCursorChanged(previousModel, currentModel);
			}
		},
		
		getSelection: function(){
			return this._selections;
		},
		
		getSelectionIds: function(){
			var ids = [];
			for (var i = 0; i < this._selections.length; i++) {
				ids.push(this.model.getId(this._selections[i]));
			}
			return ids;
		},
		
		getRowDiv: function(model){
			var rowModel = model ? model: this._modelIterator.cursor();
			if(!rowModel){
				return null;
			}
			var modelId = this.model.getId(rowModel);
			var value = this._navDict.getValue(modelId);
			return value && value.rowDomNode ? value.rowDomNode :  lib.node(modelId);
		},
		
		iterate: function(forward, forceExpand, selecting, selectableOnly /* optional */)	{
			var currentItem = null;
			
			if(!this.topIterationNodes || this.topIterationNodes.length === 0){
				return;
			}
				
			if (selectableOnly) {
				var previousItem = this.currentModel();
				
				currentItem = this._modelIterator.iterate(forward, forceExpand);
				if(currentItem){
					this._setCursorOnItem(forward, selecting);
				}
				
				while (currentItem && currentItem.isNotSelectable) {
					currentItem = this._modelIterator.iterate(forward, forceExpand);
					if(currentItem){
						this._setCursorOnItem(forward, selecting);
					}
				}
				
				if (!currentItem) {
					// got to the end of the list and didn't find anything selectable, iterate back
					this.cursorOn(previousItem, true, false, true);
					this._setCursorOnItem(forward, selecting);
				}
			} else {
				currentItem = this._modelIterator.iterate(forward, forceExpand);
				if(currentItem){
					this._setCursorOnItem(forward, selecting);
				}
			}
		},
		
		_toggleSelectionClass: function(className, on){
			this._selections.forEach(function(selection){
				var selectedDiv = this.getRowDiv(selection);
				if(!selectedDiv){
					return;
				}
				if(on){
					selectedDiv.classList.add(className);
				} else {
					selectedDiv.classList.remove(className);
				}
			}.bind(this));
		},
		
		_setCursorOnItem: function(forward, selecting) {
			this.cursorOn(null, false, forward);
			if(selecting){
				var previousModelInSelection = this._inSelection(this._modelIterator.prevCursor());
				var currentModelInselection = this._inSelection(this._modelIterator.cursor());
				if(previousModelInSelection >= 0 && currentModelInselection >= 0) {
					this.setSelection(this._modelIterator.prevCursor(), true);
				} else {
					this.setSelection(this.currentModel(), true);
				}
			}
		},
		
		_checkRow: function(model, toggle) {
			if(this.explorer.renderer._useCheckboxSelection && this._selectionPolicy !== "singleSelection"){
				var tableRow = this.getRowDiv(model);
				if(!tableRow){
					return;
				}
				var checkBox  = lib.node(this.explorer.renderer.getCheckBoxId(tableRow.id));
				var checked = toggle ? !checkBox.checked : true;
				if(checked !== checkBox.checked){
					this.explorer.renderer.onCheck(tableRow, checkBox, checked, true);
				}
			} else {
				this._select(model, toggle);
			}
		},
		
		_select: function(model, toggling){
			if(!model){
				model = this._modelIterator.cursor();
			}
			var rowDiv = this.getRowDiv(model);
			if(rowDiv){
				if (this._inSelection(model) < 0) {
					rowDiv.classList.add("checkedRow"); //$NON-NLS-0$
				} else {
					rowDiv.classList.remove("checkedRow"); //$NON-NLS-0$
				}
			}
		},
		
		_onModelGrid: function(model, mouseEvt){
			var gridChildren = this._getGridChildren(model);
			if(gridChildren){
				for(var i = 0; i < gridChildren.length; i++){
					if(mouseEvt.target === gridChildren[i].domNode){
						return true;
					}
				}
			}
			return false;
		},
		
		onClick: function(model, mouseEvt)	{
			if (this._selectionPolicy === "readonlySelection" || this.isDisabled(this.getRowDiv(model))) {
				lib.stop(mouseEvt);
			} else {
				var twistieSpan = lib.node(this.explorer.renderer.expandCollapseImageId(this.model.getId(model)));
				if(mouseEvt.target === twistieSpan){
					return;
				}
				if(this.gridClickSelectionPolicy === "none" && this._onModelGrid(model, mouseEvt)){ //$NON-NLS-0$
					return;
				}
				this.cursorOn(model, true, false, true);
				if(isPad){
					this.setSelection(model, true);
				} else if(this._ctrlKeyOn(mouseEvt)){
					this.setSelection(model, true, true);
				} else if(mouseEvt.shiftKey && this._shiftSelectionAnchor){
					var scannedSel = this._modelIterator.scan(this._shiftSelectionAnchor, model);
					if(scannedSel){
						this._clearSelection(true);
						for(var i = 0; i < scannedSel.length; i++){
							this.setSelection(scannedSel[i], true);
						}
					}
				} else {
					this.setSelection(model, false, true);
				}
			}
		},
		
		onCollapse: function(model)	{
			if(this._modelIterator.collapse(model)){
				this.cursorOn();
			}
		},
		
		//Up arrow key iterates the current row backward. If control key is on, browser's scroll up behavior takes over.
		//If shift key is on, it toggles the check box and iterates backward.
		onUpArrow: function(e) {
			this.iterate(false, false, e.shiftKey, true);
			if(!this._ctrlKeyOn(e) && !e.shiftKey){
				this.setSelection(this.currentModel(), false, true);
			}
			e.preventDefault();
			return false;
		},

		//Down arrow key iterates the current row forward. If control key is on, browser's scroll down behavior takes over.
		//If shift key is on, it toggles the check box and iterates forward.
		onDownArrow: function(e) {
			this.iterate(true, false, e.shiftKey, true);
			if(!this._ctrlKeyOn(e) && !e.shiftKey){
				this.setSelection(this.currentModel(), false, true);
			}
			e.preventDefault();
			return false;
		},

		_shouldMoveColumn: function(e){
			var model = this.currentModel();
			var gridChildren = this._getGridChildren(model);
			if(gridChildren && gridChildren.length > 1){
				if(this.isExpandable(model)){
					return this._ctrlKeyOn(e);
				}
				return true;
			} else {
				return false;
			}
		},
		
		//Left arrow key collapses the current row. If current row is not expandable(e.g. a file in file navigator), move the cursor to its parent row.
		//If current row is expandable and expanded, collapse it. Otherwise move the cursor to its parent row.
		onLeftArrow:  function(e) {
			if(this._shouldMoveColumn(e)){
				this.moveColumn(null, -1);
				e.preventDefault();
				return true;
			}
			var curModel = this._modelIterator.cursor();
			if(!curModel){
				return false;
			}
			if(this.isExpandable(curModel)){
				if(this.isExpanded(curModel)){
					this.explorer.myTree.collapse(curModel);
					e.preventDefault();
					return true;
				}
			}
			if(!this._modelIterator.topLevel(curModel)){
				this.cursorOn(curModel.parent);
				this.setSelection(curModel.parent, false, true);
			//The cursor is now on a top level item which is collapsed. We need to ask the explorer is it wants to scope up.	
			} else if (this.explorer.scopeUp && typeof this.explorer.scopeUp === "function"){ //$NON-NLS-0$
				this.explorer.scopeUp();
			}
		},
		
		//Right arrow key expands the current row if it is expandable and collapsed.
		onRightArrow: function(e) {
			if(this._shouldMoveColumn(e)){
				this.moveColumn(null, 1);
				e.preventDefault();
				return true;
			}
			var curModel = this._modelIterator.cursor();
			if(!curModel){
				return false;
			}
			if(this.isExpandable(curModel)){
				if(!this.isExpanded(curModel)){
					this.explorer.myTree.expand(curModel);
					if (this.explorer.postUserExpand) {
						this.explorer.postUserExpand(this.model.getId(curModel));
					}
					e.preventDefault();
					return false;
				}
			}
		},
		
		_isRowSelectable: function(model){
			return this.explorer.isRowSelectable ? this.explorer.isRowSelectable(model) : true;
		},

		//Space key toggles the check box on the current row if the renderer uses check box
		onSpace: function(e) {
			this.setSelection(this.currentModel(), true, true);
			e.preventDefault();
		},
		
		//Enter key simulates a href call if the current row has an href link rendered. The render has to provide the getRowActionElement function that returns the href DIV.
		onEnter: function(e) {
			var currentGrid = this.getCurrentGrid(this._modelIterator.cursor());
			if(currentGrid){
				if(currentGrid.widget){
					if(typeof currentGrid.onClick === "function"){ //$NON-NLS-0$
						currentGrid.onClick();
					} else if(typeof currentGrid.widget.focus === "function"){ //$NON-NLS-0$
						currentGrid.widget.focus();
					}
				} else {
					var evt = document.createEvent("MouseEvents"); //$NON-NLS-0$
					evt.initMouseEvent("click", true, true, window, //$NON-NLS-0$
							0, 0, 0, 0, 0, this._ctrlKeyOn(e), false, false, false, 0, null);
					currentGrid.domNode.dispatchEvent(evt);
				}
				return;
			}
			
			var curModel = this._modelIterator.cursor();
			if(!curModel){
				return;
			}
			
			if(this.explorer.renderer.getRowActionElement){
				var div = this.explorer.renderer.getRowActionElement(this.model.getId(curModel));
				if(div.href){
					if(this._ctrlKeyOn(e)){
						window.open(div.href);
					} else {
						window.location.href = div.href;
					}
				}
			}
			if(this.explorer.renderer.performRowAction){
				this.explorer.renderer.performRowAction(e, curModel);
				e.preventDefault();
				return false;
			}
		},
		
		/**
		 * Sets the isNotSelectable attribute on the specified model.
		 * @param {Object} model
		 * @param {Boolean} isNotSelectable true makes the this.iterate() with selectableOnly specified skip the item
		 */
		setIsNotSelectable: function(model, isNotSelectable) {
			model.isNotSelectable = isNotSelectable;
		},
		
		/**
		 * Disables the specified model making it no longer respond 
		 * to user input such as mouse click or key presses. The
		 * CSS style of corresponding row node is also modified to
		 * reflect its disabled state.
		 * 
		 * @param {Object} model
		 */
		disableItem: function(model) {
			var rowDiv = this.getRowDiv(model);
			if (this.isExpandable(model) && this.isExpanded(model)) {
				this._modelIterator.collapse(model);
				this.explorer.myTree.toggle(rowDiv.id); // collapse tree visually
			}
			rowDiv.classList.remove("checkedRow"); //$NON-NLS-0$
			rowDiv.classList.add("disabledNavRow"); //$NON-NLS-0$
			this.setIsNotSelectable(model, true);
		},
		
		/**
		 * Checks if the specified html row node is disabled.
		 * @return true if the specified node's classList contains the 
		 * 			"disabledNavRow" class, false otherwise
		 */
		isDisabled: function(rowDiv) {
			return rowDiv.classList.contains("disabledNavRow"); //$NON-NLS-0$
		},
		
		/**
		 * Enables the specified model.
		 * 
		 * @param {Object} model
		 */
		enableItem: function(model) {
			var rowDiv = this.getRowDiv(model);
			if (rowDiv) {
				rowDiv.classList.remove("disabledNavRow"); //$NON-NLS-0$
				this.setIsNotSelectable(model, false);
			}
		},
	};
	return ExplorerNavHandler;
}());

exports.ExplorerNavDict = (function() {
	/**
	 * Creates a new explorer navigation dictionary. The key of the dictionary is the model id. The value is a wrapper object that holds .modelItem, .rowDomNode and .gridChildren properties.
	 * The .modelItem property helps quickly looking up a model object by a given id. The .rowDomNode also helps to find out the row DOM node instead of doing a query. 
	 * The .gridChildren is an array representing all the grid navigation information, which the caller has to fill the array out.
	 *
	 * @name orion.explorerNavHandler.ExplorerNavDict
	 * @class A explorer navigation dictionary.
	 * @param {Object} model The model object that represent the overall explorer.
	 */
	function ExplorerNavDict(model) {
		this._dict= {};
		this._model = model;
	}
	ExplorerNavDict.prototype = /** @lends orion.explorerNavHandler.ExplorerNavDict.prototype */ {
		
		/**
		 * Add a row to the dictionary.
		 * @param {Object} modelItem The model item object that represent a row.
		 * @param {domNode} rowDomNode optional The DOM node that represent a row. If 
		 */
		addRow: function(modelItem, rowDomNode){
			var modelId = this._model.getId(modelItem);
			this._dict[modelId] = {model: modelItem, rowDomNode: rowDomNode};
		},
			
		/**
		 * Get the value of a key by model id.
		 *  @param {String} id The model id.
		 * @returns {Object} The value of the id from the dictionary.
		 */
		getValue: function(id) {
			return this._dict[id];
		},
		
		/**
		 * Get the grid navigation holder from a row navigation model.
		 *  @param {Object} modelItem The model item object that represent a row.
		 * @returns {Array} The .gridChildren property of the value keyed by the model id.
		 */
		getGridNavHolder: function(modelItem, lazyCreate) {
			if(!modelItem){
				return null;
			}
			var modelId = this._model.getId(modelItem);
			if(this._dict[modelId]){
				if(!this._dict[modelId].gridChildren && lazyCreate){
					this._dict[modelId].gridChildren = [];
				}
				return this._dict[modelId].gridChildren;
			}
			return null;
		},
		
		/**
		 * Initialize the grid navigation holder to null.
		 *  @param {Object} modelItem The model item object that represent a row.
		 */
		initGridNavHolder: function(modelItem) {
			if(!modelItem){
				return null;
			}
			var modelId = this._model.getId(modelItem);
			if(this._dict[modelId]){
				this._dict[modelId].gridChildren = null;
			}
		}
	};
	return ExplorerNavDict;
}());

return exports;
});

/*******************************************************************************
 * @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
 *******************************************************************************/
/*global define console*/

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) 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
 ******************************************************************************/
/*global window define document */

define('orion/webui/dropdown',['require', 'orion/webui/littlelib', 'orion/EventTarget'], function(require, 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$
			
			if (!options.skipTriggerEventListeners) {
				var self = this;
				// click on trigger opens.
				this._triggerNode.addEventListener("click", function(event) { //$NON-NLS-0$
					if (self.toggle(event))  {
						lib.stop(event);
					}
				}, false);
					
				// 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) {
							self.toggle();
							lib.stop(event);
						}
					}, 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._hookedAutoDismiss) {
						if (this._boundAutoDismiss) {
							lib.removeAutoDismiss(this._boundAutoDismiss);
						} else {
							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._hookedAutoDismiss = true;
					}
					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;	
				}
				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);
				}
			});
			
			this._hookedAutoDismiss = false; //the autoDismiss nodes need to be recalculated
		},
		
		 
		/**
		 * 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);
			}
		},
		
		/**
		 * 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. Required.
		 * @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. Required.
	 * @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$
	
		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.5 Copyright (c) 2010-2012, 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: false, XMLHttpRequest: false, ActiveXObject: false,
  define: false, window: false, process: false, Packages: false,
  java: false, location: false */

define('text',['module'], function (module) {
    

    var text, fs,
        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.5',

        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.isBuild && !config.inlineText) {
                onLoad();
                return;
            }

            masterConfig.isBuild = config.isBuild;

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

            //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)) {
        //Using special require.nodeRequire, something added by r.js.
        fs = require.nodeRequire('fs');

        text.get = function (url, callback) {
            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);
        };
    } 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;
                    if (status > 399 && status < 600) {
                        //An http 4xx or 5xx error. Signal an error.
                        err = new Error(url + ' HTTP status: ' + status);
                        err.xhr = xhr;
                        errback(err);
                    } else {
                        callback(xhr.responseText);
                    }
                }
            };
            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);
        };
    }

    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/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>';});

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
 ******************************************************************************/
/*global window define document */

define('orion/webui/tooltip',['require', 'orion/webui/littlelib'], function(require, 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", 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);
				}
			}						
		},
		
		_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
			
			// 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;
			}
			var totalRect = lib.bounds(document.documentElement);
			if (top + tipRect.height > totalRect.height) {
				if (force) {
					top = totalRect.height - tipRect.height - 1;
				} else {
					return false;
				}
			}
			if (left + tipRect.width > totalRect.width) {
				if (force) {
					left = totalRect.width - tipRect.width - 1;
				} else {
					return false;
				}
			}
			if (left < 0) {
				if (force) {
					left = 4;
				} else {
					return false;
				}
			}
			if (top < 0) {
				if (force) {
					top = 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;
		},
		
		/**
		 * Show the tooltip.
		 */			
		show: function() {
			if (this._tip && this._tip.classList.contains("tooltipShowing")) { //$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
			}
			this._tip.classList.add("tooltipShowing"); //$NON-NLS-0$
			if (this._afterShowing) {
				this._afterShowing();
			}
		},
		
		/**
		 * Hide the tooltip.
		 */			
		hide: function(hideDelay) {
			if (this._timeout) {
				window.clearTimeout(this._timeout);
				this._timeout = null;
			}
			if (!this._tip || !this._tip.classList.contains("tooltipShowing")) { //$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$
				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) 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
 ******************************************************************************/
/*jslint sub:true*/
 /*global define document window Image */
 
define('orion/commands',[
	'orion/util',
	'orion/webui/littlelib',
	'orion/webui/dropdown',
	'text!orion/webui/dropdowntriggerbutton.html',
	'text!orion/webui/dropdowntriggerbuttonwitharrow.html',
	'text!orion/webui/submenutriggerbutton.html',
	'text!orion/webui/checkedmenuitem.html',
	'orion/webui/tooltip'
], function(util, lib, Dropdown, DropdownButtonFragment, DropdownButtonWithArrowFragment, SubMenuButtonFragment, CheckedMenuItemFragment, Tooltip) {
		/**
		 * @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 (command.hrefCallback) {
					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());
		}
		
		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.
					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['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;
				}
			}
			if (processKeyFunc) {
				processKeyFunc(evt);
			} else {
				processKey(evt);
			}
		}
		
		function CommandsProxy() {
			this._init();
		}
		CommandsProxy.prototype = {
			setProxy: function(proxy) {
				this.proxy = proxy;
			},
			setKeyBindings: function(bindings) {
				this.bindings = bindings;
			},
			_init: function() {
				var self = this;
				document.addEventListener("keydown", 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);
							}
						}
					});
				});
			}
		};

		window.document.addEventListener("keydown", handleKeyEvent, false); //$NON-NLS-0$

	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) {
		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 (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;
		useImage = useImage || (!command.name && command.hasImage());
		if (command.hrefCallback) {
			element = 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 {
			element = document.createElement("button"); //$NON-NLS-0$
			element.className = "orionButton"; //$NON-NLS-0$
			if (command.extraClass) {
				element.classList.add(command.extraClass);
			}
			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);
			}
			var onClick = callback || command.callback;
			if (onClick) {
				command.onClick = onClick;
				element.addEventListener("click", function(e) { //$NON-NLS-0$
					onClick.call(commandInvocation.handler, commandInvocation);
				}, 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 (command.hrefCallback) {
			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$
					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: ["above", "below", "right", "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.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;
		},
		
		/**
		 * 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);
					}
					var text = document.createTextNode(choice.name);
					node.appendChild(text);
					itemNode.appendChild(node);
					node.choice = choice;
					node.addEventListener("click", function(event) { //$NON-NLS-0$
						if (event.target.choice) {
							event.target.choice.callback.call(event.target.choice, items);
						}
					}, false); 
					node.addEventListener("keydown", function(event) { //$NON-NLS-0$
						if (event.keyCode === lib.KEY.ENTER || event.keyCode === lib.KEY.SPACE) {
							if (event.target.choice) {
								event.target.choice.callback.call(event.target.choice, items);
							}
						}
					}, false);
				} else {  // anything not named is a separator
					commandService._generateMenuSeparator(parent);
				}
			});
		},
		
		/**
		 * 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,
		CommandsProxy: CommandsProxy,
		getKeyBindings: getKeyBindings,
		processKey: processKey,
		NO_IMAGE: NO_IMAGE,
		_testMethodProcessKey: _processKey  // only exported for test cases
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define window */
/*jslint regexp:false browser:true forin:true*/

define('orion/explorers/explorer',['i18n!orion/nls/messages', 'require', 'orion/webui/littlelib', 'orion/webui/treetable', 'orion/explorers/explorerNavHandler', 'orion/commands'], 
function(messages, require, lib, mTreeTable, mNavHandler, mCommands){

var exports = {};

exports.Explorer = (function() {
	/**
	 * Creates a new explorer.
	 *
	 * @name orion.explorer.Explorer
	 * @class A table-based explorer component.
	 * @param {orion.serviceregistry.ServiceRegistry} serviceRegistry The service registry to
	 * use for any services required by the explorer
	 * @param {orion.selection.Selection} selection The initial selection
	 * @param renderer
	 * @param {orion.commandregistry.CommandRegistry} commandRegistry The command registry to use for commands.  Optional.
	 */
	function Explorer(serviceRegistry, selection, renderer, commandRegistry) {
		this.registry = serviceRegistry;
		this.renderer = renderer;
		this.selection = selection;
		this.commandService = commandRegistry;
		if(this.renderer){
			this.renderer.explorer = this;
		}
		
		this.myTree = null;
	}
	Explorer.prototype = /** @lends orion.explorer.Explorer.prototype */ {
	
		destroy: function() {
			if (this._navHandler) {
				this._navHandler.destroy();
			}
			if (this.model) {
				this.model.destroy();
			}
			if (this.myTree) {
				this.myTree.destroy();
			}
			this.destroyed = true;
		},
		
		// we have changed an item on the server at the specified parent node
		changedItem: function(parent, children) {
			if (this.myTree) {
				this.myTree.refresh.bind(this.myTree)(parent, children, true);
			}
		},
		updateCommands: function(item){
			// update the commands in the tree if the tree exists.
			if (this.myTree) {
				this.myTree._renderer.updateCommands.bind(this.myTree._renderer)(item);
			}
		},
		
		makeNewItemPlaceholder: function(item, domId, insertAfter) {
			var placeholder = null;
			var itemRow = this.getRow(item);
			if (itemRow) {
				var parentNode = itemRow.parentNode;
				// make a row and empty column so that the new name appears after checkmarks/expansions
				var tr = document.createElement("tr"); //$NON-NLS-0$
				tr.id = domId+"placeHolderRow"; //$NON-NLS-0$
				tr.classList.add("navRow"); //$NON-NLS-0$
				var td = document.createElement("td"); //$NON-NLS-0$
				td.id = domId+"placeHolderCol"; //$NON-NLS-0$
				td.classList.add("navColumn"); //$NON-NLS-0$
				tr.appendChild(td);
				if (insertAfter) {
					// insert tr after itemRow, i.e. right before itemRow's nextSibling in the parent
					var nextSibling = itemRow.nextSibling;
					parentNode.insertBefore(tr, nextSibling);
				} else {
					// insert tr before itemRow
					parentNode.insertBefore(tr, itemRow);
				}
				
				td.style.paddingLeft = itemRow.firstChild.style.paddingLeft; //itemRow is a <tr>, we want the indentation of its <td>
				
				placeholder = {
					wrapperNode: tr, 
					refNode: td,
					destroyFunction: function() {
						try {
							if (tr && tr.parentNode) {
								tr.parentNode.removeChild(tr);
							}	
						} catch (err) {
							// tr already removed, do nothing
						}
					}
				};
			}
			
			return placeholder;
		},

		getRow: function(item) {
			var rowId = this.model.getId(item);
			if (rowId) {
				return lib.node(rowId);
			}
		},
		
		/**
		 * Collapse all the nodes in the explorer
		 */
		collapseAll: function() {
			var topLevelNodes = this._navHandler.getTopLevelNodes();
			for (var i = 0; i < topLevelNodes.length ; i++){
				this.myTree.collapse(topLevelNodes[i]);
			}
		},
		
		/**
		 * Expand all the nodes under a node in the explorer
		 * @param nodeModel {Object} the node model to be expanded. If not provided the whole tree is expanded recursively
		 */
		expandAll: function(nodeModel) {
			if(nodeModel){
				this._expandRecursively(nodeModel);
			} else {
				if(!this._navHandler){
					return;
				}
				//We already know what the top level children is under the root, from the navigation handler.
				var topLevelNodes = this._navHandler.getTopLevelNodes();
				for (var i = 0; i < topLevelNodes.length ; i++){
					this._expandRecursively(topLevelNodes[i]);
				}
			}
		},
		
		_expandRecursively: function(node){
			//If a node is not expandable, we stop here.
			if(!this._navHandler || !this._navHandler.isExpandable(node)){
				return;
			}
			var that = this;
			this.myTree.expand(node, function(){
				that.model.getChildren(node, function(children){
					if(children === undefined || children === null) {
						return;
					}
					var len = children.length;
					for (var i = 0; i < len ; i++){
						that._expandRecursively(children[i]);
					}
				});
			});
		},
		
		/**
		 * Displays tree table containing filled with data provided by given model
		 * 
		 * @param {String|Element} parentId id of parent dom element, or the element itself
		 * @param {Object} model providing data to display
		 * @param {Object} options optional parameters of the tree(custom indent, onCollapse callback)
		 */
		createTree: function (parentId, model, options){
			parentId = typeof parentId === "string" ? parentId : (parentId.id || parentId);
			if(this.selection) {
				this.selection.setSelections([]);
			}
			if(this.getNavHandler()){
				this.getNavHandler()._clearSelection();
			}
			var treeId = parentId + "innerTree"; //$NON-NLS-0$
			var existing = lib.node(treeId);
			if (existing) {
				lib.empty(existing);
			}
			if (model){
				model.rootId = treeId;
			}
			this.model = model;
			this._parentId = parentId;
			this._treeOptions = options;
			var useSelection = !options || (options && !options.noSelection);
			if(useSelection){
				this.selectionPolicy = options ? options.selectionPolicy : "";
				this._navDict = new mNavHandler.ExplorerNavDict(this.model);
			}
			this.myTree = new mTreeTable.TableTree({
				id: treeId,
				model: model,
				parent: parentId,
				onComplete: options ? options.onComplete : undefined,
				labelColumnIndex: this.renderer.getLabelColumnIndex(),
				renderer: this.renderer,
				showRoot: options ? !!options.showRoot : false,  
				indent: options ? options.indent: undefined,
				onCollapse: options ? options.onCollapse: undefined,
				navHandlerFactory: options ? options.navHandlerFactory: undefined,
				tableElement: options ? options.tableElement : undefined,
				tableBodyElement: options ? options.tableBodyElement : undefined,
				tableRowElement: options ? options.tableRowElement : undefined
			});
			this.renderer._initializeUIState();
			if(this.selectionPolicy === "cursorOnly"){ //$NON-NLS-0$
				this.initNavHandler();
			}
		},
		getNavHandler: function(){
			return this._navHandler;
		},
		
		getNavDict: function(){
			return this._navDict;
		},
		
		refreshSelection: function(){
			if(this.selection) {
				var navHandler = this.getNavHandler();
				var selections = [];
				if(navHandler && this.getNavDict()){
					var existingSels = navHandler.getSelection();
					for(var i = 0; i < existingSels.length; i++){
						var rowDiv = navHandler.getRowDiv(existingSels[i]);
						if(rowDiv && rowDiv.parentNode){
							var value = this.getNavDict().getValue(this.model.getId(existingSels[i]));
							if(value.model){
								selections.push(value.model);
							}
						}
					}
				}
				this.selection.setSelections(selections);
			}
		},
		
		getRootPath: function() {
			if (this.model && this.model.root) {
				return this.model.root.Location;
			}
			return null;
		},
		
		initNavHandler: function(){
			var options = this._treeOptions;
			
			var useSelection = !options || (options && !options.noSelection);
			if(!useSelection){
				return;
			}
			if(!this.getNavHandler()){
				if (options && options.navHandlerFactory && typeof options.navHandlerFactory.createNavHandler === "function") {
					this._navHandler = options.navHandlerFactory.createNavHandler(this, this._navDict, options);
				} else {
					var getChildrenFunc = options ? options.getChildrenFunc : null;
					this._navHandler = new mNavHandler.ExplorerNavHandler(this, this._navDict, {getChildrenFunc: getChildrenFunc, setFocus: options && options.setFocus, selectionPolicy: (options ? options.selectionPolicy : null)});
				}
			}
			var that = this;
			this.model.getRoot(function(itemOrArray){
				if(itemOrArray instanceof Array){
					that.getNavHandler().refreshModel(that.getNavDict(), that.model, itemOrArray);
				} else if(itemOrArray.children && itemOrArray.children instanceof Array){
					that.getNavHandler().refreshModel(that.getNavDict(), that.model, itemOrArray.children);
				}
				if(options && options.setFocus){
					that.getNavHandler().cursorOn(null, false, false, true);
				}
			});
		},
	    
	    _lastHash: null,
	    checkbox: false
	};
	return Explorer;
}());

/**
 * Creates generic explorer commands, like expand all and collapse all.
 * @param {orion.commands.CommandService} commandService the command service where the commands wil be added
 * @param {Function} visibleWhen optional if not provided we always display the commands
 */
exports.createExplorerCommands = function(commandService, visibleWhen) {
	function isVisible(item){
		if( typeof(item.getItemCount) === "function"){ //$NON-NLS-0$
			if(item.getItemCount() > 0){
				return visibleWhen ? visibleWhen(item) : true; 
			}
			return false;
		}
		return false;
	}
	var expandAllCommand = new mCommands.Command({
		tooltip : messages["Expand all"],
		imageClass : "core-sprite-expandAll", //$NON-NLS-0$
		id: "orion.explorer.expandAll", //$NON-NLS-0$
		groupId: "orion.explorerGroup", //$NON-NLS-0$
		visibleWhen : function(item) {
			return isVisible(item);
		},
		callback : function(data) {
			data.items.expandAll();
	}});
	var collapseAllCommand = new mCommands.Command({
		tooltip : messages["Collapse all"],
		imageClass : "core-sprite-collapseAll", //$NON-NLS-0$
		id: "orion.explorer.collapseAll", //$NON-NLS-0$
		groupId: "orion.explorerGroup", //$NON-NLS-0$
		visibleWhen : function(item) {
			return isVisible(item);
		},
		callback : function(data) {
			data.items.collapseAll();
	}});
	commandService.addCommand(expandAllCommand);
	commandService.addCommand(collapseAllCommand);
};

exports.ExplorerModel = (function() {
	/**
	 * Creates a new explorer model instance.
	 * @name orion.explorer.ExplorerModel
	 * @class Simple tree model using Children and ChildrenLocation attributes to fetch children
	 * and calculating id based on Location attribute.
	 */
	function ExplorerModel(rootPath, /* function returning promise */fetchItems, idPrefix) {
		this.rootPath = rootPath;
		this.fetchItems = fetchItems;
		this.idPrefix = idPrefix || "";
	}
	ExplorerModel.prototype = /** @lends orion.explorer.ExplorerModel.prototype */{
		destroy: function(){
			this.destroyed = true;
		},
		getRoot: function(onItem){
			var self = this;
			this.fetchItems(this.rootPath).then(function(item){
				self.root = item;
				onItem(item);
			});
		},
		getChildren: function(parentItem, /* function(items) */ onComplete){
			// the parent already has the children fetched
			if (parentItem.Children) {
				onComplete(parentItem.Children);
			} else if (parentItem.ChildrenLocation) {
				this.fetchItems(parentItem.ChildrenLocation).then( 
					function(Children) {
						parentItem.Children = Children;
						onComplete(Children);
					}
				);
			} else {
				onComplete([]);
			}
		},
		getId: function(/* item */ item){
			if (item.Location === this.root.Location) {
				return this.rootId;
			} 
			// first strip slashes so we aren't processing path separators.
			var stripSlashes = item.Location.replace(/[\\\/]/g, "");
			// these id's are used in the DOM, so we can't use characters that aren't valid in DOM id's.
			// However we need a unique substitution string for these characters, so that we don't duplicate id's
			// So we are going to substitute ascii values for invalid characters.
			// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=363062
			
			var id = this.idPrefix;
			for (var i=0; i<stripSlashes.length; i++) {
				if (stripSlashes[i].match(/[^\.\:\-\_0-9A-Za-z]/g)) {
					id += stripSlashes.charCodeAt(i);
				} else {
					id += stripSlashes[i];
				}
			}
			return id;
		}
	};
	return ExplorerModel;
}());

exports.ExplorerFlatModel = (function() {
	/**
	 * Creates a new flat explorer model.
	 * @name orion.explorer.ExplorerFlatModel
	 * @class Tree model used by orion.explorer.Explorer for flat structures
	 * @param {String} rootPath path to load tree table root, response should contain a list of items
	 * @param {Function} fetchItems A function that returns a promise that resolves to the
	 * items at the provided location.
	 */
	function ExplorerFlatModel(rootPath, fetchItems, root) {
		this.rootPath = rootPath;
		this.fetchItems = fetchItems;
		this.root = root;
	}
	
	ExplorerFlatModel.prototype = new exports.ExplorerModel();
	
	ExplorerFlatModel.prototype.getRoot = function(onItem){
		if(this.root){
			onItem(this.root);
		} else {
			var self = this;
			this.fetchItems(this.rootPath).then(function(item){
				self.root = item;
				onItem(item);
			});
		}
	};
	
	ExplorerFlatModel.prototype.getChildren = function(parentItem, /* function(items) */ onComplete){
		if(parentItem === this.root){
			onComplete(this.root);
		}else{
			onComplete([]);
		}
	};
	
	return ExplorerFlatModel;
}());

/********* Rendering json items into columns in the tree **************/
exports.ExplorerRenderer = (function() {
	function ExplorerRenderer (options, explorer) {
		this.explorer = explorer;
		this._init(options);
		this._expandImageClass = "core-sprite-openarrow"; //$NON-NLS-0$
		this._collapseImageClass = "core-sprite-closedarrow"; //$NON-NLS-0$
		this._progressImageClass = "core-sprite-progress"; //$NON-NLS-0$
		this._twistieSpriteClass = "modelDecorationSprite"; //$NON-NLS-0$
	}
	ExplorerRenderer.prototype = {
	
		_init: function(options) {
			if (options) {
				this._useCheckboxSelection = options.checkbox === undefined ? false : options.checkbox;
				this.selectionPolicy = options.singleSelection ? "singleSelection" : "";//$NON-NLS-0$
				this._cachePrefix = options.cachePrefix;
				if (options.getCheckedFunc) {
					this.getCheckedFunc = options.getCheckedFunc;
				}
				if (options.onCheckedFunc) {
					this.onCheckedFunc = options.onCheckedFunc;
				}
				this._noRowHighlighting = options.noRowHighlighting; // Whether to have alternating light/dark rows
				this._highlightSelection = true;
				this._treeTableClass = options.treeTableClass || "treetable";  //$NON-NLS-0$
				if (options.highlightSelection === false){
					this._highlightSelection = false;
				}
				if (!this.actionScopeId) {
					this.actionScopeId = options.actionScopeId;
				}
				if (!this.commandService) {
					this.commandService = options.commandService;
				}
			}
		},
		
		getLabelColumnIndex: function() {
			return this.explorer.checkbox ? 1 : 0;  // 0 if no checkboxes
		}, 
		
		initTable: function (tableNode, tableTree) {
			this.tableTree = tableTree;
			this.tableNode = tableNode;
			lib.empty(tableNode);
			if (this._treeTableClass) {
				tableNode.classList.add(this._treeTableClass); 
			}
			this.renderTableHeader(tableNode);

		},
		getActionsColumn: function(item, tableRow, renderType, columnClass, renderAsGrid){
			renderType = renderType || "tool"; //$NON-NLS-0$
			var actionsColumn = document.createElement('td'); //$NON-NLS-0$
			actionsColumn.id = tableRow.id + "actionswrapper"; //$NON-NLS-0$
			if (columnClass) {
				actionsColumn.classList.add(columnClass);
			}
			// contact the command service to render appropriate commands here.
			if (this.actionScopeId && this.commandService) {
				this.commandService.renderCommands(this.actionScopeId, actionsColumn, item, this.explorer, renderType, null, (renderAsGrid && this.explorer.getNavDict()) ? this.explorer.getNavDict().getGridNavHolder(item, true) : null);
			} else {
				window.console.log("Warning, no command service or action scope was specified.  No commands rendered."); //$NON-NLS-0$
			}
			return actionsColumn;
		},
		initCheckboxColumn: function(tableNode){
			if (this._useCheckboxSelection) {
				var th = document.createElement('th'); //$NON-NLS-0$
				return th;
			}
		},
		getCheckboxColumn: function(item, tableRow){
			if (this._useCheckboxSelection) {
				var checkColumn = document.createElement('td'); //$NON-NLS-0$
				var check = document.createElement("span"); //$NON-NLS-0$
				check.id = this.getCheckBoxId(tableRow.id);
				check.classList.add("core-sprite-check"); //$NON-NLS-0$
				check.classList.add("selectionCheckmarkSprite"); //$NON-NLS-0$
				check.rowId = tableRow.id;
				if(this.getCheckedFunc){
					check.checked = this.getCheckedFunc(item);
					if (check.checked) {
						if(this._highlightSelection){
							tableRow.classList.add("checkedRow"); //$NON-NLS-0$
						}
						check.classList.add("core-sprite-check_on"); //$NON-NLS-0$
					} else {
						if(this._highlightSelection){
							tableRow.classList.remove("checkedRow"); //$NON-NLS-0$
						}
						check.classList.remove("core-sprite-check_on");  //$NON-NLS-0$
					}
				}
				checkColumn.appendChild(check);
				var self = this;
				check.addEventListener("click", function(evt) { //$NON-NLS-0$
					var newValue = evt.target.checked ? false : true;
					self.onCheck(tableRow, evt.target, newValue, true, false, item);
				}, false);
				return checkColumn;
			}
		},
		
		getCheckBoxId: function(rowId){
			return rowId + "selectedState"; //$NON-NLS-0$
		},
			
		onCheck: function(tableRow, checkBox, checked, manually, setSelection, item){
			checkBox.checked = checked;
			if (checked) {
				checkBox.classList.add("core-sprite-check_on"); //$NON-NLS-0$
			} else {
				checkBox.classList.remove("core-sprite-check_on"); //$NON-NLS-0$
			}
			if(this.onCheckedFunc){
				this.onCheckedFunc(checkBox.rowId, checked, manually, item);
			}
			if(this.explorer.getNavHandler() && setSelection){
				this.explorer.getNavHandler().setSelection(this.explorer.getNavDict().getValue(tableRow.id).model, true);	
			}
		},
		
		storeSelections: function() {
			if(this.explorer.getNavHandler()){
				var selectionIDs = this.explorer.getNavHandler().getSelectionIds();
				var prefPath = this._getUIStatePreferencePath();
				if (prefPath && window.sessionStorage) {
					window.sessionStorage[prefPath+"selection"] = JSON.stringify(selectionIDs); //$NON-NLS-0$
				}
			}
		},
		
		_restoreSelections: function(prefPath) {
			var navDict = this.explorer.getNavDict();
			var navHandler = this.explorer.getNavHandler();
			if (!navHandler || !navDict || navHandler.getSelectionPolicy() === "cursorOnly") { //$NON-NLS-0$
				return;
			}
			var selections = window.sessionStorage[prefPath+"selection"]; //$NON-NLS-0$
			if (typeof selections === "string") { //$NON-NLS-0$
				if (selections.length > 0) {
					selections = JSON.parse(selections);
				} else {
					selections = null;
				}
			}
			var i;
			if (selections) {
				var selectedItems = [];
				for (i=0; i<selections.length; i++) {
					var wrapper = navDict.getValue(selections[i]);
					if(wrapper && wrapper.rowDomNode && wrapper.model){
						selectedItems.push(wrapper.model);
						if(this._highlightSelection){
							wrapper.rowDomNode.classList.add("checkedRow"); //$NON-NLS-0$
						}
						var check = lib.node(this.getCheckBoxId(wrapper.rowDomNode.id));
						if (check) {
							check.checked = true;
							check.classList.add("core-sprite-check_on"); //$NON-NLS-0$
						}
					}
				}
				// notify the selection service of our new selections
				if(this.explorer.selection) {
					this.explorer.selection.setSelections(selectedItems);
					if(this.explorer.getNavHandler()){
						this.explorer.getNavHandler().refreshSelection();
					}
				}
			}	
		},
		
		_storeExpansions: function(prefPath) {
			window.sessionStorage[prefPath+"expanded"] = JSON.stringify(this._expanded); //$NON-NLS-0$
		},
		
		// returns true if the selections also need to be restored.
		_restoreExpansions: function(prefPath) {
			var didRestoreSelections = false;
			var expanded = window.sessionStorage[prefPath+"expanded"]; //$NON-NLS-0$
			if (typeof expanded=== "string") { //$NON-NLS-0$
				if (expanded.length > 0) {
					expanded= JSON.parse(expanded);
				} else {
					expanded = null;
				}
			}
			var i;
			if (expanded) {
				for (i=0; i<expanded.length; i++) {
					var row= lib.node(expanded[i]);
					if (row) {
						this._expanded.push(expanded[i]);
						// restore selections after expansion in case an expanded item was selected.
						var self = this;
						this.tableTree.expand(expanded[i], function() {
							self._restoreSelections(prefPath);
						});
						didRestoreSelections = true;
					}
				}
			}
			return !didRestoreSelections;
		},
		
		_getUIStatePreferencePath: function() {
			if (this.explorer) {
				var rootPath = this.explorer.getRootPath();
				if (this._cachePrefix && rootPath) {
					var rootSegmentId = rootPath.replace(/[^\.\:\-\_0-9A-Za-z]/g, "");
					return "/" + this._cachePrefix + "/" + rootSegmentId + "/uiState"; //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				}
			}
			return null;
						
		},
		
		expandCollapseImageId: function(rowId) {
			return rowId+"__expand"; //$NON-NLS-0$
		},
		
		updateExpandVisuals: function(tableRow, isExpanded) {
			var expandImage = lib.node(this.expandCollapseImageId(tableRow.id));
			if (expandImage) {
				expandImage.classList.remove(this._expandImageClass);
				expandImage.classList.remove(this._collapseImageClass);
				expandImage.classList.remove(this._progressImageClass);
				expandImage.classList.add(isExpanded === "progress" ? this._progressImageClass : isExpanded ? this._expandImageClass : this._collapseImageClass); //$NON-NLS-0$
			}
		},

		/**
		 * Appends the image node with expand/collapse behavior
		 * @param {Element} tableRow
		 * @param {Element} placeHolder
		 * @param {String} decorateImageClass
		 * @param {String} spriteClass
		 * @returns {Element} The image node
		 */
		getExpandImage: function(tableRow, placeHolder, /* optional extra decoration */ decorateImageClass, /* optional sprite class for extra decoration */ spriteClass){
			var expandImage = document.createElement("span"); //$NON-NLS-0$
			expandImage.id = this.expandCollapseImageId(tableRow.id);
			placeHolder.appendChild(expandImage);
			expandImage.classList.add(this._twistieSpriteClass);
			expandImage.classList.add(this._collapseImageClass);
			if (decorateImageClass) {
				var decorateImage = document.createElement("span"); //$NON-NLS-0$
				placeHolder.appendChild(decorateImage);
				decorateImage.classList.add(spriteClass || "imageSprite"); //$NON-NLS-0$
				decorateImage.classList.add(decorateImageClass);
			}
			var self = this;
			// TODO should really avoid creating 1 listener per expandImage here
			expandImage.onclick = function(evt) {
				if (!self.explorer.getNavHandler().isDisabled(tableRow)) {
					self.tableTree.toggle(tableRow.id);
					var expanded = self.tableTree.isExpanded(tableRow.id);
					if (expanded) {
						self._expanded.push(tableRow.id);
						if (self.explorer.postUserExpand) {
							self.explorer.postUserExpand(tableRow.id);
						}
					} else {
						for (var i in self._expanded) {
							if (self._expanded[i] === tableRow.id) {
								self._expanded.splice(i, 1);
								break;
							}
						}
					}
					var prefPath = self._getUIStatePreferencePath();
					if (prefPath && window.sessionStorage) {
						self._storeExpansions(prefPath);
					}
				}
			};
			return expandImage;
		},
		
		render: function(item, tableRow){
			tableRow.classList.add("navRow"); //$NON-NLS-0$
			this.renderRow(item, tableRow);
		},
		
		rowsChanged: function() {
			// notify the selection service of the change in state.
			if(this.explorer.selectionPolicy !== "cursorOnly"){ //$NON-NLS-0$
				this.explorer.refreshSelection();
				this.explorer.initNavHandler();			
			}
			if (!this._noRowHighlighting){
				var even = "darkSectionTreeTableRow"; //$NON-NLS-0$
				var odd = "lightSectionTreeTableRow"; //$NON-NLS-0$
				if(lib.$(".sectionTreeTable", this.tableNode.parentNode) || lib.$(".treetable", this.tableNode.parentNode)) { //$NON-NLS-1$ //$NON-NLS-0$
					lib.$$array(".treeTableRow", this.tableNode).forEach(function(node, i) { //$NON-NLS-0$
						var on = (!(i % 2)) ? odd : even;
						var off = (on === odd) ? even : odd;
						node.classList.add(on);
						node.classList.remove(off);
					});
				}
			}
		},
		updateCommands: function(){
			if (this.commandService) {
				var rows = lib.$$array(".treeTableRow"); //$NON-NLS-0$
				for (var i=0; i<rows.length; i++) {
					var node = rows[i];			
					var actionsWrapperId = node.id + "actionswrapper"; //$NON-NLS-0$
					var actionsWrapper = lib.node(actionsWrapperId);
					this.commandService.destroy(actionsWrapper);
					// contact the command service to render appropriate commands here.
					if (this.actionScopeId) {
						this.commandService.renderCommands(this.actionScopeId, actionsWrapper, node._item, this.explorer, "tool"); //$NON-NLS-0$
					}
				}
			}
		},
		
		_initializeUIState: function() {
			this._expanded = [];
			var prefsPath = this._getUIStatePreferencePath();
			if (prefsPath && window.sessionStorage) {
				if (this._restoreExpansions(prefsPath)) {
					this._restoreSelections(prefsPath);
				}
			}
		}
	};
	return ExplorerRenderer;
}());

/**
 * @name orion.explorer.SelectionRenderer
 * @class This  renderer renders a tree table and installs a selection and cursoring model to
 * allow the user to make selections without using checkboxes.
 * Override {@link orion.explorer.SelectionRenderer#getCellHeaderElement}  and
 * {@link orion.explorer.SelectionRenderer#getCellElement} to generate table content.
 */
exports.SelectionRenderer = (function(){
	/**
	 * Create a selection renderer with the specified options.  Options are defined in
	 * ExplorerRenderer.  An additional option is added here.
	 * @param {Boolean}singleSelection If true, set the selection policy to "singleSelection".
	 *
	 */
	function SelectionRenderer(options, explorer) {
		this._init(options);
		this.explorer = explorer;
	}
	SelectionRenderer.prototype = new exports.ExplorerRenderer();

	SelectionRenderer.prototype.renderTableHeader = function(tableNode){
		var thead = document.createElement('thead'); //$NON-NLS-0$
		var row = document.createElement('tr'); //$NON-NLS-0$
		thead.classList.add("navTableHeading"); //$NON-NLS-0$
		if (this._useCheckboxSelection) {
			row.appendChild(this.initCheckboxColumn(tableNode));
		}
		
		var i = 0;
		var cell = this.getCellHeaderElement(i);
		while(cell){
			if (cell.innerHTML.length > 0) {
				cell.classList.add("navColumn"); //$NON-NLS-0$
			}
			row.appendChild(cell);			
			cell = this.getCellHeaderElement(++i);
		}
		thead.appendChild(row);
		if (i > 0) {
			tableNode.appendChild(thead);
		}
	};
	
	SelectionRenderer.prototype.renderRow = function(item, tableRow) {
		tableRow.verticalAlign = "baseline"; //$NON-NLS-0$
		tableRow.classList.add("treeTableRow"); //$NON-NLS-0$

	
		if (item.selectable === undefined || item.selectable) {
			var navDict = this.explorer.getNavDict();
			if(navDict){
				if (this.explorer.selectionPolicy !== "cursorOnly") { //$NON-NLS-0$
					tableRow.classList.add("selectableNavRow"); //$NON-NLS-0$
				}
				
				navDict.addRow(item, tableRow);
				var self = this;
				tableRow.addEventListener("click", function(evt) { //$NON-NLS-0$
					if(self.explorer.getNavHandler()){
						self.explorer.getNavHandler().onClick(item, evt);
					}
				}, false);
			}
			var checkColumn = this.getCheckboxColumn(item, tableRow);
			if(checkColumn) {
				checkColumn.classList.add('checkColumn'); //$NON-NLS-0$
				tableRow.appendChild(checkColumn);
			}
		}

		var i = 0;
		var cell = this.getCellElement(i, item, tableRow);
		while(cell){
			tableRow.appendChild(cell);
			if (i===0) {
				if(this.getPrimColumnStyle){
					cell.classList.add(this.getPrimColumnStyle(item)); //$NON-NLS-0$
				} else {
					cell.classList.add("navColumn"); //$NON-NLS-0$
				}
			} else {
				if(this.getSecondaryColumnStyle){
					cell.classList.add(this.getSecondaryColumnStyle()); //$NON-NLS-0$
				} else {
					cell.classList.add("secondaryColumn"); //$NON-NLS-0$
				}
			}
			cell = this.getCellElement(++i, item, tableRow);
		}
	};

	/**
	 * Override to return a dom element containing table header, preferably <code>th</code>
	 * @name orion.explorer.SelectionRenderer#getCellHeaderElement
	 * @function
	 * @param col_no number of column
	 */
	SelectionRenderer.prototype.getCellHeaderElement = function(col_no){};

	/**
	 * Override to return a dom element containing table cell, preferable <code>td</code>
	 * @name orion.explorer.SelectionRenderer#getCellElement
	 * @function
	 * @param col_no number of column
	 * @param item item to be rendered
	 * @param tableRow the current table row
	 */
	SelectionRenderer.prototype.getCellElement = function(col_no, item, tableRow){};
	
	return SelectionRenderer;
}());

exports.SimpleFlatModel = (function() {	
	/**
	 * Creates a new flat model based on an array of items already known.
	 *
	 * @name orion.explorer.SimpleFlatModel
	 * @param {Array} items the items in the model
	 * @param {String} idPrefix string used to prefix generated id's
	 * @param {Function} getKey function used to get the property name used for generating an id in the model
	 */
	function SimpleFlatModel(items, idPrefix, getKey) {
		this.items = items;
		this.getKey = getKey;
		this.idPrefix = idPrefix;
		this.root = {children: items};
	}
	
	SimpleFlatModel.prototype = new exports.ExplorerModel();
		
	SimpleFlatModel.prototype.getRoot = function(onItem){
		onItem(this.root);
	};
	
	SimpleFlatModel.prototype.getId = function(/* item */ item){
		var key = this.getKey(item);
		// this might be a path, so strip slashes
		var stripSlashes = key.replace(/[\\\/]/g, "");
		var id = "";
		for (var i=0; i<stripSlashes.length; i++) {
			if (stripSlashes[i].match(/[^\.\:\-\_0-9A-Za-z]/g)) {
				id += stripSlashes.charCodeAt(i);
			} else {
				id += stripSlashes[i];
			}
		}
		return this.idPrefix + id;
	};
		
	SimpleFlatModel.prototype.getChildren = function(parentItem, /* function(items) */ onComplete){
		if(parentItem === this.root){
			onComplete(this.items);
		}else{
			onComplete([]);
		}
	};
	return SimpleFlatModel;
}());

return exports;
});

/*******************************************************************************
 * @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
 *******************************************************************************/
/*global define window document navigator*/

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.tabIndex = -1;
		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
	};
});

/* global define */
define('orion/extensionCommands',[], function() {
	return {
		getOpenWithCommands: function() {
			return [];
		},
		getOpenWithCommand: function(commandService, item, openWithCommands) {
			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
 ******************************************************************************/
/*global define*/
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 varags.
		 * @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) 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
 *******************************************************************************/
/*global define console */

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 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
 *******************************************************************************/
/*global console define setTimeout clearTimeout XMLHttpRequest*/

/**
 * @name orion.xhr
 * @namespace Provides a promise-based API to {@link XMLHttpRequest}.
 */
define('orion/xhr',['orion/Deferred'], function(Deferred) {

	/**
	 * @name orion.xhr.Result
	 * @class Wraps an XHR response or failure.
	 * @property {Object} args Arguments passed to the {@link orion.xhr.xhr} call.
	 * @property {Object|ArrayBuffer|Blob|Document|String} response The <code>response</code> object returned by the XMLHttpRequest.
	 * It is typed according to the <code>responseType</code> passed to the XHR call (by default it is a {@link String}).
	 * @property {String} [responseText] The <code>response</code> returned by the XMLHttpRequest, if it is a {@link String}.
	 * If the <code>response</code> is not a String, this property is <code>null</code>.
	 * @property {Number} status The HTTP status code returned by the XMLHttpRequest.
	 * @property {String} url The URL that the XHR request was made to.
	 * @property {XMLHttpRequest} xhr The underlying XMLHttpRequest object.
	 * @property {String|Error} error <i>Optional</i>. If a timeout occurred or an error was thrown while performing the
	 * XMLHttpRequest, this field contains information about the error.
	 */

	/**
	 * @param {String} url
	 * @param {Object} options
	 * @param {XMLHttpRequest} xhr
	 * @param {String|Error} [error]
	 */
	function makeResult(url, options, xhr, error) {
		var response = typeof xhr.response !== 'undefined' ? xhr.response : xhr.responseText; //$NON-NLS-0$
		var responseText = typeof response === 'string' ? response : null; //$NON-NLS-0$
		var status;
		try {
			status = xhr.status;
		} catch (e) {
			status = 0;
		}
		var result = {
			args: options,
			response: response,
			responseText: responseText,
			status: status,
			url: url,
			xhr: xhr
		};
		if (typeof error !== 'undefined') { //$NON-NLS-0$
			result.error = error;
		}
		return result;
	}

	/**
	 * Wrapper for {@link XMLHttpRequest} that returns a promise.
	 * @name xhr
	 * @function
	 * @memberOf orion.xhr
	 * @param {String} method One of 'GET', 'POST', 'PUT', 'DELETE'.
	 * @param {String} url The URL to request.
	 * @param {Object} [options]
	 * @param {Object|ArrayBuffer|Blob|Document} [options.data] The raw data to send as the request body. (Only allowed for POST and PUT).
	 * @param {Object} [options.headers] A map of header names and values to set on the request.
	 * @param {Boolean} [options.log=false] If <code>true</code>, failed requests will be logged to the JavaScript console.
	 * @param {String} [options.responseType=''] Determines the type of the response object returned. Value must be one of the following:
	 * <ul><li><code>'arraybuffer'</code>
	 * <li><code>'blob'</code>
	 * <li><code>'document'</code>
	 * <li><code>'json'</code>
	 * <li><code>'text'</code>
	 * <li><code>''</code> (same as <code>'text'</code>)</ul>
	 * @param {Number} [options.timeout=0] Timeout in milliseconds. Defaults to 0 (no timeout).
	 * @returns {Deferred} A deferred for the result. The deferred will resolve on 2xx, 3xx status codes or reject on 4xx, 5xx status codes.
	 * In both cases a {@link orion.xhr.Result} is provided to the listener.
	 */
	// TODO: upload progress, user/password
	function _xhr(method, url, options/*, XMLHttpRequestImpl */) {
		options = options || {};
		var xhr = (arguments.length > 3 && arguments[3]) ? arguments[3] : new XMLHttpRequest(); //$NON-NLS-0$
		var d = new Deferred();
		var headers = options.headers || {};
		var log = options.log || false;
		var data;
		if (typeof headers['X-Requested-With'] === 'undefined') { //$NON-NLS-1$ //$NON-NLS-0$
			headers['X-Requested-With'] = 'XMLHttpRequest'; //$NON-NLS-1$ //$NON-NLS-0$
		}
		if (typeof options.data !== 'undefined' && (method === 'POST' || method === 'PUT')) { //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			data = options.data;
		}
		
		var cancelled = false;
		var aborted = false;
		d.promise.then(undefined, function(error) {
			cancelled = true;
			if (!aborted && error instanceof Error && error.name === "Cancel") {
				xhr.abort();
			}
		});
		
		xhr.onabort = function() {
			aborted = true;
			if (!cancelled) {
				var cancelError = new Error("Cancel");
				cancelError.name = "Cancel";
				d.reject(cancelError);
			}
		};
		xhr.onload = function() {
			var result = makeResult(url, options, xhr);
			if(200 <= xhr.status && xhr.status < 400) {
				d.resolve(result);
			} else {
				d.reject(result);
				if(log && typeof console !== 'undefined') { //$NON-NLS-0$
					console.log(new Error(xhr.statusText));
				}
			}
		};
		xhr.onerror = function() {
			var result = makeResult(url, options, xhr);
			d.reject(result);
			if (log && typeof console !== 'undefined') { //$NON-NLS-0$
				console.log(new Error(xhr.statusText));
			}
		};
		xhr.onprogress = function(progressEvent) {
			progressEvent.xhr = xhr;
			d.progress(progressEvent);
		};
	
		try {
			xhr.open(method, url, true /* async */);
			if (typeof options.responseType === 'string') { //$NON-NLS-0$
				xhr.responseType = options.responseType;
			}
			if (typeof options.timeout === 'number') { //$NON-NLS-0$
				if (typeof xhr.timeout === 'number') { //$NON-NLS-0$
					// Browser supports XHR timeout
					xhr.timeout = options.timeout;
					xhr.addEventListener('timeout', function(e) { //$NON-NLS-0$
						d.reject(makeResult(url, options, xhr, 'Timeout exceeded')); //$NON-NLS-0$
					});
				} else {
					// Use our own timer
					var timeoutId = setTimeout(function() {
						d.reject(makeResult(url, options, xhr, 'Timeout exceeded')); //$NON-NLS-0$
					}, options.timeout);
					d.promise.then(clearTimeout.bind(null, timeoutId), clearTimeout.bind(null, timeoutId));
				}
			}
			Object.keys(headers).forEach(function(key) {
				xhr.setRequestHeader(key, headers[key]);
			});
			xhr.send(data || null);
		} catch (e) {
			d.reject(makeResult(url, options, xhr, e));
		}

		return d.promise;
	}
	return _xhr;
});

/*******************************************************************************
 * @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
 *******************************************************************************/
/*global document window StopIteration*/
/*jslint regexp:false*/
// 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 window.URL === "function" && window.URL.length !== 0 &&
                (testURL = new window.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 === "..") {
                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, {
        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 = window.URL || window.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
        });
    }
    window.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
 ******************************************************************************/
/*global console define URL*/
/*jslint browser:true sub:true*/
define('orion/widgets/browse/commitInfoRenderer',[
	'orion/objects',
	"orion/URITemplate",
	'orion/webui/littlelib',
	'orion/xhr',
	'orion/URL-shim'
], function(objects, URITemplate, lib, xhr) {

	function _timeDifference(commitTimeStamp) {
		var currentDate = new Date();
		var commitDate = new Date(commitTimeStamp);
	    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 singleTerm + " ";
			}
			return number + " " + term + " ";
		}
		return "";
	}
	function calculateTime(commitTimeStamp) {
		var diff = _timeDifference(commitTimeStamp);
		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;
		}
		if(disPlayStr) {
			return disPlayStr + "ago";
		}	
		return "Just now";	
	}
	/**
	 * @name orion.widgets.browse.CommitInfoRenderer
	 * @class Comit Info renderer.
	 * @description Renders a DIV with commit information.
	 * @name orion.browse.CommitInfoOptions
	 *
	 * @property {String|DOMElement} parent the parent element for the commit information, it can be either a DOM element or an ID for a DOM element.
	 * @property {Object} commitInfo the information object of a commit information.
	 *	{
	 *	   Author: {Name: "string", Email: "email@addre.ss", Date: milliseconds(integer) },
	 *	   Committer: {Name: "string", Email: "email@addre.ss", Date: milliseconds(integer) },
	 *	   Message: "string",
	 * 	   URL: "string",
	 * 	   AvatarURL: "string"
	 *	}
	 */

	function CommitInfoRenderer(params) {
		this._parentDomNode = lib.node(params.parent);//Required
		this._commitInfo = params.commitInfo;//Required
	}
	objects.mixin(CommitInfoRenderer.prototype, /** @lends orion.widgets.Filesystem.CommitInfoRenderer */ {
		destroy: function() {
		},
		_simpleMessage: function(maxLength) {
			var message = this._commitInfo.Message ? this._commitInfo.Message : "";
			if(message) {
				var arrayofMsg = message.replace(/\r\n|\n\r|\n|\r/g,"\n").split("\n");		
				if(arrayofMsg && arrayofMsg.length > 0) {
					message = arrayofMsg[0];
				}
				if(maxLength > 10 && message.length > maxLength) {
					message = message.substring(0, maxLength-3) + "...";
				}
			}
			return message;
		},
		_generateMessageNode: function(message) {
			var messageNode;
			if(this._commitInfo.URL || this._commitInfo.SHA1) {
				messageNode = document.createElement("a"); //$NON-NLS-0$
				if(this._commitInfo.SHA1) {
					var commitURLBase = (new URL("commit", window.location.href)).href;
					commitURLBase = decodeURIComponent(commitURLBase);
					messageNode.href = new URITemplate(commitURLBase + "{/SHA1}").expand(this._commitInfo);
				} else {
					messageNode.href = this._commitInfo.URL;
				}
				messageNode.classList.add("commitInfolink"); //$NON-NLS-0$
				messageNode.appendChild(document.createTextNode(message));
			} else {
				messageNode = document.createElement("span"); //$NON-NLS-0$
				messageNode.appendChild(document.createTextNode(message));
			}
			return messageNode;
		},
		_requestPhotoURL: function(userId, avatarImage) {
			var _this = this;
			var relativeURL = "/manage/service/com.ibm.team.jazzhub.common.service.IPhotoService/getContributorPhotoURI";
			var absURL = new URL(relativeURL, window.location.href);
			absURL.query.set("contributorUserId", userId);
			var requestURL = absURL.href;
			xhr("GET", requestURL, {
				timeout: 15000
			}).then(function(result) {
				var photo = JSON.parse(result.response);
				avatarImage.src = photo.photoURI;
			}, function() { _this._useOtherPhotoURL(avatarImage);});
			
		},
		_useOtherPhotoURL: function(avatarImage) {
			if(this._commitInfo.AvatarURL) {
				avatarImage.src = this._commitInfo.AvatarURL;
			} else {
				avatarImage.src = "https://www.gravatar.com/avatar/?d=mm";
			}
		},
		renderSimple: function(maxWidth) {
			this._parentDomNode.appendChild(this._generateMessageNode(this._simpleMessage(maxWidth)));
		},
		render: function(commitLabel, showAvatar) {
			//var commitDate = this._commitInfo.Author && this._commitInfo.Author.Date ? new Date(this._commitInfo.Author.Date).toLocaleString() : "";
			var commitDate = this._commitInfo.Author && this._commitInfo.Author.Date ? calculateTime(this._commitInfo.Author.Date) : "";
			
			var message = this._simpleMessage(-1);
			var authorName = this._commitInfo.Author && this._commitInfo.Author.Name ? this._commitInfo.Author.Name : "";
			
			//Render the avatar or label
			var label = commitLabel;
			if(!label) {
				label = "Commit";
			}
			label = "Last " + label;
			if(showAvatar) {
				var avatarContainer = document.createElement("div");
				var avatarImage = new Image();//document.createElement("image");
				if(this._commitInfo.Author && this._commitInfo.Author.UserId) {
					this._requestPhotoURL(this._commitInfo.Author.UserId, avatarImage);
				} else {
					this._useOtherPhotoURL(avatarImage);
				}
				avatarImage.classList.add("commitInfoAvatar"); //$NON-NLS-0$
				avatarContainer.appendChild(avatarImage);
				this._parentDomNode.appendChild(avatarContainer);
			} else {
				var labelContainer = document.createElement("div");
				labelContainer.classList.add("commitInfoLabelContainer"); //$NON-NLS-0$
				labelContainer.appendChild(document.createTextNode(label.toUpperCase()));
				this._parentDomNode.appendChild(labelContainer);
			}
			
			//Render the message, author and date on the right of the avatar
			var messageContainer = document.createElement("div");
			messageContainer.classList.add("commitInfoMessageContainer"); //$NON-NLS-0$
			
			var authorNode = document.createElement("div");
			authorNode.classList.add("commitInfoAutorContainer"); //$NON-NLS-0$
			var fragment = document.createDocumentFragment();
			var nameLabel = document.createElement("span"); //$NON-NLS-0$
			nameLabel.appendChild(document.createTextNode(authorName)); //$NON-NLS-0$
			nameLabel.classList.add("navColumnBold"); //$NON-NLS-0$
			var segments;
			if(showAvatar) {
				var segLabel = document.createElement("span"); //$NON-NLS-0$
				segLabel.appendChild(document.createTextNode(label)); //$NON-NLS-0$
				segLabel.classList.add("navColumnBold"); //$NON-NLS-0$
				fragment.textContent = commitDate ? "${0} by ${1} " + commitDate +"." : "${0} by ${1}.";
				segments = [segLabel, nameLabel];
			} else {
				fragment.textContent = commitDate ? "by ${0} " + commitDate +"." : "by ${0}.";
				segments = [nameLabel];
			}
			lib.processDOMNodes(fragment, segments);
			authorNode.appendChild(fragment);
			messageContainer.appendChild(authorNode);
			
			var messageTextContainer = document.createElement("div");
			messageTextContainer.classList.add("commitInfoMessageTextContainer"); //$NON-NLS-0$
			messageTextContainer.appendChild(this._generateMessageNode(message));
			messageContainer.appendChild(messageTextContainer);

			this._parentDomNode.appendChild(messageContainer);
		}
	});

	return {CommitInfoRenderer: CommitInfoRenderer,
			calculateTime: calculateTime
	};
});

/*******************************************************************************
 * @license
 * Copyright (c) 2009, 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
 ******************************************************************************/
/*global define */
/*jslint regexp:false browser:true forin:true sub:true*/

define('orion/explorers/navigatorRenderer',[
	'i18n!orion/navigate/nls/messages',
	'orion/Deferred',
	'orion/explorers/explorer',
	'orion/explorers/navigationUtils',
	'orion/extensionCommands',
	'orion/objects',
	'orion/URITemplate',
	'orion/widgets/browse/commitInfoRenderer',
	'orion/webui/littlelib'
], function(messages, Deferred, mExplorer, mNavUtils, mExtensionCommands, objects, URITemplate, mCommitInfoRenderer, lib) {
		
	var max_more_info_column_length = 60;
	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;
	}
	
	/* Internal */
	function addImageToLink(contentType, link, location, replace) {
		if (contentType) {
			var image, imageClass = contentType.imageClass, imageURL = contentType.image;
			if (imageClass) {
				image = document.createElement("span"); //$NON-NLS-0$
				image.className += imageClass; // may be several classes in here
				image.classList.add("thumbnail"); //$NON-NLS-0$
			} else if (imageURL) {
				image = document.createElement("img"); //$NON-NLS-0$
				image.src = imageURL;
				// to minimize the height/width in case of a large one
				image.classList.add("thumbnail"); //$NON-NLS-0$
			}
			if (image) {
				link.replaceChild(image, replace);
			}
		}
	}
	
	var uriTemplate = new URITemplate("#{,resource,params*}"); //$NON-NLS-0$
	
	var clickedItem;
	/**
	 * Returns the last item clicked by links created with #createLink.
	 * @name orion.explorer.NavigatorRenderer.getClickedItem
	 * @function
	 */
	function getClickedItem() {
		return clickedItem;
	}
		
	/**
	 * Exported so that it can be used by other UI that wants to use navigator-style links. commandService and contentTypeService  are necessary to compute 
	 * the proper editor for a file.
	 * @name orion.explorer.NavigatorRenderer.createLink
	 * @function
	 * @param {String} folderPageURL the page you want to direct folders to (such as navigator).  Using a blank string will just hash the current page.
	 * @param {Object} item a json object describing an Orion file or folder
	 * @param {Object} commandService necessary to compute the proper editor for a file. Must be a synchronous, in-page service, not retrieved 
	 * from the service registry.
	 * @param {Object[]} [openWithCommands] The "open with" commands used to generate link hrefs. If this parameter is not provided, the caller must
	 * have already processed the service extension and added to the command registry (usually by calling {@link orion.extensionCommands.createAndPlaceFileCommandsExtension}).
	 * @param {Object} [linkProperties] gives additional properties to mix in to the HTML anchor element.
	 * @param {Object} [uriParams] A map giving additional parameters that will be provided to the URI template that generates the href.
	 * @param {Object} [separateImageHolder] Separate image holder object. {holderDom: dom}. If separateImageHolder is not defined, the file icon image is rendered in the link as the first child.
	 * @param {NavigatorRenderer} [renderer] The renderer object. Optional. If defined, renderer.updateFileNode() is called to update the file element for sub classes.
	 * If separateImageHolder is defined with holderDom property, the file icon iamge is rendered in separateImageHolder.holderDom.
	 * IF separateImageHolder is defined as an empty object, {}, the file icon iamge is not rendered at all.
	 */
	function createLink(folderPageURL, item, commandService, contentTypeService, openWithCommands, linkProperties, uriParams, separateImageHolder, renderer) {
		// TODO FIXME folderPageURL is bad; need to use URITemplates here.
		// TODO FIXME refactor the async href calculation portion of this function into a separate function, for clients who do not want the <A> created.
		item = objects.clone(item);
		var link;
		if (item.Directory) {
			link = document.createElement("a"); //$NON-NLS-0$
			link.className = "navlinkonpage"; //$NON-NLS-0$
			var template = !folderPageURL ? uriTemplate : new URITemplate(folderPageURL + "#{,resource,params*}"); //$NON-NLS-0$
			link.href = template.expand({resource: item.ChildrenLocation});
			if(item.Name){
				link.appendChild(document.createTextNode(item.Name));
			}
		} else {
			if (!openWithCommands) {
				openWithCommands = mExtensionCommands.getOpenWithCommands(commandService);
			}
			link = document.createElement("a"); //$NON-NLS-0$
			link.className= "navlink targetSelector"; //$NON-NLS-0$
			if (linkProperties && typeof linkProperties === "object") { //$NON-NLS-0$
				Object.keys(linkProperties).forEach(function(property) {
					link[property] = linkProperties[property];
				});
			}
			var imageHolderDom = null, image = null;
			if(separateImageHolder) {
				imageHolderDom = separateImageHolder.holderDom;
			} else {
				imageHolderDom = link;
			}
			if(imageHolderDom) {
				image = document.createElement("span"); //$NON-NLS-0$
				image.className = "core-sprite-file modelDecorationSprite thumbnail"; //$NON-NLS-0$
				imageHolderDom.appendChild(image);
			}
			if(item.Name){
				link.appendChild(document.createTextNode(item.Name));
			}
			var href = item.Location;
			if (uriParams && typeof uriParams === "object") { //$NON-NLS-0$
				item.params = {};
				objects.mixin(item.params, uriParams);
			}
			var openWithCommand = mExtensionCommands.getOpenWithCommand(commandService, item, openWithCommands);
			if (openWithCommand) {
				href = openWithCommand.hrefCallback({items: item});
			}
			Deferred.when(contentTypeService.getFileContentType(item), function(contentType) {
				if(imageHolderDom) {
					addImageToLink(contentType, imageHolderDom, item.Location, image);
				}
				link.href = href;
				if(renderer && typeof renderer.updateFileNode === 'function') { //$NON-NLS-0$
					renderer.updateFileNode(item, link, isImage(contentType));
				}
			});
		}
		link.addEventListener("click", function() { //$NON-NLS-0$
			clickedItem = item;
		}.bind(this), false);
		return link;
	}
		
	/**
	 * @name orion.explorer.NavigatorRenderer
	 * @class Renderer for a tree-table of files, like the Orion Navigator.
	 * @description Renderer for a tree-table of files, like the Orion Navigator.
	 * @param {Object} options
	 * @param {orion.explorer.Explorer} explorer
	 * @param {orion.commandregistry.CommandRegistry} commandRegistry
	 * @param {orion.core.ContentTypeRegistry} contentTypeService
	 */
	function NavigatorRenderer (options, explorer, commandService, contentTypeService) {
		this.explorer = explorer;
		this.commandService = commandService;
		this.contentTypeService = contentTypeService;
		this.openWithCommands = null;
		this.actionScopeId = options.actionScopeId;
		
		this._init(options);
		this.target = "_self"; //$NON-NLS-0$
	}
	NavigatorRenderer.prototype = new mExplorer.SelectionRenderer(); 

	NavigatorRenderer.prototype.wrapperCallback = function(wrapperElement) {
		wrapperElement.setAttribute("role", "tree"); //$NON-NLS-1$ //$NON-NLS-0$
	};

	NavigatorRenderer.prototype.tableCallback = function(tableElement) {
		tableElement.setAttribute("aria-label", messages["Navigator"]); //$NON-NLS-1$ //$NON-NLS-0$
		tableElement.setAttribute("role", "presentation"); //$NON-NLS-1$ //$NON-NLS-0$
	};

	/**
	 * @param {Element} rowElement
	 */
	NavigatorRenderer.prototype.rowCallback = function(rowElement, model) {
		rowElement.setAttribute("role", "treeitem"); //$NON-NLS-1$ //$NON-NLS-0$
	};
	
	
	/**
	 * @param {Element} bodyElement
	 */
	NavigatorRenderer.prototype.emptyCallback = function(bodyElement) {
		var tr = document.createElement("tr"); //$NON-NLS-0$
		var td = document.createElement("td"); //$NON-NLS-0$
		td.colSpan = this.oneColumn ? 1 : 3;
		var noFile = document.createElement("div"); //$NON-NLS-0$
		noFile.classList.add("noFile"); //$NON-NLS-0$
		noFile.textContent = messages["NoFile"];
		var plusIcon = document.createElement("span"); //$NON-NLS-0$
		plusIcon.appendChild(document.createTextNode(messages["File"]));
		lib.processDOMNodes(noFile, [plusIcon]);
		td.appendChild(noFile);
		tr.appendChild(td);
		bodyElement.appendChild(tr);
	};

	/**
	 * @param {int|string} the local time stamp
	 */
	NavigatorRenderer.prototype.getDisplayTime = function(timeStamp) {
		return new Date(timeStamp).toLocaleString();
	};
	
	/**
	 * @param {Element} parent the parent dom where the commit info is rendered
	 * @param {Object} commitInfo the commit info
	 */
	NavigatorRenderer.prototype.getCommitRenderer = function(parent, commitInfo) {
		return new mCommitInfoRenderer.CommitInfoRenderer({parent: parent, commitInfo: commitInfo});
	};
	
	/**
	 * Creates the column header element. We are really only using the header for a spacer at this point.
	 * @name orion.explorer.NavigatorRenderer.prototype.getCellHeaderElement
	 * @function
	 * @returns {Element}
	 */
	NavigatorRenderer.prototype.getCellHeaderElement = function(col_no){
		// TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=400121
		if (this.oneColumn && col_no !== 0) {
			return null;
		}

		switch(col_no){
		case 0:
		case 1:
		case 2:
			var th = document.createElement("th"); //$NON-NLS-0$
			th.style.height = "8px"; //$NON-NLS-0$
		}
	};

	/**
	 * Sets the link target to be used for file links.
	 * @name orion.explorer.NavigatorRenderer#setTarget
	 * @function
	 * @param {String} target The target (eg. "new", "_self").
	 */
	NavigatorRenderer.prototype.setTarget = function(target){
		this.target = target;
	};
		
	/**
	 * Creates a image DOM Element for the specified folder.
	 * @name orion.explorer.NavigatorRenderer#getFolderImage
	 * @type {Function}
	 * @param {Object} folder The folder to create an image for.
	 * @returns {Element} The folder image element.
	 */
	NavigatorRenderer.prototype.getFolderImage = function(folder) {
		if (!this.showFolderImage) {
			return null;
		}
		var span = document.createElement("span"); //$NON-NLS-0$
		span.className = "core-sprite-folder modelDecorationSprite"; //$NON-NLS-0$
		return span;
	};

	/**
	* Subclasses can override this function to customize the DOM Element that is created to represent a folder.
	 * The default implementation creates either a hyperlink or a plain text node.
	 * @name orion.explorer.NavigatorRenderer#createFolderNode
	 * @type {Function}
	 * @see orion.explorer.NavigatorRenderer#showFolderLinks
	 * @see orion.explorer.NavigatorRenderer#folderLink
	 * @param {Object} folder The folder to create a node for.
	 * @returns {Element} The folder element.
	 */
	// The returned element must have an <code>id</code> property.
	NavigatorRenderer.prototype.createFolderNode = function(folder) {
		var itemNode;
		if (this.showFolderLinks) { //$NON-NLS-0$
			// TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=400121
			itemNode = createLink(this.folderLink || "", folder, this.commandService, this.contentTypeService); //$NON-NLS-0$
			var image = this.getFolderImage(folder);
			if (image) {
				itemNode.insertBefore(image, itemNode.firstChild);
			}
		} else {
			itemNode = document.createElement("span"); //$NON-NLS-0$
			itemNode.textContent = folder.Name;
		}
		return itemNode;
	};

	/**
	* Subclasses can override this function to customize the DOM Element that is created to represent a file.
	 * The default implementation does nothing.
	 * @name orion.explorer.NavigatorRenderer#updateFileNode
	 * @type {Function}
	 * @param {Object} file The file model to update for.
	 * @param {Element} fileNode The file node to update.
	 * @param {Boolean} isImage The flag to indicate if the file is an image file.
	 */
	// The returned element must have an <code>id</code> property.
	NavigatorRenderer.prototype.updateFileNode = function(file, fileNode, isImage) {
	};

	/**
	 * Whether the default implementation of {@link #createFolderNode} should show folders should as links (<code>true</code>),
	 * or just plain text (<code>false</code>).
	 * @name orion.explorer.NavigatorRenderer#showFolderLinks
	 * @type {Boolean}
	 * @default true
	 */
	NavigatorRenderer.prototype.showFolderLinks = true;
	/**
	 * Gives the base href to be used by the default implementation of {@link #createFolderNode} for creating folder links.
	 * This property only takes effect if {@link #showFolderLinks} is <code>true</code>. 
	 * TODO see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=400121">Bug 400121</a>
	 * @name orion.explorer.NavigatorRenderer#folderLink
	 * @type {String}
	 * @default ""
	 */
	/**
	 * Generate the DOM element for a cell. If you override this function, you will most likely have to override {@link orion.explorers.FileExplorer#getNameNode}
	 * in your explorer class.
	 * @name orion.explorer.NavigatorRenderer#getCellElement
	 * @function
	 * @returns {Element}
	 */
	NavigatorRenderer.prototype.getCellElement = function(col_no, item, tableRow){
		var timeStampCase = item.LastCommit ? 2 : 1;
		var sizeCase = item.LastCommit ? 3 : 2;
		var commitCase = item.LastCommit ? 1 : -1;
		switch(col_no){
		case 0:
			var col = document.createElement('td'); //$NON-NLS-0$
			var span = document.createElement("span"); //$NON-NLS-0$
			span.id = tableRow.id+"MainCol"; //$NON-NLS-0$
			span.setAttribute("role", "presentation"); //$NON-NLS-1$ //$NON-NLS-0$
			col.appendChild(span);
			col.setAttribute("role", "presentation"); //$NON-NLS-1$ //$NON-NLS-0$
			span.className = "mainNavColumn"; //$NON-NLS-0$
			var itemNode;
			if (item.Directory) {
				// defined in ExplorerRenderer.  Sets up the expand/collapse behavior
				this.getExpandImage(tableRow, span);
				itemNode = this.createFolderNode(item);

				span.appendChild(itemNode);
				this.explorer._makeDropTarget(item, itemNode);
				this.explorer._makeDropTarget(item, tableRow);
			} else {
				if (!this.openWithCommands) {
					this.openWithCommands = mExtensionCommands.getOpenWithCommands(this.commandService);
				}
				itemNode = createLink("", item, this.commandService, this.contentTypeService, this.openWithCommands, { target: this.target }, null, null, this);
				span.appendChild(itemNode); //$NON-NLS-0$
			}
			if (itemNode) {
				// orion.explorers.FileExplorer#getNameNode
				itemNode.id = tableRow.id + "NameLink"; //$NON-NLS-0$
				if (itemNode.nodeType === 1) {
					mNavUtils.addNavGrid(this.explorer.getNavDict(), item, itemNode);
					itemNode.setAttribute("role", "link"); //$NON-NLS-1$ //$NON-NLS-0$
					itemNode.setAttribute("tabindex", "-1"); //$NON-NLS-1$ //$NON-NLS-0$
				}
			}
			// render any inline commands that are present.
			if (this.actionScopeId) {
				this.commandService.renderCommands(this.actionScopeId, span, item, this.explorer, "tool", null, true); //$NON-NLS-0$
			}
			return col;
		case timeStampCase:
			// TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=400121
			if (this.oneColumn) {
				return null;
			}
			var dateColumn = document.createElement('td'); //$NON-NLS-0$
			if (item.LocalTimeStamp) {
				dateColumn.textContent = this.getDisplayTime(item.LocalTimeStamp);
			}
			return dateColumn;
		case sizeCase:
			// TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=400121
			if (this.oneColumn) {
				return null;
			}
			var sizeColumn = document.createElement('td'); //$NON-NLS-0$
			if (!item.Directory && typeof item.Length === "number") { //$NON-NLS-0$
				var length = parseInt(item.Length, 10),
					kb = length / 1024;
				sizeColumn.textContent = Math.ceil(kb).toLocaleString() + " KB"; //$NON-NLS-0$
			}
			sizeColumn.style.textAlign = "right"; //$NON-NLS-0$
			return sizeColumn;
		case commitCase:// LastCommit field is optional. For file services that dod not return this properties, we do not have to render this column.
			if (this.oneColumn || !item.LastCommit) {
				return null;
			}
			var messageColumn = document.createElement('td'); //$NON-NLS-0$
			this.getCommitRenderer(messageColumn, item.LastCommit).renderSimple(max_more_info_column_length);
			return messageColumn;
		}
	};
	NavigatorRenderer.prototype.constructor = NavigatorRenderer;
	
	//return module exports
	return {
		NavigatorRenderer: NavigatorRenderer,
		isImage : isImage,
		getClickedItem: getClickedItem,
		createLink: createLink
	};
});

/*******************************************************************************
 * @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
 *******************************************************************************/

/*global define localStorage*/
define('orion/i18nUtil',['require', 'orion/Deferred'], function(require, Deferred) {

	var messageBundleDeffereds = {};

	function formatMessage(msg) {
		var args = arguments;
		return msg.replace(/\$\{([^\}]+)\}/g, function(str, index) {
			return args[(index << 0) + 1];
		});
	}

	function getCachedMessageBundle(name) {
		var item = localStorage.getItem('orion/messageBundle/' + name);
		if (item) {
			var bundle = JSON.parse(item);
			if (bundle._expires && bundle._expires > new Date().getTime()) {
				delete bundle._expires;
				return bundle;
			}
		}
		return null;
	}

	function setCachedMessageBundle(name, bundle) {
		bundle._expires = new Date().getTime() + 1000 * 900; //15 minutes
		localStorage.setItem('orion/messageBundle/' + name, JSON.stringify(bundle));
		delete bundle._expires;
	}

	function getMessageBundle(name) {
		if (messageBundleDeffereds[name]) {
			return messageBundleDeffereds[name];
		}

		var d = new Deferred();
		messageBundleDeffereds[name] = d;

		var cached = getCachedMessageBundle(name);
		if (cached) {
				d.resolve(cached);
				return d;
		}

		function _resolveMessageBundle(bundle) {
			if (bundle) {
				require(['i18n!' + name], function(bundle) { //$NON-NLS-0$
					if (bundle) {
						setCachedMessageBundle(name, bundle);
					}
					d.resolve(bundle);
				});
			} else {
				// IE disguises failure as success, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=428797
				_rejectMessageBundle(new Error(name));
			}
		}

		function _rejectMessageBundle(error) {
			d.reject(error);
		}

		try {
			require([name], _resolveMessageBundle, _rejectMessageBundle);
		} catch (ignore) {
			require(['orion/i18n!' + name], _resolveMessageBundle, _rejectMessageBundle); //$NON-NLS-0$
		}
		return d;
	}
	return {
		getMessageBundle: getMessageBundle,
		formatMessage: formatMessage
	};
});

/*******************************************************************************
 * @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
 *******************************************************************************/

/*global define window */
/*jslint browser:true*/
define('orion/inputManager',[
	'i18n!orion/edit/nls/messages',
	'orion/explorers/navigatorRenderer',
	'orion/i18nUtil',
	'orion/Deferred',
	'orion/EventTarget',
	'orion/objects',
	'orion/PageUtil'
], function(messages, mNavigatorRenderer, i18nUtil, Deferred, EventTarget, objects, PageUtil) {

	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 = ["keypress","keydown","keyup"]; //$NON-NLS-0$ //$NON-NLS-1$ //$NON-NLS-2$
		var reset = function (e) { this._resetTimer(); }.bind(this);
		for (var i = 0; i < events.length; i++) {
			var event = events[i];
			this._document.addEventListener(event, reset, true);
		}
		EventTarget.attach(this);
	}

	Idle.prototype = {
		_resetTimer: function() {
			var window = this._document.defaultView || this._document.parentWindow;
			if (this._timer) {
				window.clearTimeout(this._timer);
				this._timer = null;
			}
			if (this._timeout !== -1) {
				this._timer = window.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);
	}

	/**
	 * @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 = "";
		this.dispatcher = null;
		this._unsavedChanges = [];
	}
	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
			if (resource === fileClient.fileServiceRootURL(resource)) {
				return fileClient.loadWorkspace(resource).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(location /**, readArgs*/) {
			var cachedMetadata = this.cachedMetadata || mNavigatorRenderer.getClickedItem();
			if (cachedMetadata && cachedMetadata.Location === location &&
				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(location).then(function(newLocation) {
				return fileClient.read.apply(fileClient, [newLocation].concat(readArgs));
			});
		},
		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._saving && !this._fileMetadata.Directory && !this._readonly) {
					progress(fileClient.read(resource, true), messages.ReadingMetadata, fileURI).then(function(data) {
						if (this._fileMetadata && this._fileMetadata.Location === data.Location && this._fileMetadata.ETag !== data.ETag) {
							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._unsavedChanges = [];
								}.bind(this));
							}
						}
					}.bind(this));
				}
			} else {
				//TODO this URL parsing to retrieve the should be done in the server side.
				//TODO /gitapi/commit URLs are not supported be the orion file client.
				var metadataURI = resource;
				if (metadataURI.indexOf("/gitapi/commit/") === 0) { //$NON-NLS-0$
					var start = metadataURI.indexOf("/file"); //$NON-NLS-0$
					var end = metadataURI.indexOf("?", start); //$NON-NLS-0$
					if (end === -1) { end = metadataURI.length; }
					metadataURI = metadataURI.substring(start, end);
					this._readonly = true;
				}
				var progressTimeout = window.setTimeout(function() {
					progressTimeout = null;
					this.reportStatus(i18nUtil.formatMessage(messages.Fetching, fileURI));
				}.bind(this), 800);
				var clearTimeout = function() {
					this.reportStatus("");
					if (progressTimeout) {
						window.clearTimeout(progressTimeout);
					}
				}.bind(this);
				var errorHandler = function(error) {
					clearTimeout();
					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
				progress(this._read(metadataURI, true), messages.ReadingMetadata, metadataURI).then(function(metadata) {
					if(!metadata) {
						errorHandler({responseText: i18nUtil.formatMessage(messages.ReadingMetadataError, metadataURI)});
					} else if (metadata.Directory) {
						// Fetch children
						Deferred.when(metadata.Children || progress(fileClient.fetchChildren(metadata.ChildrenLocation), messages.Reading, fileURI), function(contents) {
							clearTimeout();
							metadata.Children = contents;
							this._setInputContents(this._parsedLocation, fileURI, contents, metadata);
						}.bind(this), errorHandler);
					} else {
						// Read contents if this is a text file
						if (this._isText(metadata)) {
							// Read text contents
							progress(fileClient.read(resource, false, true), messages.Reading, fileURI).then(function(contents) {
								clearTimeout();
								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) {
								clearTimeout();
								this._setInputContents(this._parsedLocation, fileURI, contents, metadata);
							}.bind(this), errorHandler);
						}
					}
				}.bind(this), errorHandler);
			}
		},
		processParameters: function(input) {
			var editor = this.getEditor();
			if (editor && editor.processParameters) {
				editor.processParameters(input);
			}
		},
		getAutoLoadEnabled: function() {
			return this._autoLoadEnabled;
		},
		getAutoSaveEnabled: function() {
			return this._autoSaveEnabled;
		},
		getEditor: function() {
			return this.editor;
		},
		getInput: function() {
			return this._input;
		},
		getTitle: function() {
			return this._title;
		},
		getFileMetadata: function() {
			return this._fileMetadata;
		},
		getReadOnly: function() {
			var data = this._fileMetadata;
			return this._readonly || !data || (data.Attributes && data.Attributes.ReadOnly);
		},
		getContentType: function() {
			return this._contentType;
		},
		onFocus: function(e) {
			// 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.load();
			}
		},
		onChanging: function(e) {
			if (!this._getSaveDiffsEnabled()) { 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});
		},
		reportStatus: function(msg) {
			if (this.statusReporter) {
				this.statusReporter(msg);
			} else if (this.editor) {
				this.editor.reportStatus(msg);
			}
		},
		save: function() {
			if (this._saving) { return; }
			var editor = this.getEditor();
			if (!editor || !editor.isDirty() || this.getReadOnly()) { return; }
			var failedSaving = this._errorSaving;
			this._saving = true;
			var input = this.getInput();
			this.reportStatus(messages['Saving...']);

			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._unsavedChanges;
				var length = 0;
				for (var i = 0; i < changes.length; i++) {
					length += changes[i].text.length;
				}
				if (contents.length > length) {
					data = {
						diff: changes
					};
				}
			}
			this._unsavedChanges = [];
			this._errorSaving = false;

			var etag = this.getFileMetadata().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));
			}
			var self = this;
			function successHandler(result) {
				if (input === self.getInput()) {
					self.getFileMetadata().ETag = result.ETag;
					editor.setInput(input, null, contents, true);
				}
				self.reportStatus("");
				if (failedSaving && statusService) {
					statusService.setProgressResult({Message:messages.Saved, Severity:"Normal"}); //$NON-NLS-0$
				}
				if (self.afterSave) {
					self.afterSave();
				}
				self._saving = false;
			}
			function errorHandler(error) {
				self.reportStatus("");
				handleError(statusService, error);
				self._saving = false;
				self._errorSaving = true;
			}
			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 def = self.fileClient.write(resource, contents);
						if (progress) {
							def = progress.progress(def, i18nUtil.formatMessage(messages.savingFile, input));
						}
						def.then(successHandler, errorHandler);
					} else {
						self._saving = false;
					}
				} else {
					// unknown error
					errorHandler(error);
				}
			});
		},
		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;
			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.save();
					}
				}.bind(this));
			} else {
				this._idle.setTimeout(timeout);
			}
		},
		setContentType: function(contentType) {
			this._contentType = contentType;
		},
		setInput: function(location) {
			if (this._ignoreInput) { return; }
			if (!location) {
				location = PageUtil.hash();
			}
			if (typeof location !== "string") { //$NON-NLS-0$
				return;
			}
			var editor = this.getEditor();
			if (location && location[0] !== "#") { //$NON-NLS-0$
				location = "#" + location; //$NON-NLS-0$
			}
			var input = PageUtil.matchResourceParameters(location), 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 = location;
			this._parsedLocation = input;
			this._ignoreInput = true;
			if(this.selection) {
				this.selection.setSelections(location);
			}
			this._ignoreInput = false;
			var fileURI = input.resource;
			if (fileURI) {
				if (fileURI === this._input) {
					if (editorChanged) {
						this.reportStatus("");
						this._setInputContents(input, fileURI, null, this._fileMetadata, this._isText(this._fileMetadata));
					} else {
						this.processParameters(input);
					}
				} else {
					this._input = fileURI;
					this._readonly = false;
					this._fileMetadata = null;
					this.load();
				}
			} else {
				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;
		},
		_getSaveDiffsEnabled: function() {
			return this._saveDiffsEnabled && this._acceptPatch !== null && this._acceptPatch.indexOf("application/json-patch") !== -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;
			}
			if (this._changingListener) {
				if (editor && editor.getModel && editor.getModel()) {
					editor.getModel().removeEventListener("Changing", this._changingListener); //$NON-NLS-0$
				}
				this._changingListener = 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.dispatchEvent(evt);
			this.editor = editor = evt.editor;
			if (!isDir) {
				if (editor && editor.getModel && editor.getModel()) {
					editor.getModel().addEventListener("Changing", this._changingListener = this.onChanging.bind(this)); //$NON-NLS-0$
				}
				if (!noSetInput) {
					editor.setInput(title, null, contents);
				}
				if (editor && editor.getTextView && editor.getTextView()) {
					editor.getTextView().addEventListener("Focus", this._focusListener = this.onFocus.bind(this)); //$NON-NLS-0$
				}
				this._unsavedChanges = [];
				this.processParameters(input);
			}
		}
	});
	return {
		handleError: handleError,
		InputManager: InputManager
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/
/*global window define document */
define('orion/breadcrumbs',['require', 'orion/webui/littlelib'], function (require, lib) {

    /**
     * Constructs a new BreadCrumb with the given options.
     * @param {Object} options The options object, which must specify the parent container.
     * @param options.container The parent container for the bread crumb presentation
     * @param [options.resource] The current resource
     * @param [options.rootSegmentName] The name to use for the root segment in lieu of the metadata name.
     * @param [options.workspaceRootSegmentName] The name to use for the workspace root. If not specified, the workspace root
     * will not be shown.
     * @param {Function} [options.makeHref] The callback function to make the href on a bread crumb item. If not defined "/edit/edit.html#" is used.
     * @param {Function} [option.getFirstSegment] The callback function to make DOM node for the first segment in breadcrumb. 
     * @class Bread crumbs show the current position within a resource tree and allow navigation
     * to different places in the tree. Unlike the fairy tale, bread crumbs typically don't lead
     * to a cottage made of gingerbread. Sorry!
     * @name orion.breadcrumbs.BreadCrumbs
     */

    function BreadCrumbs(options) {
        this._init(options);
    }
    BreadCrumbs.prototype = /** @lends orion.breadcrumbs.BreadCrumbs.prototype */ {
        _init: function (options) {
            var container = lib.node(options.container);
            if (!container) {
                throw "no parent container"; //$NON-NLS-0$
            }
            this._container = container;
            container.classList.remove("currentLocation"); //$NON-NLS-0$
            this._id = options.id || "eclipse.breadcrumbs"; //$NON-NLS-0$
            this._resource = options.resource || null;
            this._rootSegmentName = options.rootSegmentName;
            this._workspaceRootSegmentName = options.workspaceRootSegmentName;
			this._workspaceRootURL = options.workspaceRootURL;
            this._makeHref = options.makeHref;
            this._makeFinalHref = options.makeFinalHref;
            this._maxLength = options.maxLength;
            this.path = "";
            this.measure();
            this.render();
        },

        getNavigatorWorkspaceRootSegment: function () {
            if (this._workspaceRootSegmentName) {
                var seg;
                if (this._resource && this._resource.Parents && !this._resource.skip) {
                    seg = document.createElement('a'); //$NON-NLS-0$
					var param = this._workspaceRootURL ? this._workspaceRootURL : "";
                    if (this._makeHref) {
                        this._makeHref(seg, param );
                    } else {
                        seg.href = require.toUrl("edit/edit.html") + "#" + param; //$NON-NLS-1$ //$NON-NLS-0$
                    }
                } else {
                    seg = document.createElement('span'); //$NON-NLS-0$
                }
                lib.empty(seg);
                seg.appendChild(document.createTextNode(this._workspaceRootSegmentName));
                return seg;
            }
            return null;
        },

        MAX_LENGTH: 500,
        INCLUDE_FIRST_SECTION: true,

        segments: [],

        buildSegment: function (name) {
            var segment = document.createElement('a'); //$NON-NLS-0$
            segment.classList.add("breadcrumb"); //$NON-NLS-0$
            segment.appendChild(document.createTextNode(name));
            return segment;
        },

        addSegmentHref: function (seg, section) {
            if (this._makeHref) {
                this._makeHref(seg, section.Location, section);
            } else {
                seg.href = require.toUrl("edit/edit.html") + "#" + section.ChildrenLocation; //$NON-NLS-1$ //$NON-NLS-0$
            }
        },

        buildSegments: function (firstSegmentName, direction) {
	
			if( this._resource.Parents ){      
	            var parents = this._resource.Parents.slice(0); // create a copy
	            var seg;
	            var segmentName;
	            
	            if( parents ){
	
		            var collection = parents.slice(0);
		
		            if (direction === 'reverse') { //$NON-NLS-0$
		                collection = collection.reverse().slice(0);
		            }
		
		            collection.forEach(function (parent) {
						if(parent.skip) {
							return;
						}
		                if (firstSegmentName) {
		                    segmentName = firstSegmentName;
		                    firstSegmentName = null;
		                } else {
		                    segmentName = parent.Name;
		                }
		
		                seg = this.buildSegment(segmentName);
		                
		
			                this.path += parent.Name;
			                this.addSegmentHref(seg, parent);
		                
		                seg.include = false;
		                this.segments.push(seg);
		
		            }.bind(this));         
	            }
            }
        },

        addDivider: function () {
            var slash = document.createElement('span'); //$NON-NLS-0$
            slash.appendChild(document.createTextNode(' / ')); //$NON-NLS-0$
            this.path += "/"; //$NON-NLS-0$
            slash.classList.add("breadcrumbSeparator"); //$NON-NLS-0$		
            this.append(slash);
        },

        refresh: function () {
            this.crumbs = lib.node(this._id);

            if (this.crumbs) {
                lib.empty(this.crumbs);
            } else {
                this.crumbs = document.createElement('span'); //$NON-NLS-0$
                this.crumbs.id = this._id;
                this._container.appendChild(this.crumbs);

                this.dirty = document.createElement('span'); //$NON-NLS-0$
                this.dirty.id = "dirty"; //$NON-NLS-0$
                this.dirty.className = "modifiedFileMarker"; //$NON-NLS-0$
                this._container.appendChild(this.dirty);
            }
            
            this.crumbs.style.width = 'auto'; //$NON-NLS-0$
            this.crumbs.style.visibility = 'visible'; //$NON-NLS-0$
            this.crumbs.parentNode.className = "currentLocation"; //$NON-NLS-0$
            this.crumbs.parentNode.style.width = 'auto'; //$NON-NLS-0$
        },

        append: function (section) {
            this.crumbs.appendChild(section);
        },

        addTitle: function (seg, firstSegmentName) {
            // if we had no resource, or had no parents, we need some kind of current location in the breadcrumb

			var text = firstSegmentName || document.title;

            if (this.crumbs.childNodes.length === 0) {
                seg = document.createElement('span'); //$NON-NLS-0$
                seg.appendChild(document.createTextNode( text ));
                seg.classList.add("breadcrumb"); //$NON-NLS-0$
                seg.classList.add("currentLocation"); //$NON-NLS-0$
                this.append(seg);
            }
        },

        finalSegment: function (seg, firstSegmentName) {
        	if(this._resource.skip) {
        		return;
        	}
            var name;
            if (firstSegmentName) {
                name = firstSegmentName;
            } else {
				name = this._resource.Name;
            }
            if (this._makeFinalHref) {
               seg = this.buildSegment(name); //$NON-NLS-0$
               this.addSegmentHref(seg, this._resource);
            } else {
                seg = document.createElement('span'); //$NON-NLS-0$
                seg.appendChild(document.createTextNode( name ));
            }
            seg.classList.add("currentLocation"); //$NON-NLS-0$
            this.path += this._resource.Name;
            this.append(seg);
        },

        firstSegment: function (segment) {
            if (segment) {
                this.append(segment);

                if (this._resource && this._resource.Parents && !this._resource.skip) {
                    segment.classList.add("breadcrumb"); //$NON-NLS-0$
                    this.addDivider();
                } else { // we are at the root.  Get rid of any href since we are already here
                    if(!this._resource.skip) {
                    	segment.href = "";
                    }
                    segment.classList.add("currentLocation"); //$NON-NLS-0$
                    return;
                }
            }
        },

        drawSegments: function () {

            if (this._resource.Parents) {
                var reverseParents = this.segments.slice(0);
                reverseParents.forEach(function (parent) {
                    if (parent.include === true) {
                        this.append(parent);
                        this.addDivider();
                    }
                }.bind(this));
            }
        },

        measureSegments: function () {

            this.INCLUDE_FIRST_SECTION = true;

            if (this._resource.Parents) {
                var reverseParents = this.segments.slice(0).reverse();
                reverseParents.forEach(function (parent) {
                    this.append(parent);
                    this.addDivider();
                    if (this.crumbs.offsetWidth < this.MAX_LENGTH) {
                        parent.include = true;
                    } else {
                        this.INCLUDE_FIRST_SECTION = false;
                    }

                }.bind(this));

                this.segments = reverseParents.reverse();
            }
        },

        measure: function () {
        
        	var middleWidth = this._container;

			middleWidth.style.width = 'auto';
			
			if(this._maxLength) {
        		this.MAX_LENGTH = this._maxLength;
			} else {
        		this.MAX_LENGTH = middleWidth.offsetWidth;
        	}

            this.refresh();
            
            this.segments = [];

            this.crumbs.style.visibility = 'hidden'; //$NON-NLS-0$

            var segment = this.getNavigatorWorkspaceRootSegment();

            var firstSegmentName = this._rootSegmentName;

            if (firstSegmentName) {
                this.addTitle(segment, firstSegmentName);
            } else {
                this.finalSegment(segment, firstSegmentName);

                if (this._resource && this._resource.Parents) {
                    this.buildSegments(firstSegmentName, 'reverse'); //$NON-NLS-0$
                    this.measureSegments();
                    this.firstSegment(segment);   
                }
	
				if (this.crumbs.offsetWidth >= this.MAX_LENGTH) {
                        this.INCLUDE_FIRST_SECTION = false;
				}
            }
        },

        render: function () {

            this.refresh();

            var segment = this.getNavigatorWorkspaceRootSegment();

            var firstSegmentName = this._rootSegmentName;

            if (firstSegmentName) {
                this.addTitle(segment, firstSegmentName);
            } else {
            
				if (this.INCLUDE_FIRST_SECTION === true) {
                    this.firstSegment(segment);
                }

                if (this._resource && this._resource.Parents) {
                    this.drawSegments();
                    this.finalSegment(segment, firstSegmentName);
                }
            }

            this.crumbs.parentNode.style.width = this.crumbs.offsetWidth + 20 + 'px';    //$NON-NLS-0$
        }
    };

    BreadCrumbs.prototype.constructor = BreadCrumbs;
    return {
        BreadCrumbs: BreadCrumbs
    };
});

/*******************************************************************************
 * @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
 *******************************************************************************/
/*global define window document navigator URL*/

define('orion/fileUtils',['require', 'i18n!orion/nls/messages', 'orion/URL-shim'], function(require, messages) {
                
	/**
	 * This class contains static utility methods. It is not intended to be instantiated.
	 * @class This class contains static utility methods.
	 * @name orion.fileUtils
	 */

	function makeRelative(location) {
		if (!location) {
			return location;
		}
		var hostName = window.location.protocol + "//" + window.location.host; //$NON-NLS-0$
		if (location.indexOf(hostName) === 0) {
			return location.substring(hostName.length);
		}
		return location;
	}
	
	//cache this
	var _workspaceUrlHref;
	if(!require.toUrl){
		_workspaceUrlHref =(new URL("/", window.location.href)).href;
	} else {
		_workspaceUrlHref =(new URL(require.toUrl("workspace"), window.location.href)).href;
	}
	/**
	 * Determines if the path represents the workspace root
	 * @name orion.util#isAtRoot
	 * @function
	 */
	function isAtRoot(path) {
		if (!path) {
			return false;
		}
		if (path === "/workspace") {
			return true; // sad but true
		}
		var pathUrl = new URL(path, window.location.href);
		return pathUrl.href.indexOf(_workspaceUrlHref) === 0; //$NON-NLS-0$
	}
	
	//return module exports
	return {
		makeRelative: makeRelative,
		isAtRoot: isAtRoot
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global clearTimeout console define document setTimeout window */
/*jslint regexp:false forin:true*/

define('orion/explorers/explorer-table',[
	'i18n!orion/navigate/nls/messages',
	'orion/Deferred',
	'orion/webui/littlelib',
	'orion/i18nUtil',
	'orion/fileUtils',
	'orion/explorers/explorer',
	'orion/EventTarget',
	'orion/objects',
	'orion/util'
], function(messages, Deferred, lib, i18nUtil, mFileUtils, mExplorer, EventTarget, objects, util){

	/**
	 * Tree model used by the FileExplorer
	 */
	function FileModel(serviceRegistry, root, fileClient, idPrefix, excludeFiles, excludeFolders) {
		this.registry = serviceRegistry;
		this.root = root;
		this.fileClient = fileClient;
		this.idPrefix = idPrefix || "";
		this.excludeFiles = !!excludeFiles;
		this.excludeFolders = !!excludeFolders;
	}
	FileModel.prototype = new mExplorer.ExplorerModel(); 
	objects.mixin(FileModel.prototype, /** @lends orion.explorer.FileModel.prototype */ {
		getRoot: function(onItem){
			onItem(this.root);
		},
	
		/*
		 *	Process the parent and children, doing any filtering or sorting that may be necessary.
		 */
		processParent: function(parent, children) {
			// Note that the Parents property is not available for metadatas retrieved with fetchChildren().
			var parents = parent.Projects ? [] : [parent].concat(parent.Parents || []);
			if (this.excludeFiles || this.excludeFolders) {
				var filtered = [];
				for (var i in children) {
					var exclude = children[i].Directory ? this.excludeFolders : this.excludeFiles;
					if (!exclude) {
						filtered.push(children[i]);
						children[i].parent = parent;
						if (!children[i].Parents)
							children[i].Parents = parents;
					}
				}
				children = filtered;
			} else {
				for (var j in children) {
					children[j].parent = parent;
					if (!children[j].Parents)
						children[j].Parents = parents;
				}
			}
		
			//link the parent and children together
			parent.children = children;
	
			// not ideal, but for now, sort here so it's done in one place.
			// this should really be something pluggable that the UI defines
			parent.children.sort(this.sortChildren); 
			return children;
		},
		
		sortChildren: function(a, b) {
			var isDir1 = a.Directory;
			var isDir2 = b.Directory;
			if (isDir1 !== isDir2) {
				return isDir1 ? -1 : 1;
			}
			var n1 = a.Name && a.Name.toLowerCase();
			var n2 = b.Name && b.Name.toLowerCase();
			if (n1 < n2) { return -1; }
			if (n1 > n2) { return 1; }
			return 0;
		},
		
		getChildren: function(parentItem, /* function(items) */ onComplete) {
			var self = this;
			// the parent already has the children fetched
			if (parentItem.children) {
				onComplete(parentItem.children);
			} else if (parentItem.Directory!==undefined && parentItem.Directory===false) {
				onComplete([]);
			} else if (parentItem.Children) {
				onComplete(self.processParent(parentItem, parentItem.Children));
			} else if (parentItem.Location) {
				var progress = null;
				if(this.registry) {
					progress = this.registry.getService("orion.page.progress"); //$NON-NLS-0$
				}
				(progress ? progress.progress(this.fileClient.fetchChildren(parentItem.ChildrenLocation), messages["Fetching children of "] + parentItem.Name) : this.fileClient.fetchChildren(parentItem.ChildrenLocation)).then( //$NON-NLS-0$
					function(children) {
						if (self.destroyed) { return; }
						onComplete(self.processParent(parentItem, children));
					},
					function() {
						onComplete([]);
					}
				);
			} else {
				onComplete([]);
			}
		},
		
		hasChildren: function() {
			var result = false;
			if (this.root.Children) {
				result = this.root.Children.length > 0;
			}
			return result;
		}
	});
	
	FileModel.prototype.constructor = FileModel;


	/**
	 * Creates a new file explorer.
	 * @name orion.explorer.FileExplorer
	 * @class A user interface component that displays a table-oriented file explorer
	 * @extends orion.explorer.Explorer
	 *
	 * @param {Object} options.treeRoot an Object representing the root of the tree.
	 * @param {orion.selection.Selection} options.selection the selection service used to track selections.
	 * @param {orion.fileClient.FileClient} options.fileClient the file service used to retrieve file information
	 * @param {String|Element} options.parentId the id of the parent DOM element, or the parent DOM element itself.
	 * @param {Function} options.rendererFactory a factory that creates a renderer
	 * @param {Boolean} options.excludeFiles specifies that files should not be shown. Optional.
	 * @param {Boolean} options.excludeFolders specifies that folders should not be shown.  Optional.
	 * @param {Object} [options.navHandlerFactory] Optional factory to use for creating the explorer's nav handler. Must provide a function
	 * <code>createNavHandler(explorer, explorerNavDict, options)</code>.
	 * @param {orion.serviceregistry.ServiceRegistry} options.serviceRegistry  the service registry to use for retrieving other
	 *	Orion services.  Optional.  If not specified, then some features of the explorer will not be enabled, such as status reporting,
	 *  honoring preference settings, etc.
	 * @param {Boolean} [options.setFocus=true] Whether the explorer should steal keyboard focus when rendered. The default is to steal focus.
	 */
	/**
	 * Root model item of the tree.
	 * @name orion.explorer.FileExplorer#treeRoot
	 * @field
	 * @type Object
	 */
	/**
	 * Dispatches events describing model changes.
	 * @name orion.explorer.FileExplorer#modelEventDispatcher
	 * @type orion.EventTarget
	 */
	/**
	 * Handles model changes.
	 * @name orion.explorer.FileExplorer#modelHandler
	 * @type orion.explorer.FileExplorer.ModelHandler
	 */
	function FileExplorer(options) {
		EventTarget.attach(this);
		this.registry = options.serviceRegistry;
		this.treeRoot = options.treeRoot;
		this.selection = options.selection;
		this.fileClient = options.fileClient;
		this.excludeFiles = options.excludeFiles;
		this.excludeFolders = options.excludeFolders;
		this.navHandlerFactory = options.navHandlerFactory;
		this.parentId = options.parentId;
		this.renderer = options.rendererFactory(this);
		this.dragAndDrop = options.dragAndDrop;
		this.setFocus = options.setFocus;
		this.model = null;
		this.myTree = null;
		this.checkbox = false;
		this._hookedDrag = false;
		var modelEventDispatcher = options.modelEventDispatcher ? options.modelEventDispatcher : new EventTarget();
		this.modelEventDispatcher = modelEventDispatcher;

		// Listen to model changes from fileCommands
		var _self = this;
		this._modelListeners = {};
		["copy", "copyMultiple", "create", "delete", "deleteMultiple", "import", //$NON-NLS-5$//$NON-NLS-4$//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$//$NON-NLS-0$
		 "move", "moveMultiple"].forEach(function(eventType) { //$NON-NLS-1$//$NON-NLS-0$
				modelEventDispatcher.addEventListener(eventType, _self._modelListeners[eventType] = _self.modelHandler[eventType].bind(_self));
			});
			
		this._clickListener = function(evt) {
			if (evt.target.tagName === "A") { //$NON-NLS-0$
				var temp = evt.target;
				while (temp) {
					if (temp._item) {
						break;
					}
					temp = temp.parentNode;
				}
				if (temp && temp._item) {
					_self.onLinkClick({type: "linkClick", item: temp._item}); //$NON-NLS-0$
				}
			}
		};
		var parent = lib.node(this.parentId);
		if (parent) {
			parent.addEventListener("click", this._clickListener); //$NON-NLS-0$
		}

		// Same tab/new tab setting
		var renderer = this.renderer;
		if (this.registry) {
			this.registry.registerService("orion.cm.managedservice", //$NON-NLS-0$
				{	updated: function(properties) {
						var target;
						if (properties && properties["links.newtab"] !== "undefined") { //$NON-NLS-1$ //$NON-NLS-0$
							target = properties["links.newtab"] ? "_blank" : "_self"; //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
						} else {
							target = "_self"; //$NON-NLS-0$
						}
						if (renderer.setTarget) {
							renderer.setTarget(target);
						}
					}
				}, {pid: "nav.config"}); //$NON-NLS-0$
		}
	}
	
	var dragStartTarget, dropEffect;

	FileExplorer.prototype = Object.create(mExplorer.Explorer.prototype);
	objects.mixin(FileExplorer.prototype, /** @lends orion.explorer.FileExplorer.prototype */ {
		destroy: function() {
			var _self = this;
			Object.keys(this._modelListeners).forEach(function(eventType) {
				_self.modelEventDispatcher.removeEventListener(eventType, _self._modelListeners[eventType]);
			});
			var parent = lib.node(this.parentId);
			if (parent) {
				parent.removeEventListener("click", this._clickListener); //$NON-NLS-0$
			}
			mExplorer.Explorer.prototype.destroy.call(this);
		},
		onLinkClick: function(clickEvent) {
			this.dispatchEvent(clickEvent);
		},
		onModelCreate: function(modelEvent) {
			return this.changedItem(modelEvent.parent, true);
		},
		onModelCopy: function(modelEvent) {
			var ex = this, changedLocations = {};
			(modelEvent.items || [modelEvent]).forEach(function(item) {
				var itemParent = item.parent;
				itemParent = itemParent || ex.treeRoot;
				changedLocations[itemParent.Location] = itemParent;
			});
			return Deferred.all(Object.keys(changedLocations).map(function(loc) {
				return ex.changedItem(changedLocations[loc], true);
			}));
		},
		onModelMove: function(modelEvent) {
			var ex = this, changedLocations = {};
			(modelEvent.items || [modelEvent]).forEach(function(item) {
				var treeRoot = ex.treeRoot;
				if ((treeRoot.Location || treeRoot.ContentLocation) === item.oldValue.Location) {
					// the treeRoot was moved
					var oldRoot = ex.treeRoot;
					var newItem = item.newValue;
					var realNewItem = newItem.ChildrenLocation ? newItem : this.fileClient.read(newItem.ContentLocation, true);
					return Deferred.when(realNewItem, function(newItem) {
						ex.dispatchEvent({ type: "rootMoved", oldValue: oldRoot, newValue: newItem }); //$NON-NLS-0$
						ex.loadResourceList(newItem);
					});
				}
				var itemParent = item.oldValue.parent;
				itemParent = itemParent || ex.treeRoot;
				changedLocations[itemParent.Location] = itemParent;
				
				itemParent = item.parent;
				itemParent = itemParent || ex.treeRoot;
				changedLocations[itemParent.Location] = itemParent;
				
				// If the renamed item was an expanded directory, force an expand.
				if (item.oldValue.Directory && item.newValue && ex.isExpanded(item.oldValue)) {
					changedLocations[item.newValue.Location] = item.newValue;
				}
			});
			return Deferred.all(Object.keys(changedLocations).map(function(loc) {
				return ex.changedItem(changedLocations[loc], true);
			}));
		},
		onModelDelete: function(modelEvent) {
			var items = modelEvent.items || [modelEvent];
			var ex = this, treeRoot = ex.treeRoot;
			var newRoot;
			var treeRootDeleted = items.some(function(item) {
				if (item.oldValue.Location === (treeRoot.Location || treeRoot.ContentLocation)) {
					if (item.oldValue.Location !== (item.parent.Location || item.parent.ContentLocation)) {
						newRoot = item.parent;
					}
					return true;
				}
				return false;
			});
			if (treeRootDeleted) {
				return this.loadResourceList(newRoot);
			} else {
				// Refresh every parent folder
				var changedLocations = {};
				items.forEach(function(item) {
					var parent = item.parent;
					changedLocations[parent.Location] = parent;
				});
				return Deferred.all(Object.keys(changedLocations).map(function(loc) {
					return ex.changedItem(changedLocations[loc], true);
				}));
			}
		},
	
		/**
		 * Handles model changes. Subclasses can override these methods to control how the FileExplorer reacts to various types of model changes.
		 * The default implementations generally just refresh the affected row(s) in the explorer.
		 * @name orion.explorer.FileExplorer.ModelHandler
		 * @class Handles model changes in a FileExplorer.
		 */
		modelHandler: /** @lends orion.explorer.FileExplorer.ModelHandler.prototype */ {
			copy: function(modelEvent) {
				if (modelEvent.count) {
					return; // Handled in copyMultiple
				}
				return this.onModelCopy(modelEvent);
			},
			copyMultiple: function(modelEvent) {
				return this.onModelCopy(modelEvent);
			},
			create: function(modelEvent) {
				// refresh the node
				return this.onModelCreate(modelEvent);
			},
			"delete": function(modelEvent) { //$NON-NLS-0$
				if (modelEvent.count) {
					return; //Handled in deleteMultiple
				}
				return this.onModelDelete(modelEvent);
			},
			deleteMultiple: function(modelEvent) {
				return this.onModelDelete(modelEvent);
			},
			"import": function(modelEvent) { //$NON-NLS-0$
				var target = modelEvent.target;
				return this.changedItem(target, true);
			},
			move: function(modelEvent) {
				if (modelEvent.count) {
					return; //Handled in moveMultiple
				}
				return this.onModelMove(modelEvent);
			},
			moveMultiple: function(modelEvent) {
				return this.onModelMove(modelEvent);
			}
		},
		
		/**
		 * @param {Object} parentItem The item in the model which represents the new item's parent
		 * @param {Object} child An object describing the child that a placeholder needs to be created for.
		 * 			child.Name {String} The name of the child artifact
		 * 			child.Directory {Boolean} true if the child is a directory, false otherwise
		 * @param {String} domId The prefix to use for the Id of the newly created DOM nodes
		 * @returns A deferred that resolves with the placeholder object (see explorer.js->makeNewItemPlaceholder())
		 */
		makeNewChildItemPlaceholder: function(parentItem, child, domId) {
			var deferred = new Deferred();
			
			this.model.getChildren(parentItem, function(children){
				var item = null;
				var insertAfter = false;
				if(children) {
					children.push(child);
					children.sort(this.model.sortChildren);
					var childIndex = children.indexOf(child);
					var nextIndex = childIndex + 1;
					if (children.length > nextIndex) {
						item = children[nextIndex];
					} else {
						var previousIndex = childIndex - 1;
						//should be placed last
						if (0 <= previousIndex) {
							item = children[previousIndex];
						} else {
							item = parentItem;
						}
						
						insertAfter = true;
					}
				} else {
					//place before parentItem
					item = parentItem;
				}
				
				var placeholder = this.makeNewItemPlaceholder(item, domId, insertAfter);
				deferred.resolve(placeholder);
			}.bind(this));
			
			return deferred;
		},

		_makeDropTarget: function(item, node, persistAndReplace) {
			if (this.dragAndDrop) {
				var explorer = this;
				var performDrop = this.dragAndDrop;
				
				var dragStart = function(evt) {
					dragStartTarget = evt.target;
				};
				if (persistAndReplace) {
					if (this._oldDragStart) {
						node.removeEventListener("dragstart", this._oldDragStart, false); //$NON-NLS-0$
					}
					this._oldDragStart = dragStart;
				}
				node.addEventListener("dragstart", dragStart, false); //$NON-NLS-0$
				
				
				var dragEnd = function(evt) {
					dragStartTarget = null;
				};
				if (persistAndReplace) {
					if (this._oldDragEnd) {
						node.removeEventListener("dragend", this._oldDragEnd, false); //$NON-NLS-0$
					}
					this._oldDragEnd = dragEnd;
				}
				node.addEventListener("dragend", dragEnd, false); //$NON-NLS-0$
				
				var dragLeave = function(evt) { //$NON-NLS-0$
					node.classList.remove("dragOver"); //$NON-NLS-0$
					evt.preventDefault();
					evt.stopPropagation();
				};
				// if we are rehooking listeners on a node, unhook old before hooking and remembering new
				if (persistAndReplace) {
					if (this._oldDragLeave) {
						node.removeEventListener("dragleave", this._oldDragLeave, false); //$NON-NLS-0$
					}
					this._oldDragLeave = dragLeave;
				}
				node.addEventListener("dragleave", dragLeave, false); //$NON-NLS-0$
	
				var dragEnter = function (evt) {
					if (dragStartTarget) {
						var copy = util.isMac ? evt.altKey : evt.ctrlKey;
						dropEffect = evt.dataTransfer.dropEffect = copy ? "copy" : "move"; //$NON-NLS-1$ //$NON-NLS-0$
					} else {
						/* accessing dataTransfer.effectAllowed here throws an error on IE */
						if (!util.isIE && (evt.dataTransfer.effectAllowed === "all" ||   //$NON-NLS-0$
							evt.dataTransfer.effectAllowed === "uninitialized" ||  //$NON-NLS-0$
							evt.dataTransfer.effectAllowed.indexOf("copy") >= 0)) {   //$NON-NLS-0$
								evt.dataTransfer.dropEffect = "copy";  //$NON-NLS-0$
						}
					}
					node.classList.add("dragOver"); //$NON-NLS-0$
					lib.stop(evt);
				};
				if (persistAndReplace) {
					if (this._oldDragEnter) {
						node.removeEventListener("dragenter", this._oldDragEnter, false); //$NON-NLS-0$
					}
					this._oldDragEnter = dragEnter;
				}
				node.addEventListener("dragenter", dragEnter, false); //$NON-NLS-0$
	
				// this listener is the same for any time, so we don't need to remove/rehook.
				var dragOver = function (evt) {
					if (dragStartTarget) {
						var copy = util.isMac ? evt.altKey : evt.ctrlKey;
						dropEffect = evt.dataTransfer.dropEffect = copy ? "copy" : "move"; //$NON-NLS-1$ //$NON-NLS-0$
					} else {
						// default behavior is to not trigger a drop, so we override the default
						// behavior in order to enable drop.  
						// we have to specify "copy" again here, even though we did in dragEnter
						/* accessing dataTransfer.effectAllowed here throws an error on IE */
						if (!util.isIE && (evt.dataTransfer.effectAllowed === "all" ||   //$NON-NLS-0$
							evt.dataTransfer.effectAllowed === "uninitialized" ||  //$NON-NLS-0$
							evt.dataTransfer.effectAllowed.indexOf("copy") >= 0)) {   //$NON-NLS-0$
								evt.dataTransfer.dropEffect = "copy";  //$NON-NLS-0$
						}   
					}
					lib.stop(evt);
				};
				if (persistAndReplace && !this._oldDragOver) {
					node.addEventListener("dragover", dragOver, false); //$NON-NLS-0$
					this._oldDragOver = dragOver;
				}
	
				var progress = null;
				var statusService = null;
				if(explorer.registry) {
					progress = explorer.registry.getService("orion.page.progress"); //$NON-NLS-0$
					statusService = explorer.registry.getService("orion.page.message");	 //$NON-NLS-0$
				}
				
				var errorHandler = function(error) {
					if (statusService) {
						var errorMessage = null;
						if ("string" === typeof error) {
							errorMessage = {Severity: "Error", Message: error};
						} else if (error && error.error) {
							errorMessage = {Severity: "Error", Message: error.error};
						} else {
							errorMessage = error;	
						}
						statusService.setProgressResult(errorMessage);	//$NON-NLS-0$ 
					} else {
						window.console.log(error);
					}
				};
						
				function dropFileEntry(entry, target, explorer, performDrop, fileClient, deferredWrapper) {
					var preventNotification = false;
					var targetIsRoot = mFileUtils.isAtRoot(target.Location);
					if (deferredWrapper) {
						//this is a recursive call
						preventNotification = true;
						deferredWrapper.entryCount++;
					} else if (entry.isDirectory) {
						preventNotification = true; //we don't want to notify for each item that is in the folder
						deferredWrapper = {entryCount: 1, deferred: new Deferred()};
						explorer._makeUploadNode(target, entry.name, true).then(function(uploadNodeContainer){
							var cleanup = function() {
								uploadNodeContainer.destroyFunction();
								if (targetIsRoot) {
									explorer.loadResourceList(explorer.treeRoot.Path, true);	
								} else {
									explorer.changedItem(target);	
								}						
							};
							deferredWrapper.deferred.then(cleanup, cleanup);
						});
					}
					
					var decrementEntryCount = function() {
						if (deferredWrapper) {
							deferredWrapper.entryCount--;
							if (0 === deferredWrapper.entryCount) {
								deferredWrapper.deferred.resolve();
							}
						}
					};
					
					var internalErrorHandler = function(error) {
						if (statusService) {
							var errorMessage = null;
							if ("string" === typeof error) {
								errorMessage = {Severity: "Error", Message: error};
							} else if (error && error.error) {
								errorMessage = {Severity: "Error", Message: error.error};
							} else {
								errorMessage = error;	
							}
							statusService.setProgressResult(errorMessage);	//$NON-NLS-0$ 
						} else {
							window.console.log(error);
						}
						decrementEntryCount();
					};
					
					if (!target.Location && target.fileMetadata) {
						target = target.fileMetadata;
					}
					if (entry.isFile) {
						// can't drop files directly into workspace.
						if (targetIsRoot){ //$NON-NLS-0$
							internalErrorHandler(messages["You cannot copy files directly into the workspace.  Create a folder first."]); //$NON-NLS-0$
						} else {
							entry.file(function(file) {
								if (deferredWrapper) {
									// this is part of a folder upload, upload the file directly without showing 
									// progress for it but call decrementEntryCount when the upload finishes
									var unzip = file.name.indexOf(".zip") === file.name.length-4 && window.confirm(i18nUtil.formatMessage(messages["Unzip ${0}?"], file.name)); //$NON-NLS-1$ //$NON-NLS-0$
									var handlers = {
										error: function(event) {
											var errorMessage = messages["Uploading the following file failed: "] + file.name;
											if (statusService) {
												statusService.setProgressResult({Severity: "Error", Message: errorMessage}); //$NON-NLS-0$ 
											} else {
												window.console.log(errorMessage);
											}
										},
										loadend: decrementEntryCount
									};
									performDrop(target, file, explorer, unzip, false, handlers, true);
								} else {
									explorer._uploadFile(item, file, true);
								}
							}.bind(this));
						}
					} else if (entry.isDirectory) {
						var dirReader = entry.createReader();
						var traverseChildren = function(folder) {
							dirReader.readEntries(function(entries) {
								for (var i=0; i<entries.length; i++) {
									dropFileEntry(entries[i], folder, explorer, performDrop, fileClient, deferredWrapper); //$NON-NLS-0$
								}
								decrementEntryCount();
							});
						};
						
						if (targetIsRoot){ //$NON-NLS-0$
							(progress ? progress.progress(fileClient.createProject(target.ChildrenLocation, entry.name), i18nUtil.formatMessage(messages["Creating ${0}"], entry.name)) : 
										fileClient.createProject(target.ChildrenLocation, entry.name)).then(function(project) {
								(progress ? progress.progress(fileClient.read(project.ContentLocation, true), messages["Loading "] + project.name) :
											fileClient.read(project.ContentLocation, true)).then(function(folder) {
										traverseChildren(folder);
									}, internalErrorHandler);
							}, internalErrorHandler);
						} else {
							(progress ? progress.progress(fileClient.createFolder(target.Location, entry.name), i18nUtil.formatMessage(messages["Creating ${0}"], entry.name)) :
										fileClient.createFolder(target.Location, entry.name)).then(function(subFolder) {
								var dispatcher = explorer.modelEventDispatcher;
								if (!preventNotification) {
									dispatcher.dispatchEvent({ type: "create", parent: item, newValue: subFolder }); //$NON-NLS-0$	
								}
								traverseChildren(subFolder);
							}, internalErrorHandler);
						}
					}
				}
				
				var drop = function(evt) {
					var i, deferred;
					node.classList.remove("dragOver"); //$NON-NLS-0$
					
					if (dragStartTarget) {
						var fileClient = explorer.fileClient;
						var tmp = dragStartTarget;
						var source;
						while (tmp) {
							if (tmp._item) {
								source = tmp._item;
								break;
							}
							tmp = tmp.parentNode;
						}
						if (!source) {
							return;
						}
						
						var isCopy = dropEffect === "copy"; //$NON-NLS-0$
						var func = isCopy ? fileClient.copyFile : fileClient.moveFile;
						deferred = func.apply(fileClient, [source.Location, item.Location, source.Name]);
						if (progress) {
							var message = i18nUtil.formatMessage(messages[isCopy ? "Copying ${0}" : "Moving ${0}"], source.Location); //$NON-NLS-1$ //$NON-NLS-0$
							deferred = progress.showWhile(deferred, message);
						}
						deferred.then(function(result) {
							var dispatcher = explorer.modelEventDispatcher;
							dispatcher.dispatchEvent({type: isCopy ? "copy" : "move", oldValue: source, newValue: result, parent: item}); //$NON-NLS-1$ //$NON-NLS-0$
						}, errorHandler);
						
					// webkit supports testing for and traversing directories
					// http://wiki.whatwg.org/wiki/DragAndDropEntries
					} else if (evt.dataTransfer.items && evt.dataTransfer.items.length > 0) {
						for (i=0; i<evt.dataTransfer.items.length; i++) {
							var entry = null;
							if (typeof evt.dataTransfer.items[i].getAsEntry === "function") { //$NON-NLS-0$
								entry = evt.dataTransfer.items[i].getAsEntry();
							} else if (typeof evt.dataTransfer.items[i].webkitGetAsEntry === "function") { //$NON-NLS-0$
								entry = evt.dataTransfer.items[i].webkitGetAsEntry();
							}
							if (entry) {
								dropFileEntry(entry, item, explorer, performDrop, explorer.fileClient);
							}
						}
					} else if (evt.dataTransfer.files && evt.dataTransfer.files.length > 0) {
						for (i=0; i<evt.dataTransfer.files.length; i++) {
							var file = evt.dataTransfer.files[i];
							// this test is reverse engineered as a way to figure out when a file entry is a directory.
							// The File API in HTML5 doesn't specify a way to check explicitly (when this code was written).
							// see http://www.w3.org/TR/FileAPI/#file
							if (!file.size && !file.type) {
								errorHandler(i18nUtil.formatMessage(messages["Did not drop ${0}.  Folder drop is not supported in this browser."], file.name)); //$NON-NLS-0$
							} else if (mFileUtils.isAtRoot(item.Location)){ //$NON-NLS-0$
								errorHandler(messages["You cannot copy files directly into the workspace.  Create a folder first."]); //$NON-NLS-0$ 
							} else {
								explorer._uploadFile(item, file, true);
							}
						}
					}
					lib.stop(evt);
				};
					
				if (persistAndReplace) {
					if (this._oldDrop) {
						node.removeEventListener("drop", this._oldDrop, false); //$NON-NLS-0$
					}
					this._oldDrop = drop;
				}
				node.addEventListener("drop", drop, false); //$NON-NLS-0$
			}
		},
		
		/**
		 * Uploads the specified file as a child of the specified parent item.
		 * 
		 * @param {Object} parentItem The item in the model representing the folder under which the file should be placed.
	 	 * @param {File} file The file to upload.
	 	 * @param {Boolean} expandParent Specifies whether or not the parentItem should be expanded after the upload completes.
		 */
		_uploadFile: function(parentItem, file, expandParent) {
			var targetItem = parentItem;
			if (!targetItem.Location && targetItem.fileMetadata) {
				targetItem = targetItem.fileMetadata;
			}
			var uploadImpl = function() {
				this._makeUploadNode(targetItem, file.name, false).then(function(uploadNodeContainer){
					var refNode = uploadNodeContainer.refNode; //td
					var progressBar = uploadNodeContainer.progressBar;
					var cancelButton = uploadNodeContainer.cancelButton;
					var destroy = uploadNodeContainer.destroyFunction;
					
					var statusService = this.registry.getService("orion.page.message");	 //$NON-NLS-0$
					
					var handlers = {
						progress: function(event) {
							var percentageText = ""; //$NON-NLS-0$
							if (event.lengthComputable) {
								percentageText = Math.floor((event.loaded / event.total) * 100) + "%"; //$NON-NLS-0$
								progressBar.style.width = percentageText;
								var loadedKB = Math.round(event.loaded / 1024);
								var totalKB = Math.round(event.total / 1024);
								progressBar.title = messages["Upload progress: "] + percentageText + ",  " + loadedKB + "/" + totalKB + " KB"; //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
								refNode.title = progressBar.title;
							}
						},
						loadend: function(event) {
							destroy();
							this.changedItem(targetItem);
						}.bind(this),
						error: function(event) {
							var errorMessage = messages["Uploading the following file failed: "] + file.name; //$NON-NLS-0$
							if (statusService) {
								statusService.setProgressResult({Severity: "Error", Message: errorMessage});	//$NON-NLS-0$ 
							} else {
								window.console.log(errorMessage);
							}
						}
					};
						
					var unzip = file.name.indexOf(".zip") === file.name.length-4 && window.confirm(i18nUtil.formatMessage(messages["Unzip ${0}?"], file.name)); //$NON-NLS-1$ //$NON-NLS-0$
					var req = this.dragAndDrop(targetItem, file, this, unzip, false, handlers, true); 
					
					cancelButton.addEventListener("click", function(){ //$NON-NLS-0$
						req.abort();
					});
				}.bind(this));
			}.bind(this);
			
			if (expandParent) {
				this.expandItem(targetItem, false).then(uploadImpl);
			} else {
				uploadImpl();
			}
			
		},
		
		/**
		 * Creates a placeholder node in the navigator which shows the name
		 * of the artifact being uploaded as well as progress and an option
		 * to cancel the upload (if possible).
		 * 
		 * @param {Object} parentItem The item in the model representing the folder under which the file should be placed. Workspace root is supported.
	 	 * @param {String} artifactName The name of the file or folder being uploaded.
	 	 * @param {Boolean} isDirectory true if the artifact is a directory, false otherwise
	 	 * 
	 	 * @returns {orion.Deferred.promise} which resolves with the following object:
	 	 * 		 {Object} nodeContainer An object containing the following:
	 	 * 			nodeContainer.refNode The td of the newly created DOM node
	 	 * 			nodeContainer.tempNode The tr of the newly created DOM node (this is the top-level DOM node)
	 	 * 			nodeContainer.progressBar The DOM node containing the progress bar
	 	 * 			nodeContainer.cancelButton The DOM node of the upload cancel button (only if isFile === true)
	 	 * 			nodeContainer.destroyFunction The function that should be called to remove the temporary upload node from the navigator
		 */
		_makeUploadNode: function(parentItem, artifactName, isDirectory) {
			
			var domId = this.getRow(parentItem).id;
			var child = {Name: artifactName, Directory: isDirectory};
			
			return this.makeNewChildItemPlaceholder(parentItem, child, domId).then(function(placeholder) {
				var nodeContainer = null;
				var refNode, wrapperNode;
				if (placeholder) {
					nodeContainer = {};
					refNode = placeholder.refNode;
					wrapperNode = placeholder.wrapperNode;
					refNode.classList.add("uploadNode"); //$NON-NLS-0$
					
					nodeContainer.refNode = refNode;
					nodeContainer.wrapperNode = wrapperNode;
					
					var textSpan = document.createElement("span");
					textSpan.classList.add("uploadNodeText");
					textSpan.appendChild(document.createTextNode(artifactName));
					refNode.appendChild(textSpan); //$NON-NLS-0$
					
					var progressBarWrapper = document.createElement("span"); //$NON-NLS-0$
					progressBarWrapper.classList.add("progressBarWrapper"); //$NON-NLS-0$
					
					var progressBar = document.createElement("span"); //$NON-NLS-0$
					progressBar.classList.add("progressBar"); //$NON-NLS-0$
					progressBarWrapper.appendChild(progressBar);
					refNode.appendChild(progressBarWrapper);
					
					nodeContainer.progressBar = progressBar;
					
					if (isDirectory) {
						progressBar.classList.add("continuous"); //$NON-NLS-0$
						refNode.title = messages["Uploading "] + artifactName; //$NON-NLS-0$
						progressBar.title = refNode.title; //$NON-NLS-0$
					} else {
						var cancelButton = document.createElement("button"); //$NON-NLS-0$
						cancelButton.classList.add("imageSprite"); //$NON-NLS-0$
						cancelButton.classList.add("core-sprite-delete"); //$NON-NLS-0$
						cancelButton.classList.add("uploadCancelButton"); //$NON-NLS-0$
						cancelButton.title = messages["Cancel upload"]; //$NON-NLS-0$
						refNode.appendChild(cancelButton);
						
						nodeContainer.cancelButton = cancelButton;
					}
					
					nodeContainer.destroyFunction = placeholder.destroyFunction;
				}
				
				return nodeContainer;
			}.bind(this));
		},

		/**
		 * @name orion.explorer.FileExplorer#createModel
		 * @function Creates the explorer model.
		 * @returns {orion.explorer.FileModel}
		 */
		createModel: function() {
			return new FileModel(this.registry, this.treeRoot, this.fileClient, this.parentId, this.excludeFiles, this.excludeFolders);
		},

		/**
		 * @name orion.explorer.FileExplorer#changedItem
		 * @function
		 * we have changed an item on the server at the specified parent node
		 * @param {Object} parent The parent item under which the change occurred.
		 * @param {Boolean} forceExpand
		 * @returns {orion.Promise}
		 */
		changedItem: function(parent, forceExpand) {
			if (this.treeRoot && this.treeRoot.Location === parent.Location) {
				return this.loadResourceList(this.treeRoot, forceExpand);
			}
			var that = this;
			var deferred = new Deferred();
			parent.children = parent.Children = null;
			this.model.getChildren(parent, function(children) {
				//If a key board navigator is hooked up, we need to sync up the model
				if(that.getNavHandler()){
					//that._initSelModel();
				}
				that.myTree.refresh.bind(that.myTree)(parent, children, forceExpand);
				deferred.resolve(children);
			});
			return deferred;
		},

		/**
		 * Shows the given item.
		 * @param {Object} The item to be shown.
		 * @param {Booelan} [reroot=true] whether the receiver should re-root the tree if the item is not displayable.
		 * @returns {orion.Promise}
		 */
		showItem: function(item, reroot) {
			var deferred = new Deferred();
			if (!item || !this.model || !this.myTree || (!this.myTree.showRoot && item.Location === this.treeRoot.Location)) {
				return deferred.reject();
			}
			var row = this.getRow(item);
			if (row) {
				deferred.resolve(row._item || item);
			} else if (item.Parents && item.Parents.length>0) {
				item.Parents[0].Parents = item.Parents.slice(1);
				return this.expandItem(item.Parents[0], reroot).then(function(parent) {
					// Handles model out of sync
					var row = this.getRow(item);
					if (!row) {
						return this.changedItem(parent, true).then(function() {
							var row = this.getRow(item);
							return row ? row._item : new Deferred().reject();
						}.bind(this));
					}
					return row._item || item;
				}.bind(this));
			} else {
				// Handles file not in current tree
				if (reroot === undefined || reroot) {
					return this.reroot(item);
				}
				return deferred.reject();
			}
			return deferred;
		},

		/**
		 * Expands the given item.
		 * @param {Object} The item to be expanded.
		 * @param {Booelan} [reroot=true] whether the receiver should re-root the tree if the item is not displayable.
		 * @returns {orion.Promise}
		 */
		expandItem: function(item, reroot) {
			var deferred = new Deferred();
			this.showItem(item, reroot).then(function(result) {
				if (this.myTree.isExpanded(result)) {
					deferred.resolve(result);
				} else {
					this.myTree.expand(this.model.getId(result), function() {
						deferred.resolve(result);
					});
				}
			}.bind(this), deferred.reject);
			return deferred;
		},

		/**
		 * Shows and selects the given item.
		 * @param {Object} The item to be revealed.
		 * @param {Booelan} [reroot=true] whether the receiver should re-root the tree if the item is not displayable.
		 * @returns {orion.Promise}
		 */
		reveal: function(item, reroot) {
			return this.showItem(item, reroot).then(function(result) {
				var navHandler = this.getNavHandler();
				if (navHandler) {
					navHandler.cursorOn(result, true);
					navHandler.setSelection(result);
				}
				return result;
			}.bind(this));
		},

		/**
		 * Re-roots the tree so that the given item is displayable.
		 * @param {Object} The item to be expanded.
		 * @returns {orion.Promise}
		 */
		reroot: function(item) {
			// Do nothing by default
			return new Deferred().reject();
		},

		/**
		 * Returns whether the given item is expanded.
		 * @param {Object} The item to be expanded.
		 * @returns {orion.Promise}
		 */
		isExpanded: function(item) {
			var rowId = this.model.getId(item);
			return this.renderer.tableTree.isExpanded(rowId);
		},

		/**
		 * Returns the node that a rename text input box should appear over top of.
		 * @name orion.explorer.FileExplorer#getNameNode
		 * @function
		 * @param {Object} item Item being renamed
		 * @returns {Element}
		 */
		getNameNode: function(item) {
			var rowId = this.model.getId(item);
			if (rowId) {
				// I know this from my renderer below.
				// TODO This approach fails utterly for a custom renderer, better hope they override this method.
				return lib.node(rowId+"NameLink"); //$NON-NLS-0$
			}
		},

		/**
		 * The explorerNavHandler hooked up by the explorer will call this function when left arrow key is pressed on a 
		 * top level item that is aleady collapsed. The default implementation does nothing.
		 * @name orion.explorer.FileExplorer#scopeUp
		 * @function
		 */
		scopeUp: function() {
		},

		/**
		 * The explorerNavHandler hooked up by the explorer will call this function when the focus into command is clicked.
		 * The default implementation does nothing.
		 * @name orion.explorer.FileExplorer#scopeDown
		 * @function
		 */
		scopeDown: function(item) {
		},

		/**
		 * Load the resource at the given path.
		 * @name orion.explorer.FileExplorer#loadResourceList
		 * @function
		 * @param {String|Object} path The path of the resource to load, or an object with a ChildrenLocation or ContentLocation field giving the path.
		 * @param {Boolean} [force] If true, force reload even if the path is unchanged. Useful
		 * when the client knows the resource underlying the current path has changed.
		 * @param {Function} postLoad a function to call after loading the resource. <b>Deprecated</b>: use the returned promise instead.
		 * @returns {orion.Promise}
		 */
		loadResourceList: function(path, force, postLoad) {
			if (path && typeof path === "object") { //$NON-NLS-0$
				path = path.ChildrenLocation || path.ContentLocation;
			}
			path = mFileUtils.makeRelative(path);
			if (!force && path === this._lastPath) {
				return new Deferred().resolve(this.treeRoot);
			}			
			this._lastPath = path;
			var self = this;
			if (force || (path !== this.treeRoot.Path)) {
				return this.load(this.fileClient.loadWorkspace(path), messages["Loading "] + path).then(function(p) {
					self.treeRoot.Path = path;
					if (typeof postLoad === "function") { //$NON-NLS-0$
						postLoad();
					}
					self.dispatchEvent({ type: "rootChanged", root: self.treeRoot }); //$NON-NLS-0$
					return new Deferred().resolve(self.treeRoot);
				}, function(err) {
					self.treeRoot.Path = null;
					self.dispatchEvent({ type: "rootChanged", root: self.treeRoot }); //$NON-NLS-0$
					return new Deferred().reject(err);
				});
			}
			return new Deferred().resolve(self.treeRoot);
		},

		/**
		 * Load the explorer with the given root
		 * @name orion.explorer.FileExplorer#load
		 * @function
		 * @param {Object} root a root object or a deferred that will return the root of the FileModel
		 * @param {String} progress a string progress message describing the fetch of the root
		 * @returns {orion.Promise} A promise that resolves to the loaded <code>treeRoot</code>, or rejects with an error.
		 */
		load: function(root, progressMessage, postLoad) {
			var parent = lib.node(this.parentId);			
	
			// Progress indicator
			var progress = lib.node("progress");  //$NON-NLS-0$
			if(!progress){
				progress = document.createElement("div"); //$NON-NLS-0$
				progress.id = "progress"; //$NON-NLS-0$
				lib.empty(parent);
				parent.appendChild(progress);
			}
			lib.empty(progress);
			
			var progressTimeout = setTimeout(function() {
				lib.empty(progress);
				progress.appendChild(document.createTextNode(progressMessage));
			}, 500); // wait 500ms before displaying
						
			var self = this;
			return Deferred.when(root,
				function(root) {
					clearTimeout(progressTimeout);
					if (self.destroyed) { return; }
					self.treeRoot = {};
					// copy properties from root json to our object
					for (var property in root) {
						self.treeRoot[property] = root[property];
					}
					self.model = self.createModel();
					if (self.dragAndDrop) {
						if (self._hookedDrag) {
							// rehook on the parent to indicate the new root location
							self._makeDropTarget(self.treeRoot, parent, true);
						} else {
							// uses two different techniques from Modernizr
							// first ascertain that drag and drop in general is supported
							var supportsDragAndDrop = parent && (('draggable' in parent) || ('ondragstart' in parent && 'ondrop' in parent));  //$NON-NLS-2$  //$NON-NLS-1$  //$NON-NLS-0$ 
							// then check that file transfer is actually supported, since this is what we will be doing.
							// For example IE9 has drag and drop but not file transfer
							supportsDragAndDrop = supportsDragAndDrop && !!(window.File && window.FileList && window.FileReader);
							self._hookedDrag = true;
							if (supportsDragAndDrop) {
								self._makeDropTarget(self.treeRoot, parent, true);
							} else {
								self.dragAndDrop = null;
								window.console.log("Local file drag and drop is not supported in this browser."); //$NON-NLS-0$
							}
						}
					}
					
					var deferred = new Deferred();
					self.createTree(self.parentId, self.model, {
						onComplete: function(tree) {
							deferred.resolve(tree);
						},
						navHandlerFactory: self.navHandlerFactory,
						setFocus: (typeof self.setFocus === "undefined" ? true : self.setFocus), //$NON-NLS-0$
						selectionPolicy: self.renderer.selectionPolicy, 
						onCollapse: function(model) {
							if(self.getNavHandler()){
								self.getNavHandler().onCollapse(model);
							}
						}
					});
					
					return deferred.then(function() {
						if (typeof postLoad === "function") { //$NON-NLS-0$
							try {
								postLoad();
							} catch(e){
								if (self.registry) {
									self.registry.getService("orion.page.message").setErrorMessage(e);	 //$NON-NLS-0$
								}
							}
						}				
						if (typeof self.onchange === "function") { //$NON-NLS-0$
							self.onchange(self.treeRoot);
						}
						return self.treeRoot;
					});
				},
				function(error) {
					clearTimeout(progressTimeout);
					// Show an error message when a problem happens during getting the workspace
					if (self.registry) {
						self.registry.getService("orion.page.message").setProgressResult(error); //$NON-NLS-0$
					}
					return new Deferred().reject(error);
				}
			);
		},
		/**
		 * Called when the root item changes. This can be overridden.
		 * @name orion.explorer.FileExplorer#onchange
		 * @function
		 * @param {Object} item
		 */
		onchange: function(item) {
		}
	});
	FileExplorer.prototype.constructor = FileExplorer;

	//return module exports
	return {
		FileExplorer: FileExplorer,
		FileModel: FileModel
	};
});

/**
 * marked - a markdown parser
 * Copyright (c) 2011-2013, 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 *(=|-){3,} *\n*/,
  blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
  list: /^( *)(bull) [\s\S]+?(?:hr|\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+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
  ();

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+(?!:/|@)\\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') + '|')
  ();

/**
 * 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) {
  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],
        style: 'atx' // added by Orion
      });
      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],
        style: 'setext' // added by Orion
      });
      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);

      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[item.length-1] === '\n';
          if (!loose) loose = next;
        }

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

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

        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',
        text: cap[0]
      });
      continue;
    }

    // def
    if (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]
      };
      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][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*/;

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;

  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
 */

function sanitizeLink(link) {
	try {
		var href = link.replace(/&([#\w]+);/g, function(_, n) {
			n = n.toLowerCase();
			if (n === 'colon') return ':';
			if (n.charAt(0) === '#') {
				var charCode = n.charAt(1) === 'x' ? parseInt(n.substring(2), 16) : parseInt(n.substring(1),10);
				return String.fromCharCode(charCode);
			}
			return '';
		});
		var protocol = decodeURIComponent(href).replace(/[^\w:]/g, '').toLowerCase();
		if (protocol.indexOf('javascript:') === 0) {
			return '';
		}
	} catch (e) {
		return '';
	}
	return link;
}


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][6] === ':'
          ? this.mangle(cap[1].substring(7))
          : this.mangle(cap[1]);
        href = this.mangle('mailto:') + text;
      } else {
        text = escape(cap[1]);
        href = text;
      }
      out += '<a href="'
        + href
        + '">'
        + text
        + '</a>';
      continue;
    }

    // url (gfm)
    if (cap = this.rules.url.exec(src)) {
      src = src.substring(cap[0].length);
      text = escape(cap[1]);
      href = text;
      out += '<a href="'
        + this.options.sanitize ? sanitizeLink(href) : href
        + '">'
        + text
        + '</a>';
      continue;
    }

    // tag
    if (cap = this.rules.tag.exec(src)) {
      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);
      out += this.outputLink(cap, {
        href: this.options.sanitize ? sanitizeLink(cap[2]) : cap[2],
        title: cap[3]
      });
      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][0];
        src = cap[0].substring(1) + src;
        continue;
      }
      if (this.options.sanitize) {
      	link.href = sanitizeLink(link.href);
      }
      out += this.outputLink(cap, link);
      continue;
    }

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

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

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

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

    // del (gfm)
    if (cap = this.rules.del.exec(src)) {
      src = src.substring(cap[0].length);
      out += '<del>'
        + this.output(cap[1])
        + '</del>';
      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) {
  if (cap[0][0] !== '!') {
    return '<a href="'
      + escape(link.href)
      + '"'
      + (link.title
      ? ' title="'
      + escape(link.title)
      + '"'
      : '')
      + '>'
      + this.output(cap[1])
      + '</a>';
  } else {
    return '<img src="'
      + escape(link.href)
      + '" alt="'
      + escape(cap[1])
      + '"'
      + (link.title
      ? ' title="'
      + escape(link.title)
      + '"'
      : '')
      + '>';
  }
};

/**
 * Smartypants Transformations
 */

InlineLexer.prototype.smartypants = function(text) {
  if (!this.options.smartypants) return text;
  return text
    .replace(/--/g, '\u2014')
    .replace(/'([^']*)'/g, '\u2018$1\u2019')
    .replace(/"([^"]*)"/g, '\u201C$1\u201D')
    .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;
};

/**
 * Parsing & Compiling
 */

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

/**
 * Static Parse Method
 */

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

/**
 * Parse Loop
 */

Parser.prototype.parse = function(src) {
  this.inline = new InlineLexer(src.links, this.options);
  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 '';
    }
    case 'hr': {
      return '<hr>\n';
    }
    case 'heading': {
      return '<h'
        + this.token.depth
        + '>'
        + this.inline.output(this.token.text)
        + '</h'
        + this.token.depth
        + '>\n';
    }
    case 'code': {
      if (this.options.highlight) {
        var code = this.options.highlight(this.token.text, this.token.lang);
        if (code != null && code !== this.token.text) {
          this.token.escaped = true;
          this.token.text = code;
        }
      }

      if (!this.token.escaped) {
        this.token.text = escape(this.token.text, true);
      }

      return '<pre><code'
        + (this.token.lang
        ? ' class="'
        + this.options.langPrefix
        + escape(this.token.lang)
        + '"'
        : '')
        + '>'
        + this.token.text
        + '</code></pre>\n';
    }
    case 'table': {
      var body = ''
        , heading
        , i
        , row
        , cell
        , j;

      // header
      body += '<thead>\n<tr>\n';
      for (i = 0; i < this.token.header.length; i++) {
        heading = this.inline.output(this.token.header[i]);
        body += this.token.align[i]
          ? '<th align="' + this.token.align[i] + '">' + heading + '</th>\n'
          : '<th>' + heading + '</th>\n';
      }
      body += '</tr>\n</thead>\n';

      // body
      body += '<tbody>\n'
      for (i = 0; i < this.token.cells.length; i++) {
        row = this.token.cells[i];
        body += '<tr>\n';
        for (j = 0; j < row.length; j++) {
          cell = this.inline.output(row[j]);
          body += this.token.align[j]
            ? '<td align="' + this.token.align[j] + '">' + cell + '</td>\n'
            : '<td>' + cell + '</td>\n';
        }
        body += '</tr>\n';
      }
      body += '</tbody>\n';

      return '<table>\n'
        + body
        + '</table>\n';
    }
    case 'blockquote_start': {
      var body = '';

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

      return '<blockquote>\n'
        + body
        + '</blockquote>\n';
    }
    case 'list_start': {
      var type = this.token.ordered ? 'ol' : 'ul'
        , body = '';

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

      return '<'
        + type
        + '>\n'
        + body
        + '</'
        + type
        + '>\n';
    }
    case 'list_item_start': {
      var body = '';

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

      return '<li>'
        + body
        + '</li>\n';
    }
    case 'loose_item_start': {
      var body = '';

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

      return '<li>'
        + body
        + '</li>\n';
    }
    case 'html': {
      return !this.token.pre && !this.options.pedantic
        ? this.inline.output(this.token.text)
        : this.token.text;
    }
    case 'paragraph': {
      return '<p>'
        + this.inline.output(this.token.text)
        + '</p>\n';
    }
    case 'text': {
      return '<p>'
        + this.parseText()
        + '</p>\n';
    }
  }
};

/**
 * 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 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;
    }

    if (opt) 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(hi) {
      var out, err;

      if (hi !== true) {
        delete opt.highlight;
      }

      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(true);
    }

    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
};

/**
 * Expose
 */

marked.Parser = Parser;
marked.parser = Parser.parse;

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) 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
 ******************************************************************************/

/*global define */
define('orion/editor/i18n',{
	load: function(name, parentRequire, onLoad, config) {
		if (parentRequire.specified && parentRequire.specified("orion/bootstrap")) { //$NON-NLS-0$
			parentRequire(["orion/i18n!" + name], function(languages) { //$NON-NLS-0$
				onLoad(languages);
			});
		} else {
			parentRequire(["orion/editor/config"], function(config) { //$NON-NLS-0$
				onLoad((config && config.languages) || {});
			});
		}
	}
});

/*******************************************************************************
 * @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
 *******************************************************************************/
/*global define */

define("orion/editor/config", [ //$NON-NLS-0$
], function() {
	return {
		languages: {}
	};
});

/*******************************************************************************
 * @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: 
 *		Felipe Heidrich (IBM Corporation) - initial API and implementation
 *		Silenio Quarti (IBM Corporation) - initial API and implementation
 ******************************************************************************/

//NLS_CHARSET=UTF-8

/*global define*/

define('orion/editor/nls/root/messages',{
	"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$
	"matchingSearch": "Matching Search", //$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$
	
	"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$
	"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 entered", //$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$
	"lineColumn": "Line ${0} : Col ${1}", //$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": "Toogle 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) 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
 ******************************************************************************/

/*global define*/

define('orion/editor/nls/messages',['orion/editor/i18n!orion/editor/nls/messages', 'orion/editor/nls/root/messages'], function(bundle, root) {
	var result = {
		root: root
	};
	for (var key in bundle) {
		if (bundle.hasOwnProperty(key)) {
			if (typeof result[key] === 'undefined') {
				result[key] = bundle[key];
			}
		}
	}
	return result;
});

/*******************************************************************************
 * 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
 ******************************************************************************/
 
/*global define */
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
 *******************************************************************************/
/*global define */
/*jslint browser:true regexp:false*/
/**
 * @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) 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
 ******************************************************************************/
 
/*global define*/

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, 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
 ******************************************************************************/

/*global define */

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) 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
 *******************************************************************************/
 
/*global define window */

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.editor.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.editor.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: "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 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
 *******************************************************************************/
 
/*globals define*/

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);
		},
		/**
		 * @private
		 */
		buildStyleSheet: function(themeClass, settings) {
			
			var result = [];
			result.push("");
			
			result.push("." + themeClass + " {"); //$NON-NLS-1$ //$NON-NLS-0$
			if (settings.fontFamily) {
				result.push("\tfont-family: " + settings.fontFamily + ";"); //$NON-NLS-1$ //$NON-NLS-0$
			}
			if (settings.fontSize) {
				result.push("\tfont-size: " + settings.fontSize + ";"); //$NON-NLS-1$ //$NON-NLS-0$
			}
			if (settings.fontSize) {			
				result.push("\tcolor: " + settings.text + ";"); //$NON-NLS-1$ //$NON-NLS-0$
			}
			result.push("}"); //$NON-NLS-0$
			
			//From textview.css
			result.push("." + themeClass + ".textview {"); //$NON-NLS-1$ //$NON-NLS-0$
			if (settings.background) {		
				result.push("\tbackground-color: " + settings.background + ";"); //$NON-NLS-1$ //$NON-NLS-0$
			}
			
			result.push("}"); //$NON-NLS-0$
			
			result.push("." + themeClass + " .textviewLeftRuler {"); //$NON-NLS-1$ //$NON-NLS-0$
			
			if(settings.leftRuler){
				result.push("\tborder-right: 1px solid " + settings.leftRuler + ";"); //$NON-NLS-1$ //$NON-NLS-0$
			}
			
			result.push("}"); //$NON-NLS-0$
			
			result.push("." + themeClass + " .textviewRightRuler {"); //$NON-NLS-1$ //$NON-NLS-0$
			
			if(settings.rightRuler){
				result.push("\tborder-left: 1px solid " + settings.rightRuler + ";"); //$NON-NLS-1$ //$NON-NLS-0$
			}
			
			result.push("}"); //$NON-NLS-0$
			
			
			function defineRule(className, value, isBackground) {
				if (value) {
					result.push("." + themeClass + " ." + className + " {"); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
					result.push("\t" + (isBackground ? "background-color" : "color") + ": " + value + ";"); //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
					result.push("}"); //$NON-NLS-0$
				}
			}
			
			//From rulers.css
			defineRule("ruler.annotations", settings.annotationRuler, true); //$NON-NLS-0$
			defineRule("ruler.lines", settings.annotationRuler, true); //$NON-NLS-0$
			defineRule("ruler.folding", settings.annotationRuler, true); //$NON-NLS-0$
			defineRule("ruler.overview", settings.overviewRuler, true); //$NON-NLS-0$
			defineRule("rulerLines", settings.lineNumber, false); //$NON-NLS-0$
			defineRule("rulerLines.even", settings.lineNumberEven, false); //$NON-NLS-0$
			defineRule("rulerLines.odd", settings.lineNumberOdd, false); //$NON-NLS-0$
			
			//From annotations.css
			defineRule("annotationLine.currentLine", settings.currentLine, true); //$NON-NLS-0$
			
			//From textstyler.css
			defineRule("entity-name-tag", settings.keyword, false); //$NON-NLS-0$
			defineRule("entity-other-attribute-name", settings.attribute, false); //$NON-NLS-0$
			defineRule("string-quoted", settings.string, false); //$NON-NLS-0$
			defineRule("meta.annotation.currentLine", settings.currentLine, true); //$NON-NLS-0$
			defineRule("keyword", settings.keyword, false); //$NON-NLS-0$
			defineRule("string", settings.string, false); //$NON-NLS-0$\
			defineRule("constant", settings.constant, false); //$NON-NLS-0$
			defineRule("comment", settings.comment, false); //$NON-NLS-0$
			defineRule("comment.block.documentation", settings.comment, false); //$NON-NLS-0$
			defineRule("keyword.other.documentation.markup", settings.comment, false); //$NON-NLS-0$
			defineRule("keyword.other.documentation.markup", settings.comment, false); //$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);
				});
			}
		}
	};
	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
 ******************************************************************************/
 
/*global define*/

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;
	}

	/**
	 * @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,
		addEventListener: addEventListener,
		removeEventListener: removeEventListener,
		Animation: Animation
	};
});

/*******************************************************************************
 * @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
 *		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
 ******************************************************************************/

/*global define */

define("orion/editor/textView", [ //$NON-NLS-0$
	'i18n!orion/editor/nls/messages', //$NON-NLS-0$
	'orion/editor/textModel', //$NON-NLS-0$
	'orion/editor/keyModes', //$NON-NLS-0$
	'orion/editor/eventTarget', //$NON-NLS-0$
	'orion/editor/textTheme', //$NON-NLS-0$
	'orion/editor/util', //$NON-NLS-0$
	'orion/util' //$NON-NLS-0$
], function(messages, mTextModel, mKeyModes, mEventTarget, mTextTheme, textUtil, util) {

	/** @private */
	function getWindow(document) {
		return document.defaultView || document.parentWindow;
	}
	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, length = text.length;
		while (index < length) {
			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$
			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));
			addDelimiterFunc();
		}
	}
	/** @private */
	function getBorder(node) {
		var left,top,right,bottom;
		var window = getWindow(node.ownerDocument);
		if (window.getComputedStyle) {
			var style = window.getComputedStyle(node, null);
			left = style.getPropertyValue("border-left-width"); //$NON-NLS-0$
			top = style.getPropertyValue("border-top-width"); //$NON-NLS-0$
			right = style.getPropertyValue("border-right-width"); //$NON-NLS-0$
			bottom = style.getPropertyValue("border-bottom-width"); //$NON-NLS-0$
		} 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 window = getWindow(node.ownerDocument);
		if (window.getComputedStyle) {
			var style = window.getComputedStyle(node, null);
			left = style.getPropertyValue("padding-left"); //$NON-NLS-0$
			top = style.getPropertyValue("padding-top"); //$NON-NLS-0$
			right = style.getPropertyValue("padding-right"); //$NON-NLS-0$
			bottom = style.getPropertyValue("padding-bottom"); //$NON-NLS-0$
		} 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(document, parent, className, callback) {
		className = "_" + className + "DOMReady"; //$NON-NLS-1$ //$NON-NLS-0$
		parent.className = parent.className ? parent.className + " " + className : className; //$NON-NLS-0$
		parent.__DOMReady = callback;
		var id = className + "Style"; //$NON-NLS-0$
		if (document.getElementById(id)) { return; }
		var animationName = className + "Animation"; //$NON-NLS-0$
		function insertListener(event) {
			if (event.animationName === animationName) {
				var target = event.target;
				if (typeof target.__DOMReady === "function") { //$NON-NLS-0$
					getWindow(document).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-0$
			var frames = "", classRule = "body ." + className + " {\n"; //$NON-NLS-1$ //$NON-NLS-0$
			for (var i=0; i<props.length; i++) {
				frames +=
				"@" + props[i] + "keyframes " + animationName + " {\n" + //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				"from { opacity: 0.99; }\n" + //$NON-NLS-0$
				"to { opacity: 1; }\n" + //$NON-NLS-0$
				"}\n"; //$NON-NLS-0$
				classRule +=
				props[i] + "animation-duration: 0.001s;\n" + //$NON-NLS-0$
				props[i] + "animation-name: " + animationName + ";\n"; //$NON-NLS-1$ //$NON-NLS-0$
			}
			classRule += "}"; //$NON-NLS-0$
			return frames + classRule;
		}
		addHandler(document, "animationstart", insertListener, false); //$NON-NLS-0$
		addHandler(document, "MSAnimationStart", insertListener, false);  //$NON-NLS-0$
		addHandler(document, "webkitAnimationStart", insertListener, false); //$NON-NLS-0$
		var style = document.createElement("style"); //$NON-NLS-0$
		style.id = id;
		var head = document.getElementsByTagName("head")[0] || document.documentElement; //$NON-NLS-0$
		style.appendChild(document.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
	}
	Selection.prototype = /** @lends orion.editor.Selection.prototype */ {
		/** @private */
		clone: function() {
			return new Selection(this.start, this.end, this.caret);
		},
		/** @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 */
		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-0$
		},
		/** @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;
		}
	};
	/** @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-0$
		}
	};
	/** 
	 * 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-0$
			view.onLineStyle(e);
			var document = parent.ownerDocument;
			var lineDiv = div || util.createElement(document, "div"); //$NON-NLS-0$
			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-0$
			}
			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-0$
			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-0$
			}
			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-0$
				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 = document.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 length = t ? t.length : 0;
									if (oldEnd + length > spanEnd) { break; }
									oldEnd += length;
								}
								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-0$
				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-0$
						for (var i = 1; i < spacesCount; i++) {
							spaces += " "; //$NON-NLS-0$
						}
						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-0$
				}
			}
			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-0$
			if (style && style.tagName) {
				tagName = style.tagName.toLowerCase();
			}
			var isLink = tagName === "a"; //$NON-NLS-0$
			if (isLink) { this.hasLink = true; }
			if (isLink && !view._linksVisible) {
				tagName = "span"; //$NON-NLS-0$
			}
			var document = parent.ownerDocument;
			var child = util.createElement(parent.ownerDocument, tagName);
			child.appendChild(document.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-0$
				var window = view._getWindow();
				addHandler(child, "click", function(e) { return view._handleLinkClick(e ? e : window.event); }, false); //$NON-NLS-0$
			}
			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 document = 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 = document.createRange();
							range.setStart(textNode, index);
							range.setEnd(textNode, index + 1);
							result = new TextRect(range.getBoundingClientRect());
						} else if (util.isIE) {
							range = document.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-0$
							range.moveStart("character", index); //$NON-NLS-0$
							result = new TextRect(range.getBoundingClientRect());
							if (fixIE8) {
								result.left = lineChild.getClientRects()[0].left;
							}
						} else {
							var text = textNode.data;
							lineChild.removeChild(textNode);
							lineChild.appendChild(document.createTextNode(text.substring(0, index)));
							var span = util.createElement(document, "span"); //$NON-NLS-0$
							span.appendChild(document.createTextNode(text.substring(index, index + 1)));
							lineChild.appendChild(span);
							lineChild.appendChild(document.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._getSelection();
								if ((lineOffset <= s.start && s.start < lineOffset + nodeLength) ||  (lineOffset <= s.end && s.end < lineOffset + nodeLength)) {
									view._updateDOMSelection();
								}
							}
						}
						if (util.isIE) {
							var window = getWindow(child.ownerDocument);
							var xFactor = window.screen.logicalXDPI / window.screen.deviceXDPI;
							var yFactor = window.screen.logicalYDPI / window.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 = new Array(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 length = lineChild.firstChild.length; 
			if (lineChild.ignoreChars) {
				length -= lineChild.ignoreChars;
			}
			return length;
		},
		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-0$
									ignored++;
								} else {
									childText.push(ch === "\u00A0" ? "\t" : ch); //$NON-NLS-1$ //$NON-NLS-0$
								}
							}
						}
						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 self = this;
			function hitChild(lineChild, offset, rect) {
				var textNode = lineChild.firstChild;
				var nodeLength = self._nodeLength(lineChild);
				var document = child.ownerDocument;
				var window = getWindow(document);
				var xFactor = util.isIE ? window.screen.logicalXDPI / window.screen.deviceXDPI : 1;
				var yFactor = util.isIE ? window.screen.logicalYDPI / window.screen.deviceYDPI : 1;
				var rangeLeft, rangeTop, rangeRight, rangeBottom;
				var range, start, end;
				var rl = rect.left + lineRect.left, fixIE8, rects;
				if (util.isIE || view._isRangeRects) {
					range = view._isRangeRects ? document.createRange() : document.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-0$
							range.moveEnd("character", end - start); //$NON-NLS-0$
						}
						rects = range.getClientRects();
						var found = false;
						for (var k = 0; k < rects.length; k++) {
							rect = rects[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-0$
						range.moveEnd("character", end - start); //$NON-NLS-0$
					}
					rects = range.getClientRects();
					var trailing = false;
					if (rects.length > 0) {
						rect = rects[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-0$
						if (q === nodeLength - 1) {
							newText.push(textNode.data.substring(q));
						} else {
							newText.push(textNode.data.substring(q, q + 1));
						}
						newText.push("</span>"); //$NON-NLS-0$
					}
					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 (!self._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._getSelection();
						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 rects = self._getClientRects(child, lineRect);
				for (var j = 0; j < rects.length; j++) {
					var rect = rects[j];
					if (rect.left <= x && x < rect.right && (!view._wrapMode || (rect.top <= y && y < rect.bottom))) {
						return rect;
					}
				}
				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-0$
				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-0$
				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-0$
				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-0$
								punctuation = _isPunctuation(c);
								if (data.unit === "wordend") { //$NON-NLS-0$
									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-0$
								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-0$ 
								punctuation = _isPunctuation(c);
								if (data.unit === "wordend") { //$NON-NLS-0$
									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-0$
								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, length;
			var lineOffset = model.getLineStart(lineIndex);
			var lineText = model.getLine(lineIndex);
			var lineStart = model.getLineStart(lineIndex);
			var document = 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 = document.body.createTextRange();
				range.moveToElementText(lineChild);
				length = range.text.length;
				range.moveEnd(data.unit, step);
				result = offset + range.text.length - length;
			} 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 = document.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-0$
						}
						length = range.text.length;
						range.moveEnd(data.unit, step);
						result = offset + range.text.length - length;
						break;
					}
					lineOffset = nodeLength + lineOffset;
					lineChild = lineChild.nextSibling;
				}
			}
			var offsetInLine = result - lineStart;
			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 self = this;
			this.forEach(function(span) {
				var style = span.viewStyle;
				if (style && style.tagName && style.tagName.toLowerCase() === "a") { //$NON-NLS-0$
					child.replaceChild(self._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 {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) {
			ruler.setView(this);
			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);
			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-0$
			}
			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; }
			var scroll = this._getScroll();
			var viewPad = this._getViewPadding();
			var viewRect = this._viewDiv.getBoundingClientRect();
			if (from === "document") { //$NON-NLS-0$
				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-0$
				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;
		},
		/**
		 * 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-0$
			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>"tab" - inserts a tab character at the caret</li>
		 *       <li>"shiftTab" - noop</li>
		 *       <li>"toggleTabMode" - toggles tab mode.</li>
		 *       <li>"toggleWrapMode" - toggles wrap mode.</li>
		 *       <li>"toggleOverwriteMode" - toggles overwrite mode.</li>
		 *       <li>"enter" - inserts a line delimiter at the caret</li>
		 *     </ul>
		 *   <li>Clipboard actions.</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>
		 * </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.editor.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);
		},
		/**
		 * 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-0$
					return clone(this["_" + arg]); //$NON-NLS-0$
				}
				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-0$
				}
			}
			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) {
			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 () {
			var s = this._getSelection();
			return {start: s.start, end: s.end};
		},
		/**
		 * 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 (!defaultAction && action.handler) {
					if (action.handler(actionOptions)) {
						return true;
					}
				}
				if (action.defaultHandler) {
					return typeof action.defaultHandler(actionOptions) === "boolean"; //$NON-NLS-0$
				}
			}
			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);
		},
		/**
		 * @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);
		},
		/**
		 * @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);
		},
		/**
		 * 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);
				}
			}
			if (!ruler || ruler.getOverview() === "page") { //$NON-NLS-0$
				var child = div.firstChild;
				while (child) {
					var lineIndex = child.lineIndex;
					if (startLine <= lineIndex && lineIndex < endLine) {
						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._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.editor.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) { return; }
			if (model === this._model) { return; }
			this._model.removeEventListener("preChanging", this._modelListener.onChanging); //$NON-NLS-0$
			this._model.removeEventListener("postChanged", this._modelListener.onChanged); //$NON-NLS-0$
			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-0$
				text: newText,
				start: 0,
				removedCharCount: oldCharCount,
				addedCharCount: newCharCount,
				removedLineCount: oldLineCount,
				addedLineCount: newLineCount
			};
			this.onModelChanging(e);
			this._model = model;
			e = {
				type: "ModelChanged", //$NON-NLS-0$
				start: 0,
				removedCharCount: oldCharCount,
				addedCharCount: newCharCount,
				removedLineCount: oldLineCount,
				addedLineCount: newLineCount
			};
			this.onModelChanged(e); 
			this._model.addEventListener("preChanging", this._modelListener.onChanging); //$NON-NLS-0$
			this._model.addEventListener("postChanged", this._modelListener.onChanged); //$NON-NLS-0$
			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-0$
					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-0$
				}
			}
		},
		/**
		 * @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);
		},
		/**
		 * 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.
		 *
		 * @see orion.editor.TextView#getText
		 */
		setText: function (text, start, end) {
			var reset = start === undefined && end === undefined;
			if (start === undefined) { start = 0; }
			if (end === undefined) { end = this._model.getCharCount(); }
			if (reset) {
				this._variableLineHeight = false;
			}
			this._modifyContent({text: text, start: start, end: end, _code: true}, !reset);
			if (reset) {
				this._columnX = -1;
				this._setSelection(new Selection(0, 0, false), true);
				
				/*
				* 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.
		 *
		 * @see orion.editor.TextView#getBottomIndex
		 * @see orion.editor.TextView#getTopIndex
		 */
		setTopIndex: function(topIndex) {
			if (!this._clientDiv) { return; }
			this._scrollView(0, this._getLinePixel(Math.max(0, topIndex)) - this._getScroll().y);
		},
		/**
		 * 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.
		 *
		 * @see orion.editor.TextView#getBottomPixel
		 * @see orion.editor.TextView#getTopPixel
		 * @see orion.editor.TextView#convert
		 */
		setTopPixel: function(pixel) {
			if (!this._clientDiv) { return; }
			this._scrollView(0, Math.max(0, pixel) - this._getScroll().y);
		},
		/**
		 * 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-0$
					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 self = this;
				var window = this._getWindow();
				window.setTimeout(function() {
					self._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;
				
				/*
				* 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 (e) {
			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._getSelection().isEmpty()) {
					var rootDiv = this._rootDiv;
					var child = util.createElement(rootDiv.ownerDocument, "div"); //$NON-NLS-0$
					rootDiv.appendChild(child);
					rootDiv.removeChild(child);
				}
			}
			if (this._cursorDiv) {
				this._cursorDiv.style.display = "none"; //$NON-NLS-0$
			}
			if (this._selDiv1) {
				var color = "lightgray"; //$NON-NLS-0$
				this._selDiv1.style.background = color;
				this._selDiv2.style.background = color;
				this._selDiv3.style.background = color;
				/* Clear browser selection if selection is within clientDiv */
				var temp;
				var window = this._getWindow();
				var document = this._selDiv1.ownerDocument;
				if (window.getSelection) {
					var sel = window.getSelection();
					temp = sel.anchorNode;
					while (temp) {
						if (temp === this._clientDiv) {
							if (sel.rangeCount > 0) { sel.removeAllRanges(); }
							break;
						}
						temp = temp.parentNode;
					}
				} else if (document.selection) {
					this._ignoreSelect = false;
					temp = document.selection.createRange().parentElement();
					while (temp) {
						if (temp === this._clientDiv) {
							document.selection.empty();
							break;
						}
						temp = temp.parentNode;
					}
					this._ignoreSelect = true;
				}
			}
			if (!this._ignoreFocus) {
				this.onBlur({type: "Blur"}); //$NON-NLS-0$
			}
		},
		_handleCompositionStart: function (e) {
			if (this._ignoreEvent(e)) { return; }
			this._startIME();
			if (this._mutationObserver) {
				this._mutationObserver.disconnect();
				this._mutationObserver = null;
			}
		},
		_handleCompositionEnd: function (e) {
			if (this._ignoreEvent(e)) { return; }
			this._commitIME(e.data);
		},
		_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-0$
				var evt = this._createMouseEvent("ContextMenu", e); //$NON-NLS-0$
				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 self = this;
				var window = this._getWindow();
				window.setTimeout(function() {
					self._clientDiv.contentEditable = true;
					self._clientDiv.draggable = false;
					self._ignoreBlur = false;
				}, 0);
			}
			if (this.isListening("DragStart") && this._dragOffset !== -1) { //$NON-NLS-0$
				this._isMouseDown = false;
				this.onDragStart(this._createMouseEvent("DragStart", e)); //$NON-NLS-0$
				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-0$
				this.onDrag(this._createMouseEvent("Drag", e)); //$NON-NLS-0$
			}
		},
		_handleDragEnd: function (e) {
			if (this._ignoreEvent(e)) { return; }
			this._dropTarget = false;
			this._dragOffset = -1;
			if (this.isListening("DragEnd")) { //$NON-NLS-0$
				this.onDragEnd(this._createMouseEvent("DragEnd", e)); //$NON-NLS-0$
			}
			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-0$
					this._fixCaret();
				}
			}
		},
		_handleDragEnter: function (e) {
			if (this._ignoreEvent(e)) { return; }
			var prevent = true;
			this._dropTarget = true;
			if (this.isListening("DragEnter")) { //$NON-NLS-0$
				prevent = false;
				this.onDragEnter(this._createMouseEvent("DragEnter", e)); //$NON-NLS-0$
			}
			/*
			* 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-0$
				prevent = false;
				this.onDragOver(this._createMouseEvent("DragOver", e)); //$NON-NLS-0$
			}
			/*
			* 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-0$
				if (e.preventDefault) { e.preventDefault(); }
				return false;
			}
		},
		_handleDragLeave: function (e) {
			if (this._ignoreEvent(e)) { return; }
			this._dropTarget = false;
			if (this.isListening("DragLeave")) { //$NON-NLS-0$
				this.onDragLeave(this._createMouseEvent("DragLeave", e)); //$NON-NLS-0$
			}
		},
		_handleDrop: function (e) {
			if (this._ignoreEvent(e)) { return; }
			this._dropTarget = false;
			if (this.isListening("Drop")) { //$NON-NLS-0$
				this.onDrop(this._createMouseEvent("Drop", e)); //$NON-NLS-0$
			}
			/*
			* 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 (e) {
			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-0$
			}
			if (this._selDiv1) {
				var color = this._highlightRGB;
				this._selDiv1.style.background = color;
				this._selDiv2.style.background = color;
				this._selDiv3.style.background = color;
			}
			if (!this._ignoreFocus) {
				this.onFocus({type: "Focus"}); //$NON-NLS-0$
			}
		},
		_handleKeyDown: function (e) {
			this._cancelCheckSelection();
			if (this._ignoreEvent(e)) {	return;	}
			if (this.isListening("KeyDown")) { //$NON-NLS-0$
				var keyEvent = this._createKeyEvent("KeyDown", e); //$NON-NLS-0$
				this.onKeyDown(keyEvent); //$NON-NLS-0$
				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-0$
				var keyEvent = this._createKeyEvent("KeyPress", e); //$NON-NLS-0$
				this.onKeyPress(keyEvent); //$NON-NLS-0$
				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-0$
				var keyEvent = this._createKeyEvent("KeyUp", e); //$NON-NLS-0$
				this.onKeyUp(keyEvent); //$NON-NLS-0$
				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 window = this._getWindow();
			var result = true;
			var target = window;
			if (util.isIE || (util.isFirefox && !this._overlayDiv)) { target = this._clientDiv; }
			if (this._overlayDiv) {
				if (this._hasFocus) {
					this._ignoreFocus = true;
				}
				var self = this;
				window.setTimeout(function () {
					self.focus();
					self._ignoreFocus = false;
				}, 0);
			}
			if (this._clickCount === 1) {
				result = this._setSelectionTo(e.clientX, e.clientY, e.shiftKey, (!util.isOpera || util.isOpera >= 12.16) && this._hasFocus && this.isListening("DragStart")); //$NON-NLS-0$
				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._doubleClickSelection = null;
				this._setSelectionTo(e.clientX, e.clientY, e.shiftKey);
				this._doubleClickSelection = this._getSelection();
			}
			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-0$
					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-0$
				var mouseEvent = this._createMouseEvent("MouseDown", e); //$NON-NLS-0$
				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-0$
				this.onMouseOver(this._createMouseEvent("MouseOver", e)); //$NON-NLS-0$
			}
		},
		_handleMouseOut: function (e) {
			if (this._ignoreEvent(e)) { return; }
			if (this._animation) { return; }
			if (this.isListening("MouseOut")) { //$NON-NLS-0$
				this.onMouseOut(this._createMouseEvent("MouseOut", e)); //$NON-NLS-0$
			}
		},
		_handleMouseMove: function (e) {
			if (this._animation) { return; }
			var inClient = this._isClientDiv(e);
			if (this.isListening("MouseMove")) { //$NON-NLS-0$
				if (inClient){
					this.onMouseMove(this._createMouseEvent("MouseMove", e)); //$NON-NLS-0$
				}
			}
			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 && (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-0$
			} else if (y > bottomEdge) {
				this._doAutoScroll("down", x, y - bottomEdge); //$NON-NLS-0$
			} else if (x < leftEdge && !this._wrapMode) {
				this._doAutoScroll("left", x - leftEdge, y); //$NON-NLS-0$
			} else if (x > rightEdge && !this._wrapMode) {
				this._doAutoScroll("right", x - rightEdge, y); //$NON-NLS-0$
			} else {
				this._endAutoScroll();
				this._setSelectionTo(x, y, 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-0$
			return {
				type: type,
				event: e,
				clickCount: this._clickCount,
				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-0$
				if (this._isClientDiv(e) || (left && this._isMouseDown)) {
					this.onMouseUp(this._createMouseEvent("MouseUp", e)); //$NON-NLS-0$
				}
			}
			if (this._linksVisible) {
				return;
			}
			if (left && this._isMouseDown) {
				if (this._dragOffset !== -1) {
					var selection = this._getSelection();
					selection.extend(this._dragOffset);
					selection.collapse();
					this._setSelection(selection, true, true);
					this._dragOffset = -1;
				}
				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 && util.isFirefox) {
				this._updateDOMSelection();
				e.preventDefault();
			}
		},
		_handleMouseWheel: function (e) {
			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-0$
					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 self = this;
					this._ignoreFocus = true;
					var window = this._getWindow();
					window.setTimeout(function() {
						self._updateDOMSelection();
						self._ignoreFocus = false;
					}, 0);
				}
				if (e.preventDefault) { e.preventDefault(); }
				return false;
			}
		},
		_handleResize: function (e) {
			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._variableLineHeight) {
						this._variableLineHeight = false;
						this._resetLineHeight();
					}
					this._metrics = metrics;
					queue = true;
				}

				if (queue) {
					this._queueUpdate();
				} else {
					this._update();
				}
			}
		},
		_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-0$
				var clientHeight = this._getClientHeight ();
				var lineCount = this._model.getLineCount ();
				var viewPad = this._getViewPadding();
				var viewRect = this._viewDiv.getBoundingClientRect();
				var trackHeight = clientHeight + viewPad.top + viewPad.bottom - 2 * this._metrics.scrollWidth;
				lineIndex = Math.floor(((e.clientY - viewRect.top) - this._metrics.scrollWidth) * lineCount / trackHeight);
				if (!(0 <= lineIndex && lineIndex < lineCount)) {
					lineIndex = undefined;
				}
			}
			if (ruler) {
				switch (e.type) {
					case "click": //$NON-NLS-0$
						if (ruler.onClick) { ruler.onClick(lineIndex, e); }
						break;
					case "dblclick": //$NON-NLS-0$
						if (ruler.onDblClick) { ruler.onDblClick(lineIndex, e); }
						break;
					case "mousemove": //$NON-NLS-0$
						if (ruler.onMouseMove) { ruler.onMouseMove(lineIndex, e); }
						break;
					case "mouseover": //$NON-NLS-0$
						if (ruler.onMouseOver) { ruler.onMouseOver(lineIndex, e); }
						break;
					case "mouseout": //$NON-NLS-0$
						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-0$
					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-0$
				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 window = this._getWindow();
			var selection = window.getSelection();
			var start = this._getModelOffset(selection.anchorNode, selection.anchorOffset);
			var end = this._getModelOffset(selection.focusNode, selection.focusOffset);
			var sel = this._getSelection();
			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 window = this._getWindow();
				window.clearTimeout(this._selPollTimer);
				this._selPollTimer = null; 
			}
		},
		_pollSelectionChange: function(retryPoll) {
			var that = this;
			var window = this._getWindow();
			this._cancelPollSelectionChange();
			this._selPollTimer = window.setTimeout(function() {
				that._selPollTimer = null; 
				if (!that._clientDiv) { return; }
				var changed = that._updateSelectionFromDOM();
				if (!changed && retryPoll) {
					that._pollSelectionChange(retryPoll);
				}
			}, 100);
		},
		_handleSelectionChange: function (e) {
			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; }
			this._imeOffset = -1;
			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;
					
					this._ignoreQueueUpdate = util.isSafari;
					this._modifyContent({text: deltaText, start: start, end: end, _ignoreDOMSelection: true, _ignoreDOMSelection1: util.isChrome}, true);
					this._ignoreQueueUpdate = false;
				}
			} else {
				this._doContent(e.data);
			}
			e.preventDefault();
		},
		_handleTouchStart: function (e) {
			this._commitIME();
			var window = this._getWindow();
			if (this._touchScrollTimer) {
				this._vScrollDiv.style.display = "none"; //$NON-NLS-0$
				this._hScrollDiv.style.display = "none"; //$NON-NLS-0$
				window.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 - window.pageYOffset) || x < (touch.pageX - window.pageXOffset) ) {
						x = touch.pageX - window.pageXOffset;
						y = touch.pageY - window.pageYOffset;
				    }
				}
				var pt = this.convert({x: x, y: y}, "page", "document"); //$NON-NLS-1$ //$NON-NLS-0$
				this._lastTouchOffset = this.getOffsetAtLocation(pt.x, pt.y);
				this._touchStartTime = e.timeStamp;
				this._touching = true;
			}
		},
		_handleTouchMove: function (e) {
			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-0$
					if (!this._wrapMode) {
						this._hScrollDiv.style.display = "block"; //$NON-NLS-0$
					}
					var self = this;
					var window = this._getWindow();
					this._touchScrollTimer = window.setInterval(function() {
						var deltaX = 0, deltaY = 0;
						if (self._touching) {
							deltaX = self._touchStartX - self._touchCurrentX;
							deltaY = self._touchStartY - self._touchCurrentY;
							self._touchSpeedX = deltaX / interval;
							self._touchSpeedY = deltaY / interval;
							self._touchStartX = self._touchCurrentX;
							self._touchStartY = self._touchCurrentY;
						} else {
							if (Math.abs(self._touchSpeedX) < 0.1 && Math.abs(self._touchSpeedY) < 0.1) {
								self._vScrollDiv.style.display = "none"; //$NON-NLS-0$
								self._hScrollDiv.style.display = "none"; //$NON-NLS-0$
								window.clearInterval(self._touchScrollTimer);
								self._touchScrollTimer = null;
								return;
							} else {
								deltaX = self._touchSpeedX * interval;
								deltaY = self._touchSpeedY * interval;
								self._touchSpeedX *= 0.95;
								self._touchSpeedY *= 0.95;
							}
						}
						self._scrollView(deltaX, deltaY);
					}, interval);
				}
				if (this._touchScrollTimer) {
					e.preventDefault();
				}
			}
		},
		_handleTouchEnd: function (e) {
			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-0$
					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-0$
							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-0$
							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-0$
						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 selection = this._getSelection();
			if (selection.isEmpty()) {
				if (!args.count) {
					args.count = 1;
				}
				args.count *= -1;
				args.expandTab = this._expandTab;
				this._doMove(args, selection);
			}
			this._modifyContent({text: "", start: selection.start, end: selection.end}, true);
			return true;
		},
		_doCase: function (args) {
			var selection = this._getSelection();
			this._doMove(args, selection);
			var text = this.getText(selection.start, selection.end);
			this._setSelection(selection, true);
			switch (args.type) {
				case "lower": text = text.toLowerCase(); break; //$NON-NLS-0$
				case "capitalize": text = text.replace(/(?:^|\s)\S/g, function(a) { return a.toUpperCase(); }); break; //$NON-NLS-0$
				case "reverse":  //$NON-NLS-0$
					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;
			}
			this._doContent(text);
			return true;
		},
		_doContent: function (text) {
			var selection = this._getSelection();
			if (this._overwriteMode && selection.isEmpty()) {
				var model = this._model;
				var lineIndex = model.getLineAtOffset(selection.end);
				if (selection.end < model.getLineEnd(lineIndex)) {
					var line = this._getLine(lineIndex);
					selection.extend(line.getNextOffset(selection.getCaret(), {unit:"character", count:1})); //$NON-NLS-0$
					line.destroy();
				}
			}
			return this._modifyContent({text: text, start: selection.start, end: selection.end, _ignoreDOMSelection: true}, true);
		},
		_doCopy: function (e) {
			var selection = this._getSelection();
			if (!selection.isEmpty()) {
				var text = this._getBaseText(selection.start, selection.end);
				return this._setClipboardText(text, e);
			}
			return true;
		},
		_doCursorNext: function (args) {
			var selection = this._getSelection();
			if (!selection.isEmpty() && !args.select) {
				selection.start = selection.end;
			} else {
				this._doMove(args, selection);
			}
			if (!args.select) { selection.collapse(); }
			this._setSelection(selection, true);
			return true;
		},
		_doCursorPrevious: function (args) {
			var selection = this._getSelection();
			if (!selection.isEmpty() && !args.select) {
				selection.end = selection.start;
			} else {
				if (!args.count) {
					args.count = 1;
				}
				args.count *= -1;
				this._doMove(args, selection);
			}
			if (!args.select) { selection.collapse(); }
			this._setSelection(selection, true);
			return true;
		},
		_doCut: function (e) {
			var selection = this._getSelection();
			if (!selection.isEmpty()) {
				var text = this._getBaseText(selection.start, selection.end);
				this._doContent("");
				return this._setClipboardText(text, e);
			}
			return true;
		},
		_doDelete: function (args) {
			var selection = this._getSelection();
			if (selection.isEmpty()) {
				this._doMove(args, selection);
			}
			this._modifyContent({text: "", start: selection.start, end: selection.end}, true);
			return true;
		},
		_doEnd: function (args) {
			var selection = this._getSelection();
			var model = this._model;
			var callback;
			if (args.ctrl) {
				selection.extend(model.getCharCount());
				callback = function() {};
			} else {
				var offset = selection.getCaret();
				var lineIndex = model.getLineAtOffset(offset);
				if (this._wrapMode) {
					var line = this._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(selection, true, true, callback);
			return true;
		},
		_doEnter: function (args) {
			var model = this._model;
			var selection = this._getSelection();
			this._doContent(model.getLineDelimiter()); 
			if (args && args.noCursor) {
				selection.end = selection.start;
				this._setSelection(selection, true);
			}
			return true;
		},
		_doHome: function (args) {
			var selection = this._getSelection();
			var model = this._model;
			var callback;
			if (args.ctrl) {
				selection.extend(0);
				callback = function() {};
			} else {
				var offset = selection.getCaret();
				var lineIndex = model.getLineAtOffset(offset);
				if (this._wrapMode) {
					var line = this._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(selection, true, true, callback);
			return true;
		},
		_doLineDown: function (args) {
			var model = this._model;
			var selection = this._getSelection();
			var caret = selection.getCaret();
			var lineIndex = model.getLineAtOffset(caret), visualIndex;
			var line = this._getLine(lineIndex);
			var x = this._columnX, y = 1, lastLine = false;
			if (x === -1 || args.wholeLine || (args.select && util.isIE)) {
				var offset = args.wholeLine ? model.getLineEnd(lineIndex + 1) : caret;
				x = 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 = this._getLine(lineIndex);
				}
				selection.extend(line.getOffset(x, y));
				select = true;
			}
			if (select) {
				if (!args.select) { selection.collapse(); }
				this._setSelection(selection, true, true);
			}
			this._columnX = x;
			line.destroy();
			return true;
		},
		_doLineUp: function (args) {
			var model = this._model;
			var selection = this._getSelection();
			var caret = selection.getCaret();
			var lineIndex = model.getLineAtOffset(caret), visualIndex;
			var line = this._getLine(lineIndex);
			var x = this._columnX, firstLine = false, y;
			if (x === -1 || args.wholeLine || (args.select && util.isIE)) {
				var offset = args.wholeLine ? model.getLineStart(lineIndex - 1) : caret;
				x = 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 = this._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 = this._getLine(lineIndex);
				}
				selection.extend(line.getOffset(x, y));
				select = true;
			}
			if (select) {
				if (!args.select) { selection.collapse(); }
				this._setSelection(selection, true, true);
			}
			this._columnX = x;
			line.destroy();
			return true;
		},
		_doNoop: function () {
			return true;
		},
		_doPageDown: function (args) {
			var self = this;
			var model = this._model;
			var selection = this._getSelection();
			var caret = selection.getCaret();
			var caretLine = model.getLineAtOffset(caret);
			var lineCount = model.getLineCount();
			var scroll = this._getScroll();
			var clientHeight = this._getClientHeight(), x, line;
			if (this._lineHeight) {
				x = this._columnX;
				var caretRect = this._getBoundsAtOffset(caret);
				if (x === -1 || (args.select && util.isIE)) {
					x = caretRect.left;
				}
				var lineIndex = this._getLineIndex(caretRect.top + clientHeight);
				line = this._getLine(lineIndex);
				var linePixel = this._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(); }
				this._setSelection(selection, true, true, function() {
					self._columnX = x;
				}, rect.top + linePixel - caretRect.top);
				return true;
			}
			if (caretLine < lineCount - 1) {
				var lineHeight = this._getLineHeight();
				var lines = Math.floor(clientHeight / lineHeight);
				var scrollLines = Math.min(lineCount - caretLine - 1, lines);
				scrollLines = Math.max(1, scrollLines);
				x = this._columnX;
				if (x === -1 || (args.select && util.isIE)) {
					line = this._getLine(caretLine);
					x = line.getBoundingClientRect(caret).left;
					line.destroy();
				}
				line = this._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;
				}
				this._setSelection(selection, true, true, function() {
					self._columnX = x;
				}, scrollOffset - scroll.y);
			}
			return true;
		},
		_doPageUp: function (args) {
			var self = this;
			var model = this._model;
			var selection = this._getSelection();
			var caret = selection.getCaret();
			var caretLine = model.getLineAtOffset(caret);
			var scroll = this._getScroll();
			var clientHeight = this._getClientHeight(), x, line;
			if (this._lineHeight) {
				x = this._columnX;
				var caretRect = this._getBoundsAtOffset(caret);
				if (x === -1 || (args.select && util.isIE)) {
					x = caretRect.left;
				}
				var lineIndex = this._getLineIndex(caretRect.bottom - clientHeight);
				line = this._getLine(lineIndex);
				var linePixel = this._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(); }
				this._setSelection(selection, true, true, function() {
					self._columnX = x;
				}, rect.top + linePixel - caretRect.top);
				return true;
			}
			if (caretLine > 0) {
				var lineHeight = this._getLineHeight();
				var lines = Math.floor(clientHeight / lineHeight);
				var scrollLines = Math.max(1, Math.min(caretLine, lines));
				x = this._columnX;
				if (x === -1 || (args.select && util.isIE)) {
					line = this._getLine(caretLine);
					x = line.getBoundingClientRect(caret).left;
					line.destroy();
				}
				line = this._getLine(caretLine - scrollLines);
				selection.extend(line.getOffset(x, this._getLineHeight(caretLine - scrollLines) - 1));
				line.destroy();
				if (!args.select) { selection.collapse(); }
				var scrollOffset = Math.max(0, scroll.y - scrollLines * lineHeight);
				this._setSelection(selection, true, true, function() {
					self._columnX = x;
				}, scrollOffset - scroll.y);
			}
			return true;
		},
		_doPaste: function(e) {
			var self = this;
			var result = this._getClipboardText(e, function(text) {
				if (text) {
					if (util.isLinux && self._lastMouseButton === 2) {
						var timeDiff = new Date().getTime() - self._lastMouseTime;
						if (timeDiff <= self._clickTime) {
							self._setSelectionTo(self._lastMouseX, self._lastMouseY);
						}
					}
					self._doContent(text);
				}
			});
			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 = lineCount * lineHeight;
			var verticalScrollOffset = this._getScroll().y;
			var pixel;
			switch (type) {
				case "textStart": pixel = 0; break; //$NON-NLS-0$
				case "textEnd": pixel = verticalMaximum - clientHeight; break; //$NON-NLS-0$
				case "pageDown": pixel = verticalScrollOffset + clientHeight; break; //$NON-NLS-0$
				case "pageUp": pixel = verticalScrollOffset - clientHeight; break; //$NON-NLS-0$
				case "lineDown": pixel = verticalScrollOffset + lineHeight; break; //$NON-NLS-0$
				case "lineUp": pixel = verticalScrollOffset - lineHeight; break; //$NON-NLS-0$
				case "centerLine": //$NON-NLS-0$
					var selection = this._getSelection();
					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 (args) {
			var model = this._model;
			var selection = this._getSelection();
			selection.setCaret(0);
			selection.extend(model.getCharCount());
			this._setSelection(selection, false);
			return true;
		},
		_doTab: function (args) {
			if (!this._tabMode || this._readonly) { return; }
			var text = "\t"; //$NON-NLS-0$
			if (this._expandTab) {
				var model = this._model;
				var caret = this._getSelection().getCaret();
				var lineIndex = model.getLineAtOffset(caret);
				var lineStart = model.getLineStart(lineIndex);
				var spaces = this._tabSize - ((caret - lineStart) % this._tabSize);
				text = (new Array(spaces + 1)).join(" "); //$NON-NLS-0$
			}
			this._doContent(text);
			return true;
		},
		_doShiftTab: function (args) {
			if (!this._tabMode || this._readonly) { return; }
			return true;
		},
		_doOverwriteMode: function (args) {
			if (this._readonly) { return; }
			this.setOptions({overwriteMode: !this.getOptions("overwriteMode")}); //$NON-NLS-0$
			return true;
		},
		_doTabMode: function (args) {
			this._tabMode = !this._tabMode;
			return true;
		},
		_doWrapMode: function (args) {
			this.setOptions({wrapMode: !this.getOptions("wrapMode")}); //$NON-NLS-0$
			return true;
		},
		
		/************************************ Internals ******************************************/
		_autoScroll: function () {
			var model = this._model;
			var selection = this._getSelection();
			var pt = this.convert({x: this._autoScrollX, y: this._autoScrollY}, "page", "document"); //$NON-NLS-1$ //$NON-NLS-0$
			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-0$
				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-0$
				lineIndex = this._getLineIndex(pt.y);
				line = this._getLine(caretLine); 
				pt.x += line.getBoundingClientRect(caret, false).left;
				line.destroy();
			}
			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(selection, true);
		},
		_autoScrollTimer: function () {
			this._autoScroll();
			var self = this;
			var window = this._getWindow();
			this._autoScrollTimerID = window.setTimeout(function () {self._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 window = this._getWindow();
			if (i !== lineCount) {
				var self = this;
				this._calculateLHTimer = window.setTimeout(function() {
					self._calculateLHTimer = null;
					self._calculateLineHeightTimer(true);
				}, 0);
				return;
			}
			if (this._calculateLHTimer) {
				window.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 document = parent.ownerDocument;
			var c = " "; //$NON-NLS-0$
			var line = util.createElement(document, "div"); //$NON-NLS-0$
			line.style.lineHeight = "normal"; //$NON-NLS-0$
			var model = this._model;
			var lineText = model.getLine(0);
			var e = {type:"LineStyle", textView: this, 0: 0, lineText: lineText, lineStart: 0}; //$NON-NLS-0$
			this.onLineStyle(e);
			applyStyle(e.style, line);
			line.style.position = "fixed"; //$NON-NLS-0$
			line.style.left = "-1000px"; //$NON-NLS-0$
			var span1 = util.createElement(document, "span"); //$NON-NLS-0$
			span1.appendChild(document.createTextNode(c));
			line.appendChild(span1);
			var span2 = util.createElement(document, "span"); //$NON-NLS-0$
			span2.style.fontStyle = "italic"; //$NON-NLS-0$
			span2.appendChild(document.createTextNode(c));
			line.appendChild(span2);
			var span3 = util.createElement(document, "span"); //$NON-NLS-0$
			span3.style.fontWeight = "bold"; //$NON-NLS-0$
			span3.appendChild(document.createTextNode(c));
			line.appendChild(span3);
			var span4 = util.createElement(document, "span"); //$NON-NLS-0$
			span4.style.fontWeight = "bold"; //$NON-NLS-0$
			span4.style.fontStyle = "italic"; //$NON-NLS-0$
			span4.appendChild(document.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-0$
				}
				if ((fontStyle & 2) !== 0) {
					style.style.fontWeight = "bold"; //$NON-NLS-0$
				}
			}
			var trim = getLineTrim(line);
			parent.removeChild(line);
			
			// calculate pad and scroll width
			var pad = getPadding(this._viewDiv);
			var div1 = util.createElement(document, "div"); //$NON-NLS-0$
			div1.style.position = "fixed"; //$NON-NLS-0$
			div1.style.left = "-1000px"; //$NON-NLS-0$
			div1.style.paddingLeft = pad.left + "px"; //$NON-NLS-0$
			div1.style.paddingTop = pad.top + "px"; //$NON-NLS-0$
			div1.style.paddingRight = pad.right + "px"; //$NON-NLS-0$
			div1.style.paddingBottom = pad.bottom + "px"; //$NON-NLS-0$
			div1.style.width = "100px"; //$NON-NLS-0$
			div1.style.height = "100px"; //$NON-NLS-0$
			var div2 = util.createElement(document, "div"); //$NON-NLS-0$
			div2.style.width = "100%"; //$NON-NLS-0$
			div2.style.height = "100%"; //$NON-NLS-0$
			div1.appendChild(div2);
			parent.appendChild(div1);
			var rect1 = div1.getBoundingClientRect();
			var rect2 = div2.getBoundingClientRect();
			var scrollWidth = 0;
			if (!this._singleMode) {
				div1.style.overflow = 'hidden'; //$NON-NLS-0$
				div2.style.height = "200px"; //$NON-NLS-0$
				var w1 = div1.clientWidth;
				div1.style.overflow = 'scroll'; //$NON-NLS-0$
				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;
			if (!invalid) {
				if (this._wrapOffset || this._marginOffset) {
					div1 = util.createElement(document, "div"); //$NON-NLS-0$
					div1.style.position = "fixed"; //$NON-NLS-0$
					div1.style.left = "-1000px"; //$NON-NLS-0$
					div1.innerHTML = new Array(this._wrapOffset + 1 + (util.isWebkit ? 0 : 1)).join(" "); //$NON-NLS-0$
					parent.appendChild(div1);
					rect1 = div1.getBoundingClientRect();
					wrapWidth = Math.ceil(rect1.right - rect1.left);
					div1.innerHTML = new Array(this._marginOffset + 1).join(" "); //$NON-NLS-0$
					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,
				invalid: invalid
			};
		},
		_cancelAnimation: function() {
			if (this._animation) {
				this._animation.stop();
				this._animation = null;
			}
		},
		_clearSelection: function (direction) {
			var selection = this._getSelection();
			if (selection.isEmpty()) { return false; }
			if (direction === "next") { //$NON-NLS-0$
				selection.start = selection.end;
			} else {
				selection.end = selection.start;
			}
			this._setSelection(selection, 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);
				}
			}
			if (insertText) {
				if (!this._doContent(insertText) && !util.isWebkit) {
					line.lineRemoved = true;
					this._queueUpdate();
				}
			}
			this._imeOffset = -1;
		},
		_createActions: function () {
			this.addKeyMode(new mKeyModes.DefaultKeyMode(this));
			//1 to 1, no duplicates
			var self = this;
			this._actions = {
				"noop": {defaultHandler: function() {return self._doNoop();}}, //$NON-NLS-0$

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

				"deletePrevious": {defaultHandler: function(data) {return self._doBackspace(merge(data,{unit:"character"}));}, actionDescription: {name: messages.deletePrevious}}, //$NON-NLS-1$ //$NON-NLS-0$
				"deleteNext": {defaultHandler: function(data) {return self._doDelete(merge(data,{unit:"character"}));}, actionDescription: {name: messages.deleteNext}}, //$NON-NLS-1$ //$NON-NLS-0$
				"deleteWordPrevious": {defaultHandler: function(data) {return self._doBackspace(merge(data,{unit:"word"}));}, actionDescription: {name: messages.deleteWordPrevious}}, //$NON-NLS-1$ //$NON-NLS-0$
				"deleteWordNext": {defaultHandler: function(data) {return self._doDelete(merge(data,{unit:"word"}));}, actionDescription: {name: messages.deleteWordNext}}, //$NON-NLS-1$ //$NON-NLS-0$
				"deleteLineStart": {defaultHandler: function(data) {return self._doBackspace(merge(data,{unit: "line"}));}, actionDescription: {name: messages.deleteLineStart}}, //$NON-NLS-1$ //$NON-NLS-0$
				"deleteLineEnd": {defaultHandler: function(data) {return self._doDelete(merge(data,{unit: "line"}));}, actionDescription: {name: messages.deleteLineEnd}}, //$NON-NLS-1$ //$NON-NLS-0$
				"tab": {defaultHandler: function(data) {return self._doTab();}, actionDescription: {name: messages.tab}}, //$NON-NLS-0$
				"shiftTab": {defaultHandler: function(data) {return self._doShiftTab();}, actionDescription: {name: messages.shiftTab}}, //$NON-NLS-0$
				"enter": {defaultHandler: function(data) {return self._doEnter();}, actionDescription: {name: messages.enter}}, //$NON-NLS-0$
				"enterNoCursor": {defaultHandler: function(data) {return self._doEnter(merge(data,{noCursor:true}));}, actionDescription: {name: messages.enterNoCursor}}, //$NON-NLS-0$
				"selectAll": {defaultHandler: function(data) {return self._doSelectAll();}, actionDescription: {name: messages.selectAll}}, //$NON-NLS-0$
				"copy": {defaultHandler: function(data) {return self._doCopy();}, actionDescription: {name: messages.copy}}, //$NON-NLS-0$
				"cut": {defaultHandler: function(data) {return self._doCut();}, actionDescription: {name: messages.cut}}, //$NON-NLS-0$
				"paste": {defaultHandler: function(data) {return self._doPaste();}, actionDescription: {name: messages.paste}}, //$NON-NLS-0$
				
				"uppercase": {defaultHandler: function(data) {return self._doCase(merge(data,{type: "upper"}));}, actionDescription: {name: messages.uppercase}}, //$NON-NLS-1$ //$NON-NLS-0$
				"lowercase": {defaultHandler: function(data) {return self._doCase(merge(data,{type: "lower"}));}, actionDescription: {name: messages.lowercase}}, //$NON-NLS-1$ //$NON-NLS-0$
				"capitalize": {defaultHandler: function(data) {return self._doCase(merge(data,{unit: "word", type: "capitalize"}));}, actionDescription: {name: messages.capitalize}}, //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				"reversecase": {defaultHandler: function(data) {return self._doCase(merge(data,{type: "reverse"}));}, actionDescription: {name: messages.reversecase}}, //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				
				"toggleOverwriteMode": {defaultHandler: function(data) {return self._doOverwriteMode();}, actionDescription: {name: messages.toggleOverwriteMode}}, //$NON-NLS-0$
				"toggleTabMode": {defaultHandler: function(data) {return self._doTabMode();}, actionDescription: {name: messages.toggleTabMode}}, //$NON-NLS-0$
				"toggleWrapMode": {defaultHandler: function(data) {return self._doWrapMode();}, actionDescription: {name: messages.toggleWrapMode}} //$NON-NLS-0$
			};
		},
		_createRulerParent: function(document, className) {
			var div = util.createElement(document, "div"); //$NON-NLS-0$
			div.className = className;
			div.tabIndex = -1;
			div.style.overflow = "hidden"; //$NON-NLS-0$
			div.style.MozUserSelect = "none"; //$NON-NLS-0$
			div.style.WebkitUserSelect = "none"; //$NON-NLS-0$
			div.style.position = "absolute"; //$NON-NLS-0$
			div.style.top = "0px"; //$NON-NLS-0$
			div.style.bottom = "0px"; //$NON-NLS-0$
			div.style.cursor = "default"; //$NON-NLS-0$
			div.style.display = "none"; //$NON-NLS-0$
			div.setAttribute("aria-hidden", "true"); //$NON-NLS-1$ //$NON-NLS-0$
			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-0$
			}
			var div = util.createElement(rulerParent.ownerDocument, "div"); //$NON-NLS-0$
			div._ruler = ruler;
			div.rulerChanged = true;
			div.style.position = "relative"; //$NON-NLS-0$
			div.style.cssFloat = "left"; //$NON-NLS-0$
			div.style.styleFloat = "left"; //$NON-NLS-0$
			div.style.outline = "none"; //$NON-NLS-0$
			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);
			}
		},
		_createView: function() {
			if (this._clientDiv) { return; }
			var parent = this._parent;
			while (parent.hasChildNodes()) { parent.removeChild(parent.lastChild); }

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

			var viewDiv = util.createElement(document, "div"); //$NON-NLS-0$
			viewDiv.className = "textviewScroll"; //$NON-NLS-0$
			this._viewDiv = viewDiv;
			viewDiv.tabIndex = -1;
			viewDiv.style.position = "absolute"; //$NON-NLS-0$
			viewDiv.style.top = "0px"; //$NON-NLS-0$
			viewDiv.style.bottom = "0px"; //$NON-NLS-0$
			viewDiv.style.borderWidth = "0px"; //$NON-NLS-0$
			viewDiv.style.margin = "0px"; //$NON-NLS-0$
			viewDiv.style.outline = "none"; //$NON-NLS-0$
			viewDiv.style.background = "transparent"; //$NON-NLS-0$
			rootDiv.appendChild(viewDiv);
			
			var rightDiv = this._createRulerParent(document, "textviewRightRuler"); //$NON-NLS-0$
			this._rightDiv = rightDiv;
			rightDiv.style.right = "0px"; //$NON-NLS-0$
				
			var scrollDiv = util.createElement(document, "div"); //$NON-NLS-0$
			this._scrollDiv = scrollDiv;
			scrollDiv.style.margin = "0px"; //$NON-NLS-0$
			scrollDiv.style.borderWidth = "0px"; //$NON-NLS-0$
			scrollDiv.style.padding = "0px"; //$NON-NLS-0$
			viewDiv.appendChild(scrollDiv);
			
			var marginDiv = this._marginDiv = this._createRulerParent(document, "textviewMarginRuler"); //$NON-NLS-0$
			marginDiv.style.zIndex = "4"; //$NON-NLS-0$
			
			if (!util.isIE && !util.isIOS) {
				var clipDiv = util.createElement(document, "div"); //$NON-NLS-0$
				this._clipDiv = clipDiv;
				clipDiv.style.position = "absolute"; //$NON-NLS-0$
				clipDiv.style.overflow = "hidden"; //$NON-NLS-0$
				clipDiv.style.margin = "0px"; //$NON-NLS-0$
				clipDiv.style.borderWidth = "0px"; //$NON-NLS-0$
				clipDiv.style.padding = "0px"; //$NON-NLS-0$
				clipDiv.style.background = "transparent"; //$NON-NLS-0$
				rootDiv.appendChild(clipDiv);
				
				var clipScrollDiv = util.createElement(document, "div"); //$NON-NLS-0$
				this._clipScrollDiv = clipScrollDiv;
				clipScrollDiv.style.position = "absolute"; //$NON-NLS-0$
				clipScrollDiv.style.height = "1px"; //$NON-NLS-0$
				clipScrollDiv.style.top = "-1000px"; //$NON-NLS-0$
				clipScrollDiv.style.background = "transparent"; //$NON-NLS-0$
				clipDiv.appendChild(clipScrollDiv);
			}
			
			this._setFullSelection(this._fullSelection, true);

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

			if (util.isFirefox && !clientDiv.setCapture) {
				var overlayDiv = util.createElement(document, "div"); //$NON-NLS-0$
				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-0$
				overlayDiv.style.zIndex = "2"; //$NON-NLS-0$
				(this._clipDiv || rootDiv).appendChild(overlayDiv);
			}
			clientDiv.contentEditable = "true"; //$NON-NLS-0$
			clientDiv.setAttribute("role", "textbox"); //$NON-NLS-1$ //$NON-NLS-0$
			clientDiv.setAttribute("aria-multiline", "true"); //$NON-NLS-1$ //$NON-NLS-0$
			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 self = this;
			function checkDOMReady() {
				if (!self._rootDiv) { return; }
				self.update(true);
				if (self._metrics.invalid) {
					self._getWindow().setTimeout(function() {
						checkDOMReady();
					}, 100);
				}
			}
			DOMReady(document, rootDiv, "textview", checkDOMReady); //$NON-NLS-0$
		},
		_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},
				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},
				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-0$
						}
						break;
					}
					div = div.nextSibling;
				}
			}
		},
		_destroyView: function() {
			var clientDiv = this._clientDiv;
			if (!clientDiv) { return; }
			this._setGrab(null);
			this._unhookEvents();

			/* Destroy timers */
			var window = this._getWindow();
			if (this._autoScrollTimerID) {
				window.clearTimeout(this._autoScrollTimerID);
				this._autoScrollTimerID = null;
			}
			if (this._updateTimer) {
				window.clearTimeout(this._updateTimer);
				this._updateTimer = null;
			}
			
			var rootDiv = this._rootDiv;
			rootDiv.parentNode.removeChild(rootDiv);

			/* Destroy DOM */
			this._selDiv1 = null;
			this._selDiv2 = null;
			this._selDiv3 = 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._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 window = this._getWindow();
				window.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;
		},
		_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);
		},
		_getClientWidth: function() {
			var viewPad = this._getViewPadding();
			return Math.max(0, this._viewDiv.clientWidth - viewPad.left - viewPad.right);
		},
		_getClipboardText: function (event, handler) {
			var delimiter = this._model.getLineDelimiter();
			var clipboadText, text;
			// IE
			var window = this._getWindow();
			var clipboardData = window.clipboardData;
			// WebKit and Firefox > 21
			if (!clipboardData && event) {
				clipboardData = event.clipboardData;
			}
			if (clipboardData) {
				clipboadText = [];
				text = clipboardData.getData(util.isIE ? "Text" : "text/plain"); //$NON-NLS-1$"//$NON-NLS-0$
				convertDelimiter(text, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);});
				text = clipboadText.join("");
				if (handler) { handler(text); }
				return text;
			}
			if (util.isFirefox) {
				this._ignoreFocus = true;
				var clipboardDiv = this._clipboardDiv;
				var document = this._rootDiv.ownerDocument;
				if (!clipboardDiv) {
					clipboardDiv = util.createElement(document, "div"); //$NON-NLS-0$
					this._clipboardDiv = clipboardDiv;
					clipboardDiv.style.position = "fixed"; //$NON-NLS-0$
					clipboardDiv.style.whiteSpace = "pre"; //$NON-NLS-0$
					clipboardDiv.style.left = "-1000px"; //$NON-NLS-0$
					this._rootDiv.appendChild(clipboardDiv);
				}
				clipboardDiv.innerHTML = "<pre contenteditable=''></pre>"; //$NON-NLS-0$
				clipboardDiv.firstChild.focus();
				var self = this;
				var _getText = function() {
					var noteText = self._getTextFromElement(clipboardDiv);
					clipboardDiv.innerHTML = "";
					clipboadText = [];
					convertDelimiter(noteText, function(t) {clipboadText.push(t);}, function() {clipboadText.push(delimiter);});
					return clipboadText.join("");
				};
				
				/* 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 = document.execCommand("paste", false, null); //$NON-NLS-0$
					} 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 (event) {
						window.setTimeout(function() {
							self.focus();
							text = _getText();
							if (text && handler) {
								handler(text);
							}
							self._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;
				text = _getText();
				if (text && handler) {
					handler(text);
				}
				return text;
			}
			return "";
		},
		_getDOMText: function(child, offsetNode) {
			return child._line.getText(offsetNode);
		},
		_getTextFromElement: function(element) {
			var document = element.ownerDocument;
			var window = document.defaultView;
			if (!window.getSelection) {
				return element.innerText || element.textContent;
			}

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

			var selection = window.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;
		},
		_getLineIndex: function(y) {
			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);
			}
			return Math.max(0, Math.min(lineCount - 1, lineIndex));
		},
		_getRulerParent: function(ruler) {
			switch (ruler.getLocation()) {
				case "left": return this._leftDiv; //$NON-NLS-0$
				case "right": return this._rightDiv; //$NON-NLS-0$
				case "margin": return this._marginDiv; //$NON-NLS-0$
			}
			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 this._selection.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 self = this;
			this._modelListener = {
				/** @private */
				onChanging: function(modelChangingEvent) {
					self._onModelChanging(modelChangingEvent);
				},
				/** @private */
				onChanged: function(modelChangedEvent) {
					self._onModelChanged(modelChangedEvent);
				}
			};
			this._model.addEventListener("preChanging", this._modelListener.onChanging); //$NON-NLS-0$
			this._model.addEventListener("postChanged", this._modelListener.onChanged); //$NON-NLS-0$
			
			this._themeListener = {
				onChanged: function(themeChangedEvent) {
					self._setThemeClass(self._themeClass);
				}
			};
			this._theme.addEventListener("ThemeChanged", this._themeListener.onChanged); //$NON-NLS-0$
			
			var handlers = this._handlers = [];
			var clientDiv = this._clientDiv, viewDiv = this._viewDiv, rootDiv = this._rootDiv;
			var topNode = this._overlayDiv || clientDiv;
			var document = clientDiv.ownerDocument;
			var window = this._getWindow();
			var grabNode = util.isIE ? document : window;
			handlers.push({target: window, type: "resize", handler: function(e) { return self._handleResize(e ? e : window.event);}}); //$NON-NLS-0$
			handlers.push({target: clientDiv, type: "blur", handler: function(e) { return self._handleBlur(e ? e : window.event);}}); //$NON-NLS-0$
			handlers.push({target: clientDiv, type: "focus", handler: function(e) { return self._handleFocus(e ? e : window.event);}}); //$NON-NLS-0$
			handlers.push({target: viewDiv, type: "focus", handler: function(e) { clientDiv.focus(); }}); //$NON-NLS-0$
			handlers.push({target: viewDiv, type: "scroll", handler: function(e) { return self._handleScroll(e ? e : window.event);}}); //$NON-NLS-0$
			handlers.push({target: clientDiv, type: "textInput", handler: function(e) { return self._handleTextInput(e ? e : window.event); }}); //$NON-NLS-0$
			handlers.push({target: clientDiv, type: "keydown", handler: function(e) { return self._handleKeyDown(e ? e : window.event);}}); //$NON-NLS-0$
			handlers.push({target: clientDiv, type: "keypress", handler: function(e) { return self._handleKeyPress(e ? e : window.event);}}); //$NON-NLS-0$
			handlers.push({target: clientDiv, type: "keyup", handler: function(e) { return self._handleKeyUp(e ? e : window.event);}}); //$NON-NLS-0$
			if (util.isIE) {
				handlers.push({target: document, type: "keyup", handler: function(e) { return self._handleDocKeyUp(e ? e : window.event);}}); //$NON-NLS-0$
			}
			handlers.push({target: clientDiv, type: "contextmenu", handler: function(e) { return self._handleContextMenu(e ? e : window.event);}}); //$NON-NLS-0$
			handlers.push({target: clientDiv, type: "copy", handler: function(e) { return self._handleCopy(e ? e : window.event);}}); //$NON-NLS-0$
			handlers.push({target: clientDiv, type: "cut", handler: function(e) { return self._handleCut(e ? e : window.event);}}); //$NON-NLS-0$
			handlers.push({target: clientDiv, type: "paste", handler: function(e) { return self._handlePaste(e ? e : window.event);}}); //$NON-NLS-0$
			handlers.push({target: document, type: "selectionchange", handler: function(e) { return self._handleSelectionChange(e ? e : window.event); }}); //$NON-NLS-0$
			if (util.isIOS || util.isAndroid) {
				handlers.push({target: clientDiv, type: "touchstart", handler: function(e) { return self._handleTouchStart(e ? e : window.event); }}); //$NON-NLS-0$
				handlers.push({target: clientDiv, type: "touchmove", handler: function(e) { return self._handleTouchMove(e ? e : window.event); }}); //$NON-NLS-0$
				handlers.push({target: clientDiv, type: "touchend", handler: function(e) { return self._handleTouchEnd(e ? e : window.event); }}); //$NON-NLS-0$
			} else {
				handlers.push({target: clientDiv, type: "selectstart", handler: function(e) { return self._handleSelectStart(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: clientDiv, type: "mousedown", handler: function(e) { return self._handleMouseDown(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: clientDiv, type: "mouseover", handler: function(e) { return self._handleMouseOver(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: clientDiv, type: "mouseout", handler: function(e) { return self._handleMouseOut(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: grabNode, type: "mouseup", handler: function(e) { return self._handleMouseUp(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: grabNode, type: "mousemove", handler: function(e) { return self._handleMouseMove(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: rootDiv, type: "mousedown", handler: function(e) { return self._handleRootMouseDown(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: rootDiv, type: "mouseup", handler: function(e) { return self._handleRootMouseUp(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: topNode, type: "dragstart", handler: function(e) { return self._handleDragStart(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: topNode, type: "drag", handler: function(e) { return self._handleDrag(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: topNode, type: "dragend", handler: function(e) { return self._handleDragEnd(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: topNode, type: "dragenter", handler: function(e) { return self._handleDragEnter(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: topNode, type: "dragover", handler: function(e) { return self._handleDragOver(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: topNode, type: "dragleave", handler: function(e) { return self._handleDragLeave(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: topNode, type: "drop", handler: function(e) { return self._handleDrop(e ? e : window.event);}}); //$NON-NLS-0$
				handlers.push({target: this._clientDiv, type: util.isFirefox > 26 ? "wheel" : util.isFirefox ? "DOMMouseScroll" : "mousewheel", handler: function(e) { return self._handleMouseWheel(e ? e : window.event); }}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				if (this._clipDiv) {
					handlers.push({target: this._clipDiv, type: util.isFirefox > 26 ? "wheel" : util.isFirefox ? "DOMMouseScroll" : "mousewheel", handler: function(e) { return self._handleMouseWheel(e ? e : window.event); }}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				}
				if (util.isFirefox && (!util.isWindows || util.isFirefox >= 15)) {
					var MutationObserver = window.MutationObserver || window.MozMutationObserver;
					if (MutationObserver) {
						this._mutationObserver = new MutationObserver(function(mutations) { self._handleDataModified(mutations); });
						this._mutationObserver.observe(clientDiv, {subtree: true, characterData: true});
					} else {
						handlers.push({target: this._clientDiv, type: "DOMCharacterDataModified", handler: function (e) { return self._handleDataModified(e ? e : window.event); }}); //$NON-NLS-0$
					}
				}
				if ((util.isFirefox && (!util.isWindows || util.isFirefox >= 15)) || util.isIE) {
					handlers.push({target: this._clientDiv, type: "compositionstart", handler: function (e) { return self._handleCompositionStart(e ? e : window.event); }}); //$NON-NLS-0$
					handlers.push({target: this._clientDiv, type: "compositionend", handler: function (e) { return self._handleCompositionEnd(e ? e : window.event); }}); //$NON-NLS-0$
				}
				if (this._overlayDiv) {
					handlers.push({target: this._overlayDiv, type: "mousedown", handler: function(e) { return self._handleMouseDown(e ? e : window.event);}}); //$NON-NLS-0$
					handlers.push({target: this._overlayDiv, type: "mouseover", handler: function(e) { return self._handleMouseOver(e ? e : window.event);}}); //$NON-NLS-0$
					handlers.push({target: this._overlayDiv, type: "mouseout", handler: function(e) { return self._handleMouseOut(e ? e : window.event);}}); //$NON-NLS-0$
					handlers.push({target: this._overlayDiv, type: "contextmenu", handler: function(e) { return self._handleContextMenu(e ? e : window.event); }}); //$NON-NLS-0$
				}
				if (!this._isW3CEvents) {
					handlers.push({target: this._clientDiv, type: "dblclick", handler: function(e) { return self._handleDblclick(e ? e : window.event); }}); //$NON-NLS-0$
				}
			}

			this._hookRulerEvents(this._leftDiv, handlers);
			this._hookRulerEvents(this._rightDiv, 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 self = this;
			var window = this._getWindow();
			if (util.isIE) {
				handlers.push({target: div, type: "selectstart", handler: function() {return false;}}); //$NON-NLS-0$
			}
			handlers.push({target: div, type: util.isFirefox > 26 ? "wheel" : util.isFirefox ? "DOMMouseScroll" : "mousewheel", handler: function(e) { return self._handleMouseWheel(e ? e : window.event); }}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			handlers.push({target: div, type: "click", handler: function(e) { self._handleRulerEvent(e ? e : window.event); }}); //$NON-NLS-0$
			handlers.push({target: div, type: "dblclick", handler: function(e) { self._handleRulerEvent(e ? e : window.event); }}); //$NON-NLS-0$
			handlers.push({target: div, type: "mousemove", handler: function(e) { self._handleRulerEvent(e ? e : window.event); }}); //$NON-NLS-0$
			handlers.push({target: div, type: "mouseover", handler: function(e) { self._handleRulerEvent(e ? e : window.event); }}); //$NON-NLS-0$
			handlers.push({target: div, type: "mouseout", handler: function(e) { self._handleRulerEvent(e ? e : window.event); }}); //$NON-NLS-0$
		},
		_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-0$
				parent = (options.document || document).getElementById(parent);
			}
			if (!parent) { throw "no parent"; } //$NON-NLS-0$
			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-0$
				}
			}
			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._columnX = -1;
			this._dragOffset = -1;
			this._isRangeRects = (!util.isIE || util.isIE >= 9) && typeof parent.ownerDocument.createRange().getBoundingClientRect === "function"; //$NON-NLS-0$
			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-0$
					} else {
						viewDiv.style.pointerEvents = "none"; //$NON-NLS-0$
					}
				}
			}	
		},
		_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
			};
		},
		_modifyContent: function(e, updateCaret) {
			if (this._readonly && !e._code) {
				return false;
			}
			e.type = "Verify"; //$NON-NLS-0$
			this.onVerify(e);

			if (e.text === null || e.text === undefined) { return false; }
			
			var model = this._model;
			try {
				if (e._ignoreDOMSelection) { this._ignoreDOMSelection = true; }
				model.setText(e.text, e.start, e.end);
			} finally {
				if (e._ignoreDOMSelection) { this._ignoreDOMSelection = false; }
			}

			if (updateCaret) {
				var selection = this._getSelection ();
				selection.setCaret(e.start + e.text.length);
				try {
					if (e._ignoreDOMSelection1) { this._ignoreDOMSelection = true; }
					this._setSelection(selection, true);
				} finally {
					if (e._ignoreDOMSelection1) { this._ignoreDOMSelection = false; }
				}
			}
			this.onModify({type: "Modify"}); //$NON-NLS-0$
			return true;
		},
		_onModelChanged: function(modelChangedEvent) {
			modelChangedEvent.type = "ModelChanged"; //$NON-NLS-0$
			this.onModelChanged(modelChangedEvent);
			modelChangedEvent.type = "Changed"; //$NON-NLS-0$
			var start = modelChangedEvent.start;
			var addedCharCount = modelChangedEvent.addedCharCount;
			var removedCharCount = modelChangedEvent.removedCharCount;
			var addedLineCount = modelChangedEvent.addedLineCount;
			var removedLineCount = modelChangedEvent.removedLineCount;
			var selection = this._getSelection();
			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(selection, 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(new Array(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-0$
			this.onModelChanging(modelChangingEvent);
			modelChangingEvent.type = "Changing"; //$NON-NLS-0$
		},
		_queueUpdate: function() {
			if (this._updateTimer || this._ignoreQueueUpdate) { return; }
			var self = this;
			var window = this._getWindow();
			this._updateTimer = window.setTimeout(function() { 
				self._updateTimer = null;
				self._update();
			}, 0);
		},
		_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 = new Array(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._columnX = -1;
			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 window = this._getWindow();
			if (callback && this._scrollAnimation) {
				var self = this;
				this._animation = new Animation({
					window: window,
					duration: this._scrollAnimation,
					curve: [pixelY, 0],
					onAnimate: function(x) {
						var deltaY = pixelY - Math.floor(x);
						self._scrollView (0, deltaY);
						pixelY -= deltaY;
					},
					onEnd: function() {
						self._animation = null;
						self._scrollView (pixelX, pixelY);
						if (callback) {
							window.setTimeout(callback, 0);
						}
					}
				});
				this._animation.play();
			} else {
				this._scrollView (pixelX, pixelY);
				if (callback) {
					window.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, event) {
			var clipboardText;
			// IE
			var window = this._getWindow();
			var clipboardData = window.clipboardData;
			// WebKit and Firefox > 21
			if (!clipboardData && event) {
				clipboardData = event.clipboardData;
			}
			if (clipboardData) {
				clipboardText = [];
				convertDelimiter(text, function(t) {clipboardText.push(t);}, function() {clipboardText.push(util.platformDelimiter);});
				/*
				* Note that setData() succeeds on Firefox 22 and greater, but the return value is not a boolean like IE and Chrome.
				*/
				var success = clipboardData.setData(util.isIE ? "Text" : "text/plain", clipboardText.join("")); //$NON-NLS-1$ //$NON-NLS-0$
				if (success || util.isFirefox > 21) {
					return true;
				}
			}
			var document = this._parent.ownerDocument;
			var child = util.createElement(document, "pre"); //$NON-NLS-0$
			child.style.position = "fixed"; //$NON-NLS-0$
			child.style.left = "-1000px"; //$NON-NLS-0$
			convertDelimiter(text, 
				function(t) {
					child.appendChild(document.createTextNode(t));
				}, 
				function() {
					child.appendChild(util.createElement(document, "br")); //$NON-NLS-0$
				}
			);
			child.appendChild(document.createTextNode(" ")); //$NON-NLS-0$
			this._clientDiv.appendChild(child);
			var range = document.createRange();
			range.setStart(child.firstChild, 0);
			range.setEndBefore(child.lastChild);
			var sel = window.getSelection();
			if (sel.rangeCount > 0) { sel.removeAllRanges(); }
			sel.addRange(range);
			var self = this;
			/** @ignore */
			var cleanup = function() {
				if (child && child.parentNode === self._clientDiv) {
					self._clientDiv.removeChild(child);
				}
				self._updateDOMSelection();
			};
			var result = false;
			/* 
			* Try execCommand first, it works on firefox with clipboard permission,
			* chrome 5, safari 4.
			*/
			this._ignoreCopy = true;
			try {
				result = document.execCommand("copy", false, null); //$NON-NLS-0$
			} catch (e) {}
			this._ignoreCopy = false;
			if (!result) {
				if (event) {
					window.setTimeout(cleanup, 0);
					return false;
				}
			}
			/* no event and no permission, copy can not be done */
			cleanup();
			return true;
		},
		_setDOMSelection: function (startNode, startOffset, endNode, endOffset, startCaret) {
			this._setDOMFullSelection(startNode, startOffset, endNode, endOffset);
			var start = startNode._line.getNodeOffset(startOffset);
			var end = endNode._line.getNodeOffset(endOffset);
			var range;
			var window = this._getWindow();
			var document = this._parent.ownerDocument;
			if (window.getSelection) {
				//W3C
				var sel = window.getSelection();
				range = document.createRange();
				range.setStart(start.node, start.offset);
				range.setEnd(end.node, end.offset);
				if (this._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))
				{
					this._anchorNode = start.node;
					this._anchorOffset = start.offset;
					this._focusNode = end.node;
					this._focusOffset = end.offset;
					this._ignoreSelect = false;
					if (sel.rangeCount > 0) { sel.removeAllRanges(); }
					sel.addRange(range);
					this._ignoreSelect = true;
				}
				if (this._cursorDiv) {
					range = document.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 = this._cursorDiv.parentNode;
					var clientRect = cursorParent.getBoundingClientRect();
					if (rect && clientRect) {
						this._cursorDiv.style.top = (rect.top - clientRect.top + cursorParent.scrollTop) + "px"; //$NON-NLS-0$
						this._cursorDiv.style.left = (rect.left - clientRect.left + cursorParent.scrollLeft) + "px"; //$NON-NLS-0$
					}
				}
			} else if (document.selection) {
				if (!this._hasFocus) { return; }
				//IE < 9
				var body = document.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(document, "div"); //$NON-NLS-0$
				body.appendChild(child);
				body.removeChild(child);
				
				range = body.createTextRange();
				range.moveToElementText(start.node.parentNode);
				range.moveStart("character", start.offset); //$NON-NLS-0$
				var endRange = body.createTextRange();
				endRange.moveToElementText(end.node.parentNode);
				endRange.moveStart("character", end.offset); //$NON-NLS-0$
				range.setEndPoint("EndToStart", endRange); //$NON-NLS-0$
				this._ignoreSelect = false;
				range.select();
				this._ignoreSelect = true;
			}
		},
		_setDOMFullSelection: function(startNode, startOffset, endNode, endOffset) {
			if (!this._selDiv1) { return; }
			var selDiv = this._selDiv1;
			selDiv.style.width = "0px"; //$NON-NLS-0$
			selDiv.style.height = "0px"; //$NON-NLS-0$
			selDiv = this._selDiv2;
			selDiv.style.width = "0px"; //$NON-NLS-0$
			selDiv.style.height = "0px"; //$NON-NLS-0$
			selDiv = this._selDiv3;
			selDiv.style.width = "0px"; //$NON-NLS-0$
			selDiv.style.height = "0px"; //$NON-NLS-0$
			if (startNode === endNode && startOffset === endOffset) { return; }
			var viewPad = this._getViewPadding();
			var clientRect = this._clientDiv.getBoundingClientRect();
			var viewRect = this._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 (this._clipDiv) {
				var clipRect = this._clipDiv.getBoundingClientRect();
				hd = clipRect.left - this._clipDiv.scrollLeft;
				vd = clipRect.top;
			} else {
				var rootpRect = this._rootDiv.getBoundingClientRect();
				hd = rootpRect.left;
				vd = rootpRect.top;
			}
			this._ignoreDOMSelection = true;
			var startLine = new TextLine(this, startNode.lineIndex, startNode);
			var startRect = startLine.getBoundingClientRect(startOffset, false);
			var l = startRect.left;
			var endLine = new TextLine(this, endNode.lineIndex, endNode);
			var endRect = endLine.getBoundingClientRect(endOffset, false);
			var r = endRect.left;
			this._ignoreDOMSelection = false;
			var sel1Div = this._selDiv1;
			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-0$
			sel1Div.style.top = (sel1Top - vd) + "px"; //$NON-NLS-0$
			sel1Div.style.width = Math.max(0, sel1Right - sel1Left) + "px"; //$NON-NLS-0$
			sel1Div.style.height = Math.max(0, sel1Bottom - sel1Top) + "px"; //$NON-NLS-0$
			if (startNode.lineIndex === endNode.lineIndex) {
				sel1Right = Math.min(r, right);
				sel1Div.style.width = Math.max(0, sel1Right - sel1Left) + "px"; //$NON-NLS-0$
			} 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._selDiv3;
				sel3Div.style.left = (sel3Left - hd) + "px"; //$NON-NLS-0$
				sel3Div.style.top = (sel3Top - vd) + "px"; //$NON-NLS-0$
				sel3Div.style.width = Math.max(0, sel3Right - sel3Left) + "px"; //$NON-NLS-0$
				sel3Div.style.height = Math.max(0, sel3Bottom - sel3Top) + "px"; //$NON-NLS-0$
				if (Math.abs(startNode.lineIndex - endNode.lineIndex) > 1) {
					var sel2Div = this._selDiv2;
					sel2Div.style.left = (left - hd)  + "px"; //$NON-NLS-0$
					sel2Div.style.top = (sel1Bottom - vd) + "px"; //$NON-NLS-0$
					sel2Div.style.width = Math.max(0, right - left) + "px"; //$NON-NLS-0$
					sel2Div.style.height = Math.max(0, sel3Top - sel1Bottom) + "px"; //$NON-NLS-0$
				}
			}
		},
		_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-0$
			}
			var line = this._getLineNext();
			while (line) {
				line._line.updateLinks();
				line = this._getLineNext(line);
			}
			this._updateDOMSelection();
		},
		_setSelection: function (selection, scroll, update, callback, pageScroll) {
			if (selection) {
				this._columnX = -1;
				if (update === undefined) { update = true; }
				var oldSelection = this._selection; 
				this._selection = selection;

				/* 
				* 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 (!oldSelection.equals(selection)) {
					var e = {
						type: "Selection", //$NON-NLS-0$
						oldValue: {start:oldSelection.start, end:oldSelection.end},
						newValue: {start:selection.start, end:selection.end}
					};
					this.onSelection(e);
				}
			}
		},
		_setSelectionTo: function (x, y, extent, drag) {
			var model = this._model, offset;
			var selection = this._getSelection();
			var pt = this.convert({x: x, y: y}, "page", "document"); //$NON-NLS-1$ //$NON-NLS-0$
			var lineIndex = this._getLineIndex(pt.y), line;
			if (this._clickCount === 1) {
				line = this._getLine(lineIndex);
				offset = line.getOffset(pt.x, pt.y - this._getLinePixel(lineIndex));
				line.destroy();
				if (drag && !extent) {
					if (selection.start <= offset && offset < selection.end) {
						this._dragOffset = offset;
						return false;
					}
				}
				selection.extend(offset);
				if (!extent) { selection.collapse(); }
			} else {
				var word = (this._clickCount & 1) === 0;
				var start, end;
				if (word) {
					line = this._getLine(lineIndex);
					offset = line.getOffset(pt.x, pt.y - this._getLinePixel(lineIndex));
					if (this._doubleClickSelection) {
						if (offset >= this._doubleClickSelection.start) {
							start = this._doubleClickSelection.start;
							end = line.getNextOffset(offset, {unit:"wordend", count:1}); //$NON-NLS-0$
						} else {
							start = line.getNextOffset(offset, {unit:"word", count:-1}); //$NON-NLS-0$
							end = this._doubleClickSelection.end;
						}
					} else {
						start = line.getNextOffset(offset, {unit:"word", count:-1}); //$NON-NLS-0$
						end = line.getNextOffset(start, {unit:"wordend", count:1}); //$NON-NLS-0$
					}
					line.destroy();
				} 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(selection, true, true);
			return true;
		},
		_setFullSelection: function(fullSelection, init) {
			this._fullSelection = fullSelection;
			if (util.isWebkit) {
				this._fullSelection = true;
			}
			var parent = this._clipDiv || this._rootDiv;
			if (!parent) {
				return;
			}
			if (!this._fullSelection) {
				if (this._selDiv1) {
					parent.removeChild(this._selDiv1);
					this._selDiv1 = null;
				}
				if (this._selDiv2) {
					parent.removeChild(this._selDiv2);
					this._selDiv2 = null;
				}
				if (this._selDiv3) {
					parent.removeChild(this._selDiv3);
					this._selDiv3 = null;
				}
				return;
			}
			
			if (!this._selDiv1 && (this._fullSelection && !util.isIOS)) {
				var document = parent.ownerDocument;
				this._highlightRGB = util.isWebkit ? "transparent" : "Highlight"; //$NON-NLS-1$ //$NON-NLS-0$
				var selDiv1 = util.createElement(document, "div"); //$NON-NLS-0$
				this._selDiv1 = selDiv1;
				selDiv1.style.position = "absolute"; //$NON-NLS-0$
				selDiv1.style.borderWidth = "0px"; //$NON-NLS-0$
				selDiv1.style.margin = "0px"; //$NON-NLS-0$
				selDiv1.style.padding = "0px"; //$NON-NLS-0$
				selDiv1.style.outline = "none"; //$NON-NLS-0$
				selDiv1.style.background = this._highlightRGB;
				selDiv1.style.width = "0px"; //$NON-NLS-0$
				selDiv1.style.height = "0px"; //$NON-NLS-0$
				selDiv1.style.zIndex = "0"; //$NON-NLS-0$
				parent.appendChild(selDiv1);
				var selDiv2 = util.createElement(document, "div"); //$NON-NLS-0$
				this._selDiv2 = selDiv2;
				selDiv2.style.position = "absolute"; //$NON-NLS-0$
				selDiv2.style.borderWidth = "0px"; //$NON-NLS-0$
				selDiv2.style.margin = "0px"; //$NON-NLS-0$
				selDiv2.style.padding = "0px"; //$NON-NLS-0$
				selDiv2.style.outline = "none"; //$NON-NLS-0$
				selDiv2.style.background = this._highlightRGB;
				selDiv2.style.width = "0px"; //$NON-NLS-0$
				selDiv2.style.height = "0px"; //$NON-NLS-0$
				selDiv2.style.zIndex = "0"; //$NON-NLS-0$
				parent.appendChild(selDiv2);
				var selDiv3 = util.createElement(document, "div"); //$NON-NLS-0$
				this._selDiv3 = selDiv3;
				selDiv3.style.position = "absolute"; //$NON-NLS-0$
				selDiv3.style.borderWidth = "0px"; //$NON-NLS-0$
				selDiv3.style.margin = "0px"; //$NON-NLS-0$
				selDiv3.style.padding = "0px"; //$NON-NLS-0$
				selDiv3.style.outline = "none"; //$NON-NLS-0$
				selDiv3.style.background = this._highlightRGB;
				selDiv3.style.width = "0px"; //$NON-NLS-0$
				selDiv3.style.height = "0px"; //$NON-NLS-0$
				selDiv3.style.zIndex = "0"; //$NON-NLS-0$
				parent.appendChild(selDiv3);
				
				/*
				* Bug in Firefox. The Highlight color is mapped to list selection
				* background instead of the text selection background.  The fix
				* is to map known colors using a table or fallback to light blue.
				*/
				if (util.isFirefox && util.isMac) {
					var window = this._getWindow();
					var style = window.getComputedStyle(selDiv3, null);
					var rgb = style.getPropertyValue("background-color"); //$NON-NLS-0$
					switch (rgb) {
						case "rgb(119, 141, 168)": rgb = "rgb(199, 208, 218)"; break; //$NON-NLS-1$ //$NON-NLS-0$
						case "rgb(127, 127, 127)": rgb = "rgb(198, 198, 198)"; break; //$NON-NLS-1$ //$NON-NLS-0$
						case "rgb(255, 193, 31)": rgb = "rgb(250, 236, 115)"; break; //$NON-NLS-1$ //$NON-NLS-0$
						case "rgb(243, 70, 72)": rgb = "rgb(255, 176, 139)"; break; //$NON-NLS-1$ //$NON-NLS-0$
						case "rgb(255, 138, 34)": rgb = "rgb(255, 209, 129)"; break; //$NON-NLS-1$ //$NON-NLS-0$
						case "rgb(102, 197, 71)": rgb = "rgb(194, 249, 144)"; break; //$NON-NLS-1$ //$NON-NLS-0$
						case "rgb(140, 78, 184)": rgb = "rgb(232, 184, 255)"; break; //$NON-NLS-1$ //$NON-NLS-0$
						default: rgb = "rgb(180, 213, 255)"; break; //$NON-NLS-0$
					}
					this._highlightRGB = rgb;
					selDiv1.style.background = rgb;
					selDiv2.style.background = rgb;
					selDiv3.style.background = rgb;
				}
				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-0$
					cursorDiv.className = "textviewBlockCursor"; //$NON-NLS-0$
					this._cursorDiv = cursorDiv;
					cursorDiv.tabIndex = -1;
					cursorDiv.style.zIndex = "2"; //$NON-NLS-0$
					cursorDiv.style.color = "transparent"; //$NON-NLS-0$
					cursorDiv.style.position = "absolute"; //$NON-NLS-0$
					cursorDiv.style.pointerEvents = "none"; //$NON-NLS-0$
					cursorDiv.innerHTML = "&nbsp;"; //$NON-NLS-0$
					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-0$
			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-0$
		},
		_setSingleMode: function (singleMode, init) {
			this._singleMode = singleMode;
			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-0$
			}
			this._theme = theme;
			if (this._theme) {
				this._theme.addEventListener("ThemeChanged", this._themeListener.onChanged); //$NON-NLS-0$
			}
			this._setThemeClass(this._themeClass);
		},
		_setThemeClass: function (themeClass, init) {
			this._themeClass = themeClass;
			var viewContainerClass = "textview"; //$NON-NLS-0$
			var globalThemeClass = this._theme.getThemeClass();
			if (globalThemeClass) { viewContainerClass += " " + globalThemeClass; } //$NON-NLS-0$
			if (this._themeClass && globalThemeClass !== this._themeClass) { viewContainerClass += " " + this._themeClass; } //$NON-NLS-0$
			this._rootDiv.className = viewContainerClass;
			this._updateStyle(init);
		},
		_setWrapMode: function (wrapMode, init) {
			this._wrapMode = wrapMode && this._wrappable;
			var clientDiv = this._clientDiv;
			if (this._wrapMode) {
				clientDiv.style.whiteSpace = "pre-wrap"; //$NON-NLS-0$
				clientDiv.style.wordWrap = "break-word"; //$NON-NLS-0$
			} else {
				clientDiv.style.whiteSpace = "pre"; //$NON-NLS-0$
				clientDiv.style.wordWrap = "normal"; //$NON-NLS-0$
			}
			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; }
			var model = this._model;
			var selection = this._getSelection();
			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-0$
			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-0$
			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-0$
						pixelY += Math.floor(flag ? (1 - viewAnchorOffset) * clientHeight : -viewAnchorOffset * clientHeight);
					} else if (viewAnchor === "bottom") { //$NON-NLS-0$
						pixelY += Math.floor(flag ? viewAnchorOffset * clientHeight : -(1 - viewAnchorOffset) * clientHeight);
					} else if (viewAnchor === "center") { //$NON-NLS-0$
						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-0$
							pixelY += Math.floor(-selectionHeight);
						} else if (selectionAnchor === "bottom" && caret !== end) { //$NON-NLS-0$
							pixelY += Math.floor(selectionHeight);
						} else if (selectionAnchor === "center") { //$NON-NLS-0$
							pixelY += Math.floor(selectionHeight / 2);
						} else {
							// caret is the default
						}
					}
				} else if (pixelY !== 0 && typeof showOptions === "number") { //$NON-NLS-0$
					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 selection = this._getSelection();
			if (!selection.isEmpty()) {
				this._modifyContent({text: "", start: selection.start, end: selection.end}, true);
			}
			this._imeOffset = selection.start;
		},
		_unhookEvents: function() {
			this._model.removeEventListener("preChanging", this._modelListener.onChanging); //$NON-NLS-0$
			this._model.removeEventListener("postChanged", this._modelListener.onChanged); //$NON-NLS-0$
			this._theme.removeEventListener("ThemeChanged", this._themeListener.onChanged); //$NON-NLS-0$
			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();
				this._mutationObserver = null;
			}
		},
		_updateDOMSelection: function () {
			if (this._redrawCount > 0) { return; }
			if (this._ignoreDOMSelection) { return; }
			if (!this._clientDiv) { return; }
			var selection = this._getSelection();
			var model = this._model;
			var startLine = model.getLineAtOffset(selection.start);
			var endLine = model.getLineAtOffset(selection.end);
			var firstNode = this._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 = this._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 = this._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 = this._getLineNode(endLine);
				bottomOffset = selection.end;
			}
			this._setDOMSelection(topNode, topOffset, bottomNode, bottomOffset, selection.caret);
		},
		_update: function(hScrollOnly) {
			if (this._redrawCount > 0) { return; }
			if (this._updateTimer) {
				var window = this._getWindow();
				window.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-0$
			}
			
			/*
			* 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-0$
							child.lineIndex = -1;
						} else {
							clientDiv.removeChild(child);
						}
					}
					child = nextChild;
				}
	
				child = this._getLineNext();
				var document = viewDiv.ownerDocument;
				var frag = document.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 = document.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-0$
				}
	
				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._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-0$
				viewDiv.style.right = rightWidth + "px"; //$NON-NLS-0$

				/* Need to set the height first in order for the width to consider the vertical scrollbar */
				var scrollDiv = this._scrollDiv;
				scrollDiv.style.height = scrollHeight + "px"; //$NON-NLS-0$
				
				clientWidth = this._getClientWidth();
				if (!this._singleMode && !this._wrapMode) {
					var clientHeightNoScroll = clientHeight, clientHeightScroll = clientHeight;
					var oldHScroll = viewDiv.style.overflowX === "scroll"; //$NON-NLS-0$
					if (oldHScroll) {
						clientHeightNoScroll += scrollbarWidth;
					} else {
						clientHeightScroll -= scrollbarWidth;
					}
					var clientWidthNoScroll = clientWidth, clientWidthScroll = clientWidth;
					var oldVScroll = viewDiv.style.overflowY === "scroll"; //$NON-NLS-0$
					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-0$
					}
					if (oldVScroll !== vScroll) {
						viewDiv.style.overflowY = vScroll ? "scroll" : "hidden"; //$NON-NLS-1$ //$NON-NLS-0$
					}
					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, 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-0$
				if (this._clipScrollDiv) {
					this._clipScrollDiv.style.width = width + "px"; //$NON-NLS-0$
				}
				/* Get the left scroll after setting the width of the scrollDiv as this can change the horizontal scroll offset. */
				scroll = this._getScroll(false);
			}
			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-0$
				this._vScrollDiv.style.top = Math.floor(Math.max(0, (scroll.y * trackHeight / scrollHeight))) + "px"; //$NON-NLS-0$
				this._vScrollDiv.style.height = thumbHeight + "px"; //$NON-NLS-0$
			}
			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-0$
				this._hScrollDiv.style.top = (clientHeight - 9) + "px"; //$NON-NLS-0$
				this._hScrollDiv.style.width = thumbWidth + "px"; //$NON-NLS-0$
			}
			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-0$
				marginDiv.style.bottom = (viewDiv.style.overflowX === "scroll" ? scrollbarWidth : 0) + "px"; //$NON-NLS-1$ //$NON-NLS-0$
			}
			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-0$
				clipDiv.style.top = clipTop + "px"; //$NON-NLS-0$
				clipDiv.style.right = (rootWidth - clipWidth - clipLeft) + "px"; //$NON-NLS-0$
				clipDiv.style.bottom = (rootHeight - clipHeight - clipTop) + "px"; //$NON-NLS-0$
				clientDiv.style.left = clientLeft + "px"; //$NON-NLS-0$
				clientDiv.style.top = clientTop + "px"; //$NON-NLS-0$
				clientDiv.style.width = scrollWidth + "px"; //$NON-NLS-0$
				clientDiv.style.height = (clientHeight + top) + "px"; //$NON-NLS-0$
				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-0$
				clientDiv.style.left = (-left + leftWidth + viewPad.left) + "px"; //$NON-NLS-0$
				clientDiv.style.width = (this._wrapMode || util.isWebkit ? scrollWidth : clientWidth + left) + "px"; //$NON-NLS-0$
				if (!hScrollOnly) {
					clientDiv.style.top = (-top + viewPad.top) + "px"; //$NON-NLS-0$
					clientDiv.style.height = (clientHeight + top) + "px"; //$NON-NLS-0$
				}
				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._wrapMode) {
				viewDiv.style.overflowX = "hidden"; //$NON-NLS-0$
				viewDiv.style.overflowY = "scroll"; //$NON-NLS-0$
			} else {
				viewDiv.style.overflow = "hidden"; //$NON-NLS-0$
			}
		},
		_updateRuler: function (divRuler, topIndex, bottomIndex, rootHeight) {
			if (!divRuler) { return; }
			var document = this._parent.ownerDocument;
			var lineHeight = this._getLineHeight();
			var viewPad = this._getViewPadding();
			var div = divRuler.firstChild;
			while (div) {
				var ruler = div._ruler;
				var offset = lineHeight;
				var overview = ruler.getOverview();
				if (overview === "page") { offset += this._topIndexY; } //$NON-NLS-0$
				div.style.top = -offset + "px"; //$NON-NLS-0$
				div.style.height = (rootHeight + offset) + "px"; //$NON-NLS-0$
				
				if (div.rulerChanged) {
					applyStyle(ruler.getRulerStyle(), div);
				}
				
				var widthDiv;
				var child = div.firstChild;
				if (child) {
					widthDiv = child;
					child = child.nextSibling;
				} else {
					widthDiv = util.createElement(document, "div"); //$NON-NLS-0$
					widthDiv.style.visibility = "hidden"; //$NON-NLS-0$
					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-0$
					}
				}

				var lineDiv, frag, annotations;
				if (overview === "page") { //$NON-NLS-0$
					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 = document.createDocumentFragment();
					for (lineIndex=topIndex; lineIndex<=bottomIndex; lineIndex++) {
						if (!child || child.lineIndex > lineIndex) {
							lineDiv = util.createElement(document, "div"); //$NON-NLS-0$
							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-0$
							frag.appendChild(lineDiv);
						} else {
							if (frag.firstChild) {
								div.insertBefore(frag, child);
								frag = document.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;
					if (contentHeight < trackHeight) {
						divHeight = lineHeight;
					} else {
						divHeight = trackHeight / lineCount;
					}
					if (div.rulerChanged) {
						var count = div.childNodes.length;
						while (count > 1) {
							div.removeChild(div.lastChild);
							count--;
						}
						annotations = ruler.getAnnotations(0, lineCount);
						frag = document.createDocumentFragment();
						for (var prop in annotations) {
							lineIndex = prop >>> 0;
							if (lineIndex < 0) { continue; }
							lineDiv = util.createElement(document, "div"); //$NON-NLS-0$
							annotation = annotations[prop];
							applyStyle(annotation.style, lineDiv);
							lineDiv.style.position = "absolute"; //$NON-NLS-0$
							lineDiv.style.top = this._metrics.scrollWidth + lineHeight + Math.floor(lineIndex * divHeight) + "px"; //$NON-NLS-0$
							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-0$
							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-0$
			}
			if (util.isFirefox && util.isMac && this._highlightRGB && this._highlightRGB !== "Highlight") { //$NON-NLS-0$
				styleText += "\n.textview ::-moz-selection {background: " + this._highlightRGB + ";}"; //$NON-NLS-1$ //$NON-NLS-0$
			}
			if (styleText) {
				var document = this._clientDiv.ownerDocument;
				var node = document.getElementById("_textviewStyle"); //$NON-NLS-0$
				if (!node) {
					node = util.createElement(document, "style"); //$NON-NLS-0$
					node.id = "_textviewStyle"; //$NON-NLS-0$
					var head = document.getElementsByTagName("head")[0] || document.documentElement; //$NON-NLS-0$
					node.appendChild(document.createTextNode(styleText));
					head.insertBefore(node, head.firstChild);
				} else {
					node.removeChild(node.firstChild);
					node.appendChild(document.createTextNode(styleText));
				}
			}
		},
		_updateStyle: function (init) {
			if (!init && util.isIE) {
				this._rootDiv.style.lineHeight = "normal"; //$NON-NLS-0$
			}
			var metrics = this._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-0$
			} else {
				this._rootDiv.style.lineHeight = "normal"; //$NON-NLS-0$
			}
			this._updateStyleSheet();
			if (util.isMac && util.isWebkit) {
				var viewDiv = this._viewDiv;
				if (!metrics.invalid && metrics.scrollWidth === 0) {
					viewDiv.style.pointerEvents = "none"; //$NON-NLS-0$
					viewDiv.style.zIndex = "2"; //$NON-NLS-0$
				} else {
					viewDiv.style.pointerEvents = ""; //$NON-NLS-0$
					viewDiv.style.zIndex = ""; //$NON-NLS-0$
				}
			}
			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
 ******************************************************************************/

/*global define */

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, 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
 ******************************************************************************/

/*global define Node */

define("orion/editor/tooltip", [ //$NON-NLS-0$
	'i18n!orion/editor/nls/messages', //$NON-NLS-0$
	'orion/editor/textView', //$NON-NLS-0$
	'orion/editor/textModel', //$NON-NLS-0$
	'orion/editor/projectionTextModel', //$NON-NLS-0$
	'orion/editor/util', //$NON-NLS-0$
	'orion/util' //$NON-NLS-0$
], function(messages, mTextView, mTextModel, mProjectionTextModel, textUtil, util) {

	/** @private */
	function Tooltip (view) {
		this._view = view;
		this._fadeDelay = 500;
		this._hideDelay = 200;
		this._showDelay = 500;
		this._autoHideDelay = 5000;
		this._create(view.getOptions("parent").ownerDocument); //$NON-NLS-0$
	}
	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$
			var tooltipContents = this._tooltipContents = util.createElement(document, "div"); //$NON-NLS-0$
			tooltipDiv.appendChild(tooltipContents);
			document.body.appendChild(tooltipDiv);
			var self = this;
			textUtil.addEventListener(tooltipDiv, "mouseover", function(event) { //$NON-NLS-0$
				if (!self._hideDelay) { return; }
				var window = self._getWindow();
				if (self._delayedHideTimeout) {
					window.clearTimeout(self._delayedHideTimeout);
					self._delayedHideTimeout = null;
				}
				
				if (self._hideTimeout) {
					window.clearTimeout(self._hideTimeout);
					self._hideTimeout = null;
				}
				self._nextTarget = null;
			}, false);
			textUtil.addEventListener(tooltipDiv, "mouseout", function(event) { //$NON-NLS-0$
				var relatedTarget = event.relatedTarget || event.toElement;
				if (relatedTarget === tooltipDiv || self._hasFocus()) { return; }
				if (relatedTarget) {
					if (textUtil.contains(tooltipDiv, relatedTarget)) { return; }
				}
				self._hide();
			}, false);
			textUtil.addEventListener(tooltipDiv, "keydown", function(event) { //$NON-NLS-0$
				if (event.keyCode === 27) {
					self._hide();
				}
			}, false);
			textUtil.addEventListener(document, "mousedown", this._mouseDownHandler = function(event) { //$NON-NLS-0$
				if (!self.isVisible()) { return; }
				if (textUtil.contains(tooltipDiv, event.target || event.srcElement)) { return; }
				self._hide();
			}, true);
			this._view.addEventListener("Destroy", function() { //$NON-NLS-0$
				self.destroy();
			});
			this._hide();
		},
		_getWindow: function() {
			var document = this._tooltipDiv.ownerDocument;
			return document.defaultView || document.parentWindow;
		},
		destroy: function() {
			if (!this._tooltipDiv) { return; }
			this._hide();
			var parent = this._tooltipDiv.parentNode;
			if (parent) { parent.removeChild(this._tooltipDiv); }
			var document = this._tooltipDiv.ownerDocument;
			textUtil.removeEventListener(document, "mousedown", this._mouseDownHandler, true); //$NON-NLS-0$
			this._tooltipDiv = null;
		},
		_hasFocus: function() {
			var tooltipDiv = this._tooltipDiv;
			if (!tooltipDiv) { return false; }
			var document = tooltipDiv.ownerDocument;
			return textUtil.contains(tooltipDiv, document.activeElement);
		},
		hide: function(hideDelay) {
			if (hideDelay === undefined) {
				hideDelay = this._hideDelay;
			}
			var window = this._getWindow();
			if (this._delayedHideTimeout) {
				window.clearTimeout(this._delayedHideTimeout);
				this._delayedHideTimeout = null;
			}
			var self = this;
			if (!hideDelay) {
				self._hide();
				self.setTarget(self._nextTarget, 0);
			} else {
				self._delayedHideTimeout = window.setTimeout(function() {
					self._delayedHideTimeout = null;
					self._hide();
					self.setTarget(self._nextTarget, 0);
				}, hideDelay);
			}
		},
		_hide: function() {
			var tooltipDiv = this._tooltipDiv;
			if (!tooltipDiv) { return; }
			if (this._hasFocus()) {
				this._view.focus();
			}
			if (this._contentsView) {
				this._contentsView.destroy();
				this._contentsView = null;
			}
			if (this._tooltipContents) {
				this._tooltipContents.innerHTML = "";
			}
			tooltipDiv.style.visibility = "hidden"; //$NON-NLS-0$
			var window = this._getWindow();
			if (this._showTimeout) {
				window.clearTimeout(this._showTimeout);
				this._showTimeout = null;
			}
			if (this._delayedHideTimeout) {
				window.clearTimeout(this._delayedHideTimeout);
				this._delayedHideTimeout = null;
			}
			if (this._hideTimeout) {
				window.clearTimeout(this._hideTimeout);
				this._hideTimeout = null;
			}
			if (this._fadeTimeout) {
				window.clearInterval(this._fadeTimeout);
				this._fadeTimeout = null;
			}
		},
		isVisible: function() {
			return this._tooltipDiv && this._tooltipDiv.style.visibility === "visible"; //$NON-NLS-0$
		},
		setTarget: function(target, delay, hideDelay) {
			var visible = this.isVisible();
			if (visible) {
				if (this._hasFocus()) { return; }
				this._nextTarget = target;
				this.hide(hideDelay);
			} else {
				this._target = target;
				if (target) {
					var self = this;
					var window = self._getWindow();
					if (self._showTimeout) {
						window.clearTimeout(self._showTimeout);
						self._showTimeout = null;
					}
					if (delay === 0) {
						self.show(true);
					} else {
						self._showTimeout = window.setTimeout(function() {
							self._showTimeout = null;
							self.show(true);
						}, delay ? delay : self._showDelay);
					}
				}
			}
		},
		show: function(autoHide) {
			if (!this._target) { return; }
			var info = this._target.getTooltipInfo();
			if (!info) { return; }
			var tooltipDiv = this._tooltipDiv, tooltipContents = this._tooltipContents;
			tooltipDiv.style.left = tooltipDiv.style.right = tooltipDiv.style.width = tooltipDiv.style.height = 
				tooltipContents.style.width = tooltipContents.style.height = "auto"; //$NON-NLS-0$
			var contents = info.contents;
			if (contents instanceof Array) {
				contents = this._getAnnotationContents(contents);
			}
			if (typeof contents === "string") { //$NON-NLS-0$
				tooltipContents.innerHTML = contents;
			} else if (this._isNode(contents)) {
				tooltipContents.appendChild(contents);
			} else if (contents instanceof mProjectionTextModel.ProjectionTextModel) {
				var view = this._view;
				var options = view.getOptions();
				options.wrapMode = false;
				options.parent = tooltipContents;
				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);
				var size = contentsView.computeSize();
				tooltipContents.style.width = size.width + "px"; //$NON-NLS-0$
				tooltipContents.style.height = size.height + "px"; //$NON-NLS-0$
				contentsView.resize();
			} else {
				return;
			}
			var documentElement = tooltipDiv.ownerDocument.documentElement;
			if (info.anchor === "right") { //$NON-NLS-0$
				var right = documentElement.clientWidth - info.x;
				tooltipDiv.style.right = right + "px"; //$NON-NLS-0$
				tooltipDiv.style.maxWidth = (documentElement.clientWidth - right - 10) + "px"; //$NON-NLS-0$
			} else {
				var left = parseInt(this._getNodeStyle(tooltipDiv, "padding-left", "0"), 10); //$NON-NLS-1$ //$NON-NLS-0$
				left += parseInt(this._getNodeStyle(tooltipDiv, "border-left-width", "0"), 10); //$NON-NLS-1$ //$NON-NLS-0$
				left = info.x - left;
				tooltipDiv.style.left = left + "px"; //$NON-NLS-0$
				tooltipDiv.style.maxWidth = (documentElement.clientWidth - left - 10) + "px"; //$NON-NLS-0$
			}
			var top = parseInt(this._getNodeStyle(tooltipDiv, "padding-top", "0"), 10); //$NON-NLS-1$ //$NON-NLS-0$
			top += parseInt(this._getNodeStyle(tooltipDiv, "border-top-width", "0"), 10); //$NON-NLS-1$ //$NON-NLS-0$
			top = info.y - top;
			tooltipDiv.style.top = top + "px"; //$NON-NLS-0$
			tooltipDiv.style.maxHeight = (documentElement.clientHeight - top - 10) + "px"; //$NON-NLS-0$
			tooltipDiv.style.opacity = "1"; //$NON-NLS-0$
			tooltipDiv.style.visibility = "visible"; //$NON-NLS-0$
			if (autoHide) {
				var self = this;
				var window = this._getWindow();
				self._hideTimeout = window.setTimeout(function() {
					self._hideTimeout = null;
					var opacity = parseFloat(self._getNodeStyle(tooltipDiv, "opacity", "1")); //$NON-NLS-1$ //$NON-NLS-0$
					self._fadeTimeout = window.setInterval(function() {
						if (tooltipDiv.style.visibility === "visible" && opacity > 0) { //$NON-NLS-0$
							opacity -= 0.1;
							tooltipDiv.style.opacity = opacity;
							return;
						}
						self._hide();
					}, self._fadeDelay / 10);
				}, self._autoHideDelay);
			}
		},
		_getAnnotationContents: function(annotations) {
			var annotation;
			var newAnnotations = [];
			for (var j = 0; j < annotations.length; j++) {
				annotation = annotations[j];
				if (annotation.title !== "" && !annotation.groupAnnotation) { 
					newAnnotations.push(annotation); 
				}
			}
			annotations = newAnnotations;
			if (annotations.length === 0) {
				return null;
			}
			var self = this;
			var html;
			var document = this._tooltipDiv.ownerDocument;
			var view = this._view;
			var model = view.getModel();
			var baseModel = model.getBaseModel ? model.getBaseModel() : model;
			function getText(start, end) {
				var textStart = baseModel.getLineStart(baseModel.getLineAtOffset(start));
				var textEnd = baseModel.getLineEnd(baseModel.getLineAtOffset(end), true);
				return baseModel.getText(textStart, textEnd);
			}
			function getAnnotationHTML(annotation) {
				var title = annotation.title;
				var result = util.createElement(document, "div"); //$NON-NLS-0$
				result.className = "tooltipRow"; //$NON-NLS-0$
				if (annotation.html) {
					result.innerHTML = annotation.html;
					if (result.lastChild) {
						textUtil.addEventListener(result.lastChild, "click", function(event) { //$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(document.createTextNode("\u00A0")); //$NON-NLS-0$
				}
				if (!title) {
					title = getText(annotation.start, annotation.end);
				}
				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);
				return result;
			}
			if (annotations.length === 1) {
				annotation = annotations[0];
				if (annotation.title !== undefined) {
					html = getAnnotationHTML(annotation);
					if (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 newModel = new mProjectionTextModel.ProjectionTextModel(baseModel);
					var lineStart = baseModel.getLineStart(baseModel.getLineAtOffset(annotation.start));
					var charCount = baseModel.getCharCount();
					if (annotation.end !== charCount) {
						newModel.addProjection({start: annotation.end, end: charCount});
					}
					if (lineStart > 0) {
						newModel.addProjection({start: 0, end: lineStart});
					}
					return newModel;
				}
			} else {
				var tooltipHTML = util.createElement(document, "div"); //$NON-NLS-0$
				var em = util.createElement(document, "em"); //$NON-NLS-0$
				em.appendChild(document.createTextNode(messages.multipleAnnotations));
				tooltipHTML.appendChild(em);
				for (var i = 0; i < annotations.length; i++) {
					annotation = annotations[i];
					html = getAnnotationHTML(annotation);
					if (html) {
						tooltipHTML.appendChild(html);
					}
				}
				return tooltipHTML;
			}
		},
		_getNodeStyle: function(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;
		},
		_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$
		}
	};
	return {Tooltip: Tooltip};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define */

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$
	
	
	/** @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);
	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, 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
 ******************************************************************************/
 
/*global define*/

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() {
			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 && 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.foldingRulerFactory
	 * @param {Object} options.statusReporter
	 * @param {Object} options.textViewFactory
	 * @param {Object} options.undoStackFactory
	 * @param {Object} options.textDNDFactory
	 */
	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._foldingRulerFactory = options.foldingRulerFactory;
		this._lineNumberRulerFactory = options.lineNumberRulerFactory;
		this._contentAssistFactory = options.contentAssistFactory;
		this._keyBindingFactory = options.keyBindingFactory;
		
		this._annotationStyler = null;
		this._annotationModel = null;
		this._annotationRuler = null;
		this._lineNumberRuler = null;
		this._overviewRuler = 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 = 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 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);
			}
		},
		
		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());
		},
		
		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;
		},
		
		_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) {
			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);
		},
		
		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);
		},
				
		/**
		 * @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; }
			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]);
				}
			}
			if (rangeAnnotations.length === 0) { return null; }
			var pt = textView.convert({x: x, y: y}, "document", "page"); //$NON-NLS-1$ //$NON-NLS-0$
			var info = {
				contents: rangeAnnotations,
				anchor: "left", //$NON-NLS-0$
				x: pt.x + 10,
				y: pt.y + 20
			};
			return info;
		},
		
		/** @private */
		_highlightCurrentLine: function(newSelection, oldSelection) {
			var annotationModel = this._annotationModel;
			if (!annotationModel) { return; }
			var textView = this._textView;
			if (textView.getOptions("singleMode")) { return; } //$NON-NLS-0$
			var model = textView.getModel();
			var oldLineIndex = oldSelection ? model.getLineAtOffset(oldSelection.start) : -1;
			var lineIndex = model.getLineAtOffset(newSelection.start);
			var newEmpty = newSelection.start === newSelection.end;
			var oldEmpty = !oldSelection || oldSelection.start === oldSelection.end;
			var start = model.getLineStart(lineIndex);
			var end = model.getLineEnd(lineIndex);
			if (model.getBaseModel) {
				start = model.mapOffset(start);
				end = model.mapOffset(end);
			}
			var annotation = this._currentLineAnnotation; 
			if (oldLineIndex === lineIndex && oldEmpty && newEmpty && annotation && annotation.start === start && annotation.end === end) {
				return;
			}
			var remove = annotation ? [annotation] : null;
			var add;
			if (newEmpty) {
				var type = AT.ANNOTATION_CURRENT_LINE;
				annotation = AT.createAnnotation(type, start, end);
				add = [annotation];
			}
			this._currentLineAnnotation = annotation;
			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.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 editor = this, textView = this._textView;
			
			var self = this;
			this._listener = {
				onModelChanged: function(e) {
					self.checkDirty();
				},
				onMouseOver: function(e) {
					self._listener.onMouseMove(e);
				},
				onMouseMove: function(e) {
					var tooltip = mTooltip.Tooltip.getTooltip(textView);
					if (!tooltip) { return; }
					if (self._listener.lastMouseX === e.event.clientX && self._listener.lastMouseY === e.event.clientY) {
						return;
					}
					self._listener.lastMouseX = e.event.clientX;
					self._listener.lastMouseY = e.event.clientY;
					tooltip.setTarget({
						x: e.x,
						y: e.y,
						getTooltipInfo: function() {
							return self._getTooltipInfo(this.x, this.y);
						}
					});
				},
				onMouseOut: function(e) {
					var tooltip = mTooltip.Tooltip.getTooltip(textView);
					if (!tooltip) { return; }
					if (self._listener.lastMouseX === e.event.clientX && self._listener.lastMouseY === e.event.clientY) {
						return;
					}
					self._listener.lastMouseX = e.event.clientX;
					self._listener.lastMouseY = e.event.clientY;
					tooltip.setTarget(null);
				},
				onScroll: function(e) {
					var tooltip = mTooltip.Tooltip.getTooltip(textView);
					if (!tooltip) { return; }
					tooltip.setTarget(null, 0, 0);
				},
				onSelection: function(e) {
					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("MouseMove", this._listener.onMouseMove); //$NON-NLS-0$
			textView.addEventListener("Scroll", this._listener.onScroll); //$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);
					bookmark.title = undefined;
					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);
				}
				this.setAnnotationRulerVisible(this._annotationRulerVisible || this._annotationRulerVisible === undefined, true);
					
				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);
				}
				this.setOverviewRulerVisible(this._overviewRulerVisible || this._overviewRulerVisible === undefined, 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.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._lineNumberRuler =
				this._foldingRuler = this._currentLineAnnotation = this._title = null;
			this._dirty = false;
			this._foldingRulerVisible = this._overviewRulerVisible =
				this._lineNumberRulerVisible = this._annotationRulerVisible = undefined;
			
			var textViewUninstalledEvent = {
				type: "TextViewUninstalled", //$NON-NLS-0$
				textView: textView
			};
			this.dispatchEvent(textViewUninstalledEvent);
			BaseEditor.prototype.uninstall.call(this);
		},
		
		_updateCursorStatus: function() {
			var model = this.getModel();
			var caretOffset = this.getCaretOffset();
			var lineIndex = model.getLineAtOffset(caretOffset);
			var lineStart = model.getLineStart(lineIndex);
			var offsetInLine = caretOffset - lineStart;
			// 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;
				}
			}
			this.reportStatus(util.formatMessage(messages.lineColumn, lineIndex + 1, offsetInLine + 1));
		},
		
		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 (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.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;
			});
		},
		
		/**
		 * 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);
			} 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);
			}
		},
		
		/**
		 * @private
		 */
		_setModelText: function(contents) {
			if (this._textView) {
				this._textView.setText(contents);
				this._textView.getModel().setLineDelimiter("auto"); //$NON-NLS-0$
				this._highlightCurrentLine(this._textView.getSelection());
			}
		},
		
		/**
		 * 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) 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
 *******************************************************************************/
 /*global define */
 
define('orion/selection',["orion/EventTarget", "orion/Deferred"], function(EventTarget, Deferred){

	/**
	 * Constructs a new selection service. Clients should obtain a selection service
	 * by requesting the service <code>orion.page.selection</code> from the service registry.
	 * This service constructor is only intended to be used by page service registry
	 * initialization code.
	 * @name orion.selection.Selection
	 * @class Can provide one or more selections describing objects of interest.  Used to
	 * establish input and output relationships between components.  For example, the selection
	 * in one component can serve as the input of another component.
	 */	
	function Selection(serviceRegistry, selectionServiceId) {
		if (!selectionServiceId) {
			selectionServiceId = "orion.page.selection"; //$NON-NLS-0$
		}
		
		this._serviceRegistry = serviceRegistry;
		EventTarget.attach(this);
		this._serviceRegistration = serviceRegistry.registerService(selectionServiceId, this);
		this._selections = null;  // so we can quickly recognize the "empty" case without comparing arrays.
	}
	 
	Selection.prototype = /** @lends orion.selection.Selection.prototype */ {
		/**
		 * Obtains the current single selection and passes it to the provided function.
		 * @param {Function} onDone The function to invoke with the selection. Deprecated: just use the return value instead.
		 * @returns {Object}
		 */
		getSelection : function(onDone) {
			var result = this._getSingleSelection();
			if (typeof onDone === "function") { //$NON-NLS-0$
				onDone(result);
			}
			return result;
		},
		
		/**
		 * Obtains all current selections and passes them to the provided function.
		 * @param {Function} onDone The function to invoke with the selections. Deprecated: just use the return value instead.
		 * @returns {Array}
		 */
		getSelections: function(onDone) {
			var result = Array.isArray(this._selections) ? this._selections.slice() : [];
			if (typeof onDone === "function") { //$NON-NLS-0$
				onDone(result);
			}
			return result;
		},
		
		_getSingleSelection: function() {
			if (this._selections && this._selections.length > 0) {
				return this._selections[0];
			} 
			return null;
		},
		
		/**
		 * Sets the current selection. Dispatches a <code>selectionChanged</code> event.
		 * @param {Object|Object[]|null} itemOrArray A single selected item, or an array of selected items, or <code>null</code> (meaning no selection).
		 * @see orion.selection.Selection#event:selectionChanged
		 */
		setSelections: function(itemOrArray) {
			var oldSelection = this._selections;
			if (Array.isArray(itemOrArray)) {	
				this._selections = itemOrArray.length > 0 ? itemOrArray.slice() : null;
			} else if (itemOrArray) {
				this._selections = [itemOrArray];
			} else {
				this._selections = null;
			}
			if (oldSelection !== this._selections) {
				this.dispatchEvent({type:"selectionChanged", selection: this._getSingleSelection(), selections: this._selections}); //$NON-NLS-0$
			}
		}
		/**
		 * Dispatched when the selection has changed.
		 * @name orion.selection.Selection#selectionChanged
		 * @class
		 * @event
		 * @param {selectionChangedEvent} selectionChangedEvent
		 * @param {Object} selectionChangedEvent.selection The selected item. If there is no selection, this field is <code>null</code>. If multiple items are selected,
		 * this field refers to the first item in the list.
		 * @param {Object[]} selectionChangedEvent.selections The selected items. If there is no selection, this field is <code>null</code>.
		 * @param {String} selectionChangedEvent.type The type event type. Value is always <code>"selectionChanged"</code>.
		 */
	};
	Selection.prototype.constructor = Selection;

	//return module exports
	return {Selection: Selection};
});

/*******************************************************************************
 * @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
 ******************************************************************************/
/*jslint amd:true sub:true*/
/*global console document window  */
 
define('orion/commandRegistry',[
	'orion/commands',
	'orion/explorers/navigationUtils',
	'orion/PageUtil',
	'orion/uiUtils',
	'orion/webui/littlelib',
	'orion/webui/dropdown',
	'orion/webui/tooltip',
	'text!orion/webui/checkedmenuitem.html',
	'text!orion/webui/dropdowntriggerbutton.html',
	'text!orion/webui/submenutriggerbutton.html'
], function(Commands, mNavUtils, PageUtil, UIUtil, lib, mDropdown, mTooltip, CheckedMenuItemFragment, DropdownButtonFragment, SubMenuButtonFragment) {

	/**
	 * 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._init(options);
		this._parameterCollector = null;
	}
	CommandRegistry.prototype = /** @lends orion.commandregistry.CommandRegistry.prototype */ {
		_init: function(options) {
			this._selectionService = options.selection;
			var self = this;
			Commands.setKeyBindingProvider(function() { return self._activeBindings; });
		},
		
		/**
		 * 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 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;
				this._parameterCollector.close();
				var opened = this._parameterCollector.open(node, 
					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;
							self._parameterCollector.close();
						}, 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;
							self._parameterCollector.close();
						}, false);
						buttonParent.appendChild(button);
						button.appendChild(document.createTextNode(noString)); //$NON-NLS-0$
						button.className = "dismissButton"; //$NON-NLS-0$
						return yesButton;
					},
					function() {
						onConfirm(result);
					}
				);
				if (opened) {
					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,
								afterHiding: function() {
									this.destroy();
								},
								trigger: "click", //$NON-NLS-0$
								position: ["below", "right", "above", "left"] //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
							});
							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();
							window.setTimeout(function() {
								focusNode.focus();
								focusNode.select();
							}, 0);
							collecting = true;
						}
					}
					if (!collecting) {
						// Just call the callback with the information we had.
						return commandInvocation.command.callback.call(commandInvocation.handler || window, commandInvocation);
					}
				} else {
					// 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 bindingString, 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 {
					bindingString = UIUtil.getUserKeyString(binding.keyBinding);
					keyAssist.createItem(bindingString, binding.command.name || binding.command.tooltip, execute(binding));
				}
			}
			for (var scopedBinding in scopes) {
				if (scopes[scopedBinding].length && scopes[scopedBinding].length > 0) {
					keyAssist.createHeader(scopedBinding);
					scopes[scopedBinding].forEach(function(binding) {
						bindingString = UIUtil.getUserKeyString(binding.keyBinding);
						keyAssist.createItem(bindingString, binding.command.name || binding.command.tooltip, execute(binding));
					});
				}	
			}
		},
		
		/** 
		 * 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.
		 */	
		addCommandGroup: function(scopeId, groupId, position, title, parentPath, emptyGroupMessage, imageClass, tooltip, selectionClass, defaultActionId) {
			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(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: {}};
				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 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.editor.KeyBinding} [keyBinding] a keyBinding for the command.  Optional.
		 * @param {orion.commands.URLBinding} [urlBinding] a url binding for the command.  Optional.
		 */
		registerCommandContribution: function(scopeId, commandId, position, parentPath, bindingOnly, keyBinding, urlBinding) {
			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};
			
			var command;
			// 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);
			}
			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(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(contributions, parent, items, handler, renderType, userData, domNodeWrapperList) {
			// 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-1$ //$NON-NLS-0$
						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){
								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);
							if(domNodeWrapperList){
								mNavUtils.generateNavGrid(domNodeWrapperList, created.menuButton);
							}

							// render the children asynchronously
							if (created) {
//								window.setTimeout(function() {
									self._render(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(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(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(childContributions, parent, items, handler, renderType, userData, domNodeWrapperList); 
							// Add a trailing separator if children rendered.
							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(handler, items, userData, command, self);
						invocation.domParent = parent;
						var enabled = false;
						try {
							enabled = render && (command.visibleWhen ? command.visibleWhen(items) : 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);
							} 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) {
			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);
				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 && 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 if (hasDefault) {
					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
	 * 
	 * @name orion.commands.CommandParameter
	 * @class
	 */
	function CommandParameter (name, type, label, value, lines, eventListeners) {
		this.name = name;
		this.type = type;
		this.label = label;
		this.value = value;
		this.lines = lines || 1;
		
		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;
	}
	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.command.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]);
				}
			}
		},
		
		/**
		 * 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);
				parameters.push(newParm);
			});
			var copy = new ParametersDescription(parameters, this._options, this.getParameters);
			// this value may have changed since the options
			copy.clientCollect = this.clientCollect;
			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
	};
});

define('text!orion/banner/slideout.html',[],function () { return '<div id="slideContainer" class="slideParameters slideContainer">\n\t<span id="pageCommandParameters" class="layoutLeft parameters"></span>\n\t<span id="pageCommandDismiss" class="layoutRight parametersDismiss"></span>\n</div>';});

/*******************************************************************************
 * @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
 *******************************************************************************/
/*global document define */
/*browser:true*/

define('orion/commonHTMLFragments',['require', 'orion/webui/littlelib', 'text!orion/banner/slideout.html'], 
        function(require, lib, SlideoutTemplate){
        
	/**
	 * This module contains dynamic HTML fragments that depend on client information.
	 * @name orion.commonHTMLFragments
	 */

	function slideoutHTMLFragment(idPrefix) { 
		var tempDiv = document.createElement("div"); //$NON-NLS-0$
		tempDiv.innerHTML = SlideoutTemplate;
		
		// replacing generic id's with prefixed id's
		var node = lib.$("#slideContainer", tempDiv); //$NON-NLS-0$
		node.id = idPrefix + node.id;
		node = lib.$("#pageCommandParameters", tempDiv); //$NON-NLS-0$
		node.id = idPrefix + node.id;
		node = lib.$("#pageCommandDismiss", tempDiv); //$NON-NLS-0$
		node.id = idPrefix + node.id;
		return tempDiv.innerHTML;
	}
		
	//return the module exports
	return {
		slideoutHTMLFragment: slideoutHTMLFragment
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/
 /*global define window document*/
define('orion/section',['orion/webui/littlelib', 'orion/selection', 'orion/commandRegistry', 'orion/commonHTMLFragments', 'orion/objects', 	'orion/selection'], function(lib, mSelection, mCommands, mHTMLFragments, objects, Selection){
	
	/**
	 * Generates a section
	 * 
	 * @name orion.widgets.Section
	 * @class Generates a section
	 * @param {DomNode} parent parent node
	 * @param {String} options.id id of the section header
	 * @param {String} options.title title (in HTML) of the section
	 * @param {orion.preferences.PreferencesService} [options.preferenceService] used to store the hidden/shown state of the section if specified
	 * @param {String|Array} [options.headerClass] a class or array of classes to use in the section header, in addition to the default header classes
	 * @param {String|Array} [options.iconClass] a class or array of classes to use in the icon decorating section, no icon displayed if not provided
	 * @param {Function} [options.getItemCount] function to return the count of items in the section. If not provided, no count is shown.
	 * @param {String|DomNode} [options.content] HTML or DOM node giving the Section's initial contents. May be set later using {@link #setContent()}
	 * @param {Boolean} [options.slideout] if true section will contain generated slideout
	 * @param {Boolean} [options.canHide] if true section may be hidden
	 * @param {Boolean} [options.hidden] if true section will be hidden at first display
	 * @param {Boolean} [options.useAuxStyle] if true the section will be styled for an auxiliary pane
	 * @param {Function} [options.onExpandCollapse] a function that will be called when the expanded/collapsed state changes
	 */
	function Section(parent, options) {
		
		var that = this;
		
		this._expandImageClass = "core-sprite-openarrow"; //$NON-NLS-0$
		this._collapseImageClass = "core-sprite-closedarrow"; //$NON-NLS-0$
		this._progressImageClass = "core-sprite-progress"; //$NON-NLS-0$
		this._twistieSpriteClass = "modelDecorationSprite"; //$NON-NLS-0$
		
		// ...
		
		if (!options.id) {
			throw new Error("Missing required argument: id"); //$NON-NLS-0$
		}
		this.id = options.id;
				
		if (!options.title) {
			throw new Error("Missing required argument: title"); //$NON-NLS-0$
		}

		// setting up the section
		var wrapperClasses = options.useAuxStyle ? ["sectionWrapper", "sectionWrapperAux", "toolComposite"] : ["sectionWrapper", "toolComposite"]; //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		this.domNode = document.createElement("div"); //$NON-NLS-0$
		parent.appendChild(this.domNode);
		for(var i=0; i<wrapperClasses.length; i++) {
			this.domNode.classList.add(wrapperClasses[i]);
		}
		this.domNode.id = options.id;

		// if canHide, add twistie and stuff...
		if(options.canHide){
			this.twistie = document.createElement("span"); //$NON-NLS-0$
			this.twistie.classList.add("modelDecorationSprite"); //$NON-NLS-0$
			this.twistie.classList.add("layoutLeft"); //$NON-NLS-0$
			this.twistie.classList.add("sectionTitleTwistie"); //$NON-NLS-0$
			this.domNode.style.cursor = "pointer"; //$NON-NLS-0$
			this.domNode.appendChild(this.twistie);
			this.domNode.tabIndex = 0; //$NON-NLS-0$
			this.domNode.addEventListener("click", function(evt) { //$NON-NLS-0$
				if (evt.target === that.titleNode || evt.target === that.twistie || evt.target === that.domNode) {
					that._changeExpandedState();
				}
			}, false);
			this.domNode.addEventListener("keydown", function(evt) { //$NON-NLS-0$
				if(evt.keyCode === lib.KEY.ENTER && (evt.target === that.domNode || evt.target === that.titleNode || evt.target === that.twistie)) {
					that._changeExpandedState();
				}
			}, false);
		}
		var classes;
		if(options.iconClass){
			var icon = document.createElement("span"); //$NON-NLS-0$
			icon.classList.add("sectionIcon"); //$NON-NLS-0$
			this.domNode.appendChild(icon);
			classes = Array.isArray(options.iconClass) ? options.iconClass : [options.iconClass];
			classes.forEach(function(aClass) {
				icon.classList.add(aClass);
			});
		}
		
		if(options.headerClass){
			classes = Array.isArray(options.headerClass) ? options.headerClass : [options.headerClass];
			classes.forEach(function(aClass) {
				this.domNode.classList.add(aClass);
			}.bind(this));
		}

		this.titleActionsNode = document.createElement("div"); //$NON-NLS-0$
		this.titleActionsNode.id = options.id + "TitleActionsArea"; //$NON-NLS-0$
		this.titleActionsNode.classList.add("layoutLeft"); //$NON-NLS-0$
		this.titleActionsNode.classList.add("sectionActions"); //$NON-NLS-0$
		this.domNode.appendChild(this.titleActionsNode);

		this.titleNode = document.createElement("div"); //$NON-NLS-0$
		this.titleNode.id = options.id + "Title"; //$NON-NLS-0$
		this.titleNode.classList.add("sectionAnchor"); //$NON-NLS-0$
		this.titleNode.classList.add("sectionTitle"); //$NON-NLS-0$
		this.titleNode.classList.add("layoutLeft"); //$NON-NLS-0$
		this.domNode.appendChild(this.titleNode);
		this.titleNode.textContent = options.title;

		// Add item count
		if (typeof options.getItemCount === "function") { //$NON-NLS-0$
			var count = document.createElement("div"); //$NON-NLS-0$
			count.classList.add("layoutLeft"); //$NON-NLS-0$
			count.classList.add("sectionItemCount"); //$NON-NLS-0$
			this.domNode.appendChild(count);
			count.textContent = options.getItemCount(this);
		}

		this._progressNode = document.createElement("div"); //$NON-NLS-0$
		this._progressNode.id = options.id + "Progress"; //$NON-NLS-0$
		this._progressNode.classList.add("sectionProgress"); //$NON-NLS-0$
		this._progressNode.classList.add("sectionTitle"); //$NON-NLS-0$
		this._progressNode.classList.add("layoutLeft"); //$NON-NLS-0$
		this._progressNode.style.display = "none"; //$NON-NLS-0$
		this._progressNode.textContent = "..."; //$NON-NLS-0$ 
		this.domNode.appendChild(this._progressNode);
		
		this.titleLeftActionsNode = document.createElement("div"); //$NON-NLS-0$
		this.titleLeftActionsNode.id = options.id + "LeftTitleActionsArea"; //$NON-NLS-0$
		this.titleLeftActionsNode.classList.add("layoutLeft"); //$NON-NLS-0$
		this.titleLeftActionsNode.classList.add("sectionActions"); //$NON-NLS-0$
		this.domNode.appendChild(this.titleLeftActionsNode);
		
		// add filter search box
		var searchbox = document.createElement("div"); //$NON-NLS-0$
		searchbox.id = options.id + "FilterSearchBox"; //$NON-NLS-0$
		this.domNode.appendChild(searchbox);
		
		this._toolActionsNode = document.createElement("div"); //$NON-NLS-0$
		this._toolActionsNode.id = options.id + "ToolActionsArea"; //$NON-NLS-0$
		this._toolActionsNode.classList.add("layoutRight"); //$NON-NLS-0$
		this._toolActionsNode.classList.add("sectionActions"); //$NON-NLS-0$
		this.domNode.appendChild(this._toolActionsNode);
		this.actionsNode = document.createElement("ul"); //$NON-NLS-0$
		this.actionsNode.id = options.id + "ActionArea"; //$NON-NLS-0$
		this.actionsNode.classList.add("layoutRight"); //$NON-NLS-0$
		this.actionsNode.classList.add("commandList"); //$NON-NLS-0$
		this.actionsNode.classList.add("sectionActions"); //$NON-NLS-0$
		this.domNode.appendChild(this.actionsNode);
		this.selectionNode = document.createElement("ul"); //$NON-NLS-0$
		this.selectionNode.id = options.id + "SelectionArea"; //$NON-NLS-0$
		this.selectionNode.classList.add("layoutRight"); //$NON-NLS-0$
		this.selectionNode.classList.add("commandList"); //$NON-NLS-0$
		this.selectionNode.classList.add("sectionActions"); //$NON-NLS-0$
		this.domNode.appendChild(this.selectionNode);
		
		if(options.slideout){
			var slideoutFragment = mHTMLFragments.slideoutHTMLFragment(options.id);
			var range = document.createRange();
			range.selectNode(this.domNode);
			slideoutFragment = range.createContextualFragment(slideoutFragment);
			this.domNode.appendChild(slideoutFragment);
		}

		this._contentParent = document.createElement("div"); //$NON-NLS-0$
		this._contentParent.id = this.id + "Content"; //$NON-NLS-0$
		this._contentParent.role = "region"; //$NON-NLS-0$
		this._contentParent.classList.add("sectionTable"); //$NON-NLS-0$
		this._contentParent.setAttribute("aria-labelledby", this.titleNode.id); //$NON-NLS-0$
		parent.appendChild(this._contentParent);

		if(options.content){
			this.setContent(options.content);
		}
		
		if (typeof(options.onExpandCollapse) === "function") { //$NON-NLS-0$
			this._onExpandCollapse = options.onExpandCollapse;
		}
		this._preferenceService = options.preferenceService;
		// initially style as hidden until we determine what needs to happen
		this._collapse();
		// should we consult a preference?
		if (this._preferenceService) {
			var self = this;
			this._preferenceService.getPreferences("/window/views").then(function(prefs) {  //$NON-NLS-0$
				var isExpanded = prefs.get(self.id);
				
				if (isExpanded === undefined){
					// pref not found, check options
					if (!options.hidden) {
						self._expand();
					}
				} else if (isExpanded) {
					self._expand();
				}
				
				self._updateExpandedState(false);
			});
		} else {
			if (!options.hidden) {
				this._expand();
			}
			this._updateExpandedState(false);
		}
		this._commandService = options.commandService;
		this._lastMonitor = 0;
		this._loading = {};
	}
	
	Section.prototype = /** @lends orion.widgets.Section.prototype */ {
			
		/**
		 * Changes the title of section
		 * @param title
		 */
		setTitle: function(title){
			this.titleNode.textContent = title;
		},
		
		/**
		 * Get the header DOM node
		 * @returns {DomNode} The dom node that holds the section header.
		 */
		getHeaderElement: function(title){
			return this.domNode;
		},
		
		/**
		 * Get the title DOM node
		 * @returns {DomNode} The dom node that holds the section title.
		 */
		getTitleElement: function(title){
			return this.titleNode;
		},
		
		/**
		 * Get the title DOM node
		 * @returns {DomNode} The dom node that holds the section title.
		 */
		getActionElement: function(title){
			return this._toolActionsNode;
		},
		
		/**
		 * Changes the contents of the section.
		 * @param {String|DomNode} content
		 */
		setContent: function(content){
			if (typeof content === 'string') {  //$NON-NLS-0$
				this._contentParent.innerHTML = content;
			} else {
				this._contentParent.innerHTML = ""; //NON-NLS-0$
				this._contentParent.appendChild(content);
			}
		},

		/**
		 * @returns {DomNode} The dom node that holds the section contents.
		 */
		getContentElement: function() {
			return this._contentParent;
		},
		
		embedExplorer: function(explorer, parent, noSelection){
			this._contentParent.innerHTML = ""; //NON-NLS-0$
			if(!explorer.parent){
				explorer.parent = parent;
			}
			this._contentParent.appendChild(explorer.parent);
			explorer.section = this;
			objects.mixin(explorer, {
				createActionSections: function(){
					if(this.actionsSections)
					this.actionsSections.forEach(function(id) {
						if (!lib.node(id)) {
							var elem = document.createElement("ul"); //$NON-NLS-0$
							elem.id = id;
							elem.classList.add("commandList"); //$NON-NLS-0$
							elem.classList.add("layoutRight"); //$NON-NLS-0$
							this.section.actionsNode.appendChild(elem);
						}
					}.bind(this));
				},
				getCommandsVisible: function() {
					return this.section.actionsNode.style.visibility!=="hidden";
				},
				setCommandsVisible: function(visible, selectionPolicy) {
					this.section.actionsNode.style.visibility = visible ? "" : "hidden";
					if (undefined === selectionPolicy){
						selectionPolicy = visible ? null : "cursorOnly"; //$NON-NLS-0$	
					} 
					this.renderer.selectionPolicy = selectionPolicy;
					var navHandler = this.getNavHandler();
					if (navHandler) {
						navHandler.setSelectionPolicy(selectionPolicy);
					}
					if (visible) {
						this.updateCommands();
					} else {
						if(this.actionsSections)
						this.actionsSections.forEach(function(id) {
							if(lib.node(id)) this.commandRegistry.destroy(id);
						}.bind(this));
					}
				},
				destroy: function(){
					Object.getPrototypeOf(this).destroy.call(this);
					var _self = this;
					if(!this.actionsSections){
						return;
					}
					this.actionsSections.forEach(function(id) {
						delete _self[id];
					});
					delete this.actionsSections;
				},
				updateCommands: function(selections){
					if (!this.section.actionsNode || !this.getCommandsVisible()) {
						return;
					}
					this.createActionSections();
					Object.getPrototypeOf(this).updateCommands.call(this, selections);
				},
				loaded: function(){
					var self = this;
					if(!this.selection && !noSelection){
						if(this.serviceRegistry || this.registry){
							this.selection = new Selection.Selection(this.serviceRegistry || this.registry, this.parent.id + "Selection"); //$NON-NLS-0$
							this.selection.addEventListener("selectionChanged", function(event) { //$NON-NLS-0$
								self.updateCommands(event.selections);
							});
						} else {
							window.console.error("Could not create a Selection for the explorer because of lack of serviceRegistry");
						}
					}
					var commandsRegistered = this.registerCommands();
					if(!commandsRegistered || !commandsRegistered.then){
						self.updateCommands();
					} else {
						commandsRegistered.then(function() {
							self.updateCommands();
						});
					}
				}
			});
			if(explorer.renderer){
				explorer.renderer.section = this;
				objects.mixin(explorer.renderer, {
					getCellHeaderElement: function(col_no){
						var firstHeader = Object.getPrototypeOf(this).getCellHeaderElement.call(this, col_no);
						if(firstHeader){
							this.section.setTitle(firstHeader.innerHTML);
						}
						return null;
					}
				});
			}
		},

		createProgressMonitor: function(){
			return new ProgressMonitor(this);
		},
		
		_setMonitorMessage: function(monitorId, message){
			this._progressNode.style.display = "block"; //$NON-NLS-0$
			this._loading[monitorId] = message;
			var progressTitle = "";
			for(var loadingId in this._loading){
				if(progressTitle!==""){
					progressTitle+="\n"; //$NON-NLS-0$
				}
				progressTitle+=this._loading[loadingId];
			}
			this._progressNode.title = progressTitle;
			this._updateExpandedState(false);
		},
		
		_monitorDone: function(monitorId){
			delete this._loading[monitorId];
			var progressTitle = "";
			for(var loadingId in this._loading){
				if(progressTitle!==""){
					progressTitle+="\n"; //$NON-NLS-0$
				}
				progressTitle+=this._loading[loadingId];
			}
			this._progressNode.title = progressTitle;
			if(progressTitle===""){
				this._progressNode.style.display = "none"; //$NON-NLS-0$
			}
			this._updateExpandedState(false);
		},
		
		_changeExpandedState: function() {
			if (this.hidden){
				this._expand();
			} else {
				this._collapse();
			}
			
			this._updateExpandedState(true);
		},
		
		_updateExpandedState: function(storeValue) {
			var isExpanded = !this.hidden;
			var isProgress = this.working;
			var expandImage = this.twistie;
			var id = this.id;
			if (expandImage) {
				expandImage.classList.remove(this._expandImageClass);
				expandImage.classList.remove(this._collapseImageClass);
				expandImage.classList.remove(this._progressImageClass);
				expandImage.classList.add(isProgress ? this._progressImageClass : isExpanded ? this._expandImageClass : this._collapseImageClass); //$NON-NLS-0$
			}
			// if a preference service was specified, we remember the state.
			if (this._preferenceService && storeValue) {
				this._preferenceService.getPreferences("/window/views").then(function(prefs){ //$NON-NLS-0$
					prefs.put(id, isExpanded);
				}); 
			}
			
			// notify the client
			if (this._onExpandCollapse) {
				this._onExpandCollapse(isExpanded, this);
			}
		},
		
		_expand: function() {
			this._contentParent.classList.remove("sectionClosed"); //$NON-NLS-0$
			this.domNode.classList.remove("sectionClosed"); //$NON-NLS-0$
			this.hidden = false;
		},
		
		_collapse: function() {
			this.hidden = true;
			this._contentParent.classList.add("sectionClosed"); //$NON-NLS-0$
			this.domNode.classList.add("sectionClosed"); //$NON-NLS-0$
		}
	};
	
	Section.prototype.constructor = Section;
	
	// ProgressMonitor
	
	function ProgressMonitor(section){
		this._section = section;
		this._id = ++section._lastMonitor;
	}
	
	ProgressMonitor.prototype = {
		begin: function(message){
			this._section.working = true;
			this.status = message;
			this._section._setMonitorMessage(this.id, message);
		},
		
		worked: function(message){
			this.status = message;
			this._section._setMonitorMessage(this.id, message);
		},
		
		done: function(status){
			this._section.working = false;
			this.status = status;
			this._section._monitorDone(this.id);
		}
	};
	
	ProgressMonitor.prototype.constructor = ProgressMonitor;

	return {Section: Section};
});

/*******************************************************************************
 * 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
 ******************************************************************************/

/*global define document window URL console Blob*/
define('orion/markdownView',[
	'marked/marked',
	'orion/editor/editor',
	'orion/objects',
	'orion/webui/littlelib',
	'orion/URITemplate',
	'orion/section', 
	'orion/URL-shim'
], function(marked, mEditor, objects, lib, URITemplate, mSection) {

	var uriTemplate = new URITemplate("#{,resource,params*}"); //$NON-NLS-0$
	var extensionRegex = /\.([0-9a-z]+)(?:[\?#]|$)/i;
	var imgCount = 0;

	function filterOutputLink(outputLink, resourceURL, fileClient, isRelative) {
		return function(cap, link) {
			if (link.href.indexOf(":") === -1) { //$NON-NLS-0$
				
				try {
					var linkURL;
					if (resourceURL.protocol === "filesystem:") { //$NON-NLS-0$
						linkURL = {
							href: "filesystem:" + (new URL(link.href, resourceURL.pathname)).href //$NON-NLS-0$
						};
					} else {
						linkURL = new URL(link.href, resourceURL);
//						fileClient.toAbsURL(resourceURL, link.href);
						if (isRelative) {
							linkURL.protocol = "";
							linkURL.host = "";
						}
					}
					if (cap[0][0] !== '!') { //$NON-NLS-0$
						link.href = uriTemplate.expand({
							resource: linkURL.href
						});
					} else {
						if (fileClient.readBlob) {
							var id = "_md_img_" + imgCount++;
							fileClient.readBlob(linkURL.href).then(function(bytes) {
								var extensionMatch = linkURL.href.match(extensionRegex);
								var mimeType = extensionMatch ? "image/" +extensionMatch[1] : "image/png";
								var objectURL = URL.createObjectURL(new Blob([bytes], {type: mimeType}));
								document.getElementById(id).src = objectURL;
								URL.revokeObjectURL(objectURL);
							});
							return "<img id='" + id + "' src=''>";							
						}
						link.href = linkURL.href;
					}
				} catch(e) {
					console.log(e); // best effort
				}				
			}
			return outputLink.call(this, cap, link);
		};
	}

	function createMarked(markdown, resource, fileClient) {
		// relativizing marked's outputLink
		var outputLink = marked.InlineLexer.prototype.outputLink;
		var resourceURL = new URL(resource, window.location.href);
		marked.InlineLexer.prototype.outputLink = filterOutputLink(outputLink, resourceURL, fileClient, resource.indexOf(":") === -1); //$NON-NLS-0$
		var result = marked(markdown, {
			sanitize: true
		});
		marked.InlineLexer.prototype.outputLink = outputLink;
		return result;
	}


	function MarkdownView(options) {
		this.fileClient = options.fileClient;
		this.progress = options.progress;
		this.canHide = options.canHide;
		this._node = null;
	}
	MarkdownView.prototype = {
		display: function(node, markdown, resource, fileClient) {
			node.classList.add("orionMarkdown"); //$NON-NLS-0$
			node.innerHTML = createMarked(markdown, resource, fileClient);
		},
		displayContents: function(node, file) {
			var location = file.Location || file;
			lib.empty(node);
			var div = document.createElement("div"); //$NON-NLS-0$
			(this.progress ? this.progress.progress(this.fileClient.read(location), "Reading file " + (file.Name || location)) : this.fileClient.read(location)).then(function(markdown) {
				this.display.bind(this)(div, markdown, location, this.fileClient);
			}.bind(this));
			node.appendChild(div);
		},
		displayInFrame: function(node, file, headerClass, titleClass, defaultTitle) {
			var markdownSection = new mSection.Section(node, {id: "markdownSection", title: file.Name || defaultTitle || "readme", headerClass: headerClass, canHide: this.canHide}); //$NON-NLS-0$
			if(titleClass) {
				var titleNode = markdownSection.getTitleElement();
				if(titleNode) {
					titleNode.classList.add(titleClass);
				}
			}
			this.displayContents.call(this, markdownSection.getContentElement(), file);
		}
	};

	var BaseEditor = mEditor.BaseEditor;
	function MarkdownEditor(options) {
		this.id = "orion.viewer.markdown"; //$NON-NLS-0$
		this.fileClient = options.fileClient;
		this.metadata = options.metadata;
		BaseEditor.apply(this, arguments);
	}
		
	MarkdownEditor.prototype = Object.create(BaseEditor.prototype);
	objects.mixin(MarkdownEditor.prototype, /** @lends orion.edit.MarkdownEditor.prototype */ {
		createMarked: function(contents) {
			return createMarked(contents, this.metadata.Location, this.fileClient);
		},
		install: function() {
			var root = this._rootDiv = document.createElement("div"); //$NON-NLS-0$
			root.style.width = "100%"; //$NON-NLS-0$
			root.style.height = "100%"; //$NON-NLS-0$
			var div = this._contentDiv = document.createElement("div"); //$NON-NLS-0$
			div.classList.add("orionMarkdown"); //$NON-NLS-0$
			root.appendChild(div);
			var parent = lib.node(this._domNode);
			parent.appendChild(root);
			this._contentDiv.innerHTML = this.createMarked(this.getModel().getText());
			BaseEditor.prototype.install.call(this);
		},
		setInput: function(title, message, contents, contentsSaved) {
			BaseEditor.prototype.setInput.call(this, title, message, contents, contentsSaved);
			if (!message && !contentsSaved) {
				this._contentDiv.innerHTML = this.createMarked(contents);
			}
		},
		uninstall: function() {
			lib.empty(this._domNode);
			BaseEditor.prototype.uninstall.call(this);
		}
	});

	function MarkdownEditorView(options) {
		this._parent = options.parent;
		this.fileClient = options.fileService;
		this.metadata = options.metadata;
		this.serviceRegistry = options.serviceRegistry;
		this.contentTypeRegistry = options.contentTypeRegistry;
		this.commandRegistry = options.commandRegistry;
		this.progress = options.progressService;
		this.model = options.model;
		this.undoStack = options.undoStack;
	}
	MarkdownEditorView.prototype = {
		create: function() {
			this.editor = new MarkdownEditor({
				domNode: this._parent,
				fileClient: this.fileClient,
				metadata: this.metadata,
				model: this.model,
				undoStack: this.undoStack
			});
			this.editor.install();
		},
		destroy: function() {
			if (this.editor) {
				this.editor.destroy();
			}
			this.editor = null;
		}
	};

	return {
		MarkdownEditorView: MarkdownEditorView,
		MarkdownView: MarkdownView
	};
});

/*******************************************************************************
 * @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
 *******************************************************************************/
/*global define window document URL*/

define('orion/urlUtils',['orion/PageUtil', "orion/URL-shim"], function(PageUtil) {
                
    /**
     * Detect if the given text contains URLs encoded by "[]()". Multiple occurences of the pattern "[displayString](url)" and the non-matched part are returned as an array of segments.
     * @param {String} text The given string to detect.
	 * @returns {Array} An array containing all the segments of the given string. Each segment has the following properties:
	 *     segmentStr: String. The display string in the segment.
	 *     urlStr: String. Only present if the segment is a valid URL.
	 */
	function detectValidURL(text) {
		var regex = /\[([^\]]*)\]\(([^\)]+)\)/g;
		var match = regex.exec(text), matches=[], lastNonMatchIndex = 0;
		while (match) {
			// match[0]: the string enclosed by the opening "[" and closing ")"
			// match[1]: the string inside the pair of "[" and "]"
			// match[2]: the string inside the pair of "(" and ")"
			if (match.length === 3 && match[2].length > 0){
				var url = new URL(match[2]);
				if (url.protocol !== ":" && PageUtil.validateURLScheme(url.href)) { //Check if it is a valid URL
					if (match.index > lastNonMatchIndex) { //We have to push a plain text segment first
						matches.push({segmentStr: text.substring(lastNonMatchIndex, match.index)});
					}
					matches.push({segmentStr: match[1].length > 0 ? match[1] : match[2], urlStr: match[2]});
					lastNonMatchIndex = match.index + match[0].length;
				}
			}
			match = regex.exec(text);
		}
		if (lastNonMatchIndex === 0) {
			return null;
		}
		if (lastNonMatchIndex < text.length) {
			matches.push({segmentStr: text.substring(lastNonMatchIndex)});
		}
		return matches;
	}
	
    /**
     * Render an array of string segments.
     * @param {dom node} parentNode The given parent dom node where the segements will be rendered.
     * @param {Array} segements The given array containing all the segments. Each segment has the following properties:
	 *     segmentStr: String. The display string in the segment.
	 *     urlStr: String. Only present if the segment is a valid URL.
	 */
	function processURLSegments(parentNode, segments) {
		segments.forEach(function(segment) {
			if (segment.urlStr){
				var link = document.createElement("a"); //$NON-NLS-0$
		        link.href = segment.urlStr;
		        link.appendChild(document.createTextNode(segment.segmentStr));
				parentNode.appendChild(link);
			} else {
				var plainText = document.createElement("span"); //$NON-NLS-0$
				plainText.textContent = segment.segmentStr;
				parentNode.appendChild(plainText);
			}
		});
	}
	
	//return module exports
	return {
		detectValidURL: detectValidURL,
		processURLSegments: processURLSegments
	};
});

/*******************************************************************************
 * 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
 ******************************************************************************/
 
/*global define document window*/
define('orion/widgets/browse/browseView',[
	'i18n!orion/edit/nls/messages',
	'orion/explorers/explorer-table',
	'orion/explorers/navigatorRenderer',
	'orion/markdownView', 
	'orion/PageUtil',
	'orion/URITemplate',
	'orion/webui/littlelib',
	'orion/objects',
	'orion/Deferred',
	'orion/webui/dropdown',
	'orion/widgets/browse/commitInfoRenderer',
	'orion/urlUtils',
	'orion/section'
], function(messages, mExplorerTable, mNavigatorRenderer, mMarkdownView, PageUtil, URITemplate, lib, objects, Deferred, mDropdown, mCommitInfoRenderer, mUrlUtils, mSection) {
	
	var FileExplorer = mExplorerTable.FileExplorer;
	var NavigatorRenderer = mNavigatorRenderer.NavigatorRenderer;
	
	var uriTemplate = new URITemplate("#{,resource,params*}"); //$NON-NLS-0$
	function FolderNavRenderer() {
		NavigatorRenderer.apply(this, arguments);
	}
	FolderNavRenderer.prototype = Object.create(NavigatorRenderer.prototype);
	objects.mixin(FolderNavRenderer.prototype, {
		showFolderImage: true,
		/**
		 * override NavigatorRenderer's prototype
		 */
		createFolderNode: function(folder) {
			var folderNode = mNavigatorRenderer.NavigatorRenderer.prototype.createFolderNode.call(this, folder);
			if (this.showFolderLinks && folderNode.tagName === "A") { //$NON-NLS-0$
				folderNode.href = uriTemplate.expand({resource: folder.Location}); //$NON-NLS-0$
			}
			folderNode.classList.add("folderNavFolder"); //$NON-NLS-0$
			folderNode.classList.add("navlink"); //$NON-NLS-0$
			folderNode.classList.add("targetSelector"); //$NON-NLS-0$
			folderNode.classList.remove("navlinkonpage"); //$NON-NLS-0$
			if (this.explorer.readonly && this.explorer.clickHandler) { //$NON-NLS-0$
				folderNode.href = "javascript:void(0)";
				folderNode.addEventListener("click", function(){this.explorer.clickHandler(folder.Location);}.bind(this)
				, false);
			}
			return folderNode;
		},
		/**
		 * override NavigatorRenderer's prototype
		 */
		updateFileNode: function(file, fileNode, isImage) {
			mNavigatorRenderer.NavigatorRenderer.prototype.updateFileNode.call(this, file, fileNode, isImage);
			if (this.explorer.readonly && fileNode.tagName === "A") { //$NON-NLS-0$
				if(this.explorer.clickHandler){
					fileNode.href = "javascript:void(0)";
					fileNode.addEventListener("click", function(){this.explorer.clickHandler(file.Location);}.bind(this)
					, false);
				} else {
					fileNode.href = uriTemplate.expand({resource: file.Location});
				}
			}
		},
		/**
		 * override NavigatorRenderer's prototype
		 */
		getCellHeaderElement: function(col_no) {
			if(this.explorer.breadCrumbMaker) {
				return null;
			}
			var td;
			if (col_no === 0) {
				td = document.createElement("th"); //$NON-NLS-0$
				td.colSpan = 1;
				var root = this.explorer.treeRoot;
				td.appendChild(document.createTextNode(root.Parents ? root.Name : this.explorer.fileClient.fileServiceName(root.Location)));
				return td;
			}
			return null;
		},
		/**
		 * override NavigatorRenderer's prototype
		 */
		emptyCallback: function(bodyElement) {
			if (this.explorer.readonly) {
				this.explorer.folderViewer.updateMessageContents("This folder is empty.", ["emptyViewTable"], null, true);
				return;
			}
			mNavigatorRenderer.NavigatorRenderer.prototype.emptyCallback.call(this, bodyElement);
		},
		/**
		 * override NavigatorRenderer's prototype
		 */
		getExpandImage: function() {
			return null;
		},
		/**
		 * override NavigatorRenderer's prototype
		 */
		getDisplayTime: function(timeStamp) {
			return mCommitInfoRenderer.calculateTime(timeStamp);
		}
	});
	
	function FolderNavExplorer(options) {
		options.setFocus = false;   // do not steal focus on load
		options.cachePrefix = null; // do not persist table state
		options.rendererFactory = function(explorer) {
			return new FolderNavRenderer({
				checkbox: false,
				treeTableClass: "sectionTreeTable",
				cachePrefix: "FolderNavigator" //$NON-NLS-0$
			}, explorer, options.commandRegistry, options.contentTypeRegistry);
		};
		FileExplorer.apply(this, arguments);
		this.commandsId = ".folderNav"; //$NON-NLS-0$
		this.fileClient = options.fileClient;
		this.commandRegistry = options.commandRegistry;
		this.contentTypeRegistry = options.contentTypeRegistry;
		this.readonly = options.readonly;
		this.folderViewer = options.folderViewer;
		this.editorInputManager = options.editorInputManager;
		this.breadCrumbMaker = options.breadCrumbMaker;
		this.clickHandler = options.clickHandler;
		this.treeRoot = {};
		this.parent = lib.node(options.parentId);	
		this.toolbarId = this.parent.id + "Tool"; //$NON-NLS-0$
		this.newActionsScope = this.parent.id + "NewScope"; //$NON-NLS-0$
		this.selectionActionsScope = this.parent.id + "SelectionScope"; //$NON-NLS-0$
		this.actionsSections = [this.newActionsScope, this.selectionActionsScope];
	}
	FolderNavExplorer.prototype = Object.create(FileExplorer.prototype);
	objects.mixin(FolderNavExplorer.prototype, /** @lends orion.FolderNavExplorer.prototype */ {
		loadRoot: function(root) {
			if (root) {
				this.load(root, "Loading " + root.Name).then(this.loaded.bind(this));
			} else {
				this.loadResourceList(PageUtil.matchResourceParameters().resource + "?depth=1", false).then(this.loaded.bind(this)); //$NON-NLS-0$
			}
		},
		loaded: function(){
		},
		// Returns a deferred that completes once file command extensions have been processed
		registerCommands: function() {
			return new Deferred().resolve();
		},
		updateCommands: function(selections) {
		},
		setCommandsVisible: function(section, visible) {
			section.actionsNode.style.visibility = visible ? "" : "hidden";
			var selectionPolicy = visible ? null : "cursorOnly"; //$NON-NLS-0$
			this.renderer.selectionPolicy = selectionPolicy;
			var navHandler = this.getNavHandler();
			if (navHandler) {
				navHandler.setSelectionPolicy(selectionPolicy);
			}
			if (visible) {
				this.updateCommands();
			} else {
				if(this.actionsSections) {
					this.actionsSections.forEach(function(id) {
						if(lib.node(id)) {
							this.commandRegistry.destroy(id);
						}
					}.bind(this));
				}
			}
		}
	});
	
	/** 
	 * Constructs a new BrowseView object.
	 * 
	 * @class 
	 * @name orion.BrowseView
	 */
	function BrowseView(options) {
		this._parent = options.parent;
		this._browser = options.browser;
		this._metadata = options.metadata;
		this.editorInputManager = options.inputManager;
		this.fileClient = options.fileService;
		this.progress = options.progressService;
		this.commandRegistry = options.commandRegistry;
		this.contentTypeRegistry = options.contentTypeRegistry;
		this.preferences = options.preferences;
		this.readonly = true;
		this.editorView = options.editorView;
		this._maxEditorLines = options.maxEditorLines;
		this.binaryView = options.binaryView;
		this.messageView = options.messageView;
		this.breadCrumbInHeader = options.breadCrumbInHeader;
		this.isMarkdownView = options.isMarkdownView;
		this.infoDropDownHandlers =  options.infoDropDownHandlers;
		this.snippetShareOptions = options.snippetShareOptions;
		this.breadCrumbMaker = options.breadCrumbMaker;
		this.branchSelector = options.branchSelector;
		this.componentSelector = options.componentSelector;
		this.clickHandler = options.clickHandler;
		this._init();
	}
	BrowseView.prototype = /** @lends orion.BrowseView.prototype */ {
		_init: function(){
			this.markdownView = new mMarkdownView.MarkdownView({
				fileClient : this.fileClient,
				canHide: !this.readonly,
				progress : this.progress
			});
		},
		_isCommandsVisible: function() {
			return !this.readonly;
		},
		displayWorkspaceView: function(){
			if(!this._node){
				this._node = document.createElement("div"); //$NON-NLS-0$
				this._node.classList.add("browse_inner_container"); //$NON-NLS-0$
			}
			this._parent.appendChild(this._node);
		},
		displayBrowseView: function(root){
			var children = root && root.Children;
			var readmeMd;
			if(children) {
				for (var i=0; i<children.length; i++) {
					var child = children[i];
					if (!child.Directory && child.Name && child.Name.toLowerCase() === "readme.md") { //$NON-NLS-0$
						readmeMd = child;
					}
	
				}
			}
			var div;
			if(!this._node){
				this._node = document.createElement("div"); //$NON-NLS-0$
				this._node.classList.add("browse_inner_container"); //$NON-NLS-0$
			}
			this._parent.appendChild(this._node);
			
			function renderSections(sectionsOrder){
				sectionsOrder.forEach(function(sectionName){
					if(sectionName === "folderNav") {
						var navNode = document.createElement("div"); //$NON-NLS-0$
						navNode.id = "folderNavNode"; //$NON-NLS-0$
						this._foldersSection = new mSection.Section(this._node, {id: "folderNavSection", headerClass: ["sectionTreeTableHeader"], title: "Browse", canHide: !this.readonly});
						this.sectionContents = document.createElement("div"); //$NON-NLS-0$
						this.sectionContents.classList.add("browseSectionWrapper"); 
						this._foldersSection.setContent(this.sectionContents);
						//Render the action node
						if(!this.messageView && this.infoDropDownHandlers && this.infoDropDownHandlers.length > 0) {
							var actionNode = this._foldersSection.getActionElement();
							if(actionNode) {
								lib.empty(actionNode);
								this._destroyInfoDropDowns();
								var letfNode = document.createElement("div"), rightNode=document.createElement("div");
								letfNode.classList.add("layoutLeft");
								rightNode.classList.add("layoutRight");
								actionNode.appendChild(letfNode);
								actionNode.appendChild(rightNode);
								this.actionNode = rightNode;
								
								this.infoDropDownHandlers.forEach(function(handler) {
									var dropdownHolder = document.createElement("div")
									dropdownHolder.classList.add("infoDropDownHolder");
									letfNode.appendChild(dropdownHolder);
									var range = document.createRange();
									range.selectNode(dropdownHolder);
									var infoFragment = range.createContextualFragment(handler.popupTemplate);
									dropdownHolder.appendChild(infoFragment);
									var infoDropDown = new mDropdown.Dropdown({
										triggerNode: lib.node(handler.triggerNodeId), 
										dropdown: lib.node(handler.dropdownNodeId)
									});
									infoDropDown.getItems = function() {
										var inputNode = lib.node(handler.popupTextAreaId);
										inputNode.value = handler.getTextAreaValue();
										return [inputNode];
									};
									infoDropDown._focusDropdownNode = function() {
										lib.node(handler.popupTextAreaId).select();
									};
									infoDropDown._positionDropdown = function() {
										this._dropdownNode.style.left = "";
										this._dropdownNode.style.top = "";
										this._dropdownNode.style.left = this._triggerNode.offsetLeft + this._triggerNode.offsetWidth - this._dropdownNode.offsetWidth  + "px";
									}.bind(infoDropDown);
									this.infoDropDowns.push(infoDropDown);
								}.bind(this));
							}
						} else if (!this.messageView) {
							this.actionNode =  this._foldersSection.getActionElement();
						}
						if(!this.messageView && this.commandRegistry) {
							this.commandRegistry.renderCommands("orion.browse.sectionActions", this.actionNode, {}, "button");
						}
						//Render the branch and component selector 
						var titleNode = this._foldersSection.getTitleElement();
						if(titleNode) {
							lib.empty(titleNode);
							if(this.branchSelector) {
								titleNode.appendChild(this.branchSelector.parentNode);
								this.branchSelector.refresh();
							}
							if(this.componentSelector) {
								titleNode.appendChild(this.componentSelector.parentNode);
								this.componentSelector.refresh();
							}
							if(this.snippetShareOptions && this.snippetShareOptions.oHref) {
								var titleLink = document.createElement("a"); //$NON-NLS-0$
								titleLink.href = this.snippetShareOptions.oHref;
								titleLink.appendChild(document.createTextNode("Click here to see the original file."));
								titleLink.classList.add("downloadLinkName"); //$NON-NLS-0$
								titleNode.appendChild(titleLink);
							}
						}
						//Render the bread crumb 
						if(this.breadCrumbMaker) {
							var bcNodeContainer = document.createElement("div"), bcNode=document.createElement("div");
							bcNodeContainer.appendChild(bcNode);
							if(this.breadCrumbInHeader) {
								bcNodeContainer.classList.add("breadCrumbContainerInHeader"); 
								titleNode.appendChild(bcNodeContainer);
								this.breadCrumbMaker(bcNode, this._foldersSection.getHeaderElement().offsetWidth - 150/*branch selector width*/ - 50);
							} else {
								bcNodeContainer.classList.add("breadCrumbContainer"); 
								this.sectionContents.appendChild(bcNodeContainer);
								this.breadCrumbMaker(bcNode, this._foldersSection.getHeaderElement().offsetWidth - 5);
							}
						}
						//Render the branch level commit information 
						var commitInfo = this.branchSelector ? this.branchSelector.getCommitInfo() : null;
						if(commitInfo) {
							var commitNodeContainer = document.createElement("div");
							commitNodeContainer.classList.add("commitInfoContainer"); 
							this.sectionContents.appendChild(commitNodeContainer);
							new mCommitInfoRenderer.CommitInfoRenderer({parent: commitNodeContainer, commitInfo: commitInfo}).render(this.componentSelector ? "Delivery" : "Commit", true);
						}
						//Render the section contents
						if(this.messageView) {
							if(typeof this.messageView.message === "string") {
								this.updateMessageContents(this.messageView.message, this.messageView.classes ? this.messageView.classes : ["messageViewTable"], this.messageView.tdClass);
							}						
						} else if(this.editorView) {//To embed an orion editor in the section
							this.sectionContents.appendChild(this.editorView.getParent());
							this.editorView.getParent().style.height = "30px"; //$NON-NLS-0$
							this.editorView.create();
							this.resetTextModel = this.snippetShareOptions && this.snippetShareOptions.e ? true : false;
							var textView = this.editorView.editor.getTextView();
							textView.getModel().addEventListener("Changed", this._editorViewModelChangedListener = function(e){ //$NON-NLS-0$
								var textModel = textView.getModel();
								if(this.resetTextModel) {
									var newContents = textModel.getText(this.snippetShareOptions.s ?  this.snippetShareOptions.s : 0, this.snippetShareOptions.e);
									this.resetTextModel = false;
									textModel.setText(newContents);
									return;
								}
								var linesToRender = textModel.getLineCount();
								if(this._maxEditorLines && this._maxEditorLines > 0 && linesToRender >this._maxEditorLines) {
									linesToRender = this._maxEditorLines;
								}
								var textViewheight = textView.getLineHeight() * linesToRender + 20;
								this.editorView.getParent().style.height = textViewheight + "px"; //$NON-NLS-0$
								if(this._browser) {
									this._browser.onTextViewCreated(textView);
								}
							}.bind(this));
							this.editor = this.editorView.editor;
						} else if(this.isMarkdownView) {
							div = document.createElement("div"); //$NON-NLS-0$
							this.markdownView.displayContents(div, this._metadata);
							this.sectionContents.appendChild(div);
						} else if(this.binaryView) {
							if(this.binaryView.domElement) {
								this.updateBinaryView(this.binaryView.domElement, this.binaryView.message);
							}						
						} else {
							this.folderNavExplorer = new FolderNavExplorer({
								parentId: navNode,
								folderViewer: this,
								readonly: this.readonly,
								breadCrumbMaker: this.breadCrumbMaker,
								clickHandler: this.clickHandler,
								fileClient: this.fileClient,
								editorInputManager: this.editorInputManager,
								commandRegistry: this.commandRegistry,
								contentTypeRegistry: this.contentTypeRegistry
							});
							this.sectionContents.appendChild(this.folderNavExplorer.parent);
							this.folderNavExplorer.setCommandsVisible(this._foldersSection, this._isCommandsVisible());
							this.folderNavExplorer.loadRoot(this._metadata);
						}
					} else if(sectionName === "readme"){
						if (readmeMd) {
							div = document.createElement("div"); //$NON-NLS-0$
							this.markdownView.displayInFrame(div, readmeMd,  ["sectionTreeTableHeader", "readmeHeader"], "readmeTitle");
							this._node.appendChild(div);
						}
					}
				}.bind(this));
			}
			
			var sectionsOrder = ["folderNav", "readme"];
			renderSections.apply(this, [sectionsOrder]);
		},
		updateBinaryView: function(domElement, message) {
			var binaryTable = document.createElement("table");
			binaryTable.classList.add("binaryViewTable");
			var tr, td;
			tr = document.createElement("tr");
			td = document.createElement("td"); 
			if(message){
				var messageDiv = document.createElement("div");
				messageDiv.textContent = message;
				messageDiv.classList.add("binaryViewMessage");
				td.appendChild(messageDiv);
			}
			var binaryContent = document.createElement("div");
			binaryContent.appendChild(domElement);
			td.appendChild(binaryContent);
			tr.appendChild(td);
			binaryTable.appendChild(tr);
			this.sectionContents.appendChild(binaryTable);
		},
		updateMessageContents: function(message, messageClasses, tdClass, doNotEmpty) {
			var messageTable = document.createElement("table");
			if(messageClasses){
				messageClasses.forEach( function(messageClass) {
					messageTable.classList.add(messageClass);
				});
			}
			var tr = document.createElement("tr");
			var td = document.createElement("td"); 
			if(tdClass) {
				td.classList.add(tdClass);
			}
			var messageContent = document.createElement("div");
			var segments = mUrlUtils.detectValidURL(message);
			if (segments) {
				mUrlUtils.processURLSegments(messageContent, segments);				
			} else {
				messageContent.appendChild(document.createTextNode(message));
			}
			//messageContent.appendChild(document.createTextNode(message));
			td.appendChild(messageContent);
			tr.appendChild(td);
			messageTable.appendChild(tr);
			if(!doNotEmpty) {
				lib.empty(this.sectionContents);
			}
			this.sectionContents.appendChild(messageTable);
		},
		create: function() {
			if(this._metadata && this._metadata.Projects){ //this is a workspace root
				this.displayWorkspaceView();
			}
			if(this.editorView || this.binaryView || this.isMarkdownView || this.messageView) {
				this.displayBrowseView(this._metadata);
			} else if(this._metadata.Children){
				this.displayBrowseView(this._metadata);
			} else if(this._metadata.ChildrenLocation){
				this.progress.progress(this.fileClient.fetchChildren(this._metadata.ChildrenLocation), "Fetching children of " + this._metadata.Name).then(function(children) {
					this._metadata.Children = children;
					this.displayBrowseView(this._metadata);
				}.bind(this));
			}
		},
		_destroyInfoDropDowns: function() {
			if(this.infoDropDowns) {
				this.infoDropDowns.forEach(function(infoDropDown){
					infoDropDown.destroy();
				});
			}
			this.infoDropDowns = [];
		},
		destroy: function() {
			if(this.editorView) {
				this.editorView. editor.getTextView().getModel().removeEventListener("Changed", this._editorViewModelChangedListener); //$NON-NLS-0$
				this.editorView.destroy();
				this.editor = null;
			}
			if (this.folderNavExplorer) {
				this.folderNavExplorer.destroy();
			}
			this._destroyInfoDropDowns();
			this.folderNavExplorer = null;
			if (this._node && this._node.parentNode) {
				this._node.parentNode.removeChild(this._node);
			}
			this._node = null;
		}
	};
	return {BrowseView: BrowseView};
});

/*******************************************************************************
 * @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
 *******************************************************************************/
/*global define prompt window*/

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
 ******************************************************************************/
/* globals define window */

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
 *******************************************************************************/
/*global define prompt */

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 = {};

	/**
	 * 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-0$
			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-0$
			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-0$
			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-0$
			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-0$
			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-0$
			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-0$
			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-0$
			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-0$
			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-0$
			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-0$
			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-0$
			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();
			var selection = editor.getSelection();
			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;
			editor.setText(text, insertOffset, insertOffset);
			editor.setSelection(insertOffset + delimiter.length, 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();
			var selection = editor.getSelection();
			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;
			editor.setText(text, insertOffset, insertOffset);
			editor.setSelection(insertOffset, 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 selection = editor.getSelection();
			var model = editor.getModel();
			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);
			editor.setText("", lineStart, lineEnd);
			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 selection = editor.getSelection();
			var firstLine = model.getLineAtOffset(selection.start);
			var lastLine = model.getLineAtOffset(selection.end > selection.start ? selection.end - 1 : selection.end);
			if (firstLine !== lastLine) {
				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-0$
				var text = options.expandTab ? new Array(options.tabSize + 1).join(" ") : "\t"; //$NON-NLS-1$ //$NON-NLS-0$
				editor.setText(lines.join(text), lineStart, lineEnd);
				editor.setSelection(lineStart === selection.start ? selection.start : selection.start + text.length, selection.end + ((lastLine - firstLine + 1) * text.length));
				return true;
			}
			return false;
		},
		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();
			var selection = editor.getSelection();
			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) {
				return true;
			}
			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;
			}
			this.startUndo();
			editor.setText("", lineStart, lineEnd);
			editor.setText(text, insertOffset, insertOffset);
			editor.setSelection(insertOffset + delimiterLength, insertOffset + delimiterLength + text.length);
			this.endUndo();
			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();
			var selection = editor.getSelection();
			var firstLine = model.getLineAtOffset(selection.start);
			if (firstLine === 0) {
				return true;
			}
			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;
			}
			this.startUndo();
			editor.setText("", lineStart, lineEnd);
			editor.setText(text, insertOffset, insertOffset);
			editor.setSelection(insertOffset, insertOffset + text.length - delimiterLength);
			this.endUndo();
			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() {
					tooltip.setTarget({
						getTooltipInfo: function() {
							var tooltipCoords = view.convert({
								x: view.getLocationAtOffset(foundAnnotation.start).x,
								y: view.getLocationAtOffset(model.getLineStart(nextLine)).y
							}, "document", "page"); //$NON-NLS-1$ //$NON-NLS-0$
							return {
								contents: foundAnnotations,
								x: tooltipCoords.x,
								y: tooltipCoords.y + Math.floor(view.getLineHeight(nextLine) * 1.33)
							};
						}
					}, 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();
			var selection = editor.getSelection();
			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);
			editor.setText(lines.join(""), 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));
			editor.setSelection(start, 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-0$
			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-2$ //$NON-NLS-1$ //$NON-NLS-0$
			textView.setAction("autoPairSquareBracket", function() { //$NON-NLS-0$
				return this.autoPairBrackets("[", "]"); //$NON-NLS-1$ //$NON-NLS-0$
			}.bind(this));

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

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

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

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

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

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

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

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

			// Autocomplete double quotations
			textView.setKeyBinding(new mKeyBinding.KeyBinding('"', false, false, false, false, "keypress"), "autoPairDblQuotation"); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
			textView.setAction("autoPairDblQuotation", function() { //$NON-NLS-0$
				return this.autoPairQuotations('"'); //$NON-NLS-1$ //$NON-NLS-0$
			}.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$
			var selection = editor.getSelection();
			if (selection.start === selection.end) {
				var model = editor.getModel();
				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 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 lineDelimiter = model.getLineDelimiter();
				var matchCommentStart = /^[\s]*\/\*[\*]*[\s]*$/;
				var matchCommentDelimiter = /^[\s]*\*/;
				var matchCommentEnd = /\*\/[\s]*$/;
				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 (this.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;

					editor.setText(text, selection.start - whitespaceBeforeCaret, selection.end + whitespaceAfterCaret);
					editor.setCaretOffset(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 (this.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$
						}
						editor.setText(text, selection.start, selection.end + lineTextAfterCaret.length);
						editor.setCaretOffset(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;
								}
								editor.setText(text, selection.start, selection.end + lineTextAfterCaret.length);
								editor.setCaretOffset(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);
					editor.setText(text, selection.start, selection.end);
					editor.setCaretOffset(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++; }
					editor.setText(model.getLineDelimiter() + prefix, selection.start, selection.end);
					return true;
				}
			}
			return false;
		},
		addBlockComment: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var model = editor.getModel();
			var selection = editor.getSelection();
			var open = "/*", close = "*/", commentTags = new RegExp("/\\*" + "|" + "\\*/", "g"); //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$

			var result = this._findEnclosingComment(model, selection.start, selection.end);
			if (result.commentStart !== undefined && result.commentEnd !== undefined) {
				return true; // Already in a comment
			}

			var text = model.getText(selection.start, selection.end);
			if (text.length === 0) { return true; }

			var oldLength = text.length;
			text = text.replace(commentTags, "");
			var newLength = text.length;

			editor.setText(open + text + close, selection.start, selection.end);
			editor.setSelection(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 selection = editor.getSelection();
			var model = editor.getModel();
			var currentOffset = editor.getCaretOffset();
			var nextChar = (currentOffset === model.getCharCount()) ? "" : model.getText(selection.start, selection.start + 1).trim(); //$NON-NLS-0$
			var isClosingBracket = new RegExp("^$|[)}\\]>]"); //$NON-NLS-0$ // matches any empty string and closing bracket

			if (selection.start === selection.end && isClosingBracket.test(nextChar)) { //$NON-NLS-0$
				// 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
				editor.setText(openBracket + closeBracket, selection.start, selection.start);
				editor.setCaretOffset(selection.start + 1);
				return true;
			} else if (selection.start !== selection.end) {
				// Wrap the selected text with the specified opening and closing brackets and keep selection on text
				var text = model.getText(selection.start, selection.end);
				editor.setText(openBracket + text + closeBracket, selection.start, selection.end);
				editor.setSelection(selection.start + 1, selection.end + 1);
				return true;
			}
			return false;
		},
		/**
		 * 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 selection = editor.getSelection();
			var model = editor.getModel();
			var currentOffset = editor.getCaretOffset();
			var prevChar = (currentOffset === 0) ? "" : model.getText(selection.start - 1, selection.start).trim(); //$NON-NLS-0$
			var nextChar = (currentOffset === model.getCharCount()) ? "" : model.getText(selection.start, selection.start + 1).trim(); //$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

			// 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)) { return false; }
				editor.setText(quotation + text + quotation, selection.start, selection.end);
				editor.setSelection(selection.start + 1, selection.end + 1);
			} else if (nextChar === quotation) {
				// Skip over the next character if it matches the specified quotation mark
				editor.setCaretOffset(selection.start + 1);
			} else if (prevChar === quotation || isQuotation.test(nextChar) || isAlpha.test(prevChar) || !isClosingBracket.test(nextChar)) {
				// Insert the specified quotation mark
				return false;
			} else {
				// No selection - wrap the caret with the opening and closing quotation marks, and maintain the caret position inbetween the quotations
				editor.setText(quotation + quotation, selection.start, selection.start);
				editor.setCaretOffset(selection.start + 1);
			}
			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 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: proposal.escapePosition
				});
			} else if (proposal.groups && proposal.groups.length > 0 && this.linkedMode) {
				this.linkedMode.enterLinkedMode({
					groups: proposal.groups,
					escapePosition: proposal.escapePosition
				});
			} else if (proposal.escapePosition) {
				//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 selection = editor.getSelection();
			if (selection.start !== selection.end) { return false; }
			var model = editor.getModel();
			var caretOffset = editor.getCaretOffset();
			var prevChar = (caretOffset === 0) ? "" : model.getText(selection.start - 1, selection.start); //$NON-NLS-0$
			var nextChar = (caretOffset === model.getCharCount()) ? "" : model.getText(selection.start, selection.start + 1); //$NON-NLS-0$

			if ((prevChar === "(" && nextChar === ")") || //$NON-NLS-1$ //$NON-NLS-0$
				(prevChar === "[" && nextChar === "]") || //$NON-NLS-1$ //$NON-NLS-0$
				(prevChar === "{" && nextChar === "}") || //$NON-NLS-1$ //$NON-NLS-0$
				(prevChar === "<" && nextChar === ">") || //$NON-NLS-1$ //$NON-NLS-0$
				(prevChar === '"' && nextChar === '"') || //$NON-NLS-1$ //$NON-NLS-0$
				(prevChar === "'" && nextChar === "'")) { //$NON-NLS-1$ //$NON-NLS-0$
				editor.setText("", selection.start, selection.start + 1); //$NON-NLS-0$
			}
			return false;
		},
		_findEnclosingComment: function(model, start, end) {
			var open = "/*", close = "*/"; //$NON-NLS-1$ //$NON-NLS-0$
			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();
			var caretOffset = editor.getCaretOffset();
			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) {
				editor.setSelection(offset, offset);
				return true;
			}
			return false;
		},
		removeBlockComment: function() {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var model = editor.getModel();
			var selection = editor.getSelection();
			var open = "/*", close = "*/"; //$NON-NLS-1$ //$NON-NLS-0$

			// 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;
				}
			}

			if (newStart !== undefined && newEnd !== undefined) {
				editor.setText(model.getText(newStart + open.length, newEnd), newStart, newEnd + close.length);
				editor.setSelection(newStart, newEnd);
			} else {
				// Otherwise find enclosing comment block
				var result = this._findEnclosingComment(model, selection.start, selection.end);
				if (result.commentStart === undefined || result.commentEnd === undefined) {
					return true;
				}

				var text = model.getText(result.commentStart + open.length, result.commentEnd);
				editor.setText(text, result.commentStart, result.commentEnd + close.length);
				editor.setSelection(selection.start - open.length, 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();
			var selection = editor.getSelection();
			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);
			textView.setRedraw(false);
			this.startUndo();
			if (uncomment) {
				for (k = lineIndices.length - 1; k >= 0; k--) {
					index = lineIndices[k] + model.getLineStart(firstLine + k);
					editor.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);
					editor.setText(comment, index, index);
				}
				selStart = lineStart === selection.start ? selection.start : selection.start + l;
				selEnd = selection.end + (l * (lastLine - firstLine + 1));
			}
			this.endUndo();
			textView.setRedraw(true);
			editor.setSelection(selStart, selEnd);
			return true;
		},
		trimTrailingWhitespaces: function() {
			var editor = this.editor;
			var model = editor.getModel();
			var selection = editor.getSelection();
			editor.getTextView().setRedraw(false);
			editor.getUndoStack().startCompoundChange();
			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.
					 */
					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);
					}
				}
			}
			editor.getUndoStack().endCompoundChange();
			editor.getTextView().setRedraw(true);
			editor.setSelection(selection.start, selection.end, false);
		},
		startUndo: function() {
			if (this.undoStack) {
				this.undoStack.startCompoundChange();
			}
		},
		skipClosingBracket: function(closingChar) {
			var editor = this.editor;
			var textView = editor.getTextView();
			if (textView.getOptions("readonly")) { return false; } //$NON-NLS-0$
			var selection = editor.getSelection();
			var model = editor.getModel();
			var currentOffset = editor.getCaretOffset();
			var nextChar = (currentOffset === model.getCharCount()) ? "" : model.getText(selection.start, selection.start + 1); //$NON-NLS-0$

			if (nextChar === closingChar) {
				editor.setCaretOffset(selection.start + 1);
				return true;
			}
			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, 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
 ******************************************************************************/

/*global define */

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.getSelection();
				this.endCaret = view.getCaretOffset();
			}
			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 (this.changes.length > 1 && view) {
				view.setRedraw(true);
			}
			if (select && view) {
				var start = this.startSelection.start;
				var end = this.startSelection.end;
				view.setSelection(this.startCaret ? start : end, this.startCaret ? end : start);
			}
			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 (this.changes.length > 1, view) {
				view.setRedraw(true);
			}
			if (select && view) {
				var start = this.endSelection.start;
				var end = this.endSelection.end;
				view.setSelection(this.endCaret ? start : end, this.endCaret ? end : start);
			}
			var owner = this.owner;
			if (owner && owner.redo) {
				owner.redo();
			}
			return this.changes.length > 0;
		},
		merge: function(start, text, previousText, type, end) {
			var length = this.changes.length;
			if (length > 0) {
				return this.changes[length - 1].merge(start, text, previousText, type, end);
			}
			return false;
		},
		/** @ignore */
		start: function (view) {
			if (view) {
				this.startSelection = view.getSelection();
				this.startCaret = view.getCaretOffset();
			}
			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 true;
		},
		/**
		 * 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;
			}
		},
		_onChanging: function(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)) {
					return;
				}
			}
			this.add(new Change(this.model, start, text, previousText, type));
		}
	};
	
	return {
		UndoStack: UndoStack
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define*/

define("orion/editor/rulers", ['i18n!orion/editor/nls/messages', 'orion/editor/annotations', 'orion/editor/tooltip', 'orion/util'], function(messages, mAnnotations, mTooltip, util) { //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$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) {
		this._location = rulerLocation || "left"; //$NON-NLS-0$
		this._overview = rulerOverview || "page"; //$NON-NLS-0$
		this._rulerStyle = rulerStyle;
		this._view = null;
		var self = this;
		this._listener = {
			onTextModelChanged: function(e) {
				self._onTextModelChanged(e);
			},
			onAnnotationModelChanged: function(e) {
				self._onAnnotationModelChanged(e);
			}
		};
		this.setAnnotationModel(annotationModel);
	}
	Ruler.prototype = /** @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 ruler location.
		 *
		 * @returns {String} the ruler location, which is either "left" or "right".
		 *
		 * @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".
		 *
		 * @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;
		},
		/**
		 * 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;
		},
		/**
		 * 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$
			}
		},
		/**
		 * This event is sent when the user clicks a line annotation.
		 *
		 * @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 baseModel = model;
			var start = model.getLineStart(lineIndex), lineStart = start;
			var end = start;
			var annotationModel = this._annotationModel;
			if (annotationModel) {
				var selection = view.getSelection();
				end = model.getLineEnd(lineIndex, true);
				if (start <= selection.start && selection.start < end) {
					start = selection.start;
				}
				if (model.getBaseModel) {
					start = model.mapOffset(start);
					end = model.mapOffset(end);
					baseModel = model.getBaseModel();
				}
				var annotation, iter = annotationModel.getAnnotations(start, end);
				var clickedAnnotation = null;
				while (!annotation && iter.hasNext()) {
					var a = iter.next();
					if (!this.isAnnotationTypeVisible(a.type)) { continue; }
					clickedAnnotation = a;
					if (a.start <= start) { continue; }
					annotation = a; 
				}
				if (clickedAnnotation && clickedAnnotation.groupId !== undefined) {
					if (this._currentClickGroup === clickedAnnotation.groupId) {
						this._currentClickGroup = null;
					} else {
						this._currentClickGroup = clickedAnnotation.groupId;
					}
					this._setCurrentGroup(lineIndex);
				} 
				if (annotation && baseModel.getLineAtOffset(annotation.start) === baseModel.getLineAtOffset(start)) {
					start = annotation.start;
					end = annotation.end;
				} else {
					end = start = lineStart;
				}
				
				if (model.getBaseModel) {
					start = model.mapOffset(start, true);
					end = model.mapOffset(end, true);
				}
			}
			var tooltip = mTooltip.Tooltip.getTooltip(this._view);
			if (tooltip) {
				tooltip.setTarget(null);
			}
			this._view.setSelection(end, start, 1/3, function(){});
		},
		/**
		 * 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.
		 */
		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;
			var self = this;
			tooltip.setTarget({
				y: e.clientY,
				getTooltipInfo: function() {
					return self._getTooltipInfo(self._tooltipLineIndex, this.y);
				}
			});
		},
		/**
		 * 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.
		 */
		onMouseOut: function(lineIndex, e) {
			if (!this._currentClickGroup) {
				this._setCurrentGroup(-1);
			}
			var tooltip = mTooltip.Tooltip.getTooltip(this._view);
			if (!tooltip) { return; }
			tooltip.setTarget(null);
		},
		/** @ignore */
		_getTooltipInfo: function(lineIndex, y) {
			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);
			}
			var contents = this._getTooltipContents(lineIndex, annotations);
			if (!contents) { return null; }
			var info = {
				contents: contents,
				anchor: this.getLocation()
			};
			var rect = view.getClientArea();
			if (this.getOverview() === "document") { //$NON-NLS-0$
				rect.y = view.convert({y: y}, "view", "document").y; //$NON-NLS-1$ //$NON-NLS-0$
			} else {
				rect.y = view.getLocationAtOffset(model.getLineStart(lineIndex)).y;
			}
			view.convert(rect, "document", "page"); //$NON-NLS-1$ //$NON-NLS-0$
			info.x = rect.x;
			info.y = rect.y;
			if (info.anchor === "right") { //$NON-NLS-0$
				info.x += rect.width;
			}
			return info;
		},
		/** @ignore */
		_getTooltipContents: function(lineIndex, annotations) {
			return annotations;
		},
		/** @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; }
					var 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 & 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.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 =  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.setTarget(null);
			}
			if (annotation.expanded) {
				annotation.collapse();
			} else {
				annotation.expand();
			}
		}
	};
	/** @ignore */
	FoldingRuler.prototype._getTooltipContents = function(lineIndex, annotations) {
		if (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]);
		}
	};
	
	return {
		Ruler: Ruler,
		AnnotationRuler: AnnotationRuler,
		LineNumberRuler: LineNumberRuler,
		OverviewRuler: OverviewRuler,
		FoldingRuler: FoldingRuler
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/
 
/*global define */

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 selection = view.getSelection();
			var model = view.getModel();
			if (model.getBaseModel) {
				selection.start = model.mapOffset(selection.start);
				selection.end = model.mapOffset(selection.end);
				model = model.getBaseModel();
			}
			var text = model.getText(selection.start, selection.end);
			if (text) {
				this._dragSelection = selection;
				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("", this._dragSelection.start, this._dragSelection.end);
				}
				if (this._dropText) {
					var text = this._dropText;
					var offset = this._dropOffset;
					if (move) {
						if (offset >= this._dragSelection.end) {
							offset -= this._dragSelection.end - this._dragSelection.start;
						} else if (offset >= this._dragSelection.start) {
							offset = this._dragSelection.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) 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
 ******************************************************************************/

/*global define*/

define("orion/editor/templates", [], function() { //$NON-NLS-0$

	/** 
	 * Removes prefix from string.
	 * @param {String} prefix
	 * @param {String} string
	 */
	function chop(prefix, string) {
		return string.substring(prefix.length);
	}
	
	var tabVar = "${tab}"; //$NON-NLS-0$
	var delimiterVar = "${delimiter}"; //$NON-NLS-0$
	var cursorVar = "${cursor}"; //$NON-NLS-0$
	
	function Template (prefix, description, template, name) {
		this.prefix = prefix;
		this.description = description;
		this.template = template;
		this.name = name;
		this._parse();
	}
	Template.prototype = /** @lends orion.editor.Template.prototype */ {
		getProposal: function(prefix, offset, context) {
			//any returned positions need to be offset based on current cursor position and length of prefix
			var startOffset = offset-prefix.length;
			var groups = {};
			var escapePosition;
			var delimiter = context.delimiter !== undefined ? context.delimiter : "\n"; //$NON-NLS-0$
			if (context.indentation) {
				delimiter += context.indentation;
			}
			var tab = context.tab !== undefined ? context.tab : "\t"; //$NON-NLS-0$
			var delta = 0;
			var variables = this.variables;
			var segments = this.segments, proposal = [];
			for (var i = 0; i < segments.length; i++) {
				var segment = segments[i];
				var variable = variables[segment];
				if (variable !== undefined) {
					switch (segment) {
						case tabVar:
							segment = tab;
							break;
						case delimiterVar:
							segment = delimiter;
							break;
						case cursorVar:
							segment = "";
							escapePosition = delta;
							break;
						default:
							var g = groups[segment];
							if (!g) {
								g = groups[segment] = {data: variable.data, positions: []};
							}
							segment = variable.substitution;
							if (g.data && g.data.values) { segment = g.data.values[0]; }
							g.positions.push({
								offset: startOffset + delta,
								length: segment.length
							});
					}
				}
				proposal.push(segment);
				delta += segment.length;
			}
			var newGroups = [];
			for (var p in groups) {
				if (groups.hasOwnProperty(p)) {
					newGroups.push(groups[p]);
				}
			}
			proposal = proposal.join("");
			if (escapePosition === undefined) {
				escapePosition = proposal.length;
			}
			return {
				proposal: proposal,
				name: this.name,
				description: this.description,
				groups: newGroups,
				escapePosition: startOffset + escapePosition,
				style: 'noemphasis'
			};
		},
		match: function(prefix) {
			return this.prefix.indexOf(prefix) === 0;
		},
		_parse: function() {
			var template = this.template;
			var segments = [], variables = {}, segment, start = 0;
			template = template.replace(/\n/g, delimiterVar);
			template = template.replace(/\t/g, tabVar);
			template.replace(/\$\{((?:[^\\}]+|\\.))*\}/g, function(group, text1, index) {
				var text = group.substring(2,group.length-1);
				var variable = group, substitution = text, data = null;
				var colon = substitution.indexOf(":"); //$NON-NLS-0$
				if (colon !== -1) {
					substitution = substitution.substring(0, colon);
					variable = "${"+ substitution + "}"; //$NON-NLS-1$ //$NON-NLS-0$
					data = JSON.parse(text.substring(colon + 1).replace("\\}", "}").trim()); //$NON-NLS-1$ //$NON-NLS-0$
				}
				var v = variables[variable];
				if (!v) { v = variables[variable] = {}; }
				v.substitution = substitution;
				if (data) {
					v.data = data;
				}
				segment = template.substring(start, index);
				if (segment) { segments.push(segment); }
				segments.push(variable);
				start = index + group.length;
				return substitution;
			});
			segment = template.substring(start, template.length);
			if (segment) { segments.push(segment); }
			this.segments = segments;
			this.variables = variables;
		}
	};
	
	function TemplateContentAssist (keywords, templates) {
		this._keywords = keywords || [];
		this._templates = [];
		this.addTemplates(templates || []);
	}
	TemplateContentAssist.prototype = /** @lends orion.editor.TemplateContentAssist.prototype */ {
		addTemplates: function(json) {
			var templates = this.getTemplates();
			for (var j = 0; j < json.length; j++) {
				templates.push(new Template(json[j].prefix, json[j].description, json[j].template, json[j].name));
			}
		},
		/**
		 * Called by the content assist engine to initialize this provider before any <tt>computeProposals()</tt> calls.
		 * This implementation does nothing; subclasses may override.
		 */
		initialize: function() {
		},
		computeProposals: function(buffer, offset, context) {
			var prefix = this.getPrefix(buffer, offset, context);
			var proposals = [];
			if (this.isValid(prefix, buffer, offset, context)) {
				proposals = proposals.concat(this.getTemplateProposals(prefix, offset, context));
				proposals = proposals.concat(this.getKeywordProposals(prefix));
			}
			return proposals;
		},
		getKeywords: function() {
			return this._keywords;
		},
		getKeywordProposals: function(prefix) {
			var proposals = [];
			var keywords = this.getKeywords();
			if (keywords) {
				for (var i = 0; i < keywords.length; i++) {
					if (keywords[i].indexOf(prefix) === 0) {
						proposals.push({proposal: chop(prefix, keywords[i]), 
							description: keywords[i], 
							style: 'noemphasis_keyword'//$NON-NLS-0$
						});
					}
				}
				
				if (0 < proposals.length) {
					proposals.splice(0, 0,{
						proposal: '',
						description: 'Keywords', //$NON-NLS-0$
						style: 'noemphasis_title_keywords', //$NON-NLS-0$
						unselectable: true
					});	
				}
			}
			return proposals;
		},
		getPrefix: function(buffer, offset, context) {
			return context.prefix;
		},
		getTemplates: function() {
			return this._templates;
		},
		getTemplateProposals: function(prefix, offset, context) {
			var proposals = [];
			var templates = this.getTemplates();
			for (var t = 0; t < templates.length; t++) {
				var template = templates[t];
				if (template.match(prefix)) {
					var proposal = template.getProposal(prefix, offset, context);
					this.removePrefix(prefix, proposal);
					proposals.push(proposal);
				}
			}
			
			if (0 < proposals.length) {
				//sort the proposals by name
				proposals.sort(function(p1, p2) {
					if (p1.name < p2.name) return -1;
					if (p1.name > p2.name) return 1;
					return 0;
				});
				// if any templates were added to the list of 
				// proposals, add a title as the first element
				proposals.splice(0, 0, {
					proposal: '',
					description: 'Templates', //$NON-NLS-0$
					style: 'noemphasis_title', //$NON-NLS-0$
					unselectable: true
				});
			}
			
			return proposals;
		},
		removePrefix: function(prefix, proposal) {
			var overwrite = proposal.overwrite = proposal.proposal.substring(0, prefix.length) !== prefix;
			if (!overwrite) {
				proposal.proposal = chop(prefix, proposal.proposal);
			}
		},
		isValid: function(prefix, buffer, offset, context) {
			return true;
		}
	};
	
	return {
		Template: Template,
		TemplateContentAssist: TemplateContentAssist
	};
});

/*******************************************************************************
 * @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
 *******************************************************************************/
/*global define */

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/editor/templates', //$NON-NLS-0$
	'orion/objects', //$NON-NLS-0$
	'orion/util' //$NON-NLS-0$
], function(messages, mKeyBinding, mKeyModes, mAnnotations, mTemplates, objects) {

	var exports = {};

	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);
					}
				}
				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) {
					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 mTemplates.TemplateContentAssist(group.data.values);
						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 (model.escapePosition !== undefined) {
						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--) {
				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; }
					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
 *******************************************************************************/
/*global define */

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$
], function(mActions, mUndoStack, mRulers, mAnnotations, mTextDND, mLinkedMode) {

	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 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
 ******************************************************************************/
/*global define */

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);
});

/*******************************************************************************
 * @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
 ******************************************************************************/
/*global define*/

define('orion/editorPreferences',[], function() {

	var SETTINGS_SECTION = "/editor/settings"; //$NON-NLS-0$
	var SETTINGS_KEY = "editorSettings"; //$NON-NLS-0$

	var defaults = {
		autoSave: false,
		autoSaveVisible: true,
		autoSaveLocalVisible: true,
		autoSaveTimeout: 250,
		autoSaveTimeoutVisible: true,
		themeVisible: true,
		themeLocalVisible: true,
		fontSizeVisible: true,
		fontSizeLocalVisible: true,
		autoLoad: true,
		autoLoadVisible: true,
		saveDiffs: true,
		saveDiffsVisible: true,
		contentAssistAutoTrigger: true,
		contentAssistAutoTriggerVisible: true,
		showOccurrences: false,
		showOccurrencesVisible: true,
		autoPairParentheses: true,
		autoPairParenthesesVisible: true,
		autoPairBraces: true,
		autoPairBracesVisible: true,
		autoPairSquareBrackets: true,
		autoPairSquareBracketsVisible: true,
		autoPairAngleBrackets: false,
		autoPairAngleBracketsVisible: true,
		autoPairQuotations: true,
		autoPairQuotationsVisible: true,
		autoCompleteComments: true,
		autoCompleteCommentsVisible: true,
		smartIndentation: true,
		smartIndentationVisible: true,
		trimTrailingWhiteSpace: false,
		trimTrailingWhiteSpaceVisible: true,
		tabSize: 4,
		tabSizeVisible: true,
		expandTab: false,
		expandTabVisible: true,
		scrollAnimation: true,
		scrollAnimationVisible: true,
		scrollAnimationTimeout: 300,
		scrollAnimationTimeoutVisible: true,
		annotationRuler: true,
		annotationRulerVisible: true,
		lineNumberRuler: true,
		lineNumberRulerVisible: true,
		foldingRuler: true,
		foldingRulerVisible: true,
		overviewRuler: true,
		overviewRulerVisible: true,
		showTabs: false,
		showTabsVisible: true,
		showSpaces: false,
		showSpacesVisible: true,
		wordWrap: false,
		wordWrapVisible: true,
		showMargin: false,
		showMarginVisible: true,
		marginOffset: 80,
		marginOffsetVisible: true,
		keyBindings: "Default",
		keyBindingsVisible: true,
		keyBindingsLocalVisible: true,
		splitOrientation: "horizontal",
		splitOrientationVisible: true,
		splitOrientationLocalVisible: true
	};

	function EditorPreferences(preferences, callback) {
		this._preferences = preferences;
		this._callback = callback;
		if (callback) {
			var storageKey = preferences.listenForChangedSettings(SETTINGS_SECTION, function (e) {
				if (e.key === storageKey) {
					callback();
				}
			}.bind(this));
		}
	}

	EditorPreferences.prototype = /** @lends edit.EditorPreferences.prototype */ {
		_initialize: function(prefs) {
			var settings = prefs.get(SETTINGS_KEY) || {};
			for (var property in defaults) {
				if (!settings.hasOwnProperty(property)) {
					settings[property] = defaults[property];
				}
			}
			return settings;
		},
		getPrefs: function(callback) {
			this._preferences.getPreferences(SETTINGS_SECTION).then(function(prefs) {
				var object = this._initialize(prefs);
				if (typeof object === "string") { //$NON-NLS-0$
					object = JSON.parse(object);
				}
				callback(object);
			}.bind(this));
		},
		setPrefs: function(object, callback) {
			this._preferences.getPreferences(SETTINGS_SECTION).then(function(prefs) {
				prefs.put(SETTINGS_KEY, object);
				object = this._initialize(prefs);
				if (callback) {
					callback(object);
				}
				if (this._callback) {
					this._callback(object);
				}
			}.bind(this));
		}
	};

	return { EditorPreferences: EditorPreferences };
});

/*******************************************************************************
 * @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
 ******************************************************************************/
/*global define */

define('orion/widgets/themes/ThemePreferences',[], function() {

	function ThemePreferences(preferences, themeData) {
		this._preferences = preferences;
		this._themeData = themeData;
		var themeInfo = themeData.getThemeStorageInfo();
		this._themeVersion = themeInfo.version;
		var storageKey = preferences.listenForChangedSettings(themeInfo.storage, 2, function(e) {
			if (e.key === storageKey) {
				this.apply();
			}
		}.bind(this));
	}
	
	ThemePreferences.prototype = /** @lends orion.editor.ThemePreferences.prototype */ {
		_initialize: function(themeInfo, themeData, prefs) {
			var styles, selected;
			if (this._themeVersion === undefined || prefs.get('version') === this._themeVersion) { //$NON-NLS-0$
				// Version matches (or ThemeData hasn't provided an expected version). Trust prefs
				styles = prefs.get(themeInfo.styleset);
				selected = prefs.get('selected'); //$NON-NLS-0$
				if (selected) {
					selected = JSON.parse(selected);
				}
			} else {
				// Stale theme prefs. Overwrite everything
				styles = null;
				selected = null;
			}

			if (!styles){
				styles = themeData.getStyles();
				prefs.put(themeInfo.styleset, JSON.stringify(styles)); 
			}
			if (!selected || selected[themeInfo.selectedKey] === undefined) {
				selected = selected || {}; 
				selected[themeInfo.selectedKey] = themeInfo.defaultTheme;
				prefs.put('selected', JSON.stringify(selected)); //$NON-NLS-0$
			}
			// prefs have now been updated
			prefs.put('version', this._themeVersion); //$NON-NLS-0$
		},
		apply: function() {
			this.setTheme();
		},
		getTheme: function(callback) {
			var themeData = this._themeData;
			var themeInfo = themeData.getThemeStorageInfo();
			this._preferences.getPreferences(themeInfo.storage, 2).then(function(prefs) {
				this._initialize(themeInfo, themeData, prefs);
				var selected = JSON.parse(prefs.get('selected')); //$NON-NLS-0$
				var styles = JSON.parse(prefs.get(themeInfo.styleset)), style;
				if (styles) {	
					for (var i = 0; i < styles.length; i++ ){
						if( styles[i].name === selected[themeInfo.selectedKey] ){
							style = styles[i];
							break;
						}
					}
				}
				callback({
					style: style,
					styles: styles
				});
			}.bind(this));
		},
		setTheme: function(name, styles) {
			var themeData = this._themeData;
			var themeInfo = themeData.getThemeStorageInfo();
			this._preferences.getPreferences(themeInfo.storage, 2).then(function(prefs) {
				this._initialize(themeInfo, themeData, prefs);
				var selected = JSON.parse(prefs.get('selected')); //$NON-NLS-0$
				if (!name) {
					name = selected[themeInfo.selectedKey];
				}
				selected[themeInfo.selectedKey] = name;
				prefs.put('selected', JSON.stringify(selected)); //$NON-NLS-0$
				if (styles) {
					prefs.put(themeInfo.styleset, JSON.stringify(styles)); 
				} else {
					styles = JSON.parse(prefs.get(themeInfo.styleset));
				}
				for (var i = 0; i <styles.length; i++) {
					if (styles[i].name === selected[themeInfo.selectedKey]) {
						themeData.processSettings(styles[i]);
						break;
					}	
				}
				prefs.put('version', this._themeVersion);
			}.bind(this));
		},
		setFontSize: function(size) {
			var themeData = this._themeData;
			var themeInfo = themeData.getThemeStorageInfo();
			this._preferences.getPreferences(themeInfo.storage, 2).then(function(prefs) {
				this._initialize(themeInfo, themeData, prefs);
				var selected = JSON.parse(prefs.get('selected')); //$NON-NLS-0$
				var styles = JSON.parse(prefs.get(themeInfo.styleset)), style;
				if (styles) {	
					for( var s = 0; s < styles.length; s++ ){
						styles[s].fontSize = size;
						if( styles[s].name ===  selected[themeInfo.selectedKey] ){
							style = styles[s];
						}
						
					}
				}
				prefs.put(themeInfo.styleset , JSON.stringify(styles));
				themeData.processSettings(style);
			}.bind(this));
		}
	};

	return{
		ThemePreferences: ThemePreferences
	};
});

/*******************************************************************************
 * @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).
 *
 ******************************************************************************/

//NLS_CHARSET=UTF-8
/*global define */

define('orion/settings/nls/root/messages',{
	"Plugin Description": "Plugin Description",
	"Create": "Create",
	"Loading...": "Loading...",
	"Label:": "Label:",
	"Title": "Title",
	"Plugins": "Plugins",
	"User Profile": "User Profile",
	"Git": "Git",
	"Git Settings": "Git Settings",
	"General": "General",
	"Navigation": "Navigation",
	"Links": "Links",
	"Open in same tab": "Open in same tab",
	"Open in new tab": "Open in new tab",
	"Font": "Font",
	"Family": "Family",
	"Sans Serif": "Sans Serif",
	"Serif": "Serif",
	"Size": "Size",
	"8pt": "8pt",
	"9pt": "9pt",
	"10pt": "10pt",
	"12pt": "12pt",
	"Color": "Color",
	"Background": "Background",
	"Strings": "Strings",
	"String Types": "String Types",
	"blue": "blue",
	"Weight": "Weight",
	"Normal": "Normal",
	"Bold": "Bold",
	"Comments": "Comments",
	"Comment Types": "Comment Types",
	"green": "green",
	"Keywords": "Keywords",
	"Keyword Types": "Keyword Types",
	"darkred": "darkred",
	"Categories": "Categories",
	"Username": "Username",
	"Full Name": "Full Name",
	"Email Address": "Email Address",
	"Email Confirmed": "Email Confirmed",
	"Account": "Account",
	"Current Password": "Current Password",
	"New Password": "New Password",
	"Verify Password": "Verify Password",
	"Password": "Password",
	"OpenId": "OpenId",
	"AOL": "AOL",
	"Yahoo": "Yahoo",
	"Google": "Google",
	"Unlink": "Unlink",
	"Link": "Link",
	"Unlinked": "Unlinked",
	"Linked": "Linked",
	"Linked Accounts": "Linked Accounts",
	"Git Email Address": "Git Email Address",
	"Git Username": "Git Username",
	"Git Credentials Storage": "Git Credentials Storage",
	"Update": "Update",
	"Update Profile Settings": "Update Profile Settings",
	"Update Git User Settings": "Update Git User Settings",
	"Update Git Credentials": "Update Git Credentials",
	"User profile data successfully updated.": "User profile data successfully updated.",
	"Git user data successfully updated.": "Git user data successfully updated.",
	"Git Credentials successfully updated.": "Git Credentials successfully updated.",
	"Edit Plugin JavaScript": "Edit Plugin JavaScript",
	"Install Plugin": "Install Plugin",
	"Plugin Name:": "Plugin Name:",
	"Author Name:": "Author Name:",
	"Licence:": "Licence:",
	"Description:": "Description:",
	"A plugin for Eclipse Orion": "A plugin for Eclipse Orion",
	"Plugin Link": "Plugin Link",
	"Install": "Install",
	"Install a plugin by specifying its URL": "Install a plugin by specifying its URL",
	"Plugin URL:": "Plugin URL:",
	"Disable": "Disable",
	"DisableTooltip": "Disable the plugin",
	"Enable": "Enable",
	"EnableTooltip": "Enable the plugin",
	"Reload all": "Reload all",
	"Reload all installed plugins": "Reload all installed plugins",
	"Create a new Orion Plugin": "Create a new Orion Plugin",
	"Find More Orion Plugins": "Find More Orion Plugins",
	"Get Plugins": "Get Plugins",
	"Reload": "Reload",
	"Reload the plugin": "Reload the plugin",
	"Delete": "Delete",
	"Delete this plugin from the configuration": "Delete this plugin from the configuration",
	"DeleteUser" : "Delete User Profile as well as workspaces and projects",
	"DeleteUserComfirmation" : "WARNING: This will permanently delete your user profile as well all your work!",
	"Type a plugin url here ...": "Type a plugin url here ...",
	"Installed ": "Installed ",
	"Installing ": "Installing ",
	"Already installed": "Already installed",
	" plugin": " plugin",
	"Reloaded ": "Reloaded ",
	"Uninstalled ": "Uninstalled ",
	"Are you sure you want to uninstall '": "Are you sure you want to uninstall '",
	"Services": "Services",
	"Value": "Value",
	"JavaScript Object": "JavaScript Object",
	"click here, then check javascript console to drill down": "click here, then check javascript console to drill down",
	"Item": "Item",
	"Git Config": "Git Config",
	"Clear Git Credentials": "Clear Git Credentials",
	"Enable Storage": "Enable Storage",
	"Please be aware that your credentials will be stored persistently in the browser." : "Please be aware that your credentials will be stored persistently in the browser.",
	"Do you wish to enable the Key Storage?" : "Do you wish to enable the Key Storage?",
	"general": "General",
	"validation": "Validation",
	"Deleted git credentials for ${0}": "Deleted git credentials for ${0}",
	"Editor": "Editor",
	"editorSettings": "Settings",
	"EditorThemes": "Themes",
	"Import": "Import",
	"Import a theme": "Import a theme",
	"Export": "Export",
	"Export a theme": "Export a theme",
	"Theme name:": "Theme name:",
	"yourTheme": "yourTheme",
	"fileManagement" : "File Management",
	"typing": "Typing",
	"autoSave": "Auto Save",
	"autoSaveTimeout": "Save interval (ms)",
	"autoLoad": "Auto Load",
	"saveDiffs": "Save file as diffs",
	"trimTrailingWhiteSpace": "Trim Trailing Whitespace on Save",
	"Restore": "Restore Defaults",
	"Default": "Default",
	"keys": "Keys",
	"tabs": "Tabs",
	"tabSize": "Tab Size",
	"expandTab": "Insert spaces for tabs",
	"smoothScrolling": "Smooth Scrolling",
	"scrollAnimation": "Scrolling Animation",
	"scrollAnimationTimeout": "Scrolling Duration (ms)",
	"keyBindings": "Key Bindings",
	"rulers": "Rulers",
	"annotationRuler": "Show Annotation Ruler",
	"lineNumberRuler": "Show Line Number Ruler",
	"foldingRuler": "Show Folding Ruler",
	"overviewRuler": "Show Overview Ruler",
	"whitespaces": "White Spaces",
	"wrapping": "Wrapping",
	"wordWrap": "Word Wrap",
	"showMargin": "Show Margin",
	"marginOffset": "Margin Column",
	"showTabs": "Show Tabs",
	"showSpaces": "Show Spaces",
	"autoSaveTimeoutInvalid": "Invalid save interval.",
	"scrollAnimationTimeoutInvalid": "Invalid scrolling duration.",
	"tabSizeInvalid": "Invalid tab size.",
	"localSettingsTooltip" : "Toggle whether this setting is shown in the local editor settings drop down.",
	"editorSettingsInfo": "Use the ${0} and ${1} to toggle whether a given setting is shown in the local editor settings drop down ${2}.",
	"autoPairParentheses": "Autopair (Parentheses)",
	"autoPairBraces": "Autopair {Braces}",
	"autoPairSquareBrackets": "Autopair [Square] Brackets",
	"autoPairAngleBrackets": "Autopair <Angle> Brackets",
	"autoPairQuotations": 'Autopair "Strings"',
	"autoCompleteComments": "Autocomplete /** Block Comments */",
	"smartIndentation": "Smart Indentation",
	"sourceControl": "Source Control",
	"showBlame": "Show Blame",
	"languageTools": "Language Tools",
	"showOccurrences": "Show Occurrences",
	"contentAssistAutoTrigger": "Show Content Assist automatically",
	"Editor preferences updated": "Editor preferences updated",
	"Editor defaults restored": "Editor defaults restored",
	"Theme": "Theme",
	"Font Size": "Font Size",
	"New Theme Name:": "New Theme Name:",
	"Font Size:": "Font Size:",
	"UI Theme": "UI Theme",
	"Navigation Bar": "Navigation Bar",
	"Navigation Text": "Navigation Text",
	"Search Box": "Search Box",
	"Tool Panel": "Tool Panel",
	"Selection Bar": "Selection Bar",
	"Location": "Location",
	"Content": "Content",
	"Main Panel": "Main Panel",
	"Button": "Button",
	"Button Text": "Button Text",
	"Section Text": "Section Text",
	"Side Panel": "Side Panel",
	"Line Color": "Line Color",
	"Line Numbers": "Line Numbers",
	"Foreground": "Foreground",
	"Current Line": "Current Line",
	"HTML Attribute": "HTML Attribute",
	"Overview Ruler": "Overview Ruler",
	"HTML Tag": "HTML Tag",
	"Annotation Ruler": "Annotation Ruler",
	"Show Guide": "Show Guide",
	"Check Guide": "Check Guide",
	"Cancel": "Cancel",
	"Revert Theme": "Revert Theme",
	"Update Theme": "Update Theme",
	"Theme:": "Theme:",
	"clickDiagram": "Click diagram to style.",
	"clickUpdate": "Click Update button to save preview.",
	"CSS Class Name": "CSS Class Name",
	"CSS Attribute": "CSS Attribute",
	"CSS Value": "CSS Value",
	"CSS Text": "CSS Text",
	"COLOR:": "Color:",
	"NEW COLOR:": "New Color:",
	"Ok": "Ok",
	"OR HEX:": "Or Hex: ",
	"splitEditors": "Split Editors",
	"splitOrientation": "Split Orientation",
	"horizontal": "Horizontal",
	"vertical": "Vertical"
});

/*******************************************************************************
 * @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). 
 * 
 ******************************************************************************/
define('orion/settings/nls/messages',['orion/i18n!orion/settings/nls/messages', 'orion/settings/nls/root/messages'], function(bundle, root) {
	var result = {
			root:root
	};
	Object.keys(bundle).forEach(function(key) {
		if (typeof result[key] === 'undefined') {
			result[key] = bundle[key];
		}
	});
	return result;
});

/*******************************************************************************
 * @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: Anton McConville - IBM Corporation - initial API and implementation
 ******************************************************************************/
/*jslint amd:true*/
define('orion/widgets/themes/ThemeVersion',[], function() {

	/**
	 * Version string for theme data. Please update this string whenever you change the style of a themable element.
	 */
	var THEMES_VERSION = "6.1";

	return THEMES_VERSION;
});

/*******************************************************************************
 * @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
 ******************************************************************************/
/*global widgets orion window console define localStorage ActiveXObject DOMParser*/
/*jslint browser:true*/

define('orion/widgets/themes/editor/ThemeData',[
		'i18n!orion/settings/nls/messages',
		'orion/editor/textTheme',
		'orion/widgets/themes/ThemeVersion'
], function(messages, mTextTheme, THEMES_VERSION) {

	// *******************************************************************************
	//
	// If you change any styles in this file, you must increment the version number
	// in ThemeVersion.js.
	//
	// *******************************************************************************

		/* Synchronizing colors and styles for HTML, CSS and JS files like this ...
	
			Using Prospecto as an example:
			
			-----------------------------------------------
							CSS			HTML		JS
			-----------------------------------------------
			ORANGE			Class		Tag			Keyword
			darkSlateGray	Text		Text		Text
			darkSeaGreen	Comments	Comments	Comments
			cornFlowerblue	String		String		String
			----------------------------------------------- */

		function StyleSet(){
		
		}
		
		function multiply(a,b){
			var resultString = 'Result:';
			var result = a*b;
			return resultString + result;
		}
		
		var defaultFont = '"Consolas", "Monaco", "Vera Mono", monospace';
		var defaultFontSize = '10pt';

		function ThemeData() {

		this.styles = [];

		var eclipse = new StyleSet();

		eclipse.name = 'Eclipse';
		eclipse.annotationRuler = 'white';
		eclipse.background = 'white';
		eclipse.comment = 'green';
		eclipse.keyword = '#7f0055';
		eclipse.text = 'darkSlateGray';
		eclipse.string = 'blue';
		eclipse.overviewRuler = 'white';
		eclipse.lineNumberOdd = '#444';
		eclipse.lineNumberEven = '#444';
		eclipse.lineNumber = '#444';
		eclipse.currentLine = '#EAF2FE';
		eclipse.constant = 'blue';
		eclipse.tag = 'darkorange';
		eclipse.attribute = 'cadetBlue';
		eclipse.fontFamily = defaultFont;
		eclipse.fontSize = defaultFontSize;
		eclipse.rightRuler = '#DDDDDD';
		eclipse.leftRuler = '#DDDDDD'; 

		this.styles.push(eclipse);

		var prospecto = new StyleSet();

		prospecto.name = 'Prospecto';
		prospecto.annotationRuler = 'white';
		prospecto.background = 'white';
		prospecto.comment = '#3C802C';
		prospecto.keyword = '#CC4C07';
		prospecto.text = '#333';
		prospecto.string = '#446FBD';
		prospecto.overviewRuler = 'white';
		prospecto.lineNumberOdd = '#CCCCCC';
		prospecto.lineNumberEven = '#CCCCCC';
		prospecto.lineNumber = '#CCCCCC';
		prospecto.currentLine = '#EAF2FE';
		prospecto.tag = '#CC4C07';
		prospecto.attribute = 'cadetBlue';
		prospecto.fontFamily = defaultFont;
		prospecto.fontSize = defaultFontSize;
		prospecto.rightRuler = '#EEEEEE';
		prospecto.leftRuler = '#EEEEEE';
		prospecto.constant = 'darkOrchid';


		this.styles.push(prospecto);

		var blue = new StyleSet();

		blue.name = 'Blue';
		blue.annotationRuler = 'lavender';
		blue.background = 'aliceBlue';
		blue.comment = 'indigo';
		blue.keyword = 'cornFlowerBlue';
		blue.text = 'navy';
		blue.string = 'cornFlowerBlue';
		blue.overviewRuler = 'lavender';
		blue.lineNumberOdd = 'darkSlateGray';
		blue.lineNumberEven = 'darkSlateGray';
		blue.lineNumber = 'darkSlateGray';
		blue.currentLine = 'white';
		blue.tag = 'cornFlowerBlue';
		blue.attribute = 'cadetBlue';
		blue.rightRuler = 'white';
		blue.leftRuler = 'white';
		blue.fontFamily = defaultFont;
		blue.fontSize = defaultFontSize;
		blue.constant = 'blue';

		this.styles.push(blue);

		var ambience = new StyleSet();

		ambience.name = 'Ambience';
		ambience.annotationRuler = '#3D3D3D';
		ambience.background = 'darkgrey';
		ambience.comment = 'mediumslateblue';
		ambience.keyword = 'cornFlowerBlue';
		ambience.text = 'darkseagreen';
		ambience.string = 'lightcoral';
		ambience.overviewRuler = 'white';
		ambience.lineNumberOdd = 'black';
		ambience.lineNumberEven = 'black';
		ambience.lineNumber = 'black';
		ambience.currentLine = 'lightcyan';
		ambience.tag = 'cornFlowerBlue';
		ambience.attribute = 'cadetBlue';
		ambience.rightRuler = '#baa289';
		ambience.leftRuler = '#baa289';
		ambience.fontFamily = defaultFont;
		ambience.fontSize = defaultFontSize;
		ambience.constant = 'blue';

		this.styles.push(ambience);

		var tierra = new StyleSet();

		tierra.name = 'Tierra';
		tierra.annotationRuler = 'moccasin';
		tierra.background = 'lemonchiffon';
		tierra.comment = 'darkseagreen';
		tierra.keyword = 'darkred';
		tierra.text = '#555555';
		tierra.string = 'orangered';
		tierra.overviewRuler = 'moccasin';
		tierra.lineNumberOdd = 'chocolate';
		tierra.lineNumberEven = 'chocolate';
		tierra.lineNumber = 'chocolate';
		tierra.currentLine = '#baa289';
		tierra.tag = 'darkred';
		tierra.attribute = 'cadetBlue';
		tierra.rightRuler = '#baa289';
		tierra.leftRuler = '#baa289';
		tierra.fontFamily = defaultFont;
		tierra.fontSize = defaultFontSize;
		tierra.constant = 'blue';

		this.styles.push(tierra);

		var nimbus = new StyleSet();

		nimbus.name = 'Nimbus';
		nimbus.annotationRuler = '#232323';
		nimbus.background = '#333333';
		nimbus.comment = 'darkseagreen';
		nimbus.keyword = 'darkorange';
		nimbus.text = '#DDDDDD';
		nimbus.string = 'cornflowerblue';
		nimbus.overviewRuler = '#232323';
		nimbus.lineNumberOdd = '#555555';
		nimbus.lineNumberEven = '#555555';
		nimbus.lineNumber = '#555555';
		nimbus.currentLine = 'dimgrey';
		nimbus.tag = 'darkorange';
		nimbus.attribute = 'cadetBlue';
		nimbus.rightRuler = '#3a3a3a';
		nimbus.leftRuler = '#3a3a3a';
		nimbus.fontFamily = defaultFont;
		nimbus.fontSize = defaultFontSize;
		nimbus.constant = '#01B199';

		this.styles.push(nimbus);

		var adelante = new StyleSet();

		adelante.name = 'Adelante';
		adelante.annotationRuler = '#E2D2B2';
		adelante.background = '#F1E7C8';
		adelante.comment = '#5D774E';
		adelante.keyword = '#AF473B';
		adelante.text = 'dimgray';
		adelante.string = '#DE5D3B';
		adelante.overviewRuler = '#E2D2B2';
		adelante.lineNumberOdd = '#AF473B';
		adelante.lineNumberEven = '#AF473B';
		adelante.lineNumber = '#AF473B';
		adelante.currentLine = '#9e937b';
		adelante.tag = '#AF473B';
		adelante.attribute = 'cadetBlue';
		adelante.rightRuler = '#9e937b';
		adelante.leftRuler = '#9e937b';
		adelante.fontFamily = defaultFont;
		adelante.fontSize = defaultFontSize;
		adelante.constant = 'blue';

		this.styles.push(adelante);

		var raspberry = new StyleSet();

		raspberry.name = 'Raspberry Pi';
		raspberry.annotationRuler = 'seashell';
		raspberry.background = 'seashell';
		raspberry.comment = '#66B32F';
		raspberry.keyword = '#E73E36';
		raspberry.text = 'dimgray';
		raspberry.string = 'darkorange';
		raspberry.overviewRuler = 'seashell';
		raspberry.lineNumberOdd = '#f6b8b6';
		raspberry.lineNumberEven = '#f6b8b6';
		raspberry.lineNumber = '#E73E36';
		raspberry.currentLine = '#F5B1AE';
		raspberry.tag = '#E73E36';
		raspberry.attribute = 'cadetBlue';
		raspberry.rightRuler = '#fbdfde';
		raspberry.leftRuler = '#fbdfde';
		raspberry.fontFamily = defaultFont;
		raspberry.fontSize = defaultFontSize;
		raspberry.constant = 'blue';

		this.styles.push(raspberry);

		}
		
		function getStyles(){
			return this.styles;
		}
		
		ThemeData.prototype.styles = [];
		ThemeData.prototype.getStyles = getStyles;
		
		var fontSettable = true;
		
		ThemeData.prototype.fontSettable = fontSettable;
		
		function getThemeStorageInfo(){
			return {
				storage:'/themes',
				styleset:'editorstyles',
				defaultTheme:'Prospecto',
				selectedKey: 'editorSelected',
				version: THEMES_VERSION
			}; 
		}

		ThemeData.prototype.getThemeStorageInfo = getThemeStorageInfo;

		function getViewData() {

		var dataset = {};
		dataset.top = 10;
		dataset.left = 10;
		dataset.width = 400;
		dataset.height = 350;

		var LEFT = dataset.left;
		var TOP = dataset.top;

		dataset.shapes = [{
			type: 'TEXT',
			name: messages["Line Numbers"],
			label: '1',
			x: LEFT + 20,
			y: TOP + 20,
			fill: 'darkSlateGray',
			family: 'lineNumber',
			font: '9pt sans-serif'
		},
		{
			type: 'RECTANGLE',
			name: messages.Background,
			x: LEFT + 46,
			y: TOP,
			width: 290,
			height: dataset.height,
			family: 'background',
			fill: 'white'
		},
		{
			type: 'TEXT',
			name: messages.Strings,
			label: "'text/javascript'",
			x: LEFT + 134,
			y: TOP + 20,
			fill: 'darkorange',
			family: 'string',
			font: '9pt sans-serif'
		},
		
		{
			type: 'TEXT',
			name: messages.Foreground,
			label: '=',
			x: LEFT + 124,
			y: TOP + 20,
			fill: 'darkSlateGray',
			family: 'text',
			font: '9pt sans-serif'
		},

		
		{
			type: 'RECTANGLE',
			name: messages["Current Line"],
			x: LEFT + 46,
			y: TOP + 87,
			width: 290,
			height: 18,
			family: 'currentLine',
			fill: '#eaf2fd'
		},
		
		{
			type: 'TEXT',
			name: messages["HTML Attribute"],
			label: 'type',
			x: LEFT + 98,
			y: TOP + 20,
			fill: 'darkGray',
			family: 'attribute',
			font: '9pt sans-serif'
		},
		
		{
			type: 'RECTANGLE',
			name: messages["Overview Ruler"],
			x: LEFT + 336,
			y: TOP,
			width: 14,
			height: dataset.height,
			family: 'overviewRuler',
			fill: 'white'
		},	
		{
			type: 'TEXT',
			name: messages.Comments,
			label: '/* comment */',
			x: LEFT + 75,
			y: TOP + 40,
			fill: 'darkSeaGreen',
			family: 'comment',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["HTML Tag"],
			label: '<script',
			x: LEFT + 55,
			y: TOP + 20,
			fill: 'darkorange',
			family: 'keyword',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["HTML Tag"],
			label: '>',
			x: LEFT + 213,
			y: TOP + 20,
			fill: 'darkorange',
			family: 'keyword',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages.Strings,
			label: '\'Result\'',
			x: LEFT + 164,
			y: TOP + 80,
			fill: 'cornflowerBlue',
			family: 'string',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages.Foreground,
			label: 'multiply(a,b){',
			x: LEFT + 120,
			y: TOP + 60,
			fill: 'darkSlateGray',
			family: 'text',
			font: '9pt sans-serif'
		},
		
		{
			type: 'TEXT',
			name: messages.Keywords,
			label: 'function',
			x: LEFT + 75,
			y: TOP + 60,
			fill: 'darkorange',
			family: 'keyword',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages.Keywords,
			label: 'var',
			x: LEFT + 95,
			y: TOP + 80,
			fill: 'darkorange',
			family: 'keyword',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages.Foreground,
			label: 'output = ',
			x: LEFT + 115,
			y: TOP + 80,
			fill: 'darkSlateGray',
			family: 'text',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages.Foreground,
			label: ';',
			x: LEFT + 205,
			y: TOP + 80,
			fill: 'darkSlateGray',
			family: 'text',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages.Keywords,
			label: 'var',
			x: LEFT + 95,
			y: TOP + 100,
			fill: 'darkorange',
			family: 'keyword',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages.Foreground,
			label: 'result = a*b;',
			x: LEFT + 115,
			y: TOP + 100,
			fill: 'darkSlateGray',
			family: 'text',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages.Keywords,
			label: 'return',
			x: LEFT + 95,
			y: TOP + 120,
			fill: 'darkorange',
			family: 'keyword',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages.Foreground,
			label: 'output + result;',
			x: LEFT + 135,
			y: TOP + 120,
			fill: 'darkSlateGray',
			family: 'text',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages.Foreground,
			label: '}',
			x: LEFT + 75,
			y: TOP + 140,
			fill: 'darkSlateGray',
			family: 'text',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["HTML Tag"],
			label: '</script>',
			x: LEFT + 55,
			y: TOP + 160,
			fill: 'darkorange',
			family: 'keyword',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["HTML Tag"],
			label: '<style',
			x: LEFT + 55,
			y: TOP + 200,
			fill: 'darkorange',
			family: 'keyword',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["HTML Attribute"],
			label: 'type',
			x: LEFT + 95,
			y: TOP + 200,
			fill: 'darkGray',
			family: 'attribute',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages.Foreground,
			label: '=',
			x: LEFT + 121,
			y: TOP + 200,
			fill: 'darkSlateGray',
			family: 'text',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages.Strings,
			label: "'text/css'",
			x: LEFT + 131,
			y: TOP + 200,
			fill: 'darkorange',
			family: 'string',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["HTML Tag"],
			label: '>',
			x: LEFT + 180,
			y: TOP + 200,
			fill: 'darkorange',
			family: 'keyword',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["CSS Class Name"],
			label: '.some-class',
			x: LEFT + 75,
			y: TOP + 220,
			fill: 'darkorange',
			family: 'keyword',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["CSS Class Name"],
			label: '{',
			x: LEFT + 145,
			y: TOP + 220,
			fill: 'darkSlateGray',
			family: 'text',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["CSS Attribute"],
			label: 'color:',
			x: LEFT + 95,
			y: TOP + 240,
			fill: 'darkSlateGray',
			family: 'text',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["CSS Value"],
			label: '#123456',
			x: LEFT + 130,
			y: TOP + 240,
			fill: 'darkSlateGray',
			family: 'string',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["CSS Text"],
			label: ';',
			x: LEFT + 180,
			y: TOP + 240,
			fill: 'darkSlateGray',
			family: 'text',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["CSS Class Name"],
			label: '}',
			x: LEFT + 75,
			y: TOP + 260,
			fill: 'darkSlateGray',
			family: 'text',
			font: '9pt sans-serif'
		},
		{
			type: 'TEXT',
			name: messages["HTML Tag"],
			label: '</style>',
			x: LEFT + 55,
			y: TOP + 280,
			fill: 'darkorange',
			family: 'keyword',
			font: '9pt sans-serif'
		},
		/* <style type='text/css'></style> */
		{
			type: 'RECTANGLE',
			name: messages["Annotation Ruler"],
			x: LEFT,
			y: TOP,
			width: 46,
			height: dataset.height,
			family: 'annotationRuler',
			fill: 'white'
		}];

		for (var line = 0; line < 16; line++) {
			dataset.shapes.push({
				type: 'TEXT',
				name: messages["Line Numbers"],
				label: line + 1,
				x: LEFT + 20,
				y: TOP + (20 * line) + 20,
				fill: 'darkSlateGray',
				family: 'lineNumber',
				font: '9pt sans-serif'
			});
		}

		return dataset;
	}
		
		function parseToXML ( text ) {
		      try {
		        var xml = null;
		        
		        if ( window.DOMParser ) {
		
		          var parser = new DOMParser();
		          xml = parser.parseFromString( text, "text/xml" );
		          
		          var found = xml.getElementsByTagName( "parsererror" );
		
		          if ( !found || !found.length || !found[ 0 ].childNodes.length ) {
		            return xml;
		          }
		
		          return null;
		        } else {
		
		          xml = new ActiveXObject( "Microsoft.XMLDOM" );
		
		          xml.async = false;
		          xml.loadXML( text );
		
		          return xml;
		        }
		      } catch ( e ) {
		        // suppress
		      }
		 }
		 
		ThemeData.prototype.parseToXML = parseToXML;
		
		function selectFontSize( size ){
			console.log( 'fontsize: ' + size );
		}
		
		ThemeData.prototype.selectFontSize = selectFontSize;
		
		function importTheme(data){
			console.log( 'import theme' );
			console.log( data );
			
			var body = data.parameters.valueFor("name");
				
			var xml = this.parseToXML( body );
			
			var newStyle = new StyleSet();
			
			newStyle.name = xml.getElementsByTagName("colorTheme")[0].attributes[1].value;
			newStyle.annotationRuler = xml.getElementsByTagName("background")[0].attributes[0].value; 
			newStyle.background = xml.getElementsByTagName("background")[0].attributes[0].value;
			newStyle.comment = xml.getElementsByTagName("singleLineComment")[0].attributes[0].value;
			newStyle.keyword = xml.getElementsByTagName("keyword")[0].attributes[0].value;
			newStyle.text = xml.getElementsByTagName("foreground")[0].attributes[0].value;
			newStyle.string = xml.getElementsByTagName("string")[0].attributes[0].value;
			newStyle.overviewRuler = xml.getElementsByTagName("background")[0].attributes[0].value;
			newStyle.lineNumberOdd = xml.getElementsByTagName("lineNumber")[0].attributes[0].value;
			newStyle.lineNumberEven = xml.getElementsByTagName("lineNumber")[0].attributes[0].value;
			newStyle.lineNumber = xml.getElementsByTagName("lineNumber")[0].attributes[0].value;
			newStyle.currentLine = xml.getElementsByTagName("selectionBackground")[0].attributes[0].value;
			
			data.items.addTheme( newStyle );
		}
		
		ThemeData.prototype.importTheme = importTheme;
		
		function processSettings( settings, preferences ){
			var themeClass = "editorTheme";
			var theme = mTextTheme.TextTheme.getTheme();
			theme.setThemeClass(themeClass, theme.buildStyleSheet(themeClass, settings));
		}

		ThemeData.prototype.processSettings = processSettings;

		ThemeData.prototype.getViewData = getViewData;

		return{
			ThemeData:ThemeData,
			getStyles:getStyles
		};
	}
);

/*******************************************************************************
 * @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
 *******************************************************************************/

/*global define */

define('orion/widgets/browse/readonlyEditorView',[
	'orion/editor/editor',
	'orion/editor/textView',
	'orion/editor/textModel',
	'orion/editor/projectionTextModel',
	'orion/editor/editorFeatures',
	'orion/editorPreferences',
	'orion/widgets/themes/ThemePreferences',
	'orion/widgets/themes/editor/ThemeData',
	'orion/objects'
], function(
	mEditor, mTextView, mTextModel, mProjectionTextModel, mEditorFeatures, 
	mEditorPreferences, mThemePreferences, mThemeData, objects
) {
	/**
	 * Constructs a new ReadonlyEditorView object.
	 *
	 * @class
	 * @name orion.ReadonlyEditorView
	 */
	function ReadonlyEditorView(options) {
		this._parent = options.parent;
		this.renderToolbars = options.renderToolbars;
		this.inputManager = options.inputManager;
		this.preferences = options.preferences;
		this.statusReporter = options.statusReporter;
		this.model = options.model;
		this.syntaxHighlighter = options.syntaxHighlighter;
		this.readonly = true;
		this.settings = {};
		this._init();
	}
	ReadonlyEditorView.prototype = /** @lends orion.ReadonlyEditorView.prototype */ {
		getParent: function() {
			return this._parent;
		},
		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);
			this.updateStyler(prefs);
			var textView = editor.getTextView();
			if (textView) {
				textView.setOptions(this.updateViewOptions(prefs));
			}
			editor.setAnnotationRulerVisible(prefs.annotationRuler);
			editor.setLineNumberRulerVisible(prefs.lineNumberRuler);
			editor.setFoldingRulerVisible(prefs.foldingRuler);
			editor.setOverviewRulerVisible(prefs.overviewRuler);
			if (this.renderToolbars) {
				this.renderToolbars(inputManager.getFileMetadata());
			}
		},
		updateStyler: function(prefs) {
			var styler = this.syntaxHighlighter.getStyler();
			if (styler) {
				if (styler.setTabsVisible) {
					styler.setTabsVisible(prefs.showTabs);
				}
				if (styler.setSpacesVisible) {
					styler.setSpacesVisible(prefs.showSpaces);
				}
			}
		},
		_init: function() {
			var editorPreferences = null;
			if(this.preferences) {
				editorPreferences = this.editorPreferences = new mEditorPreferences.EditorPreferences (this.preferences, function (prefs) {
					if (!prefs) {
						editorPreferences.getPrefs(this.updateSettings.bind(this));
					} else {
						this.updateSettings(prefs);
					}
				}.bind(this));
			}
			var themePreferences = null;
			if(this.preferences) {
				themePreferences = new mThemePreferences.ThemePreferences(this.preferences, new mThemeData.ThemeData());
				themePreferences.apply();
			}
			var editorDomNode = this._parent;
			var inputManager = this.inputManager;
			
			var textViewFactory = function() {
				var options = this.updateViewOptions(this.settings);
				objects.mixin(options, {
					parent: editorDomNode,
					model: new mProjectionTextModel.ProjectionTextModel(this.model || new mTextModel.TextModel())
				});
				var textView = new mTextView.TextView(options);
				return textView;
			}.bind(this);

			var editor = this.editor = new mEditor.Editor({
				textViewFactory: textViewFactory,
				annotationFactory: new mEditorFeatures.AnnotationFactory(),
				foldingRulerFactory: new mEditorFeatures.FoldingRulerFactory(),
				lineNumberRulerFactory: new mEditorFeatures.LineNumberRulerFactory(),
				statusReporter: this.statusReporter,
				domNode: editorDomNode
			});
			editor.id = "orion.editor"; //$NON-NLS-0$
			inputManager.addEventListener("InputChanged", function(event) { //$NON-NLS-0$
				var textView = editor.getTextView();
				if (textView) {
					textView.setOptions(this.updateViewOptions(this.settings));
					this.syntaxHighlighter.setup(event.contentType, editor.getTextView(), editor.getAnnotationModel(), event.title, true).then(function() {
						this.updateStyler(this.settings);
					}.bind(this));
				}
			}.bind(this));
			if(this.editorPreferences) {
				this.editorPreferences.getPrefs(this.updateSettings.bind(this));
			} else {
				editor.setAnnotationRulerVisible(false);
			}
		},
		create: function() {
			this.editor.install();
		},
		destroy: function() {
			this.editor.uninstall();
		},
		getStyleAccessor: function() {
			this.syntaxHighlighter.getStyler().getStyleAccessor();
		}
	};
	return {ReadonlyEditorView: ReadonlyEditorView};
});



/*******************************************************************************
 * @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
 ******************************************************************************/
/*global console define URL*/
/*jslint browser:true sub:true*/
define('orion/widgets/browse/resourceSelector',[
	'orion/objects',
	'orion/webui/littlelib',
	'orion/commands',
	'orion/URITemplate',
	'orion/URL-shim'
], function(objects, lib, Commands, URITemplate, _) {
	var uriTemplate = new URITemplate("#{,resource,params*}"); //$NON-NLS-0$

	/**
	 * @name orion.widgets.browse.ResourceSelector
	 * @class Resource selector.
	 * @description Renders a toolbar that displays the current resource name and a dropdown of all its siblings and provides a menu for changing the resource from the siblings.
	 * @param {orion.commands.CommandRegistry} params.commandRegistry
	 * @param {orion.fileClient.FileClient} params.fileClient
	 * @param {EventTarget} params.resourceChangeListener the resourceChange event listener that hadles the root change
	 * @param {EventTarget} params.resourceChangeDispatcher the "filesystemChanged" event dispatcher
	 * @param {Element} params.parentNode
	 */
	function ResourceSelector(params) {
		this.resourceChangeListener = params.resourceChangeListener;
		this.resourceChangeDispatcher = params.resourceChangeDispatcher;
		this.fetchChildren = params.fetchChildren;
		this.fileClient = params.fileClient;
		this.commandRegistry = params.commandRegistry;
		this.commandScopeId = params.commandScopeId;
		this.dropDownId = params.dropDownId;
		this.dropDownTooltip = params.dropDownTooltip;
		this.allItems = params.allItems;
		this.activeResourceLocation = params.activeResourceLocation;
		this.labelHeader = params.labelHeader;
		this.parentNode = params.parentNode;
		this.listener = function(event) {
			this.refresh(event.root);
		}.bind(this);
		if(this.resourceChangeListener){
			this.resourceChangeListener.addEventListener("resourceChanged", this.listener); //$NON-NLS-0$
		}
		this.render();
	}
	objects.mixin(ResourceSelector.prototype, /** @lends orion.widgets.Filesystem.ResourceSelector */ {
		destroy: function() {
			if(this.resourceChangeListener) {
				this.resourceChangeListener.removeEventListener("resourceChanged", this.listener); //$NON-NLS-0$
			}
			this.commandRegistry.destroy(this.parentNode);
			lib.empty(this.parentNode);
			this.resourceChangeListener = this.resourceChangeDispatcher = this.listener = this.parentNode = null;
		},
		registerCommands: function() {
			if (!this.commandsRegistered) {
				this.commandsRegistered = true;
				var commandRegistry = this.commandRegistry;
				var switchBrCommand = new Commands.Command({
					imageClass: "core-sprite-openarrow", //$NON-NLS-0$
					//selectionClass: "dropdownSelection", //$NON-NLS-0$
					//tooltip: this.dropDownTooltip,
					id: this.dropDownId,
					visibleWhen: function(item) {
						return true;
					},
					choiceCallback: this._switchBrMenuCallback.bind(this),
					positioningNode: this.parentNode
				});
				commandRegistry.addCommand(switchBrCommand);
				commandRegistry.registerCommandContribution(this.commandScopeId, this.dropDownId, 1); //$NON-NLS-1$ //$NON-NLS-0$
			}
		},
		_switchBrMenuCallback: function(items) {
			var _self = this;
			return this.allItems.map(function(resource) { //$NON-NLS-0$
				return {
					name: resource.Name,
					checked: resource.Location === _self.activeResourceLocation,
					callback: _self.setActiveResource.bind(_self, {resource: resource, changeHash: true})
				};
			});
		},
		render: function() {
			this.resourceName = document.createElement("div"); //$NON-NLS-0$
			this.resourceName.classList.add("browserResourceSelectorName"); //$NON-NLS-0$
			this.resourceName.classList.add("layoutLeft"); //$NON-NLS-0$
			this.menu = document.createElement("ul"); //$NON-NLS-0$
			this.menu.classList.add("commandList"); //$NON-NLS-0$
			this.menu.classList.add("layoutRight"); //$NON-NLS-0$
			this.parentNode.appendChild(this.resourceName);
			this.parentNode.appendChild(this.menu);

			this.registerCommands();

			//this.resourceName.addEventListener("click", this._openMenu.bind(this)); //$NON-NLS-0$
			this.parentNode.addEventListener("click", this._openMenu.bind(this)); //$NON-NLS-0$
		},
		_openMenu: function(event) {
			var menu = lib.$(".dropdownTrigger", this.menu); //$NON-NLS-0$
			if (menu) {
				var click = document.createEvent("MouseEvents"); //$NON-NLS-0$
				click.initEvent("click", true, true); //$NON-NLS-0$
				menu.dispatchEvent(click);
			}
		},
		/**
		 * @returns {String|DocumentFragment}
		 */
		_resourceLabel: function() {
			var fragment = document.createDocumentFragment();
			if(this.labelHeader) {
				fragment.textContent = "${0} " + this.getActiveResource(this.activeResourceLocation).Name; //$NON-NLS-0$
				var nameLabel = document.createElement("span"); //$NON-NLS-0$
				nameLabel.appendChild(document.createTextNode(this.labelHeader + ":")); //$NON-NLS-0$
				nameLabel.classList.add("browserResourceSelectorNameLabel");
				lib.processDOMNodes(fragment, [nameLabel]);
			} else {
				fragment.textContent = this.getActiveResource(this.activeResourceLocation).Name; //$NON-NLS-0$
			}
			return fragment;
		},
		refresh: function() {
			lib.empty(this.resourceName);
			this.resourceName.appendChild(this._resourceLabel());
			this.commandRegistry.destroy(this.menu);
			this.commandRegistry.renderCommands(this.commandScopeId, this.menu, {}, "menu"); //$NON-NLS-1$ //$NON-NLS-0$
		},
		/**
		 * @param {Object|String} location The ChildrenLocation, or an object with a ChildrenLocation field.
		 */
		setActiveResource: function(params) {
			this.activeResourceLocation = params.resource.Location;
			if(this.fetchChildren) {//Lazy fetch
				if(params.resource.selectorAllItems){
					if(this.resourceChangeDispatcher) {
						this.resourceChangeDispatcher.dispatchEvent({ type: "resourceChanged", newResource: params.resource, defaultChild: params.defaultChild, changeHash: params.changeHash}); //$NON-NLS-0$
					}
				} else {
					this.fileClient.fetchChildren(params.resource.Location).then(function(contents){
						if(contents && contents.length > 0) {
							contents.sort(function(a, b) {
								var	n1 = a.Name && a.Name.toLowerCase();
								var	n2 = b.Name && b.Name.toLowerCase();
								if (n1 < n2) { return -1; }
								if (n1 > n2) { return 1; }
								return 0;
							}); 
							params.resource.selectorAllItems = contents;
							if(this.resourceChangeDispatcher) {
								this.resourceChangeDispatcher.dispatchEvent({ type: "resourceChanged", newResource: params.resource, defaultChild: params.defaultChild, changeHash: params.changeHash}); //$NON-NLS-0$
							}
						} else {
							params.resource.selectorAllItems = [{Name: "none", Location: params.resource.Location, Directory: true}];
							if(this.resourceChangeDispatcher) {
								this.resourceChangeDispatcher.dispatchEvent({ type: "resourceChanged", newResource: params.resource, defaultChild: params.defaultChild, changeHash: params.changeHash}); //$NON-NLS-0$
							}
						}
					}.bind(this),
					function(err) {
						console.log(err);
					}.bind(this));
				}
			} else {
				window.location = uriTemplate.expand({resource: params.resource.Location}); //$NON-NLS-0$
			}
		},
		getActiveResource: function(location){
			if(!location) {
				location = this.activeResourceLocation;
			}
			var activeResource = this.allItems[0];
			this.allItems.some(function(item){
				if(item.Location === location) {
					activeResource = item;
					return true;
				}
			});
			return activeResource;
		},
		setCommitInfo: function(location, commitInfo) {
			//if(commitInfo) {
				this.getActiveResource(location).LastCommit = commitInfo;
			//}
		},
		getCommitInfo: function() {
			var activeResource = this.getActiveResource();
			if(activeResource && activeResource.LastCommit) {
				return activeResource.LastCommit;
			}
			return null;
		}
	});

	return {ResourceSelector: ResourceSelector};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global window define */
/*jslint forin:true devel:true*/

/** @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 funcName + messages[" is not supported in this file system"];
		}
		return fileService[funcName].apply(fileService, funcArgs);
	}
	
	function _copy(sourceService, sourceLocation, targetService, targetLocation) {
		
		if (!sourceService.readBlob) {
			throw messages["source file service does not support binary read"];
		}

		if (!targetService.writeBlob) {
			throw messages["target file service does not support binary write"];
		}
	
		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]);
				}
			}
		}
		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")		 //$NON-NLS-0$
			};

			var patternString = _references[j].getProperty("pattern") || _references[j].getProperty("top").replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); //$NON-NLS-1$ //$NON-NLS-0$
			if (patternString[0] !== "^") { //$NON-NLS-0$
				patternString = "^" + patternString; //$NON-NLS-0$
			}
			_patterns[j] = new RegExp(patternString);			
			_services[j] = serviceRegistry.getService(_references[j]);
			_names[j] = _references[j].getProperty("Name"); //$NON-NLS-0$
			
			if(_references[j].getProperty("NameKey") && _references[j].getProperty("nls")){
				i18nUtil.getMessageBundle(_references[j].getProperty("nls")).then(function(j, pluginMessages){
					_fileSystemsRoots[j].Name = pluginMessages[_references[j].getProperty("NameKey")]; //$NON-NLS-0$
					_names[j] = pluginMessages[_references[j].getProperty("NameKey")]; //$NON-NLS-0$
				}.bind(this, j));
			}
		}
				
		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) {
				if (_patterns[i].test(location)) {
					return i;
				}
			}
			throw messages['No Matching FileService for location:'] + 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 strat 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) 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
 *******************************************************************************/
/*jslint amd:true*/
define('orion/contentTypes',[], function() {
	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 getFilenameContentType(/**String*/ filename, contentTypes) {
		function winner(best, other, filename, extension) {
			var nameMatch = other.filename.indexOf(filename) >= 0;
			var extMatch = contains(other.extension, extension.toLowerCase());
			if (nameMatch || extMatch) {
				if (!best || (nameMatch && contains(best.extension, extension.toLowerCase()))) {
					return other;
				}
			}
			return best;
		}
		if (typeof filename !== "string") { //$NON-NLS-0$
			return null;
		}
		var extension = filename && filename.split(".").pop(); //$NON-NLS-0$
		var best = null;
		for (var i=0; i < contentTypes.length; i++) {
			var type = contentTypes[i];
			if (winner(best, type, filename, extension) === type) {
				best = type;
			}
		}
		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 buildMap(contentTypeDatas) {
		var map = Object.create(null);
		contentTypeDatas.map(process).forEach(function(contentType) {
			if (!Object.prototype.hasOwnProperty.call(map, contentType.id)) {
				map[contentType.id] = contentType;
			}
		});
		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], types = array(serviceRef.getProperty("contentTypes")); //$NON-NLS-0$
			for (var j=0; j < types.length; j++) {
				contentTypeDatas.push(types[j]);
			}
		}
		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;
			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,
		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: IBM Corporation - initial API and implementation
 *               Alex Lakatos - fix for bug#369781
 ******************************************************************************/

/*global define */

define("orion/editor/textStyler", ['orion/editor/annotations', 'orion/editor/eventTarget'], function(mAnnotations, mEventTarget) { //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$

	/*
	 * 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;
	};

	var createPatternBasedAdapter = function(grammars, rootId) {
		return new PatternBasedAdapter(grammars, rootId);
	};

	function PatternBasedAdapter(grammars, rootId) {
		this._patternManager = new PatternManager(grammars, rootId);
	}
	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 = [];
			startIndex = startIndex || 0;
			endIndex = endIndex || Infinity;
			maxBlockCount = maxBlockCount || Infinity;
			block.blockPatterns.forEach(function(current) {
				var 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) {
						var 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;
		},
		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
			};
		},
		parse: function(text, offset, block, _styles, ignoreCaptures) {
			if (!text) {
				return;
			}
			var patterns = block.linePatterns;
			if (!patterns) {
				return;
			}

			var matches = [];
			patterns.forEach(function(current) {
				var regex = current.regex || current.regexBegin;
				regex.oldLastIndex = regex.lastIndex;
				var result = this._findMatch(regex, text, 0);
				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 = 0;
			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, result;
				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;
			});
		},
		setStyler: function(/*styler*/) {	
		},
		verifyBlock: function(baseModel, text, ancestorBlock, changeCount) {
			var result = null;
			var matches = [];
			var parentBlock = ancestorBlock.parent;
			parentBlock.blockPatterns.forEach(function(current) {
				var 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 */
				var 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 {
						current.index = indexCounter[0]++;
						resultObject[current.id] = current;
					}
				}
			}.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.id]) {
						current.index = indexCounter[0]++;
						resultObject[current.id] = current;
					}
				}
			}.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.start, this.end), this, this.start, 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 = this._spacesVisible = this._tabsVisible = false;
		this._highlightCaretLine = false;
		this._foldingEnabled = true;
		this._detectTasks = true;
		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};
		this._rootBlock = this._stylerAdapter.createBlock(rootBounds, this, model, null);
		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 = {
		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;
			}
		},
		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), 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;
		},
		setDetectHyperlinks: function() {
		},
		setDetectTasks: function(enabled) {
			this._detectTasks = enabled;
		},
		setFoldingEnabled: function(enabled) {
			this._foldingEnabled = enabled;
		},
		setHighlightCaretLine: function(highlight) {
			this._highlightCaretLine = highlight;
		},
		setSpacesVisible: function(visible) {
			if (this._spacesVisible === visible) { return; }
			this._spacesVisible = visible;
			this.setWhitespacesVisible(this._tabsVisible || this._spacesVisible, false);
			this._view.redraw();
		},
		setTabsVisible: function(visible) {
			if (this._tabsVisible === visible) { return; }
			this._tabsVisible = visible;
			this.setWhitespacesVisible(this._tabsVisible || this._spacesVisible, false);
			this._view.redraw();
		},
		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 annotation = this._createFoldingAnnotation(viewModel, baseModel, block.start, block.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 = [];
					this._stylerAdapter.parse(baseModel.getText(block.contentStart, block.end), block.contentStart, 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(bracketMatch, block, text, start, end) {
			var result = [], styles = [];
			var offset = start, blocks = block.getBlocks();
			var startIndex = binarySearch(blocks, start, 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 (offset < blockStart) {
					this._stylerAdapter.parse(text.substring(offset - start, blockStart - start), offset, block, styles);
					styles.forEach(function(current) {
						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 = [];
				}
				offset = blockEnd;
			}
			if (offset < end) {
				this._stylerAdapter.parse(text.substring(offset - start, end - start), offset, 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(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(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(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 model = this._view.getModel();
				var selection = this._view.getSelection();
				if (selection.start === selection.end && model.getLineAtOffset(selection.start) === lineIndex) {
					return this._caretLineStyle;
				}
			}
			return null;
		},
		_getStyles: function(block, model, text, start) {
			if (model.getBaseModel) {
				start = model.mapOffset(start);
			}
			var end = start + text.length;

			var styles = [];
			var offset = start, blocks = block.getBlocks();
			var startIndex = binarySearch(blocks, start, 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 (offset < blockStart) {
					/* content on that line that preceeds the start of the block */
					this._stylerAdapter.parse(text.substring(offset - start, blockStart - start), offset, block, styles);
				}
				var s = Math.max(offset, blockStart);
				if (s === blockStart) {
					/* currently in the block's "start" segment */
					var startString = this._stylerAdapter.getBlockStartStyle(blocks[i], text.substring(s - start), 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 - offset - (blocks[i].end - blocks[i].contentEnd));
					var endString = this._stylerAdapter.getBlockEndStyle(blocks[i], testString, e, endStyles);
					if (endString) {
						e -= endString.length;
					}
				}

				var blockSubstyles = this._getStyles(blocks[i], model, text.substring(s - start, e - start), s);
				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);
				offset = blockEnd;
			}
			if (offset < end) {
				/* content on that line that follows the end of the block */
				this._stylerAdapter.parse(text.substring(offset - start, end - start), offset, block, styles);
			}
			if (model.getBaseModel) {
				for (var j = 0; j < styles.length; j++) {
					var length = styles[j].end - styles[j].start;
					styles[j].start = model.mapOffset(styles[j].start, true);
					styles[j].end = styles[j].start + length;
				}
			}
			return styles;
		},
		_isRenderingWhitespace: function() {
			return this._whitespacesVisible && (this._tabsVisible || this._spacesVisible);
		},
		_onDestroy: function() {
			this.destroy();
		},
		_onLineStyle: function(e) {
			if (e.textView === this._view) {
				e.style = this._getLineStyle(e.lineIndex);
			}
			e.ranges = this._getStyles(this._rootBlock, e.textView.getModel(), e.lineText, e.lineStart);
			e.ranges.forEach(function(current) {
				if (current.style) {
					current.style = {styleClass: current.style.replace(/\./g, " ")};
				}
			});
			if (this._isRenderingWhitespace()) {
				if (this._spacesVisible) {
					this._spliceStyles(this._spacePattern, e.ranges, e.lineText, e.lineStart);
				}
				if (this._tabsVisible) {
					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 matches are not affected by this change */
					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) {
				this.dispatchEvent({
					type: "BlocksChanged",
					old: [ancestorBlock],
					"new": [ancestorBlock]
				});
			} else {
				this.dispatchEvent({
					type: "BlocksChanged",
					old: blocks.slice(blockStart, blockEnd),
					"new": 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);
						if (!(block && annotation.start === block.start && annotation.end === block.end)) {
							remove.push(annotation);
							annotation.expand();
						} else {
							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 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._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 oldSelection = e.oldValue;
			var newSelection = e.newValue;
			var model = this._view.getModel();
			var lineIndex;
			if (this._highlightCaretLine) {
				var oldLineIndex = model.getLineAtOffset(oldSelection.start);
				lineIndex = model.getLineAtOffset(newSelection.start);
				var newEmpty = newSelection.start === newSelection.end;
				var oldEmpty = oldSelection.start === oldSelection.end;
				if (!(oldLineIndex === lineIndex && oldEmpty && newEmpty)) {
					if (oldEmpty) {
						this._view.redrawLines(oldLineIndex, oldLineIndex + 1);
					}
					if ((oldLineIndex !== lineIndex || !oldEmpty) && newEmpty) {
						this._view.redrawLines(lineIndex, lineIndex + 1);
					}
				}
			}
			if (!this._annotationModel) { return; }

			var remove = this._bracketAnnotations, add, caret;
			if (newSelection.start === newSelection.end && (caret = this._view.getCaretOffset()) > 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: 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;
				} 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;
			if (!block.doNotFold && block.start <= end && start <= block.end) {
				var index = binarySearch(allFolding, block.start, true);
				if (!(index < allFolding.length && allFolding[index].start === block.start && allFolding[index].end === block.end)) {
					var annotation = this._createFoldingAnnotation(viewModel, baseModel, block.start, block.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) 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
 ******************************************************************************/

/*global define*/

define("orion/editor/stylers/lib/syntax", [], function() { //$NON-NLS-0$
	return {
		id: "orion.lib", //$NON-NLS-0$
		grammars: [{
			id: "orion.lib", //$NON-NLS-0$
			repository: {
				brace_open: {
					match: "{", //$NON-NLS-0$
					name: "punctuation.section.block.begin" //$NON-NLS-0$
				},
				brace_close: {
					match: "}", //$NON-NLS-0$
					name: "punctuation.section.block.end" //$NON-NLS-0$
				},
				bracket_open: {
					match: "\\[", //$NON-NLS-0$
					name: "punctuation.section.bracket.begin" //$NON-NLS-0$
				},
				bracket_close: {
					match: "\\]", //$NON-NLS-0$
					name: "punctuation.section.bracket.end" //$NON-NLS-0$
				},
				parenthesis_open: {
					match: "\\(", //$NON-NLS-0$
					name: "punctuation.section.parens.begin" //$NON-NLS-0$
				},
				parenthesis_close: {
					match: "\\)", //$NON-NLS-0$
					name: "punctuation.section.parens.end" //$NON-NLS-0$
				},
				doc_block: {
					begin: {match: "/\\*\\*", literal: "/**"}, //$NON-NLS-0$
					end: {match: "\\*/", literal: "*/"}, //$NON-NLS-0$
					name: "comment.block.documentation", //$NON-NLS-0$
					patterns: [
						{
							match: "@(?:(?!\\*/)\\S)*", //$NON-NLS-0$
							name: "keyword.other.documentation.tag" //$NON-NLS-0$
						}, {
							match: "\\<\\S*\\>", //$NON-NLS-0$
							name: "keyword.other.documentation.markup" //$NON-NLS-0$
						}, {
							match: "(\\b)(TODO)(\\b)(((?!\\*/).)*)", //$NON-NLS-0$
							name: "meta.annotation.task.todo", //$NON-NLS-0$
							captures: {
								2: {name: "keyword.other.documentation.task"}, //$NON-NLS-0$
								4: {name: "comment.block"} //$NON-NLS-0$
							}
						}
					]
				},
				number_decimal: {
					match: "\\b-?(?:\\.\\d+|\\d+\\.?\\d*)(?:[eE][+-]?\\d+)?\\b", //$NON-NLS-0$
					name: "constant.numeric.number" //$NON-NLS-0$
				},
				number_hex: {
					match: "\\b0[xX][0-9A-Fa-f]+\\b", //$NON-NLS-0$
					name: "constant.numeric.hex" //$NON-NLS-0$
				},
				string_doubleQuote: {
					match: '"(?:\\\\.|[^"])*"?', //$NON-NLS-0$
					name: "string.quoted.double" //$NON-NLS-0$
				},
				string_singleQuote: {
					match: "'(?:\\\\.|[^'])*'?", //$NON-NLS-0$
					name: "string.quoted.single" //$NON-NLS-0$
				},
				todo_comment_singleLine: {
					match: "(\\b)(TODO)(\\b)(.*)", //$NON-NLS-0$
					name: "meta.annotation.task.todo", //$NON-NLS-0$
					captures: {
						2: {name: "keyword.other.documentation.task"}, //$NON-NLS-0$
						4: {name: "comment.line"} //$NON-NLS-0$
					}
				}
			}
		}, {
			id: "orion.c-like", //$NON-NLS-0$
			repository: {
				comment_singleLine: {
					match: {match: "//.*", literal: "//"}, //$NON-NLS-0$
					name: "comment.line.double-slash", //$NON-NLS-0$
					patterns: [
						{
							include: "orion.lib#todo_comment_singleLine" //$NON-NLS-0$
						}
					]
				},
				comment_block: {
					begin: {match: "/\\*", literal: "/*"}, //$NON-NLS-0$
					end: {match: "\\*/", literal: "*/"}, //$NON-NLS-0$ 
					name: "comment.block", //$NON-NLS-0$
					patterns: [
						{
							match: "(\\b)(TODO)(\\b)(((?!\\*/).)*)", //$NON-NLS-0$
							name: "meta.annotation.task.todo", //$NON-NLS-0$
							captures: {
								2: {name: "keyword.other.documentation.task"}, //$NON-NLS-0$
								4: {name: "comment.block"} //$NON-NLS-0$
							}
						}
					]
				}
			}
		}],
		keywords: []
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define*/

define("orion/editor/stylers/application_javascript/syntax", ["orion/editor/stylers/lib/syntax"], function(mLib) { //$NON-NLS-1$ //$NON-NLS-0$
	var keywords = [
		"break", //$NON-NLS-0$
		"case", "class", "catch", "continue", "const", //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"debugger", "default", "delete", "do", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"else", "enum", "export", "extends", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"false", "finally", "for", "function", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"if", "implements", "import", "in", "instanceof", "interface", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"let", //$NON-NLS-0$
		"new", "null", //$NON-NLS-1$ //$NON-NLS-0$
		"package", "private", "protected", "public", //$NON-NLS-0$ //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		"return", //$NON-NLS-0$
		"static", "super", "switch", //$NON-NLS-0$ //$NON-NLS-1$ //$NON-NLS-2$
		"this", "throw", "true", "try", "typeof", //$NON-NLS-0$ //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
		"undefined", //$NON-NLS-0$
		"var", "void", //$NON-NLS-0$ //$NON-NLS-1$
		"while", "with", //$NON-NLS-0$ //$NON-NLS-1$
		"yield" //$NON-NLS-0$
	];

	var grammars = mLib.grammars;
	grammars.push({
		id: "orion.js", //$NON-NLS-0$
		contentTypes: ["application/javascript"], //$NON-NLS-0$
		patterns: [
			{
				begin: "'(?:\\\\.|[^\\\\'])*\\\\$", //$NON-NLS-0$
				end: "^(?:$|(?:\\\\.|[^\\\\'])*('|[^\\\\]$))", //$NON-NLS-0$
				name: "string.quoted.single.js" //$NON-NLS-0$
			}, {
				begin: '"(?:\\\\.|[^\\\\"])*\\\\$', //$NON-NLS-0$
				end: '^(?:$|(?:\\\\.|[^\\\\"])*("|[^\\\\]$))', //$NON-NLS-0$
				name: "string.quoted.double.js" //$NON-NLS-0$
			},
			{include: "orion.lib#string_doubleQuote"}, //$NON-NLS-0$
			{include: "orion.lib#string_singleQuote"}, //$NON-NLS-0$
			{include: "orion.c-like#comment_singleLine"}, //$NON-NLS-0$
			{
				match: "/(?![\\s\\*])(?:\\\\.|[^/])+/(?:[gim]{0,3})", //$NON-NLS-0$
				name: "string.regexp.js" //$NON-NLS-0$
			},
			{include: "orion.lib#doc_block"}, //$NON-NLS-0$
			{include: "orion.c-like#comment_block"}, //$NON-NLS-0$
			{include: "orion.lib#brace_open"}, //$NON-NLS-0$
			{include: "orion.lib#brace_close"}, //$NON-NLS-0$
			{include: "orion.lib#bracket_open"}, //$NON-NLS-0$
			{include: "orion.lib#bracket_close"}, //$NON-NLS-0$
			{include: "orion.lib#parenthesis_open"}, //$NON-NLS-0$
			{include: "orion.lib#parenthesis_close"}, //$NON-NLS-0$
			{include: "orion.lib#number_decimal"}, //$NON-NLS-0$
			{include: "orion.lib#number_hex"}, //$NON-NLS-0$
			{
				match: "\\b(?:" + keywords.join("|") + ")\\b", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				name: "keyword.control.js" //$NON-NLS-0$
			}
		]
	});
	return {
		id: grammars[grammars.length - 1].id,
		grammars: grammars,
		keywords: keywords
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define*/

define("orion/editor/stylers/text_css/syntax", ["orion/editor/stylers/lib/syntax"], function(mLib) { //$NON-NLS-1$ //$NON-NLS-0$
	var keywords = [
		"alignment-adjust", "alignment-baseline", "animation-delay", "animation-direction", "animation-duration", "animation-iteration-count", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"animation-name", "animation-play-state", "animation-timing-function", "animation", "appearance", //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"azimuth", "backface-visibility", "background-attachment", "background-clip", "background-color", "background-image", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"background-origin", "background-position", "background-repeat", "background-size", "background", "baseline-shift", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"binding", "bleed", "bookmark-label", "bookmark-level", "bookmark-state", "bookmark-target", "border-bottom-color", //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"border-bottom-left-radius", "border-bottom-right-radius", "border-bottom-style", "border-bottom-width", "border-bottom", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"border-collapse", "border-color", "border-image-outset", "border-image-repeat", "border-image-slice", "border-image-source", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"border-image-width", "border-image", "border-left-color", "border-left-style", "border-left-width", "border-left", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"border-radius", "border-right-color", "border-right-style", "border-right-width", "border-right", "border-spacing", "border-style", //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"border-top-color", "border-top-left-radius", "border-top-right-radius", "border-top-style", "border-top-width", "border-top", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"border-width", "border", "bottom", "box-align", "box-decoration-break", "box-direction", "box-flex-group", "box-flex", "box-lines", //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"box-ordinal-group", "box-orient", "box-pack", "box-shadow", "box-sizing", "break-after", "break-before", "break-inside", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"caption-side", "clear", "clip", "color-profile", "color", "column-count", "column-fill", "column-gap", "column-rule-color", //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"column-rule-style", "column-rule-width", "column-rule", "column-span", "column-width", "columns", "content", "counter-increment", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"counter-reset", "crop", "cue-after", "cue-before", "cue", "cursor", "direction", "display", "dominant-baseline", //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"drop-initial-after-adjust", "drop-initial-after-align", "drop-initial-before-adjust", "drop-initial-before-align", "drop-initial-size", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"drop-initial-value", "elevation", "empty-cells", "fit-position", "fit", "flex-align", "flex-flow", "flex-inline-pack", "flex-order", //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"flex-pack", "float-offset", "float", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"font-weight", "font", "grid-columns", "grid-rows", "hanging-punctuation", "height", "hyphenate-after", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"hyphenate-before", "hyphenate-character", "hyphenate-lines", "hyphenate-resource", "hyphens", "icon", "image-orientation", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"image-rendering", "image-resolution", "inline-box-align", "left", "letter-spacing", "line-height", "line-stacking-ruby", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"line-stacking-shift", "line-stacking-strategy", "line-stacking", "list-style-image", "list-style-position", "list-style-type", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"list-style", "margin-bottom", "margin-left", "margin-right", "margin-top", "margin", "mark-after", "mark-before", "mark", //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"marker-offset", "marks", "marquee-direction", "marquee-loop", "marquee-play-count", "marquee-speed", "marquee-style", "max-height", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index", "nav-left", "nav-right", "nav-up", "opacity", "orphans", //$NON-NLS-10$ //$NON-NLS-9$ //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"outline-color", "outline-offset", "outline-style", "outline-width", "outline", "overflow-style", "overflow-x", "overflow-y", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"overflow", "padding-bottom", "padding-left", "padding-right", "padding-top", "padding", "page-break-after", "page-break-before", "page-break-inside", //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"page-policy", "page", "pause-after", "pause-before", "pause", "perspective-origin", "perspective", "phonemes", "pitch-range", //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"pitch", "play-during", "position", "presentation-level", "punctuation-trim", "quotes", "rendering-intent", "resize", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"rest-after", "rest-before", "rest", "richness", "right", "rotation-point", "rotation", "ruby-align", "ruby-overhang", "ruby-position", //$NON-NLS-9$ //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"ruby-span", "size", "speak-header", "speak-numeral", "speak-punctuation", "speak", "speech-rate", "stress", "string-set", "table-layout", //$NON-NLS-9$ //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"target-name", "target-new", "target-position", "target", "text-align-last", "text-align", "text-decoration", "text-emphasis", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"text-height", "text-indent", "text-justify", "text-outline", "text-shadow", "text-transform", "text-wrap", "top", "transform-origin", //$NON-NLS-8$ //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"transform-style", "transform", "transition-delay", "transition-duration", "transition-property", "transition-timing-function", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"transition", "unicode-bidi", "vertical-align", "visibility", "voice-balance", "voice-duration", "voice-family", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"voice-pitch-range", "voice-pitch", "voice-rate", "voice-stress", "voice-volume", "volume", "white-space-collapse", "white-space", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"widows", "width", "word-break", "word-spacing", "word-wrap", "z-index" //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
	];

	var grammars = mLib.grammars;
	grammars.push({
		id: "orion.css", //$NON-NLS-0$
		contentTypes: ["text/css"], //$NON-NLS-0$
		patterns: [
			{
				begin: "'(?:\\\\.|[^\\\\'])*\\\\$", //$NON-NLS-0$
				end: "^(?:$|(?:\\\\.|[^\\\\'])*('|[^\\\\]$))", //$NON-NLS-0$
				name: "string.quoted.single.css" //$NON-NLS-0$
			}, {
				begin: '"(?:\\\\.|[^\\\\"])*\\\\$', //$NON-NLS-0$
				end: '^(?:$|(?:\\\\.|[^\\\\"])*("|[^\\\\]$))', //$NON-NLS-0$
				name: "string.quoted.double.css" //$NON-NLS-0$
			},
			{include: "orion.lib#string_doubleQuote"}, //$NON-NLS-0$
			{include: "orion.lib#string_singleQuote"}, //$NON-NLS-0$
			{include: "orion.c-like#comment_block"}, //$NON-NLS-0$
			{include: "orion.lib#brace_open"}, //$NON-NLS-0$
			{include: "orion.lib#brace_close"}, //$NON-NLS-0$
			{include: "orion.lib#bracket_open"}, //$NON-NLS-0$
			{include: "orion.lib#bracket_close"}, //$NON-NLS-0$
			{include: "orion.lib#parenthesis_open"}, //$NON-NLS-0$
			{include: "orion.lib#parenthesis_close"}, //$NON-NLS-0$
			{include: "orion.lib#number_decimal"}, //$NON-NLS-0$
			{include: "#number_hex"}, //$NON-NLS-0$
			{
				match: "(?i)\\b-?(?:\\.\\d+|\\d+\\.?\\d*)(?:%|em|ex|ch|rem|vw|vh|vmin|vmax|in|cm|mm|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?\\b", //$NON-NLS-0$
				name: "constant.numeric.value.css" //$NON-NLS-0$
			},
			{			
				match: "(?:-webkit-|-moz-|-ms-|-o-|\\b)(?:" + keywords.join("|") + ")\\b", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				name: "keyword.control.css" //$NON-NLS-0$
			}
		],
		repository: {
			number_hex: {
				match: "#[0-9A-Fa-f]+\\b", //$NON-NLS-0$
				name: "constant.numeric.hex.css" //$NON-NLS-0$
			}
		}
	});
	return {
		id: grammars[grammars.length - 1].id,
		grammars: grammars,
		keywords: keywords
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define*/

define("orion/editor/stylers/application_xml/syntax", ["orion/editor/stylers/lib/syntax"], function(mLib) { //$NON-NLS-1$ //$NON-NLS-0$

	var grammars = mLib.grammars;
	grammars.push({
		id: "orion.xml", //$NON-NLS-0$
		contentTypes: ["application/xml", "application/xhtml+xml"], //$NON-NLS-1$ //$NON-NLS-0$
		patterns: [
			{include: "#comment"}, //$NON-NLS-0$
			{include: "#doctype"}, //$NON-NLS-0$
			{include: "#xmlDeclaration"}, //$NON-NLS-0$
			{include: "#tag"}, //$NON-NLS-0$
			{include: "#ampersandEscape"} //$NON-NLS-0$
		],
		repository: {
			ampersandEscape: {
				match: "&lt;|&gt;|&amp;", //$NON-NLS-0$
				name: "constant.character" //$NON-NLS-0$
			},
			comment: {
				begin: {match: "<!--", literal: "<!--"}, //$NON-NLS-0$
				end: {match: "-->", literal: "-->"}, //$NON-NLS-0$
				name: "comment.block.xml", //$NON-NLS-0$
				patterns: [
					{
						match: "(\\b)(TODO)(\\b)(((?!-->).)*)", //$NON-NLS-0$
						name: "meta.annotation.task.todo", //$NON-NLS-0$
						captures: {
							2: {name: "keyword.other.documentation.task"}, //$NON-NLS-0$
							4: {name: "comment.line"} //$NON-NLS-0$
						}
					}
				]
			},
			doctype: {
				begin: "<!(?:doctype|DOCTYPE)", //$NON-NLS-0$
				end: ">", //$NON-NLS-0$
				name: "meta.tag.doctype.xml", //$NON-NLS-0$
				captures: {
					0: {name: "entity.name.tag.doctype.xml"}, //$NON-NLS-0$
				},
				patterns: [
					{include: "#comment"}, //$NON-NLS-0$
					{include: "orion.lib#string_doubleQuote"}, //$NON-NLS-0$
					{include: "orion.lib#string_singleQuote"} //$NON-NLS-0$
				]
			},
			tag: {
				begin: "</?[A-Za-z0-9]+", //$NON-NLS-0$
				end: "/?>", //$NON-NLS-0$
				captures: {
					0: {name: "entity.name.tag.xml"}, //$NON-NLS-0$
				},
				name: "meta.tag.xml", //$NON-NLS-0$
				patterns: [
					{include: "#comment"}, //$NON-NLS-0$
					{include: "orion.lib#string_doubleQuote"}, //$NON-NLS-0$
					{include: "orion.lib#string_singleQuote"} //$NON-NLS-0$
				]	
			},
			xmlDeclaration: {
				begin: "<\\?xml", //$NON-NLS-0$
				end: "\\?>", //$NON-NLS-0$
				captures: {
					0: {name: "entity.name.tag.declaration.xml"}, //$NON-NLS-0$
				},
				patterns: [
					{include: "#comment"}, //$NON-NLS-0$
					{include: "orion.lib#string_doubleQuote"}, //$NON-NLS-0$
					{include: "orion.lib#string_singleQuote"} //$NON-NLS-0$
				],
				name: "meta.tag.declaration.xml" //$NON-NLS-0$
			}
		}
	});
	return {
		id: grammars[grammars.length - 1].id,
		grammars: grammars,
		keywords: []
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define*/

define("orion/editor/stylers/text_html/syntax", ["orion/editor/stylers/lib/syntax", "orion/editor/stylers/application_javascript/syntax", "orion/editor/stylers/text_css/syntax", "orion/editor/stylers/application_xml/syntax"], //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
	function(mLib, mJS, mCSS, mXML) {

	var grammars = mLib.grammars.concat(mJS.grammars).concat(mCSS.grammars).concat(mXML.grammars);
	grammars.push({
		id: "orion.html", //$NON-NLS-0$
		contentTypes: ["text/html"], //$NON-NLS-0$
		patterns: [
			{
				begin: "(?i)(<style)([^>]*)(>)", //$NON-NLS-0$
				end: "(?i)(</style>)", //$NON-NLS-0$
				captures: {
					1: {name: "entity.name.tag.html"}, //$NON-NLS-0$
					3: {name: "entity.name.tag.html"} //$NON-NLS-0$
				},
				contentName: "source.css.embedded.html", //$NON-NLS-0$
				patterns: [
					{include: "orion.css"} //$NON-NLS-0$
				]
			}, {
				begin: "(?i)<script\\s*>|<script\\s.*?(?:language\\s*=\\s*(['\"])javascript\\1|type\\s*=\\s*(['\"])(?:text|application)/(?:javascript|ecmascript)\\2).*?>", //$NON-NLS-0$
				end: "(?i)</script>", //$NON-NLS-0$
				captures: {
					0: {name: "entity.name.tag.html"} //$NON-NLS-0$
				},
				contentName: "source.js.embedded.html", //$NON-NLS-0$
				patterns: [
					{include: "orion.js"} //$NON-NLS-0$
				]
			},
			{include: "orion.xml#comment"}, //$NON-NLS-0$
			{include: "orion.xml#doctype"}, //$NON-NLS-0$
			{include: "orion.xml#tag"}, //$NON-NLS-0$
			{include: "orion.xml#ampersandEscape"} //$NON-NLS-0$
		]
	});
	return {
		id: grammars[grammars.length - 1].id,
		grammars: grammars,
		keywords: []
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define*/

define("orion/editor/stylers/text_x-java-source/syntax", ["orion/editor/stylers/lib/syntax"], function(mLib) { //$NON-NLS-1$ //$NON-NLS-0$
	var keywords = [
		"abstract", //$NON-NLS-0$
		"boolean", "break", "byte", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"case", "catch", "char", "class", "continue", //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"default", "do", "double", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"else", "extends", //$NON-NLS-1$ //$NON-NLS-0$
		"false", "final", "finally", "float", "for", //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"if", "implements", "import", "instanceof", "int", "interface", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"long", //$NON-NLS-0$
		"native", "new", "null", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"package", "private", "protected", "public", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"return", //$NON-NLS-0$
		"short", "static", "super", "switch", "synchronized", //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"this", "throw", "throws", "transient", "true", "try", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"void", "volatile", //$NON-NLS-1$ //$NON-NLS-0$
		"while" //$NON-NLS-0$
	];

	var grammars = mLib.grammars;
	grammars.push({
		id: "orion.java", //$NON-NLS-0$
		contentTypes: ["text/x-java-source"], //$NON-NLS-0$
		patterns: [
			{include: "orion.lib#string_doubleQuote"}, //$NON-NLS-0$
			{include: "orion.lib#string_singleQuote"}, //$NON-NLS-0$
			{include: "orion.c-like#comment_singleLine"}, //$NON-NLS-0$
			{include: "orion.lib#doc_block"}, //$NON-NLS-0$
			{include: "orion.c-like#comment_block"}, //$NON-NLS-0$
			{include: "orion.lib#brace_open"}, //$NON-NLS-0$
			{include: "orion.lib#brace_close"}, //$NON-NLS-0$
			{include: "orion.lib#bracket_open"}, //$NON-NLS-0$
			{include: "orion.lib#bracket_close"}, //$NON-NLS-0$
			{include: "orion.lib#parenthesis_open"}, //$NON-NLS-0$
			{include: "orion.lib#parenthesis_close"}, //$NON-NLS-0$
			{include: "orion.lib#number_decimal"}, //$NON-NLS-0$
			{include: "orion.lib#number_hex"}, //$NON-NLS-0$
			{
				match: "\\b(?:" + keywords.join("|") + ")\\b", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				name: "keyword.control.java" //$NON-NLS-0$
			}
		]
	});
	return {
		id: grammars[grammars.length - 1].id,
		grammars: grammars,
		keywords: keywords
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define*/

define("orion/editor/stylers/application_json/syntax", ["orion/editor/stylers/lib/syntax"], function(mLib) { //$NON-NLS-1$ //$NON-NLS-0$
	var keywords = ["false", "true"]; //$NON-NLS-1$ //$NON-NLS-0$

	var grammars = mLib.grammars;
	grammars.push({
		id: "orion.json", //$NON-NLS-0$
		contentTypes: ["application/json"], //$NON-NLS-0$
		patterns: [
			{include: "orion.lib#string_doubleQuote"}, //$NON-NLS-0$
			{include: "orion.lib#string_singleQuote"}, //$NON-NLS-0$
			{include: "orion.lib#brace_open"}, //$NON-NLS-0$
			{include: "orion.lib#brace_close"}, //$NON-NLS-0$
			{include: "orion.lib#bracket_open"}, //$NON-NLS-0$
			{include: "orion.lib#bracket_close"}, //$NON-NLS-0$
			{include: "orion.lib#parenthesis_open"}, //$NON-NLS-0$
			{include: "orion.lib#parenthesis_close"}, //$NON-NLS-0$
			{include: "orion.lib#number_decimal"}, //$NON-NLS-0$
			{include: "orion.lib#number_hex"}, //$NON-NLS-0$
			{
				match: "\\b(?:" + keywords.join("|") + ")\\b", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				name: "keyword.control.json" //$NON-NLS-0$
			}
		]
	});
	return {
		id: grammars[grammars.length - 1].id,
		grammars: grammars,
		keywords: keywords
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define*/

define("orion/editor/stylers/text_x-php/syntax", ["orion/editor/stylers/lib/syntax"], function(mLib) { //$NON-NLS-0$
	var keywords = [
		"abstract", "and", "array", "as", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"break", //$NON-NLS-0$
		"callable", "case", "catch", "class", "clone", "const", "continue", //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"declare", "default", "die", "do", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"echo", "else", "elseif", "empty", "enddeclare", "endfor", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"endforeach", "endif", "endswitch", "endwhile", "eval", "exit", "extends", //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"false", "FALSE", "final", "finally", "for", "foreach", "function", //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"global", "goto", //$NON-NLS-1$ //$NON-NLS-0$
		"if", "implements", "include", "include_once", "insteadof", "interface", "instanceof", "isset", //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"list", //$NON-NLS-0$
		"namespace", "new", "null", "NULL", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"or", //$NON-NLS-0$
		"parent", "print", "private", "protected", "public", //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"require", "require_once", "return", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"self", "static", "switch", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"throw", "trait", "try", "true", "TRUE", //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"unset", "use", //$NON-NLS-1$ //$NON-NLS-0$
		"var", //$NON-NLS-0$
		"while", //$NON-NLS-0$
		"xor", //$NON-NLS-0$
		"yield", //$NON-NLS-0$
		"__halt_compiler", "__CLASS__", "__DIR__", "__FILE__", "__FUNCTION__",  //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"__LINE__", "__METHOD__", "__NAMESPACE__", "__TRAIT__"  //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
	];

	var grammars = mLib.grammars;
	grammars.push({
		id: "orion.php", //$NON-NLS-0$
		contentTypes: ["text/x-php"], //$NON-NLS-0$
		patterns: [
			{
				begin: "(?i)<(\\?|%(?!php))(?:=|php)?(?:\\s|$)", //$NON-NLS-0$
				end: "[\\1]>", //$NON-NLS-0$
				captures: {
					0: {name: "entity.name.declaration.php"} //$NON-NLS-0$
				},
				contentName: "source.php.embedded", //$NON-NLS-0$
				patterns: [
					{include: "orion.lib#string_doubleQuote"}, //$NON-NLS-0$
					{include: "orion.lib#string_singleQuote"}, //$NON-NLS-0$
					{include: "orion.c-like#comment_singleLine"}, //$NON-NLS-0$
					{include: "orion.lib#doc_block"}, //$NON-NLS-0$
					{include: "orion.c-like#comment_block"}, //$NON-NLS-0$
					{
						match: {match: "#.*", literal: "#"}, //$NON-NLS-0$
						name: "comment.line.number-sign.php", //$NON-NLS-0$
						patterns: [
							{include: "orion.lib#todo_comment_singleLine"} //$NON-NLS-0$
						]
					}, {
						begin: "<<<(\\w+)$", //$NON-NLS-0$
						end: "^\\1;$", //$NON-NLS-0$
						name: "string.unquoted.heredoc.php" //$NON-NLS-0$
					}, {
						begin: "<<<'(\\w+)'$", //$NON-NLS-0$
						end: "^\\1;$", //$NON-NLS-0$
						name: "string.unquoted.heredoc.nowdoc.php" //$NON-NLS-0$
					},
					{include: "orion.lib#brace_open"}, //$NON-NLS-0$
					{include: "orion.lib#brace_close"}, //$NON-NLS-0$
					{include: "orion.lib#bracket_open"}, //$NON-NLS-0$
					{include: "orion.lib#bracket_close"}, //$NON-NLS-0$
					{include: "orion.lib#parenthesis_open"}, //$NON-NLS-0$
					{include: "orion.lib#parenthesis_close"}, //$NON-NLS-0$
					{
						match: "\\b0[bB][01]+\\b", //$NON-NLS-0$
						name: "constant.numeric.binary.php" //$NON-NLS-0$
					},
					{include: "orion.lib#number_decimal"}, //$NON-NLS-0$
					{include: "orion.lib#number_hex"}, //$NON-NLS-0$
					{
						match: "\\b(?:" + keywords.join("|") + ")\\b", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
						name: "keyword.control.php" //$NON-NLS-0$
					}
				]
			}, {
				include: "orion.html" //$NON-NLS-0$
			}
		]
	});

	return {
		id: grammars[grammars.length - 1].id,
		grammars: grammars,
		keywords: keywords
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define*/

define("orion/editor/stylers/text_x-python/syntax", ["orion/editor/stylers/lib/syntax"], function(mLib) { //$NON-NLS-0$
	var keywords = [
		"and", "as", "assert", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"break", //$NON-NLS-0$
		"class", "continue", //$NON-NLS-1$ //$NON-NLS-0$
		"def", "del", //$NON-NLS-1$ //$NON-NLS-0$
		"exec", "elif", "else", "except", "Ellipsis", //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"False", "finally", "for", "from", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"global", //$NON-NLS-0$
		"if", "import", "in", "is", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"lambda", //$NON-NLS-0$
		"not", "None", "NotImplemented", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"or", //$NON-NLS-0$
		"pass", "print", //$NON-NLS-1$ //$NON-NLS-0$
		"raise", "return", //$NON-NLS-1$ //$NON-NLS-0$
		"try", "True", //$NON-NLS-1$ //$NON-NLS-0$
		"while", "with", //$NON-NLS-1$ //$NON-NLS-0$
		"yield" //$NON-NLS-0$
	];

	var grammars = mLib.grammars;
	grammars.push({
		id: "orion.python", //$NON-NLS-0$
		contentTypes: ["text/x-python"], //$NON-NLS-0$
		patterns: [
			{
				begin: "(['\"])\\1\\1", //$NON-NLS-0$
				end: "\\1\\1\\1", //$NON-NLS-0$
				name: "string.quoted.triple.python" //$NON-NLS-0$
			}, 
			{include: "orion.lib#string_doubleQuote"}, //$NON-NLS-0$
			{include: "orion.lib#string_singleQuote"}, //$NON-NLS-0$
			{
				begin: {match: "#", literal: "#"}, //$NON-NLS-0$
				end: {match: "$", literal: ""}, //$NON-NLS-0$
				name: "comment.line.number-sign.python", //$NON-NLS-0$
				patterns: [
					{
						include: "orion.lib#todo_comment_singleLine" //$NON-NLS-0$
					}
				]
			},
			{include: "orion.lib#brace_open"}, //$NON-NLS-0$
			{include: "orion.lib#brace_close"}, //$NON-NLS-0$
			{include: "orion.lib#bracket_open"}, //$NON-NLS-0$
			{include: "orion.lib#bracket_close"}, //$NON-NLS-0$
			{include: "orion.lib#parenthesis_open"}, //$NON-NLS-0$
			{include: "orion.lib#parenthesis_close"}, //$NON-NLS-0$
			{include: "#number_decimal"}, //$NON-NLS-0$
			{include: "orion.lib#number_hex"}, //$NON-NLS-0$ 
			{
				match: "\\b(?:" + keywords.join("|") + ")\\b", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				name: "keyword.control.python" //$NON-NLS-0$
			}
		],
		repository: {
			number_decimal: {
				match: "\\b-?(?:\\.\\d+|\\d+\\.?\\d*)[lL]?\\b", //$NON-NLS-0$
				name: "constant.numeric.number.python" //$NON-NLS-0$
			}
		}
	});
	return {
		id: grammars[grammars.length - 1].id,
		grammars: grammars,
		keywords: keywords
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define*/

define("orion/editor/stylers/text_x-ruby/syntax", ["orion/editor/stylers/lib/syntax"], function(mLib) { //$NON-NLS-1$ //$NON-NLS-0$
	var keywords = [
		"alias", "alias_method", "and", "attr_reader", "attr_writer", "attr_accessor", "attr", //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"BEGIN", "begin", "break", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"case", "class", "catch", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"def", "defined?", "do", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"else", "elsif", "END", "end", "ensure", "extend", //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"false", "for", "if", "fail", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"gem", //$NON-NLS-0$
		"in", "include", "initialize", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"load",  "loop", "lambda", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"module", "module_function", //$NON-NLS-1$ //$NON-NLS-0$
		"new", "next", "nil", "not", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"or", //$NON-NLS-0$
		"public", "prepend", "private", "protected", //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"raise", "redo", "require", "require_relative", "rescue", "retry", "return", //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"self", "super", //$NON-NLS-1$ //$NON-NLS-0$
		"then", "throw", "true", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"undef", "unless", "until", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
		"when", "while", //$NON-NLS-1$ //$NON-NLS-0$
		"yield", //$NON-NLS-0$
		"__ENCODING__", "__END__", "__FILE__", "__LINE__" //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
	];

	var grammars = mLib.grammars;
	grammars.push({
		id: "orion.ruby", //$NON-NLS-0$
		contentTypes: ["text/x-ruby"], //$NON-NLS-0$
		patterns: [
			{include: "orion.lib#string_doubleQuote"}, //$NON-NLS-0$
			{include: "orion.lib#string_singleQuote"}, //$NON-NLS-0$
			{
				match: "/(?![\\s])(?:\\\\.|[^/])+/(?:[ioxmuesn]\\b)?", //$NON-NLS-0$
				name: "string.regexp.ruby" //$NON-NLS-0$
			}, {
				match: {match: "#.*", literal: "#"}, //$NON-NLS-0$
				name: "comment.line.number-sign.ruby", //$NON-NLS-0$
				patterns: [
					{include: "orion.lib#todo_comment_singleLine"} //$NON-NLS-0$
				]
			}, {
				begin: {match: "^=begin\\b", literal: "=begin"}, //$NON-NLS-0$
				end: {match: "^=end\\b", literal: "=end"}, //$NON-NLS-0$
				name: "comment.block.ruby", //$NON-NLS-0$
				patterns: [
					{
						match: "(\\b)(TODO)(\\b)(((?!\\*/).)*)", //$NON-NLS-0$
						name: "meta.annotation.task.todo", //$NON-NLS-0$
						captures: {
							2: {name: "keyword.other.documentation.task"}, //$NON-NLS-0$
							4: {name: "comment.block"} //$NON-NLS-0$
						}
					}
				]
			},
			{include: "orion.lib#brace_open"}, //$NON-NLS-0$
			{include: "orion.lib#brace_close"}, //$NON-NLS-0$
			{include: "orion.lib#bracket_open"}, //$NON-NLS-0$
			{include: "orion.lib#bracket_close"}, //$NON-NLS-0$
			{include: "orion.lib#parenthesis_open"}, //$NON-NLS-0$
			{include: "orion.lib#parenthesis_close"}, //$NON-NLS-0$
			{include: "orion.lib#number_decimal"}, //$NON-NLS-0$
			{include: "orion.lib#number_hex"}, //$NON-NLS-0$
			{
				match: "\\b0[bB][01]+\\b", //$NON-NLS-0$
				name: "constant.numeric.binary.ruby" //$NON-NLS-0$
			}, {
				match: "\\b(?:" + keywords.join("|") + ")\\b", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
				name: "keyword.control.ruby" //$NON-NLS-0$
			}
		]
	});
	return {
		id: grammars[grammars.length - 1].id,
		grammars: grammars,
		keywords: keywords
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define*/

define("orion/editor/stylers/application_x-ejs/syntax", ["orion/editor/stylers/lib/syntax", "orion/editor/stylers/application_javascript/syntax", "orion/editor/stylers/application_xml/syntax"], //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
	function(mLib, mJS, mXML) {

	var grammars = mLib.grammars.concat(mJS.grammars).concat(mXML.grammars);
	grammars.push({
		id: "orion.ejs", //$NON-NLS-0$
		contentTypes: ["application/x-ejs"], //$NON-NLS-0$
		patterns: [
			{include: "orion.xml#comment"}, //$NON-NLS-0$
			{include: "orion.xml#doctype"}, //$NON-NLS-0$
			{
				begin: "<%=?(?:\\s|$)", //$NON-NLS-0$
				end: "%>", //$NON-NLS-0$
				captures: {
					0: {name: "entity.name.declaration.js"} //$NON-NLS-0$
				},
				contentName: "source.js.embedded.ejs", //$NON-NLS-0$
				patterns: [
					{include: "orion.js"} //$NON-NLS-0$
				]
			},
			{include: "orion.xml#tag"}, //$NON-NLS-0$
			{include: "orion.xml#ampersandEscape"} //$NON-NLS-0$
		]
	});
	return {
		id: grammars[grammars.length - 1].id,
		grammars: grammars,
		keywords: []
	};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global define*/

define("orion/editor/stylers/text_x-yaml/syntax", [], function() { //$NON-NLS-0$
	var keywords = ["false", "null", "true"]; //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
	var casts = ["!!bool", "!!float", "!!int", "!!map", "!!null", "!!omap", "!!seq", "!!str"]; //$NON-NLS-7$ //$NON-NLS-6$ //$NON-NLS-5$ //$NON-NLS-4$ //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$

	var id = "orion.yaml"; //$NON-NLS-0$
	var grammar = {
		id: id,
		contentTypes: ["text/x-yaml"], //$NON-NLS-0$
		patterns: [
			{include: "#numberSignComment"}, //$NON-NLS-0$
			{
				match: "^%(?:YAML|TAG)\\s.*", //$NON-NLS-0$
				name: "meta.directive.yaml" //$NON-NLS-0$
			}, {
				begin: "^.*?:(?:[\\t ]|$)", //$NON-NLS-0$
				end: "$", //$NON-NLS-0$
				contentName: "string.unquoted.yaml", //$NON-NLS-0$
				beginCaptures: {
					0: {
						name: "entity.name.key.yaml"
					}
				},
				patterns: [
					{include: "#numberSignComment"}, //$NON-NLS-0$
					{
						match: "^\\s*[&*]\\s*$", //$NON-NLS-0$
						name: "entity.name.tag.yaml" //$NON-NLS-0$
					}, {
						match: "(?i)^\\s*(?:" + keywords.join("|") + ")\\s*$", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
						name: "keyword.control.yaml" //$NON-NLS-0$
					}, {
						match: "(?i)^\\s*(?:" + casts.join("|") + ")\\b", //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
						name: "keyword.control.yaml" //$NON-NLS-0$
					}, {
						match: "(?i)^\\s*(?:-?[0-9]*(?:\\.[0-9]+)?(?:e[-+][1-9][0-9]*)?)\\s*$", //$NON-NLS-0$
						name: "constant.numeric" //$NON-NLS-0$
					}, {
						match: "(?i)^\\s*(?:-?[1-9][0-9]*|0|-?\\.inf|\\.nan)\\s*$", //$NON-NLS-0$
						name: "constant.numeric" //$NON-NLS-0$
					}
				]
			}, {
				match: "---|\\.\\.\\.", //$NON-NLS-0$
				name: "meta.separator.yaml" //$NON-NLS-0$
			}
		],
		repository: {
			numberSignComment: {
				begin: {match: "(?:^|\\s)#", literal: "#"}, //$NON-NLS-0$
				end: {match: "$", literal: ""}, //$NON-NLS-0$
				name: "comment.line.number-sign.yaml", //$NON-NLS-0$
				patterns: [
					{include: "orion.lib#todo_comment_singleLine"} //$NON-NLS-0$
				]
			}
		}
	};

	return {
		id: id,
		grammars: [grammar],
		keywords: casts.concat(keywords)
	};
});

/*******************************************************************************
 *
 * @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
 *******************************************************************************/
/*jslint browser:true devel:true sub:true*/
/*global define eclipse:true orion:true window*/

define('orion/widgets/browse/staticDataSource',[
	'orion/Deferred',
	"orion/editor/textStyler", 
	"orion/editor/stylers/application_javascript/syntax",
	"orion/editor/stylers/text_css/syntax",
	"orion/editor/stylers/text_html/syntax",
	"orion/editor/stylers/text_x-java-source/syntax",
	"orion/editor/stylers/application_json/syntax",
	"orion/editor/stylers/text_x-php/syntax",
	"orion/editor/stylers/text_x-python/syntax",
	"orion/editor/stylers/text_x-ruby/syntax",
	'orion/editor/stylers/application_x-ejs/syntax',
	'orion/editor/stylers/application_xml/syntax',
	'orion/editor/stylers/text_x-yaml/syntax',
], function(Deferred, mStyler, mJS, mCss, mHtml, mJava, mJson, mPhp, mPython, mRuby, mEJS, mXml, mYaml) {
	var ContentTypes = [{	id: "text/plain",
			name: "Text",
			extension: ["txt"],
			imageClass: "file-sprite-text modelDecorationSprite"
		},
		{	id: "application/javascript",
			"extends": "text/plain",
			name: "JavaScript",
			extension: ["js"],
			imageClass: "file-sprite-javascript modelDecorationSprite"
		},
		{	id: "text/html",
			"extends": "text/plain",
			name: "HTML",
			extension: ["html", "htm"],
			imageClass: "file-sprite-html modelDecorationSprite"
		},
		{	id: "text/css",
			"extends": "text/plain",
			name: "CSS",
			extension: ["css"],
			imageClass: "file-sprite-css modelDecorationSprite"
		},
		{	id: "application/json",
			"extends": "text/plain",
			name: "JSON",
			extension: ["json"],
			imageClass: "file-sprite-text modelDecorationSprite"
		},
		{	id: "application/xml",
			"extends": "text/plain",
			name: "XML",
			extension: ["xml"],
			imageClass: "file-sprite-xml"
		},
		{	id: "application/x-ejs",
			"extends": "text/plain",
			name: "Embedded Javascript",
			extension: ["ejs"],
			imageClass: "file-sprite-javascript modelDecorationSprite"
		},
		{	id: "text/x-java-source",
			"extends": "text/plain",
			name: "Java",
			extension: ["java"]
		},
		{	id: "text/x-python",
			"extends": "text/plain",
			name: "Python",
			extension: ["py", "rpy", "pyw", "cpy", "SConstruct", "Sconstruct", "sconstruct", "SConscript", "gyp", "gypi"]
		},
		{	id: "text/x-ruby",
			"extends": "text/plain",
			name: "Ruby",
			extension: ["rb", "rbx", "rjs", "Rakefile", "rake", "cgi", "fcgi", "gemspec", "irbrc", "capfile", "ru", "prawn", "Gemfile", "Guardfile", "Vagrantfile", "Appraisals", "Rantfile"]
		},
		{	id: "text/x-php",
			"extends": "text/plain",
			name: "PHP",
			extension: ["php", "php3", "php4", "php5", "phpt", "phtml", "aw", "ctp"]
		},
		{	id: "text/x-markdown",
			"extends": "text/plain",
			name: "Markdown",
			extension: ["md"]
		},
		{	id: "text/x-yaml",
			"extends": "text/plain",
			name: "YAML",
			extension: ["yaml", "yml"]
		},
		{	id: "text/conf",
			"extends": "text/plain",
			name: "Conf",
			extension: ["conf"]
		},
		{	id: "text/sh",
			"extends": "text/plain",
			name: "sh",
			extension: ["sh"]
		},
		{	id: "application/browser-renderable",
			name: "browser-renderable"
		},
		{	id: "application/pdf",
			"extends": "application/browser-renderable",
			name: "PDF",
			extension: ["pdf"]
		},
		{	id: "application/octet-stream",
			name: "octet-stream",
			extension: ["exe", "bin", "doc", "ppt"]
		},
		{	id: "application/zip",
			"extends": "application/octet-stream",
			name: "ZIP",
			extension: ["war", "jar", "zip", "rar"]
		},
		// Image types
		{	id: "image/gif",
			name: "GIF",
			extension: ["gif"],
			imageClass: "file-sprite-image modelDecorationSprite"
		},
		{	id: "image/jpeg",
			name: "JPG",
			extension: ["jpg", "jpeg", "jpe"],
			imageClass: "file-sprite-image modelDecorationSprite"
		},
		{	id: "image/ico",
			name: "ICO",
			extension: ["ico"],
			imageClass: "file-sprite-image modelDecorationSprite"
		},
		{	id: "image/png",
			name: "PNG",
			extension: ["png"],
			imageClass: "file-sprite-image modelDecorationSprite"
		},
		{	id: "image/tiff",
			name: "TIFF",
			extension: ["tif", "tiff"],
			imageClass: "file-sprite-image modelDecorationSprite"
		},
		{	id: "image/svg",
			name: "SVG",
			extension: ["svg"],
			imageClass: "file-sprite-image modelDecorationSprite"
	}];
	
	function SyntaxHighlighter() {
		this.styler = null;
	}
	
	SyntaxHighlighter.prototype = {
		setup: function(fileContentType, textView, annotationModel, fileName, allowAsync) {
			if (this.styler) {
				if (this.styler.destroy) {
					this.styler.destroy();
				}
				this.styler = null;
			}
			return this.highlight(fileContentType, textView, annotationModel);
		},
		highlight: function(fileContentType, textView, annotationModel) {
			if (this.styler) {
				this.styler.destroy();
				this.styler = null;
			}
			var stylerAdapter = null;
			if (fileContentType) {
				switch(fileContentType.id) {
					case "application/javascript": //$NON-NLS-0$
						stylerAdapter = new mStyler.createPatternBasedAdapter(mJS.grammars, "orion.js");
						break;
					case "application/x-ejs": //$NON-NLS-0$
						stylerAdapter = new mStyler.createPatternBasedAdapter(mEJS.grammars, "orion.ejs"); //$NON-NLS-0$
						break;
					case "text/css": //$NON-NLS-0$
						stylerAdapter = new mStyler.createPatternBasedAdapter(mCss.grammars, "orion.css"); //$NON-NLS-0$
						break;
					case "text/html": //$NON-NLS-0$
						stylerAdapter = new mStyler.createPatternBasedAdapter(mHtml.grammars, "orion.html"); //$NON-NLS-0$
						break;
					case "text/x-java-source": //$NON-NLS-0$
						stylerAdapter = new mStyler.createPatternBasedAdapter(mJS.grammars, "orion.js"); //$NON-NLS-0$
						break;
					case "application/json": //$NON-NLS-0$
						stylerAdapter = new mStyler.createPatternBasedAdapter(mJson.grammars, "orion.json"); //$NON-NLS-0$
						break;
					case "text/x-python": //$NON-NLS-0$
						stylerAdapter = new mStyler.createPatternBasedAdapter(mPython.grammars, "orion.py"); //$NON-NLS-0$
						break;
					case "text/x-ruby": //$NON-NLS-0$
						stylerAdapter = new mStyler.createPatternBasedAdapter(mRuby.grammars, "orion.rb"); //$NON-NLS-0$
						break;
					case "text/x-php": //$NON-NLS-0$
						stylerAdapter = new mStyler.createPatternBasedAdapter(mPhp.grammars, "orion.php"); //$NON-NLS-0$
						break;
					case "application/xml": //$NON-NLS-0$
					case "application/xhtml+xml": //$NON-NLS-0$
						stylerAdapter = new mStyler.createPatternBasedAdapter(mXml.grammars, "orion.xml"); //$NON-NLS-0$
						break;
					case "text/x-yaml": //$NON-NLS-0$
						stylerAdapter = new mStyler.createPatternBasedAdapter(mYaml.grammars, "orion.yaml"); //$NON-NLS-0$
						break;
				}
			}
			if(stylerAdapter) {
				this.styler = new mStyler.TextStyler(textView, annotationModel, stylerAdapter); //$NON-NLS-0$
			}
			return new Deferred().resolve();
		},
		getStyler: function() {
			return this.styler;
		}
	};
	
	return {ContentTypes: ContentTypes,
			SyntaxHighlighter: SyntaxHighlighter};
});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*global window define */
/*jslint forin:true devel:true*/

/** @namespace The global container for eclipse APIs. */

define('orion/widgets/browse/emptyFileClient',["orion/Deferred"], function(Deferred){
	
	var reameFile = {
		Directory: false,
		Length: 1159,
		LocaltimeStamp: 1389974936424,
		Location: "readme",
		Name: "README.md"
	};
	
	var reameFileMeta = {
		Directory: false,
		Length: 1159,
		LocaltimeStamp: 1389974936424,
		Location: "readme",
		Name: "README.md",
		Parents: []
	};
	
	var readmeContents = "The goal of Orion is to build developer tooling that works in the browser, at web scale.\n" +
						 "The vision behind Orion is to move software development to the web as a web experience, by\n" + 
						 "enabling open tool integration through HTTP and REST, JSON, OAuth, OpenID, and others.\n" + 
						"The idea is to exploit internet design principles throughout, instead of trying to bring\n" + 
						"existing desktop IDE concepts to the browser. See the [Orion wiki](http://wiki.eclipse.org/Orion) for more\n" + 
						"information about Orion.\n" +
						"\n" +
						"Contributing\n" +
						"------------\n" +
						"\n" +
						"Orion source code is available in an Eclipse Git repository, and there is also a mirror\n" +
						"on GitHub. For complete details on getting the source and getting setup to develop Orion,\n" +
						"see the [Orion wiki](http://wiki.eclipse.org/Orion/Getting_the_source).\n" +
						"\n" +
						"Bug reports and patches are welcome in [bugzilla](https://bugs.eclipse.org/bugs/enter_bug.cgi?product=Orion).\n" +
						"\n" +
						"License\n" +
						"-------\n" +
						"\n" +
						"This repository contains the Orion client and Node-based server. This source code is available\n" +
						"under the [Eclipse Public License](http://www.eclipse.org/legal/epl-v10.html)\n" +
						"and [Eclipse Distribution License](http://www.eclipse.org/org/documents/edl-v10.php).\n";
	
	var emptyRoot = {
		Children: [reameFile],
		ChildrenLocation: "children",
		Directory: true,
		Length: 0,
		LocaltimeStamp: 1389974936424,
		Location: "root",
		Name: ""
	};
	
	function FileClient() {
	}
	
	FileClient.prototype =  {
		/**
		 * Returns the name of the file service managing this location
		 * @param location The location of the item 
		 */
		fileServiceName: function(location) {
			return "Empty";
		},
		 
		/**
		 * Returns the root url of the file service managing this location
		 * @param location The location of the item 
		 */
		fileServiceRootURL: function(location) {
			return "";
		},
		 
		/**
		 * 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 new Deferred().resolve([reameFile]);
		},

		/**
		 * Loads all the user's workspaces. Returns a deferred that will provide the loaded
		 * workspaces when ready.
		 */
		loadWorkspaces: function() {
			return new Deferred().resolve(emptyRoot);
		},
		
		/**
		 * 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) {
			if(isMetadata){
				return new Deferred().resolve(reameFileMeta);
			}
			return new Deferred().resolve(readmeContents);
		},
		
		/**
		 * 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 new Deferred().resolve(emptyRoot);
		}
	};//end FileClient prototype
	FileClient.prototype.constructor = FileClient;

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

define('text!orion/widgets/browse/repoAndBaseUrlTrigger.html',[],function () { return '<div id="orion.browse.repoURLTrigger" class="infoDropdownLink"><span>Configure eclipse client</span></div>\n<div id="orion.browse.repoURLDropdown" class="dropdownMenu">\n\t<div class="infoDropdownHeader">\n\t\t<div class="infoDropdownHeaderClose  core-sprite-close imageSprite" id="orion.browse.repoURLClose"></div>\n\t</div>\n\t<div class="infoDropdownHeaderTitle-backup" style="text-align: center; font-weight: bold;" id="orion.browse.repoURLTitle">Paste this configuration into<br/>File &gt; Accept JazzHub Client Configuration</div>\n\t<textarea id="orion.browse.repoURLInput" class="repoAndBaseUrlInput dropdownSubMenu" type="text" readonly></textarea>\n</div>';});

define('text!orion/widgets/browse/repoUrlTrigger.html',[],function () { return '<div id="orion.browse.repoURLTrigger" class="infoDropdownLink"><span>Git URL</span></div>\n<div id="orion.browse.repoURLDropdown" class="dropdownMenu infoContainerDropdown">\n\t<div class="infoDropdownHeader">\n\t\t<div class="infoDropdownHeaderClose  core-sprite-close imageSprite" id="orion.browse.repoURLClose"></div>\n\t\t<span class="infoDropdownHeaderTitle" id="orion.browse.repoURLTitle">Git URL</span>\n\t</div>\n\t<input id="orion.browse.repoURLInput" class = "repoUrlInput dropdownSubMenu" type="text" readonly></input>\n</div>';});

define('text!orion/widgets/browse/shareSnippetTrigger.html',[],function () { return '<div id="orion.browse.shareSnippetTrigger" class="infoDropdownLink"><span>Share Snippet</span></div>\n<div id="orion.browse.shareSnippetDropdown" class="dropdownMenu infoContainerDropdown">\n\t<div class="infoDropdownHeader">\n\t\t<div class="infoDropdownHeaderClose  core-sprite-close imageSprite" id="orion.browse.shareSnippetClose"></div>\n\t\t<span class="infoDropdownHeaderTitle" id="orion.browse.shareSnippetTitle">Copy the html tags</span>\n\t</div>\n\t<input id="orion.browse.shareSnippetInput" class = "shareSnippetInput dropdownSubMenu" type="text" readonly></input>\n</div>';});

/*******************************************************************************
 * @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
 ******************************************************************************/

/*jslint browser:true devel:true sub:true*/
/*global define window document URL*/
define('orion/fileDownloader',[
	'i18n!orion/navigate/nls/messages',
	'orion/i18nUtil',
	'orion/URL-shim'
], function(messages, i18nUtil) {

	var orion_download_initiator = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
	
	function downloadSupported() {
		return (typeof orion_download_initiator.download !== "undefined") || (typeof window.navigator !== "undefined" && window.navigator.msSaveBlob);
	} 
	
	function _makeError(error) {
		var newError = {
			Severity: "Error", //$NON-NLS-0$
			Message: messages.noResponse
		};
		if (error.status === 0) {
			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);
	}

	/**
	 * @name orion.download.FileDownloader
	 * @class
	 * @description 
	 * <p>Requires service {@link orion.core.ContentTypeRegistry}</p>
	 * 
	 * @param {orion.fileClient.FileClient} [fileClient] The file client that supports readBlob API.
	 * @param {orion.status.StatusReportingService} [statusService=null] Optional. If defined status is reported while downloading.
	 * @param {orion.progress.ProgressService} [progressService=null] Optional. If defined progress is reported while downloading.
	 */
	function FileDownloader(fileClient, statusService, progressService) {
		this.fileClinet = fileClient;
		this.statusService = statusService;
		this.progressService = progressService;
	}
	FileDownloader.prototype = /** @lends orion.download.FileDownloader.prototype */ {
		_isSupported: function(forceDownload) {
			if(!forceDownload && !downloadSupported()) {
				if(this.statusService && this.statusService.setProgressResult) {
					this.statusService.setProgressResult({Message: messages["Download not supported"], Severity: "Error"});
				}
				return false;
			}
			return true;			
		},
		downloadFromLocation: function(fileMetaData, contentType, forceDownload) {
			if(!this._isSupported(forceDownload)) {
				return;
			}
			var progressService = this.progressService;
			var progress = function(deferred, msgKey, uri) {
				if (!progressService) { return deferred; }
				return progressService.progress(deferred, i18nUtil.formatMessage(msgKey, uri));
			};
			var errorHandler = function(error) {
				//clearTimeout();
				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);
			if(this.statusService && this.statusService.setProgressResult) {
				this.statusService.setProgressResult({Message: messages["Downloading..."]});
			}
			progress(this.fileClinet.readBlob(fileMetaData.Location), messages["Downloading..."], fileMetaData.Location).then(function(contents) {
				if(this.statusService && this.statusService.setProgressMessage) {
					this.statusService.setProgressMessage("");
				}
				this.downloadFromBlob(contents, fileMetaData.Name, contentType, forceDownload);
			}.bind(this), errorHandler);
		},
		downloadFromBlob: function(blobContents, fileName, contentType, forceDownload, createLink) {
			if(!this._isSupported(forceDownload)) {
				return;
			}
			var cType = (contentType && contentType.id) ? contentType.id : "application/octet-stream";
			var blobObj = new Blob([blobContents],{type: cType}); 
			var downloadLink = createLink ? document.createElementNS("http://www.w3.org/1999/xhtml", "a") : document.createElement("a"); //$NON-NLS-1$ //$NON-NLS-0$
			if(typeof downloadLink.download !== "undefined") {//Chrome and FireFox
				var objectURLLink = URL.createObjectURL(blobObj);
				downloadLink.href = objectURLLink;
				downloadLink.download = fileName;
				if(!createLink) {
					var event = document.createEvent("MouseEvents");
					event.initMouseEvent(
						"click", true, false, window, 0, 0, 0, 0, 0
						, false, false, false, false, 0, null
					);
					downloadLink.dispatchEvent(event);
				} else {
					return downloadLink;
				}
			} else if(typeof window.navigator !== "undefined" && window.navigator.msSaveOrOpenBlob) {//IE 9+
				if(!createLink) {
					window.navigator.msSaveBlob(blobObj, fileName);
				} else {
					downloadLink.href = "javascript:void(0)";
					downloadLink.addEventListener("click", function(){
						window.navigator.msSaveBlob(blobObj, fileName);
					});
					return downloadLink;
				}
			}
		}
	};
	return {
		FileDownloader: FileDownloader,
		downloadSupported: downloadSupported
	};
});

/*******************************************************************************
 *
 * @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
 *******************************************************************************/
/*jslint browser:true devel:true sub:true*/
/*global define console eclipse:true orion:true window URL*/

define('orion/widgets/browse/fileBrowser',[
	'orion/PageUtil', 
	'orion/inputManager',
	'orion/breadcrumbs',
	'orion/widgets/browse/browseView',
	'orion/explorers/navigatorRenderer',
	'orion/widgets/browse/readonlyEditorView',
	'orion/widgets/browse/resourceSelector',
	'orion/commandRegistry',
	'orion/fileClient',
	'orion/contentTypes',
	'orion/widgets/browse/staticDataSource',
	'orion/widgets/browse/emptyFileClient',
	'orion/Deferred',
	'orion/URITemplate',
	'orion/objects',
	'orion/EventTarget',
	'text!orion/widgets/browse/repoAndBaseUrlTrigger.html',
	'text!orion/widgets/browse/repoUrlTrigger.html',
	'text!orion/widgets/browse/shareSnippetTrigger.html',
	'orion/commands',
	'orion/webui/littlelib',
	'orion/i18nUtil',
	'orion/fileDownloader',
	'orion/util',
	'orion/URL-shim'
], function(
	PageUtil, mInputManager, mBreadcrumbs, mBrowseView, mNavigatorRenderer, mReadonlyEditorView, mResourceSelector,
	mCommandRegistry, mFileClient, mContentTypes, mStaticDataSource, mEmptyFileClient, Deferred, URITemplate, objects, 
	EventTarget, RepoAndBaseURLTriggerTemplate, RepoURLTriggerTemplate, ShareSnippetTriggerTemplate, mCommands, lib, i18nUtil, mFileDownloader, util
) {
	
	function ResourceChangeHandler() {
		EventTarget.attach(this);
	}
	
	function statusService(fileBrowser){
		this.fileBrowser = fileBrowser;
	}
	objects.mixin(statusService.prototype, {
		setProgressResult: function(error){
			if(error.Cancel) {
				return;
			}
			var messageTdClass = "warningMessageViewTable";
			if(this.fileBrowser._currentEditorView && this.fileBrowser._currentEditorView.messageView) {
				this.fileBrowser._currentEditorView.updateMessageContents(error.Message, ["messageViewTable"], messageTdClass);
			} else {
				var browseViewOptons = {
					parent: this.fileBrowser._parentDomNode,
					messageView: {message: error.Message, classes: ["messageViewTable"], tdClass: messageTdClass}
				};
				this.fileBrowser._switchView(new mBrowseView.BrowseView(browseViewOptons));
			}
		}
	});
	
	function repoURLHandler(repoURL, baseURL, fileBrowser){
		this.triggerNodeId = "orion.browse.repoURLTrigger";
		this.dropdownNodeId = "orion.browse.repoURLDropdown";
		this.popupTextAreaId = "orion.browse.repoURLInput";
		
		this.baseURL = baseURL;
		this.repoURL = repoURL;
		this.fileBrowser = fileBrowser;
		
		if (this.baseURL) {
			this.popupTemplate = RepoAndBaseURLTriggerTemplate;
		} else {
			this.popupTemplate = RepoURLTriggerTemplate;
		}
		
		this.getTextAreaValue = function() {
			if(this.baseURL) {
				var found = this.repoURL.match(/\/([^\/]+)\/([^\/]+)$/);
				if (found) {
					this.popupTextAreaValue = "teamRepository=" + this.baseURL + "\n" +
									   "userId=" + decodeURIComponent(found[1]) + "\n" + 
									   "userName=" + decodeURIComponent(found[1]) + "\n" + 
									   "projectAreaName=" + decodeURIComponent(found[1]) + " | " + decodeURIComponent(found[2]);
								
					// Check if we can find a stream ID to put into the configuration
					var file = this.fileBrowser._branchSelector.getActiveResource(this.fileBrowser._branchSelector.activeResourceLocation);
					
					if (file && file.RTCSCM && file.RTCSCM.ItemId) {
						this.popupTextAreaValue = this.popupTextAreaValue + "\nstreamId="+ file.RTCSCM.ItemId;
					}
				} else {
					this.popupTextAreaValue = this.baseURL;
				}
			} else {
				this.popupTextAreaValue = this.repoURL;
			}
		
			return this.popupTextAreaValue;
		}.bind(this);
	}
	
	function shareSnippetHandler(widgetSource){
		this.widgetSource = widgetSource;
		this.triggerNodeId = "orion.browse.shareSnippetTrigger";
		this.dropdownNodeId = "orion.browse.shareSnippetDropdown";
		this.popupTextAreaId = "orion.browse.shareSnippetInput";
		this.popupTemplate = ShareSnippetTriggerTemplate;
		this.tags = '<div id="${0}"></div><link rel="stylesheet" type="text/css" href="${1}"/><script src="${2}"></script>' +
					'<script> orion.browse.browser("${3}","${4}",${5},null,{maxL:20,oHref:"${6}",fURI:"${7}",s:${8},e:${9}});</script>';
		this.getTextAreaValue = function() {
			if(this.textView) {
				var snippetContainerId = "snippet_container_" + Math.floor((Math.random()*1000000)+1);
				var selection = this.textView.getSelection();
				var start = 0;
				var end = 0;
				if(selection.start !== selection.end) {
					start = selection.start;
					end = selection.end;
				}
				var originalHref = new URITemplate("#{,resource,params*}").expand({resource:this.currentSnippetURI, params: {start: start, end: end}});
				var url = new URL(window.location.href);
				url.hash = originalHref;
				var base = this.widgetSource.base ? '"' + this.widgetSource.base + '"' : 'null';
	
            	var tagString = i18nUtil.formatMessage(this.tags, snippetContainerId, this.widgetSource.css, this.widgetSource.js, snippetContainerId, 
            										   this.widgetSource.repo, base, url.href, this.currentSnippetURI, start, end);
				return tagString;
			}
			return "Nothing to share";
		}.bind(this);
	}
	
	/**
	 * @class This object describes the options for the readonly file system browser.
	 * <p>
	 * <b>See:</b><br/>
	 * {@link orion.browse.FileBrowser}<br/>
	 * </p>		 
	 * @name orion.browse.FileBrowserOptions
	 *
	 * @property {String|DOMElement} parent the parent element for the file browser, it can be either a DOM element or an ID for a DOM element.
	 * @property {Number} maxEditorLines the max number of lines that are allowed in the editor DIV. When displaying a file, if not defined 0 is used to represent that editor will use the full contents height.
	 * @property {orion.fileClient.FileClient} fileClient the file client implementation that has all the interfaces from orion.fileClient.FileClient.
	 * @property {orion.highlight.SyntaxHighlighter} optional syntaxHighlighter the syntax highlighter that hihglights  a supported language file. If not defined a static default one is used.
	 * @property {orion.core.ContentType} contentTypeService optional the content type service that knows a file's content type. If not defined a static default one is used.
	 * @property {orion.preferences.PreferencesService} [preferences=null] the editor preferences. If not defined the default editor preferences is used.
	 */
	/**
	 * Constructs a new readonly file system browser.
	 * 
	 * @param {orion.browse.FileBrowserOptions} options the browser options.
	 * 
	 * @class A FileBrowser is a user interface for browsing a readonly file system.
	 * @name orion.browse.FileBrowser
	 */
	function FileBrowser(options) {
		var url = new URL(window.location.href);
		this.shareSnippet = url.query.get("shareSnippet") === "true" && options.widgetSource;
		if(this.shareSnippet) {
			this.widgetSource = options.widgetSource;
		}
		this._parentDomNode = lib.node(options.parent);//Required
		this.snippetShareOptions = options.snippetShareOptions;
		if(!this.snippetShareOptions) {
			this._parentDomNode.classList.add("browserParentDom");
		} 
		if(options.fileClient) {
			this._fileClient = options.fileClient;
		} else if(options.serviceRegistry) {
			this._fileClient = new mFileClient.FileClient(options.serviceRegistry);
		} else if(!options.init){
			this._fileClient = new mEmptyFileClient.FileClient();		
		}
		this.repoURL = options.repoURL;
		this.baseURL = options.baseURL;
		this.codeURL = options.codeURL;
		this._syntaxHighlighter = options.syntaxHighlighter;//Required
		if(!this._syntaxHighlighter) {
			this._syntaxHighlighter =  new mStaticDataSource.SyntaxHighlighter();
		}
		this._contentTypeService = options.contentTypeService;//Required
		if(!this._contentTypeService) {
			this._contentTypeService =  new mContentTypes.ContentTypeRegistry(mStaticDataSource.ContentTypes);
		}
		this._preferences = options.preferences;//Optional
		this.rootName = options.rootName;
		this.shouldLoadWorkSpace = options.shouldLoadWorkSpace;
		if(typeof options.selectorNumber === "number") {
			if(options.selectorNumber >= 1) {
				this._showBranch = true;
			} 
			if(options.selectorNumber >= 2) {
				this._showComponent = true;
			} 
		}
		this._breadCrumbInHeader= options.breadCrumbInHeader;
		this._resourceChangeHandler = new ResourceChangeHandler();
		this._resourceChangeHandler.addEventListener("resourceChanged", function(event){
			if(!this._componentSelector || !event || !event.newResource || !event.newResource.selectorAllItems) {
				return;
			}
			this._componentSelector.allItems = event.newResource.selectorAllItems;
			var currentComponentLocation = event.defaultChild;
			if(!currentComponentLocation) {
				var firstDirLocation = null;
				event.newResource.selectorAllItems.some(function(component){
					if(component.Directory) {
						if(!firstDirLocation) {//Remember the first directory in the component list
							firstDirLocation = component.Location;
						}
						//If the component name contains "default", we should set it as default in the selector
						if(component.Name && component.Name.toLowerCase().indexOf("default") >= 0) { //$NON-NLS-0$
							currentComponentLocation = component.Location;
							return true;
						}
					}
				});
				if(!currentComponentLocation) {//If no component name contains "default", use the first directory as the defaul. 
					currentComponentLocation = firstDirLocation;
				}
				if(event.changeHash) {
					window.location = new URITemplate("#{,resource,params*}").expand({resource:currentComponentLocation});
				} else {
					this.refresh(new URITemplate("{,resource}").expand({resource:currentComponentLocation}));
				}
			}
			var activeComp = this._componentSelector.getActiveResource(currentComponentLocation);
			this._componentSelector.activeResourceLocation = activeComp.Location;
			this._componentSelector.refresh();
		}.bind(this)); 
		this._init(options);
	}
	objects.mixin(FileBrowser.prototype, {
		_init: function(options){
			this._commandRegistry = new mCommandRegistry.CommandRegistry({});
			this._maxEditorLines = options.maxEditorLines;
			
			var browseViewOptons = {
				parent: this._parentDomNode,
				messageView: {message: "Loading..."}
			};
			this._statusService = new statusService(this);
			this._switchView(new mBrowseView.BrowseView(browseViewOptons));
			
			this._uriTemplate = new URITemplate("#{,resource,params*}"); //$NON-NLS-0$
			
			if(!this.snippetShareOptions) {
				window.addEventListener("hashchange", function() { //$NON-NLS-0$
					this.refresh(PageUtil.hash());
				}.bind(this));
			}
			if(this._fileClient) {
				this.startup();
			}
		},
		_registerCommands: function() {
			var editCodeCommand = new mCommands.Command({
				imageClass: "core-sprite-edit", //$NON-NLS-0$
				id: "orion.browse.gotoEdit",
				visibleWhen: function() {
					return true;
				},
				hrefCallback : function() {
					return this.codeURL ? this.codeURL : (new URL("code", window.location.href)).href;
				}.bind(this)			
			});
			this._commandRegistry.addCommand(editCodeCommand);
			this._commandRegistry.registerCommandContribution("orion.browse.sectionActions", "orion.browse.gotoEdit", 1); //$NON-NLS-1$ //$NON-NLS-0$
		},
		startup: function(serviceRegistry) {
			if(serviceRegistry) {
				this._fileClient = new mFileClient.FileClient(serviceRegistry);	
			}
			if(this.shareSnippet) {
				this.shareSnippetHandler = new shareSnippetHandler(this.widgetSource);
			}
			if(this.repoURL) {
				this.repoURLHandler = this.snippetShareOptions ? null : new repoURLHandler(this.repoURL, this.baseURL, this);
			}
			if(!this.snippetShareOptions) {
				//this._registerCommands();
			}
			this._inputManager = new mInputManager.InputManager({
				fileClient: this._fileClient,
				statusReporter: this._statusReport,
				statusService: this._statusService,
				contentTypeRegistry: this._contentTypeService
			});
			//We have to overide the inputManager's function here if the widget does not need to call loadWorkSpace on file service.
			if(!this.shouldLoadWorkSpace){
				this._inputManager._maybeLoadWorkspace = function(resource) {
					return new Deferred().resolve(resource);
				};
			}
			this._inputManager._unknownContentTypeAsText = function() {
				return true;
			};
			this._inputManager.addEventListener("InputChanged", function(evt) { //$NON-NLS-0$
				if(!evt.metadata || !evt.input) {
					return;
				}
				var metadata = evt.metadata;
				if(!this.snippetShareOptions) {
					if(this._branches && this._branchSelector){
						this._activeBranchLocation = this._branches[0].Location;
						this._activeComponentLocation = null;
						var newLocation = null;
						if(metadata.Parents) {
							if(metadata.Parents.length > 0) {//The input change happens on a sub folder of a branch, we need to find the branch in its parent chain
								this._activeBranchLocation = metadata.Parents[metadata.Parents.length-1].Location;
								if(metadata.Parents.length > 1) {
									this._activeComponentLocation = metadata.Parents[metadata.Parents.length-2].Location;
								} else {
									this._activeComponentLocation = metadata.Location;
								}
							} else {//The input change happens jsut on a branch, we need to we need to set the commit information in the branch selector
								this._activeBranchLocation = metadata.Location;
								this._activeComponentLocation = metadata.Location;
							}
							//TODO: in the future if the root fetchChildren provides the commit information in each child that represents a branch, we dod not nee to do this
							this._branchSelector.setCommitInfo(this._activeBranchLocation, metadata.LastCommit);
						} else {//The input change happens on the root directory of a repo, we need to set the default to "master" if it exist
							this._branches.some(function(branch){
								if(branch.Name.toLowerCase() === "master") { //$NON-NLS-0$
									this._activeBranchLocation = branch.Location;
									newLocation = branch.Location;
									return true;
								}
							}.bind(this));
							newLocation = newLocation || this._branches[0].Location;
							this._activeBranchLocation = this._activeBranchLocation || this._branches[0].Location;
						}
						this._branchSelector.activeResourceLocation = this._activeBranchLocation;
						
						if(this._showComponent) {
							this._branchSelector.setActiveResource({resource: this._branchSelector.getActiveResource(this._activeBranchLocation), changeHash: false,  defaultChild: this._activeComponentLocation});
							if(!this._activeComponentLocation) {
								return;
							}
						} else if(newLocation){
							this.refresh(new URITemplate("{,resource}").expand({resource:newLocation}));
							return;
						}
					}
					this._breadCrumbName = evt.name;
					this._breadCrumbTarget = metadata;
					if (evt.input === null || evt.input === undefined) {
						this._breadCrumbName = this._lastRoot ? this._lastRoot.Name : "";
						this._breadCrumbTarget = this._lastRoot;
					}
				}
				var view = this._getEditorView(evt.input, evt.contents, metadata);
				this._setEditor(view ? view.editor : null);
				evt.editor = this._editor;
			}.bind(this));

			var editorContainer = document.createElement("div"); //$NON-NLS-0$
			var editorOptions = {
				parent: editorContainer,
				syntaxHighlighter: this._syntaxHighlighter,
				inputManager: this._inputManager,
				preferences: this._preferences,
				statusReporter: function(message, type, isAccessible) {this._statusReport(message, type, isAccessible);}.bind(this)
			};
			this._editorView = new mReadonlyEditorView.ReadonlyEditorView(editorOptions);
			if(!this.snippetShareOptions) {
				if(this._showBranch) {
					var branchSelectorContainer = document.createElement("div"); //$NON-NLS-0$
					branchSelectorContainer.classList.add("resourceSelectorContainer"); //$NON-NLS-0$
					var rootURL = this._fileClient.fileServiceRootURL("");
					this._fileClient.fetchChildren(rootURL).then(function(contents){
						if(contents && contents.length > 0) {
							contents.sort(function(a, b) {
								var	n1 = a.Name && a.Name.toLowerCase();
								var	n2 = b.Name && b.Name.toLowerCase();
								if (n1 < n2) { return -1; }
								if (n1 > n2) { return 1; }
								return 0;
							}); 
							this._branches = contents;
							this._branchSelector = new mResourceSelector.ResourceSelector({
								commandRegistry: this._commandRegistry,
								fileClient: this._fileClient,
								parentNode: branchSelectorContainer,
								labelHeader: this._showComponent ? "Stream" : "Branch",
								resourceChangeDispatcher: this._showComponent ? this._resourceChangeHandler : null,
								fetchChildren: this._showComponent ? true : false,
								commandScopeId: "orion.browse.brSelector", //$NON-NLS-0$
								dropDownId: "orion.browse.switchbr", //$NON-NLS-0$
								dropDownTooltip: this._showComponent ? "Select a stream" : "Select a branch", //$NON-NLS-0$
								allItems: contents
							});
							if(this._showComponent){
								var compSelectorContainer = document.createElement("div"); //$NON-NLS-0$
								compSelectorContainer.classList.add("resourceSelectorContainer"); //$NON-NLS-0$
								compSelectorContainer.classList.add("componentSelectorContainer"); //$NON-NLS-0$
								this._componentSelector = new mResourceSelector.ResourceSelector({
									commandRegistry: this._commandRegistry,
									fileClient: this._fileClient,
									parentNode: compSelectorContainer,
									labelHeader: "Component",
									commandScopeId: "orion.browse.compSelector", //$NON-NLS-0$
									dropDownId: "orion.browse.switchcomp", //$NON-NLS-0$
									dropDownTooltip: "Select a component", //$NON-NLS-0$
									allItems: contents
								});
							}
						}
						this.refresh(PageUtil.hash());
					}.bind(this),
					function(error){
						mInputManager.handleError(this._statusService, error);
					}.bind(this));
				} else {
					this.refresh(PageUtil.hash());
				}
			} else {
				this.refresh(new URITemplate("{,resource}").expand({resource:this.snippetShareOptions.fURI}));
			}
		},
		_switchView: function(view) {
			if (this._currentEditorView !== view) {
				if (this._currentEditorView) {
					this._currentEditorView.destroy();
				}
				this._currentEditorView = view;
				if (this._currentEditorView) {
					this._currentEditorView.create();
				}
			}
			return this._currentEditorView;
		},
		_breadCrumbMaker: function(bcContainer, maxLength){
			this._renderBreadCrumb({
				task: "Browse", //$NON-NLS-0$
				name: this._breadCrumbName,
				target: this._breadCrumbTarget,
				breadCrumbContainer: bcContainer,
				makeBreadcrumbLink: function(segment, folderLocation, folder) {this._makeBreadCrumbLink(segment, folderLocation, folder);}.bind(this),
				makeBreadcrumFinalLink: false,
				fileClient: this._fileClient,
				maxLength: maxLength
			});
		},
		
		_setEditor: function(newEditor) {
			if (this._editor === newEditor) { return; }
			this._editor = newEditor;
		},
		_statusReport: function(message, type, isAccessible) {
			if (type === "progress") { //$NON-NLS-0$
				//TODO: Render message in the section header?
			} else if (type === "error") { //$NON-NLS-0$
				//TODO: Render message in the section header?
			} else {
				//TODO: Render message in the section header?
			}
		},
		_makeBreadCrumbLink: function(segment, folderLocation, folder) {
			var resource = folder ? folder.Location : null;
			if(!resource) {
				resource = folderLocation ? folderLocation : this._fileClient.fileServiceRootURL(folderLocation);
			}
			segment.href = this._uriTemplate.expand({resource: resource});
		},
		_renderBreadCrumb: function(options) {
			var fileSystemRootName;
			var breadcrumbRootName = options.breadcrumbRootName;
			var fileClient = options.fileClient;
			if (options.target) { // we have metadata
				if (fileClient && !options.breadcrumbTarget) {
					fileSystemRootName = breadcrumbRootName ? breadcrumbRootName + " " : ""; //$NON-NLS-1$ //$NON-NLS-0$
					fileSystemRootName = fileSystemRootName + fileClient.fileServiceName(options.target.Location);
					breadcrumbRootName = null;
				}
			} else {
				if (!options.breadcrumbTarget) {
					breadcrumbRootName = breadcrumbRootName || options.task || options.name;
				}
			}
			var locationNode = lib.node(options.breadCrumbContainer);
			if (locationNode) {
				lib.empty(locationNode);
				var resource = options.breadcrumbTarget || options.target;
				if(this._componentSelector) {
					if(resource.Parents) {
						if(resource.Parents.length === 1) {//Skip the branch level parents
							resource.Parents[0].skip = true;
							resource.skip = true;
						} else if(resource.Parents.length > 1) {//Skip the component level parents
							resource.Parents[resource.Parents.length -1].skip = true;
							resource.Parents[resource.Parents.length -2].skip = true;
						} else {
							resource.skip = true;
						}
					}
				} else if(this._branchSelector && resource.Parents) {
					if(resource.Parents.length > 0) {
						resource.Parents[resource.Parents.length -1].skip = true;
					} else {
						resource.skip = true;
					}
				}
				var workspaceRootURL = (fileClient && resource && resource.Location) ? fileClient.fileServiceRootURL(resource.Location) : null;
				var bcRootName = this.rootName ? this.rootName : workspaceRootURL;
				if(this._componentSelector) {
					//TODO: We need a better way to show the root of a repo
					//bcRootName = "Component Root(" + this._componentSelector.getActiveResource().Name + ")";
				} else {
					//TODO: We need a better way to show the root of a repo
					//bcRootName = "Branch Root(" + this._branchSelector.getActiveResource().Name + ")";
				}
				new mBreadcrumbs.BreadCrumbs({
					container: locationNode,
					maxLength: options.maxLength,
					resource: resource,
					rootSegmentName: breadcrumbRootName,
					workspaceRootSegmentName: bcRootName,
					workspaceRootURL: this._calculateRootURL(workspaceRootURL),
					makeFinalHref: options.makeBreadcrumFinalLink,
					makeHref: options.makeBreadcrumbLink
				});
			}
		},
		_calculateRootURL: function(workspaceRootURL) {
			if(this._activeComponentLocation && this._componentSelector) {
				return this._activeComponentLocation;
			} else if(this._activeBranchLocation && this._branchSelector) {
				return this._activeBranchLocation;
			}
			return workspaceRootURL;
		},
		onTextViewCreated: function(textView) {
			if(this.shareSnippetHandler) {
				this.shareSnippetHandler.textView = textView;
				this.shareSnippetHandler.currentSnippetURI = (this._currentURI && this._currentURI.length > 0) ? this._currentURI : "";
			}
			if(!this.snippetShareOptions) {
				var editorParams = PageUtil.matchResourceParameters();
				if(editorParams && editorParams.end && editorParams.end !== editorParams.start) {
					window.setTimeout(function() {
						textView.setSelection(editorParams.start, editorParams.end, true);}, 500);
				}
			}
		},
		_isKnownBinary: function(cType) {
			var binaryType = this._contentTypeService.getContentType("application/octet-stream"); //$NON-NLS-0$
			return this._contentTypeService.isExtensionOf(cType, binaryType);
		},
		_isBrowserRenderable: function(cType) {
			var renderableType = this._contentTypeService.getContentType("application/browser-renderable"); //$NON-NLS-0$
			return this._contentTypeService.isExtensionOf(cType, renderableType);
		},
		_testBlobURLSupported: function(contents, cType) {
			var blobObj= new Blob([contents],{type: cType.id});
			var objectURLLink = URL.createObjectURL(blobObj);
		   	var downloadLink = document.createElement("image"); 
		   	downloadLink.setAttribute("src", objectURLLink); 
		   	//downloadLink.style.width = 640+"px"; 
			//downloadLink.style.height = 480+"px"; 
			downloadLink.onerror = function() {
				this._statusService.setProgressResult({Severity: "error", Message: "This type of file is not supportef in Safri. Please use Chrome or FireFox."});									
			}.bind(this);
		},
		_generateViewLink: function(contents, metadata, cType, browseViewOptons, message) {
			var downloadLink = document.createElement("a"); //$NON-NLS-0$
			var blobObj = new Blob([contents],{type: cType.id});
			downloadLink.href = URL.createObjectURL(blobObj);
			downloadLink.classList.add("downloadLinkName"); //$NON-NLS-0$
			downloadLink.appendChild(document.createTextNode("View " + metadata.Name));
			browseViewOptons.binaryView = {domElement: downloadLink, message: message};
		},
		_generateDownloadLink: function(contents, metadata, cType, browseViewOptons, message) {
			var downloader = new mFileDownloader.FileDownloader(this._fileClient);
			var linkElement = downloader.downloadFromBlob(contents, metadata.Name, cType, true, true);
			linkElement.classList.add("downloadLinkName"); //$NON-NLS-0$
			linkElement.appendChild(document.createTextNode("Download " + metadata.Name));
			browseViewOptons.binaryView = {domElement: linkElement, message: message};
		},
		_getEditorView: function(input, contents, metadata) {
			var view = null;
			if (metadata && input) {
				this._currentURI = metadata.Location;
				var browseViewOptons = {
					parent: this._parentDomNode,
					browser: this,
					maxEditorLines: this._maxEditorLines,
					breadCrumbInHeader: this._breadCrumbInHeader,
					metadata: metadata,
					branchSelector: this._branchSelector,
					componentSelector: this._componentSelector,
					commandRegistry: this._commandRegistry,
					contentTypeRegistry: this._contentTypeService,
					inputManager: this._inputManager,
					infoDropDownHandlers: this.repoURLHandler ? [this.repoURLHandler] : [],
					fileService: this._fileClient,
					snippetShareOptions: this.snippetShareOptions,
					//clickHandler: function(location) {this.refresh(location);}.bind(this),
					breadCrumbMaker: this.snippetShareOptions ? null: function(bcContainer, maxLength) {this._breadCrumbMaker(bcContainer, maxLength);}.bind(this)
				};
				if (!metadata.Directory) {
					if(this.shareSnippetHandler) {
						this.shareSnippetHandler.textView = null;
					}
					var cType = this._contentTypeService.getFileContentType(metadata);
					if(!cType) {//The content type is not registered
						if(this._inputManager._unknownContentTypeAsText) {//If we treate 
							browseViewOptons.editorView = this._editorView;
							if(this.shareSnippetHandler) {
								browseViewOptons.infoDropDownHandlers.unshift(this.shareSnippetHandler);
							}
						} else {
							this.makeDownloadLink();
						}
					} else if(this._isKnownBinary(cType)) {
						if(mFileDownloader.downloadSupported()) {
							this._generateDownloadLink(contents, metadata, cType, browseViewOptons);
						} else {
							this._generateViewLink(contents, metadata, {id: "text/plain"}, browseViewOptons, 
							'Directly downloading the contents of files is not supported in your browser. You can click the link below, then save the resulting page using "Save As...".');
						}
					} else if(this._isBrowserRenderable(cType)) {
						if(util.isIE) {//IE(tested on 10 and 11) does not support objectURL on a link yet. http://stackoverflow.com/questions/17951644/can-i-open-object-url-in-ie
							this._generateDownloadLink(contents, metadata, cType, browseViewOptons);
						} else {
							this._generateViewLink(contents, metadata, cType, browseViewOptons);
						}
					} else if(cType.id === "text/x-markdown") {
						browseViewOptons.isMarkdownView = true;
					} else if(!mNavigatorRenderer.isImage(cType)) {
						browseViewOptons.editorView = this._editorView;
						if(this.shareSnippetHandler) {
							browseViewOptons.infoDropDownHandlers.unshift(this.shareSnippetHandler);
						}
					} else {
						var objectURL = URL.createObjectURL(new Blob([contents],{type: cType.id}));
						var image = document.createElement("img"); //$NON-NLS-0$
						image.src = objectURL;
						image.classList.add("readonlyImage"); //$NON-NLS-0$
						browseViewOptons.binaryView = {domElement: image};
					}
				}
				view = new mBrowseView.BrowseView(browseViewOptons);
			}
			return this._switchView(view);
		},
		refresh: function(uri) {
			if(!uri) {
				uri = new URITemplate("{,resource}").expand({resource:this._fileClient.fileServiceRootURL("")});
			}
			this._inputManager.setInput(uri);
		},
		create: function() {
		},
		destroy: function() {
		}
	});
	return {FileBrowser: FileBrowser};
});

/*******************************************************************************
 * @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
 *******************************************************************************/
/*global define console */

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-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
 *******************************************************************************/

/*global define setTimeout clearTimeout addEventListener removeEventListener document console localStorage location URL Worker XMLSerializer XMLHttpRequest*/

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;
    }

    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");
                        }
                        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");
                        }
                        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 ("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) {
                            if (_deferredLoad) {
                                _deferredLoad.reject(message.error);
                            }
                        } else {
                            throw new Error("Bad method: " + message.method);
                        }
                    }
                } else {
                    var deferred = _responseReferences[String(message.id)];
                    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;
        };

        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));
                }
                return new Deferred().resolve();
            }
            var deferredStateChange = new Deferred();
            _deferredStateChange = deferredStateChange;
            _state = "starting";
            _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() {
                _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));
                deferredStateChange.reject(new Error("Failed to load plugin: " + _url));
                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) {
            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() {
                	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();
        };
    }

    /**
     * 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 _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 sendTimeout(message) {
                    var error = new Error(message);
                    error.name = "timeout";
                    handler({
                        method: "timeout",
                        error: error
                    });
                }

                var loadTimeout = setTimeout(sendTimeout.bind(null, "Load timeout for: " + url), timeout || 15000);
                var iframe = document.createElement("iframe"); //$NON-NLS-0$
                iframe.name = url + "_" + new Date().getTime();
                iframe.src = url;
                iframe.onload = function() {
                    clearTimeout(loadTimeout);
                    loadTimeout = setTimeout(sendTimeout.bind(null, "Plugin handshake timeout for: " + url), 5000);
                };
                iframe.sandbox = "allow-scripts allow-same-origin"; //$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.connected = function() {
                	clearTimeout(loadTimeout);
                };
                channel.close = function() {
                    clearTimeout(loadTimeout);
                    if (iframe) {
                    	var parent = iframe.parentNode;
                        if (parent) {
                        	parent.removeChild(iframe);
                        }
                        iframe = null;
                    }
                };
                _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());
            },
            persist: function(url, manifest) {
                _storage.setItem("plugin." + url, JSON.stringify(manifest)); //$NON-NLS-0$
            },
            postMessage: function(message, channel) {
                channel.target.postMessage((channel.useStructuredClone ? message : JSON.stringify(message)), channel.url);
            },
            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) {
                        internalRegistry.disconnect(channel);
                        channel = null;
                        d.reject(message.error);
                    }
                });
                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 _messageHandler(event) { //$NON-NLS-0$
            var source = event.source;
            _channels.some(function(channel) {
                if (source === channel.target) {
                    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
                    }
                    return true; // e.g. break
                }
            });
        }


        this.init = function() {
            if (_state === "starting" || _state === "active" || _state === "stopping") {
                return;
            }
            addEventListener("message", _messageHandler, false);
            var storageKeys = [];
            for (var i = 0, length = _storage.length; i < length; i++) {
                storageKeys.push(_storage.key(i));
            }
            storageKeys.forEach(function(key) {
                if (key.indexOf("plugin.") === 0) {
                    var url = key.substring("plugin.".length);
                    var manifest = JSON.parse(_storage.getItem(key));
                    if (manifest.created) {
                        _plugins.push(new Plugin(url, manifest, internalRegistry));
                    }
                }
            });
            _plugins.sort(function(a, b) {
                return a._getCreated() < b._getCreated() ? -1 : 1;
            });
            
            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 = {};
                        }
                        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));
            }
            _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 = [];
            var now = new Date().getTime();
            _plugins.forEach(function(plugin) {
                var autostart = plugin._getAutostart();
                if (plugin.getLastModified() === 0) {
                    deferreds.push(plugin.update().then(function() {
                        if ("started" === autostart) {
                            return plugin.start({
                                "transient": true
                            });
                        }
                        if ("lazy" === autostart) {
                            return plugin.start({
                                "lazy": true,
                                    "transient": true
                            });
                        }
                        plugin._resolve();
                    }));
                    return;
                }

                if ("started" === autostart) {
                    deferreds.push(plugin.start({
                        "transient": true
                    }));
                } else if ("lazy" === autostart) {
                    deferreds.push(plugin.start({
                        "lazy": true,
                            "transient": true
                    }));
                    if (now > plugin.getLastModified() + 86400000) { // 24 hours
                        plugin.update();
                    }
                } 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) {
                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
 ******************************************************************************/
/*global define require URL document console prompt XMLHttpRequest window*/
var _browser_script_source = null; //We need to know where the browser 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.browse.browser") {
					_browser_script_source = _all_script[i].src;
					break;
				}
			} else {
				var regex = /.*built-browser.*.js/;
				if (_all_script[i].src && regex.exec(_all_script[i].src)) {
					_browser_script_source = _all_script[i].src;
					break;
				}
			}
		}
		if (_browser_script_source) {
			break;
		}
	}
	if (!_browser_script_source) {
		_browser_script_source = _all_script[_all_script.length - 1].src;
	}
}

define('browse/builder/browse', ['orion/widgets/browse/fileBrowser', 'orion/serviceregistry', 'orion/pluginregistry', 'orion/URL-shim'], function(mFileBrowser, mServiceRegistry, mPluginRegistry) {
	function Browser(params) { // parentId, repo, base
		if (typeof params === "string") {
			params = {
				parentId: arguments[0],
				repo: arguments[1],
			};
			if (arguments.length > 2) {
				params.base = arguments[2];
			}
			if (arguments.length > 3) {
				params.codeURL = arguments[3];
			}
			if (arguments.length > 4) {
				params.snippetShareOptions = arguments[4];
			}
		} else {
			params = params || {};
		}
		var pluginURL;
		var url = new URL(params.repo || window.location.href);
		var repo = url.href;
		var base = params.base;
		var selectorNumber = 1;

		if (!params.rootName) {
			var found = repo.match(/\/([^/]+)\/([^/]+)$/);
			if (found) {
				params.rootName = decodeURIComponent(found[1]) + " | " + decodeURIComponent(found[2]);
				if (params.rootName.match(/\.git$/)) {
					params.rootName = params.rootName.substring(0, params.rootName.length - 4);
				}
			}
		}
		try {
			if (url.host === "github.com") {
				pluginURL = new URL("../../plugins/GitHubFilePlugin.html?repo=" + url.href, _browser_script_source);
			} else if (url.pathname.indexOf("/git/") === 0) {
				pluginURL = new URL("/gerrit/plugins/gerritfs/static/plugins/GerritFilePlugin.html", url);
				pluginURL.query.set("project", url.pathname.substring(5));
			} else if (url.pathname.indexOf("/ccm") === 0) {
				if (!base) {
					var ccmPath = url.pathname.match(/^\/ccm[^/]*/);
					base = new URL(ccmPath, repo).href;
				}
				pluginURL = new URL(base + "/service/com.ibm.team.filesystem.service.jazzhub.IOrionFilesystem/sr/pluginOrionWs.html?" + repo);
				selectorNumber = 2;
			} else if (url.pathname.indexOf("/project/") === 0) {
				if (!base) {
					throw "No Jazz SCM base server defined - " + repo;
				}
				pluginURL = new URL(base + "/service/com.ibm.team.filesystem.service.jazzhub.IOrionFilesystem/sr/pluginOrionWs.html?" + repo);
				selectorNumber = 2;
			} else {
				throw "Bad Repo URL - " + repo;
			}
		} catch (exception) {
			(new mFileBrowser.FileBrowser({parent: params.parentId, init: true}))._statusService.setProgressResult({Severity: "error", Message: exception}); //$NON-NLS-0$;
			return;
		}
		var serviceRegistry = new mServiceRegistry.ServiceRegistry();
		var plugins = {};
		plugins[pluginURL.href] = {autostart: "started", lastModified: -1};
		this._fileBrowser = new mFileBrowser.FileBrowser({
			parent: params.parentId,
			repoURL: repo,
			baseURL: (selectorNumber === 2 ? base : null),
			codeURL: params.codeURL,
			snippetShareOptions: params.snippetShareOptions,
			selectorNumber: selectorNumber,
			rootName: params.rootName,
			widgetSource: _browser_script_source ? {repo: params.repo, base: params.base, js: _browser_script_source , css: _browser_script_source.replace(/built-browser.*.js/, "built-browser.css")} : null,
			maxEditorLines: params.snippetShareOptions && params.snippetShareOptions.maxL ? params.snippetShareOptions.maxL : 300,
			init: true
		});
		var pluginRegistry = new mPluginRegistry.PluginRegistry(serviceRegistry, {
			storage: {},
			plugins: plugins
		});
		var errorMessage = "Unable to display repository contents at this time. Refresh the browser to try again.";
		pluginRegistry.start().then(function() {
			var allReferences = serviceRegistry.getServiceReferences("orion.core.file"); //$NON-NLS-0$
			if(allReferences.length === 0) {//If there is no file service reference, we treat it as plugin activation error.
				this._fileBrowser._statusService.setProgressResult({Severity: "Warning", Message: errorMessage}); //$NON-NLS-0$
			} else {//Plugin activation succeeds, start up the readonly widget.
				this._fileBrowser.startup(serviceRegistry);
			}
		}.bind(this), function() {//The pluginRegistry starts with rejection(not sure if it is reachable though, we treat it as plugin activation error.
			this._fileBrowser._statusService.setProgressResult({Severity: "Warning", Message: errorMessage}); //$NON-NLS-0$
		}.bind(this));
	}

	Browser.prototype = {
		getFileBrowser: function() {
			return this._fileBrowser;
		}
	};
	return Browser;
});
		orion = this.orion || (this.orion = {});
		var browse = orion.browse || (orion.browse = {});
		browse.browser = require('browse/builder/browse');
