* Returns a JSON object with HTTP response overrides

HTMLDocument.prototype.getResponseData = function getResponseData(enabled) {

var body = (this._instrumentationEnabled) ?

keep keep keep keep keep replace replace keep keep

/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $, CodeMirror */
/*unittests: HTML Instrumentation*/

/**
 * HTMLInstrumentation
 *
 * This module contains functions for "instrumenting" html code. Instrumented code
 * adds a "data-brackets-id" attribute to every tag in the code. The value of this
 * tag is guaranteed to be unique.
 *
 * The primary function is generateInstrumentedHTML(). This does just what it says -
 * it will read the HTML content in the doc and generate instrumented code by injecting
 * "data-brackets-id" attributes.
 *
 * There are also helper functions for returning the tagID associated with a specified
 * position in the document.
 */
define(function (require, exports, module) {
    "use strict";

    var DocumentManager = require("document/DocumentManager"),
        Tokenizer       = require("language/HTMLTokenizer").Tokenizer,
        PriorityQueue   = require("thirdparty/priority_queue").PriorityQueue,
        MurmurHash3     = require("thirdparty/murmurhash3_gc");

    var seed = Math.floor(Math.random() * 65535);

    // Hash of scanned documents. Key is the full path of the doc. Value is an object
    // with two properties: timestamp and dom. Timestamp is the document timestamp,
    // dom is the root node of a simple DOM tree.
    var _cachedValues = {};

    /**
     * @private
     * Returns true if the specified tag is empty. This could be an empty HTML tag like
     * <meta> or <link>, or a closed tag like

     */
    function _isEmptyTag(payload) {
        if (payload.closed || !payload.nodeName) {
            return true;
        }

        if (/(!doctype|area|base|basefont|br|wbr|col|frame|hr|img|input|isindex|link|meta|param|embed)/i
                .test(payload.nodeName)) {
            return true;
        }

        return false;
    }

    /**
     * Remove a document from the cache
     */
    function _removeDocFromCache(evt, document) {
        if (_cachedValues.hasOwnProperty(document.file.fullPath)) {
            delete _cachedValues[document.file.fullPath];
            $(document).off(".htmlInstrumentation");
        }
    }

    var tagID = 1;

    /**
     * Scan a document to prepare for HTMLInstrumentation
     * @param {Document} doc The doc to scan.
     * @return {Object} Root DOM node of the scanned document.
     */
    function scanDocument(doc) {
        if (!_cachedValues.hasOwnProperty(doc.file.fullPath)) {
            $(doc).on("change.htmlInstrumentation", function () {
                if (_cachedValues[doc.file.fullPath]) {
                    _cachedValues[doc.file.fullPath].dirty = true;
                }
            });

            // Assign to cache, but don't set a value yet
            _cachedValues[doc.file.fullPath] = null;
        }

        var cachedValue = _cachedValues[doc.file.fullPath];
        if (cachedValue && !cachedValue.dirty && cachedValue.timestamp === doc.diskTimestamp) {
            return cachedValue.dom;
        }

        var text = doc.getText(),
            dom = _buildSimpleDOM(text);

        // Cache results
        _cachedValues[doc.file.fullPath] = {
            timestamp: doc.diskTimestamp,
            dom: dom,
            dirty: false
        };

        return dom;
    }

    /**
     * Generate instrumented HTML for the specified document. Each tag has a "data-brackets-id"
     * attribute with a unique ID for its value. For example, "
" becomes something like
     * "
". The attribute value is just a number that is guaranteed
     * to be unique.
     * @param {Document} doc The doc to scan.
     * @return {string} instrumented html content
     */
    function generateInstrumentedHTML(doc) {
        var dom = scanDocument(doc),
            gen = doc.getText();

        // Walk through the dom nodes and insert the 'data-brackets-id' attribute at the
        // end of the open tag
        var insertCount = 0;

        function walk(node) {
            if (node.tag) {
                var attrText = " data-brackets-id='" + node.tagID + "'";

                // Insert the attribute as the first attribute in the tag.
                var insertIndex = node.start + node.tag.length + 1 + insertCount;
                gen = gen.substr(0, insertIndex) + attrText + gen.substr(insertIndex);
                insertCount += attrText.length;
            }

            if (node.children) {
                node.children.forEach(walk);
            }
        }

        walk(dom);
        return gen;
    }

    /**
     * Mark the text for the specified editor. Either the scanDocument() or
     * the generateInstrumentedHTML() function must be called before this function
     * is called.
     *
     * NOTE: This function is "private" for now (has a leading underscore), since
     * the API is likely to change in the future.
     *
     * @param {Editor} editor The editor whose text should be marked.
     * @return none
     */
    function _markText(editor) {
        var cache = _cachedValues[editor.document.file.fullPath],
            dom = cache && cache.dom;

        if (!dom) {
            console.error("Couldn't find the dom for " + editor.document.file.fullPath);
            return;
        }

        _markTextFromDOM(editor, dom);
    }

    function _getMarkerAtDocumentPos(editor, pos) {
        var i,
            cm = editor._codeMirror,
            marks = cm.findMarksAt(pos),
            match;

        var _distance = function (mark) {
            var markerLoc = mark.find();
            if (markerLoc) {
                var markPos = markerLoc.from;
                return (cm.indexFromPos(pos) - cm.indexFromPos(markPos));
            } else {
                return Number.MAX_VALUE;
            }
        };

        for (i = 0; i < marks.length; i++) {
            if (!match) {
                match = marks[i];
            } else {
                if (_distance(marks[i]) < _distance(match)) {
                    match = marks[i];
                }
            }
        }

        return match;
    }

    /**
     * Get the instrumented tagID at the specified position. Returns -1 if
     * there are no instrumented tags at the location.
     * The _markText() function must be called before calling this function.
     *
     * NOTE: This function is "private" for now (has a leading underscore), since
     * the API is likely to change in the future.
     *
     * @param {Editor} editor The editor to scan.
     * @return {number} tagID at the specified position, or -1 if there is no tag
     */
    function _getTagIDAtDocumentPos(editor, pos) {
        var match = _getMarkerAtDocumentPos(editor, pos);

        return (match) ? match.tagID : -1;
    }

    var voidElements = {
        area: true,
        base: true,
        basefont: true,
        br: true,
        col: true,
        command: true,
        embed: true,
        frame: true,
        hr: true,
        img: true,
        input: true,
        isindex: true,
        keygen: true,
        link: true,
        meta: true,
        param: true,
        source: true,
        track: true,
        wbr: true
    };

    function SimpleDOMBuilder(text, startOffset) {
        this.stack = [];
        this.t = new Tokenizer(text, {}, {
            onopentagend: this._handleOpenTagEnd.bind(this),
            onselfclosingtag: function () { },
            oncomment: function () { },
            ontext: function () { }
        });
        this.lastTag = null;
        this.currentTag = null;
        this.startOffset = startOffset || 0;
    }

    function _updateHash(node) {
        if (node.children) {
            var i,
                weight = 0,
                hashes = "";
            for (i = 0; i < node.children.length; i++) {
                weight += node.children[i].weight;
                hashes += node.children[i].signature;
            }
            node.weight = weight;
            node.signature = MurmurHash3.hashString(hashes, hashes.length, seed);
        } else {
            var tagLabel = node.tag + node.tagID;
            node.signature = MurmurHash3.hashString(tagLabel, tagLabel.length, seed);
        }
    }

    SimpleDOMBuilder.prototype._handleOpenTagEnd = function () {
        if (this.currentTag) {
            // We're closing an open tag. Record the end of the open tag as the end of the
            // range. (If we later find a close tag for this tag, the end will get overwritten
            // with the end of the close tag.)
            // TODO: current index is private in the tokenizer right now
            this.currentTag.end = this.startOffset + this.t._index + 1;
            this.lastTag = this.currentTag;
            this.currentTag = null;
        }
    };

    function getTextNodeID(textNode) {
        var childIndex = textNode.parent.children.indexOf(textNode);
        if (childIndex === 0) {
            return textNode.parent.tagID + ".0";
        }
        return textNode.parent.children[childIndex - 1].tagID + "t";
    }

    SimpleDOMBuilder.prototype.build = function () {
        var token, tagLabel;
        var stack = this.stack;
        var attributeName = null;
        var nodeMap = {};
        var signatureMap = {};

        while ((token = this.t.nextToken()) !== null) {
            if (!token.contents) {
                console.error("Token has no contents: ", token);
            } else if (token.type === "opentagname") {
                var newTag = {
                    tag: token.contents,
                    children: [],
                    attributes: {},
                    parent: (stack.length ? stack[stack.length - 1] : null),
                    start: this.startOffset + token.start - 1,
                    weight: 0
                };
                newTag.tagID = this.getID(newTag);
                nodeMap[newTag.tagID] = newTag;
                if (newTag.parent) {
                    newTag.parent.children.push(newTag);
                }
                this.currentTag = newTag;
                if (voidElements.hasOwnProperty(newTag.tag)) {
                    tagLabel = newTag.tag + newTag.tagID;
                    newTag.weight = 0;
                    newTag.signature = MurmurHash3.hashString(tagLabel, tagLabel.length, seed);
                    signatureMap[newTag.signature] = newTag;
                } else {
                    stack.push(newTag);
                }
            } else if (token.type === "closetag") {
                this.lastTag = stack.pop();
                _updateHash(this.lastTag);
                this.lastTag.end = this.startOffset + token.end + 1;
                signatureMap[this.lastTag.signature] = this.lastTag;
                if (this.lastTag.tag !== token.contents) {
                    console.error("Mismatched tag: ", this.lastTag.tag, token.contents);
                }
            } else if (token.type === "attribname") {
                attributeName = token.contents;
            } else if (token.type === "attribvalue" && attributeName !== null) {
                this.currentTag.attributes[attributeName] = token.contents;
                attributeName = null;
            } else if (token.type === "text") {
                // TODO: Not clear why we need this since it should already have been nulled out in
                // _handleOpenTagEnd, but that doesn't seem to be sufficient for some reason.
                this.currentTag = null;
                if (stack.length) {
                    var parent = stack[stack.length - 1];
                    var newNode = {
                        parent: stack[stack.length - 1],
                        content: token.contents,
                        weight: token.contents.length,
                        signature: MurmurHash3.hashString(token.contents, token.contents.length, seed)
                    };
                    parent.children.push(newNode);
                    newNode.tagID = getTextNodeID(newNode);
                    nodeMap[newNode.tagID] = newNode;
                    signatureMap[newNode.signature] = newNode;
                }
            }
        }

        var dom = (stack.length ? stack[0] : this.lastTag);
        dom.nodeMap = nodeMap;
        dom.signatureMap = signatureMap;
        return dom;
    };

    SimpleDOMBuilder.prototype.getID = function () {
        return tagID++;
    };

    function _buildSimpleDOM(text) {
        var builder = new SimpleDOMBuilder(text);
        return builder.build();
    }

    function _dumpDOM(root) {
        var result = "",
            indent = "";

        function walk(node) {
            if (node.tag) {
                result += indent + "TAG " + node.tagID + " " + node.tag + " " + JSON.stringify(node.attributes) + "\n";
            } else {
                result += indent + "TEXT " + node.tagID + " " + node.content + "\n";
            }
            if (node.children) {
                indent += "  ";
                node.children.forEach(walk);
                indent = indent.slice(2);
            }
        }
        walk(root);

        return result;
    }

    // TODO: I'd be worried about stack overflows here
    function _markTags(cm, node) {
        node.children.forEach(function (childNode) {
            if (childNode.tag) {
                _markTags(cm, childNode);
            }
        });
        var mark = cm.markText(cm.posFromIndex(node.start), cm.posFromIndex(node.end));
        mark.tagID = node.tagID;
    }

    function _markTextFromDOM(editor, dom) {
        var cm = editor._codeMirror;

        // Remove existing marks
        var marks = cm.getAllMarks();
        cm.operation(function () {
            marks.forEach(function (mark) {
                if (mark.hasOwnProperty("tagID")) {
                    mark.clear();
                }
            });
        });

        // Mark
        _markTags(cm, dom);
    }

    function DOMUpdater(previousDOM, editor, changeList) {
        var text, startOffset = 0;

        // TODO: if there's more than one change in the changelist, we need to figure out how
        // to find all the affected ranges since we can't directly map the ranges of intermediate
        // changes to marked ranges after the fact.
        //
        // Probably the best we can do is try to determine what the first and last affected positions
        // are in the whole changelist, then figure out what marks those positions are in, and walk
        // up the tree to the common parent. However, that's not trivial since each change's position
        // is specified in terms of the document's state after the previous change. It's probably easy
        // to find the first affected position, but harder to find the last. Perhaps we could just
        // dirty the whole doc after the first affected position.
        //
        // For now, just punt and redo the whole doc if there's more than one change.
        if (changeList && !changeList.next) {
            // If both the beginning and end of the change are still within the same marked
            // range after the change, then assume we can just dirty the tag containing them,
            // otherwise punt. (TODO: We could chase upward in the tree to find the common parent if
            // the tags for the beginning and end of the change are different.)
            var startMark = _getMarkerAtDocumentPos(editor, changeList.from),
                endMark = _getMarkerAtDocumentPos(editor, changeList.to);
            if (startMark && startMark === endMark) {
                var newMarkRange = startMark.find();
                if (newMarkRange) {
                    text = editor._codeMirror.getRange(newMarkRange.from, newMarkRange.to);
                    //console.log("incremental update: " + text);
                    this.changedTagID = startMark.tagID;
                    startOffset = editor._codeMirror.indexFromPos(newMarkRange.from);
                }
            }
        }

        if (!this.changedTagID) {
            // We weren't able to incrementally update, so just rebuild and diff everything.
            text = editor.document.getText();
        }

        this.constructor(text, startOffset);
        this.editor = editor;
        this.cm = editor._codeMirror;
        this.previousDOM = previousDOM;
    }

    DOMUpdater.prototype = new SimpleDOMBuilder();

    DOMUpdater.prototype.getID = function (newTag) {
        // TODO: _getTagIDAtDocumentPos is likely a performance bottleneck
        // Get the mark at the start of the tagname (not before the beginning of the tag, because that's
        // actually inside the parent).
        var currentTagID = _getTagIDAtDocumentPos(this.editor, this.cm.posFromIndex(newTag.start + 1));
        if (currentTagID === -1 || (newTag.parent && currentTagID === newTag.parent.tagID)) {
            currentTagID = tagID++;
        }
        return currentTagID;
    };

    DOMUpdater.prototype._updateMarkedRanges = function (nodeMap) {
        // TODO: this is really inefficient - should we cache the mark for each node?
        var updateIDs = Object.keys(nodeMap),
            cm = this.cm,
            marks = cm.getAllMarks();
        marks.forEach(function (mark) {
            if (mark.hasOwnProperty("tagID") && nodeMap[mark.tagID]) {
                var node = nodeMap[mark.tagID];
                mark.clear();
                mark = cm.markText(cm.posFromIndex(node.start), cm.posFromIndex(node.end));
                mark.tagID = node.tagID;
                updateIDs.splice(updateIDs.indexOf(node.tagID), 1);
            }
        });

        // Any remaining updateIDs are new.
        updateIDs.forEach(function (id) {
            var node = nodeMap[id],
                mark = cm.markText(cm.posFromIndex(node.start), cm.posFromIndex(node.end));
            mark.tagID = id;
        });
    }

    DOMUpdater.prototype._buildNodeMap = function (root) {
        var nodeMap = {};

        function walk(node) {
            if (node.tagID) {
                nodeMap[node.tagID] = node;
            }
            if (node.children) {
                node.children.forEach(walk);
            }
        }

        walk(root);
        root.nodeMap = nodeMap;
    }

    DOMUpdater.prototype._handleDeletions = function (nodeMap, oldSubtreeMap, newSubtreeMap) {
        var deletedIDs = [];
        Object.keys(oldSubtreeMap).forEach(function (key) {
            if (!newSubtreeMap.hasOwnProperty(key)) {
                deletedIDs.push(key);
                delete nodeMap[key];
            }
        });

        // TODO: again, really inefficient - should we cache the mark for each node?
        var marks = this.cm.getAllMarks();
        marks.forEach(function (mark) {
            if (mark.hasOwnProperty("tagID") && deletedIDs.indexOf(mark.tagID) !== -1) {
                mark.clear();
            }
        });
    }

    DOMUpdater.prototype.update = function () {
        var newSubtree = this.build(),
            result = {
                // default result if we didn't identify a changed portion
                newDOM: newSubtree,
                oldSubtree: this.previousDOM,
                newSubtree: newSubtree
            };

        if (this.changedTagID) {
            // Find the old subtree that's going to get swapped out.
            var oldSubtree = this.previousDOM.nodeMap[this.changedTagID],
                parent = oldSubtree.parent;

            // If we didn't have a parent, then the whole tree changed anyway, so
            // we'll just return the default result.
            if (parent) {
                var childIndex = parent.children.indexOf(oldSubtree);
                if (childIndex === -1) {
                    // This should never happen...
                    console.error("DOMUpdater.update(): couldn't locate old subtree in tree");
                } else {
                    // Swap the new subtree in place of the old subtree.
                    oldSubtree.parent = null;
                    newSubtree.parent = parent;
                    parent.children[childIndex] = newSubtree;

                    // Overwrite any node mappings in the parent DOM with the
                    // mappings for the new subtree. We keep the nodeMap around
                    // on the new subtree so that the differ can use it later.
                    // TODO: should we ever null out the nodeMap on the subtree?
                    $.extend(this.previousDOM.nodeMap, newSubtree.nodeMap);

                    // Update marked ranges for all items in the new subtree.
                    this._updateMarkedRanges(newSubtree.nodeMap);

                    // Build a local nodeMap for the old subtree so the differ can
                    // use it.
                    this._buildNodeMap(oldSubtree);

                    // Clean up the info for any deleted nodes that are no longer in
                    // the new tree.
                    this._handleDeletions(this.previousDOM.nodeMap, oldSubtree.nodeMap, newSubtree.nodeMap);

                    // Update the signatures for all parents of the new subtree.
                    var curParent = parent;
                    while (curParent) {
                        _updateHash(curParent);
                        curParent = curParent.parent;
                    }

                    // TODO: add/update marked ranges for any nodes inside subtree
                    // (will need to offset the start/end of each node by the offset
                    // of the beginning of the changed range)

                    result.newDOM = this.previousDOM;
                    result.oldSubtree = oldSubtree;
                }
            }
        }

        return result;
    };

    function attributeCompare(edits, oldNode, newNode) {
        // shallow copy the old attributes object so that we can modify it
        var oldAttributes = $.extend({}, oldNode.attributes),
            newAttributes = newNode.attributes;
        Object.keys(newAttributes).forEach(function (attributeName) {
            if (oldAttributes[attributeName] !== newAttributes[attributeName]) {
                var type = oldAttributes.hasOwnProperty(attributeName) ? "attrChange" : "attrAdd";
                edits.push({
                    type: type,
                    tagID: oldNode.tagID,
                    attribute: attributeName,
                    value: newAttributes[attributeName]
                });
            }
            delete oldAttributes[attributeName];
        });
        Object.keys(oldAttributes).forEach(function (attributeName) {
            edits.push({
                type: "attrDel",
                tagID: oldNode.tagID,
                attribute: attributeName
            });
        });
    }

    function NodeState(node) {
        this.node = node;
        this.childPointer = 0;
    }

    NodeState.prototype.toJSON = function () {
        return {
            tagID: this.node.tagID,
            childPointer: this.childPointer
        };
    };

    function DOMNavigator(root) {
        this.stack = [new NodeState(root)];
    }

    DOMNavigator.prototype.next = function () {
        var parentState = this.stack[this.stack.length - 1];
        var children = parentState.node.children;
        if (parentState.childPointer > children.length - 1) {
            this.stack.pop();
            if (this.stack.length === 0) {
                return null;
            }
            this.stack[this.stack.length - 1].childPointer++;
            return this.next();
        }
        var child = children[parentState.childPointer];
        if (child.children) {
            this.stack.push(new NodeState(child));
            return child;
        } else {
            parentState.childPointer++;
            return child;
        }
    };

    DOMNavigator.prototype.getPosition = function () {
        var parentState = this.stack[this.stack.length - 1];
        return {
            tagID: parentState.node.tagID,
            child: parentState.childPointer - 1
        };
    };

    function domdiffOld(edits, oldNode, newNode) {
        var oldNav = new DOMNavigator(oldNode),
            newNav = new DOMNavigator(newNode);

        attributeCompare(edits, oldNode, newNode);

        var oldChild = oldNav.next(),
            newChild = newNav.next();

        while (oldChild && newChild) {
            if (oldChild.tag && newChild.tag) {
                attributeCompare(edits, oldChild, newChild);

            // Check to see if they're both text nodes
            } else if (!oldChild.tag && !newChild.tag) {
                if (oldChild.content !== newChild.content) {
                    var position = oldNav.getPosition();
                    edits.push({
                        type: "textReplace",
                        tagID: position.tagID,
                        child: position.child,
                        content: newChild.content
                    });
                }
            }
            oldChild = oldNav.next();
            newChild = newNav.next();
        }
    }

    function compareByWeight(a, b) {
        return b.weight - a.weight;
    }

    function domdiff(oldNode, newNode) {
        var queue = new PriorityQueue(compareByWeight),
            edits = [],
            matches = {},
            elementInserts = {},
            textInserts = {},
            textChanges = {},
            currentElement,
            oldElement,
            elementDeletes = {};

        queue.push(newNode);

        while (currentElement = queue.shift()) {
            oldElement = oldNode.nodeMap[currentElement.tagID];
            if (oldElement) {
                matches[currentElement.tagID] = true;
                if (oldElement.children) {
                    if (oldElement.signature !== currentElement.signature) {
                        if (currentElement.children) {
                            currentElement.children.forEach(function (child) {
                                queue.push(child);
                            });
                        }
                    }
                } else {
                    if (oldElement.signature !== currentElement.signature) {
                        textChanges[currentElement.tagID] = true;
                    }
                }
            } else {
                if (currentElement.children) {
                    elementInserts[currentElement.tagID] = true;
                } else {
                    textInserts[currentElement.tagID] = true;
                }
                if (currentElement.children) {
                    currentElement.children.forEach(function (child) {
                        queue.push(child);
                    });
                }
            }
        }

        Object.keys(matches).forEach(function (tagID) {
            var currentElement;
            var subtreeRoot = newNode.nodeMap[tagID];
            if (subtreeRoot.children) {
                attributeCompare(edits, oldNode.nodeMap[tagID], subtreeRoot);
                var nav = new DOMNavigator(subtreeRoot);
                while (currentElement = nav.next()) {
                    var currentTagID = currentElement.tagID;
                    if (!oldNode.nodeMap[currentTagID]) {
                        // this condition can happen for new elements
                        continue;
                    }
                    matches[currentTagID] = true;
                    if (currentElement.children) {
                        attributeCompare(edits, oldNode.nodeMap[currentTagID], currentElement);
                    }
                }
            }
        });

        var initializeTextEdit = function (element) {
            var edit = {
                parentID: element.parent.tagID
            };

            var match = /(\d+)t/.exec(element.tagID);
            if (match) {
                edit.afterID = match[1];
            } else {
                edit.firstChild = true;
            }
            return edit;
        }

        var findDeletions = function (element) {
            if (!matches[element.tagID]) {
                if (element.children) {
                    edits.push({
                        type: "elementDelete",
                        tagID: element.tagID
                    });
                } else {
                    var edit = initializeTextEdit(element);
                    edit.type = "textDelete";
                    edits.push(edit);
                }
            } else if (element.children) {
                var i;
                for (i = 0; i < element.children.length; i++) {
                    findDeletions(element.children[i]);
                }
            }
        };

        findDeletions(oldNode);

        Object.keys(elementInserts).forEach(function (nonMatchingID) {
            var newElement = newNode.nodeMap[nonMatchingID];
            var childIndex = newElement.parent.children.indexOf(newElement);
            edits.push({
                type: "elementInsert",
                tagID: newElement.tagID,
                tag: newElement.tag,
                attributes: newElement.attributes,
                parentID: newElement.parent.tagID,
                child: childIndex
            });
        });

        Object.keys(textInserts).forEach(function (nonMatchingID) {
            var newElement = newNode.nodeMap[nonMatchingID];
            edits.push({
                type: "textInsert",
                tagID: newElement.parent.tagID,
                child: newElement.parent.children.indexOf(newElement),
                content: newElement.content
            });
        });

        Object.keys(textChanges).forEach(function (changedID) {
            var changedElement = newNode.nodeMap[changedID];
            var edit = initializeTextEdit(changedElement);
            edit.type = "textReplace";
            edit.content = changedElement.content;
            edits.push(edit);
        });
        return edits;
    }

    function _updateDOM(previousDOM, editor, changeList) {
        var updater = new DOMUpdater(previousDOM, editor, changeList);
        var result = updater.update();
        var edits = domdiff(result.oldSubtree, result.newSubtree);
        return {
            dom: result.newDOM,
            edits: edits
        };
    }

    function getUnappliedEditList(editor, changeList) {
        var cachedValue = _cachedValues[editor.document.file.fullPath];
        if (!cachedValue || !cachedValue.dom) {
            console.warn("No previous DOM to compare change against");
            return [];
        } else {
            var result = _updateDOM(cachedValue.dom, editor, changeList);
            _cachedValues[editor.document.file.fullPath] = {
                timestamp: editor.document.diskTimestamp, // TODO: update?
                dom: result.dom
            };
            return result.edits;
        }
    }

    $(DocumentManager).on("beforeDocumentDelete", _removeDocFromCache);

    exports.scanDocument = scanDocument;
    exports.generateInstrumentedHTML = generateInstrumentedHTML;
    exports.getUnappliedEditList = getUnappliedEditList;
    exports._markText = _markText;
    exports._getMarkerAtDocumentPos = _getMarkerAtDocumentPos;
    exports._getTagIDAtDocumentPos = _getTagIDAtDocumentPos;
    exports._buildSimpleDOM = _buildSimpleDOM;
    exports._markTextFromDOM = _markTextFromDOM;
    exports._updateDOM = _updateDOM;
    exports._DOMNavigator = DOMNavigator;
    exports._seed = seed;
});


/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define */
define(function (require, exports, module) {
    'use strict';

    // Each suite or spec must have this.category === "performance" to be filtered properly
    require("perf/Performance-test");
});


brackets-extensibility

Used in the management of Brackets extensions


/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define */

define({

    /**
     * Chyby
     */

    // Obecné chyby souboru
    "GENERIC_ERROR"                     : "(chyba {0})",
    "NOT_FOUND_ERR"                     : "Soubor nenalezen.",
    "NOT_READABLE_ERR"                  : "Soubor nelze číst.",
    "NO_MODIFICATION_ALLOWED_ERR"       : "Cílová složka nemůže být změněna.",
    "NO_MODIFICATION_ALLOWED_ERR_FILE"  : "Oprávnění neumožní provádět změny.",
    "FILE_EXISTS_ERR"                   : "Soubor již existuje.",

    // Řetězce chyb projektu
    "ERROR_LOADING_PROJECT"             : "Chyba při otevírání projektu",
    "OPEN_DIALOG_ERROR"                 : "Došlo k chybě při zobrazování dialogu Otevřít soubor. (chyba {0})",
    "REQUEST_NATIVE_FILE_SYSTEM_ERROR"  : "Došlo k chybě při načítání adresáře {0}. (chyba {1})",
    "READ_DIRECTORY_ENTRIES_ERROR"      : "Došlo k chybě při načítání obsahu složky {0}. (chyba {1})",

    // Řetězce chyb otevírání/ukládání souboru
    "ERROR_OPENING_FILE_TITLE"          : "Chyba při otevírání souboru",
    "ERROR_OPENING_FILE"                : "Došlo k chybě při otevírání souboru {0}. {1}",
    "ERROR_RELOADING_FILE_TITLE"        : "Chyba při načítání změn z disku",
    "ERROR_RELOADING_FILE"              : "Došlo k chybě při načítání souboru {0}. {1}",
    "ERROR_SAVING_FILE_TITLE"           : "Chyba při ukládání souboru",
    "ERROR_SAVING_FILE"                 : "Došlo k chybě při ukládání souboru {0}. {1}",
    "ERROR_RENAMING_FILE_TITLE"         : "Chyba při přejmenování souboru",
    "ERROR_RENAMING_FILE"               : "Došlo k chybě při přejmenování souboru {0}. {1}",
    "ERROR_DELETING_FILE_TITLE"         : "Chyba při mazání souboru",
    "ERROR_DELETING_FILE"               : "Došlo k chybě při mazání souboru {0}. {1}",
    "INVALID_FILENAME_TITLE"            : "Špatné jméno souboru",
    "INVALID_FILENAME_MESSAGE"          : "Jméno souboru nemůže obsahovat znaky: /?*:;{}<>\\|",
    "FILE_ALREADY_EXISTS"               : "Soubor {0} již existuje.",
    "ERROR_CREATING_FILE_TITLE"         : "Chyba při tvorbě souboru",
    "ERROR_CREATING_FILE"               : "Došlo k chybě při vytváření souboru {0}. {1}",

    // Řetězce chyb aplikace
    "ERROR_IN_BROWSER_TITLE"            : "Ouha! {APP_NAME} ještě neběží v prohlížeči.",
    "ERROR_IN_BROWSER"                  : "{APP_NAME} je vytvořen v HTML, ale nyní pracuje jako desktopová aplikace, takže ji můžete použít pro úpravu lokálních souborů. Prosím, použijte shell aplikace v github.com/adobe/brackets-shell repo pro spuštění {APP_NAME}.",

    // Řetězce chyb indexování souboru
    "ERROR_MAX_FILES_TITLE"             : "Chyba při indexování souborů",
    "ERROR_MAX_FILES"                   : "Maximální počet souborů byl indexován. Funkce pro vyhledávání v indexovaných souborech nemusí fungovat správně.",

    // Řetezce chyb - živý náhled
    "ERROR_LAUNCHING_BROWSER_TITLE"     : "Chyba při spouštění prohlížeče",
    "ERROR_CANT_FIND_CHROME"            : "Google Chrome prohlížeč nebyl nalezen. Je nainstalován?",
    "ERROR_LAUNCHING_BROWSER"           : "Došlo k chybě při spouštění prohlížeče. (chyba {0})",

    "LIVE_DEVELOPMENT_ERROR_TITLE"      : "Živý náhled - chyba",
    "LIVE_DEVELOPMENT_RELAUNCH_TITLE"   : "Připojování k prohlížeči",
    "LIVE_DEVELOPMENT_ERROR_MESSAGE"    : "Aby se mohl živý náhled připojit, je třeba restartovat Chrome s povolenou možností vzdálené ladění.

 Chcete restartovat Chrome a povolit vzdálené ladění?",
    "LIVE_DEV_LOADING_ERROR_MESSAGE"    : "Nelze načíst stránku s živým náhledem",
    "LIVE_DEV_NEED_HTML_MESSAGE"        : "Otevřete HTML soubor pro zobrazení v živém náhledu.",
    "LIVE_DEV_NEED_BASEURL_MESSAGE"     : "Pro spuštění živého náhledu se server-side souborem, musíte specifikovat URL pro tento projekt.",
    "LIVE_DEV_SERVER_NOT_READY_MESSAGE" : "Chyba při spouštění HTTP serveru pro soubory živého náhledu. Prosím, zkuste to znovu.",
    "LIVE_DEVELOPMENT_INFO_TITLE"       : "Vítejte v živém náhledu!",
    "LIVE_DEVELOPMENT_INFO_MESSAGE"     : "Živý náhled připojí {APP_NAME} k vašemu prohlížeči. Spustí náhled HTML souboru, který se aktualizuje pokaždé, kdy editujete svůj kód.

V této verzi {APP_NAME}, živý náhled funguje pouze v Google Chrome a aktualizuje změny v CSS souborech. Změny v HTML nebo JavaScript souborech jsou automaticky načteny, když soubor uložíte.

(Tato zpráva se zobrazí pouze jednou.)",
    "LIVE_DEVELOPMENT_TROUBLESHOOTING"  : "Pro více informací navštivte Troubleshooting Live Development connection errors.",

    "LIVE_DEV_STATUS_TIP_NOT_CONNECTED" : "Živý náhled",
    "LIVE_DEV_STATUS_TIP_PROGRESS1"     : "Živý náhled: Připojování\u2026",
    "LIVE_DEV_STATUS_TIP_PROGRESS2"     : "Živý náhled: Spouštění\u2026",
    "LIVE_DEV_STATUS_TIP_CONNECTED"     : "Zrušit živý náhled",
    "LIVE_DEV_STATUS_TIP_OUT_OF_SYNC"   : "Živý náhled: Klikněte pro odpojení (uložte soubor)",

    "LIVE_DEV_DETACHED_REPLACED_WITH_DEVTOOLS" : "Živý náhled byl zrušen, protože byly otevřeny vývojářské nástroje prohlížeče",
    "LIVE_DEV_DETACHED_TARGET_CLOSED"          : "Živý náhled byl zrušen, protože dokument byl zavřen v prohlížeči",
    "LIVE_DEV_NAVIGATED_AWAY"                  : "Živý náhled byl zrušen, protože prohlížeč přešel na stránku, která není součástí projektu",
    "LIVE_DEV_CLOSED_UNKNOWN_REASON"           : "Živý náhled byl zrušen z neznámého důvodu ({0})",

    "SAVE_CLOSE_TITLE"                  : "Uložit změny",
    "SAVE_CLOSE_MESSAGE"                : "Chcete uložit změny v souboru {0}?",
    "SAVE_CLOSE_MULTI_MESSAGE"          : "Chcete uložit změny v následujících souborech?",
    "EXT_MODIFIED_TITLE"                : "Externí změny",
    "EXT_MODIFIED_MESSAGE"              : "{0} byl změněn, ale neuložené změny se nachází také v {APP_NAME}.

 Kterou verzi chcete zachovat?",
    "EXT_DELETED_MESSAGE"               : "{0} byl smazán z disku, ale změny nebyly uloženy v {APP_NAME}.

Chcete uložit změny?",

    // Najít, Nahradit, Nahradit v souborech
    "SEARCH_REGEXP_INFO"                : "Použijte /re/ syntax pro regexp hledání",
    "FIND_RESULT_COUNT"                 : "{0} výsledků",
    "WITH"                              : "S",
    "BUTTON_YES"                        : "Ano",
    "BUTTON_NO"                         : "Ne",
    "BUTTON_STOP"                       : "Stop",

    "OPEN_FILE"                         : "Otevřít soubor",
    "CHOOSE_FOLDER"                     : "Vybrat složku",

    "RELEASE_NOTES"                     : "Poznámky k verzi",
    "NO_UPDATE_TITLE"                   : "Vše je aktuální!",
    "NO_UPDATE_MESSAGE"                 : "Verze {APP_NAME} je aktuální.",

    "FIND_IN_FILES_TITLE"               : "pro \"{4}\" {5} - {0} {1} v {2} {3}",
    "FIND_IN_FILES_SCOPED"              : "v {0}",
    "FIND_IN_FILES_NO_SCOPE"            : "v projektu",
    "FIND_IN_FILES_FILE"                : "soubor",
    "FIND_IN_FILES_FILES"               : "soubory",
    "FIND_IN_FILES_MATCH"               : "výsledek",
    "FIND_IN_FILES_MATCHES"             : "výsledky",
    "FIND_IN_FILES_MORE_THAN"           : "Více než ",
    "FIND_IN_FILES_MAX"                 : " (zobrazuji prvních {0} záznamů)",
    "FIND_IN_FILES_FILE_PATH"           : "Soubor: {0}",
    "FIND_IN_FILES_LINE"                : "řádek: {0}",

    "ERROR_FETCHING_UPDATE_INFO_TITLE"  : "Chyba při získávání informací o aktualizaci",
    "ERROR_FETCHING_UPDATE_INFO_MSG"    : "Nelze získat aktualizace. Ujistěte se, že máte připojení na internet a zkuste to znovu.",

    /**
     * Správce projektu
     */
    "PROJECT_LOADING" : "Načítání\u2026",
    "UNTITLED" : "Nový",
    "WORKING_FILES"     : "Pracovní soubory",

    /**
     * Jména kláves
     */
    "KEYBOARD_CTRL"   : "Ctrl",
    "KEYBOARD_SHIFT"  : "Shift",
    "KEYBOARD_SPACE"  : "Space",

    /**
     * Řetezce příkazového řádku
     */
    "STATUSBAR_CURSOR_POSITION"             : "Řádek {0}, Sloupec {1}",
    "STATUSBAR_INDENT_TOOLTIP_SPACES"       : "Přepnout odsazení na mezery",
    "STATUSBAR_INDENT_TOOLTIP_TABS"         : "Přepnout odsazení na tabulátory",
    "STATUSBAR_INDENT_SIZE_TOOLTIP_SPACES"  : "Změnit počet mezer použitých pro odsazení",
    "STATUSBAR_INDENT_SIZE_TOOLTIP_TABS"    : "Změnit šířku tabulátoru",
    "STATUSBAR_SPACES"                      : "mezery",
    "STATUSBAR_TAB_SIZE"                    : "Velikost tabulátoru",
    "STATUSBAR_LINE_COUNT_SINGULAR"         : "Řádek: {0}",
    "STATUSBAR_LINE_COUNT_PLURAL"           : "Řádky: {0}",

    /**
     * Příkazy
     */

    // Příkazy menu Soubor
    "FILE_MENU"                           : "Soubor",
    "CMD_FILE_NEW"                        : "Nový soubor",
    "CMD_FILE_NEW_FOLDER"                 : "Nová složka",
    "CMD_FILE_OPEN"                       : "Otevřít\u2026",
    "CMD_ADD_TO_WORKING_SET"              : "Přidat k pracovní sadě",
    "CMD_OPEN_FOLDER"                     : "Otevřít složku\u2026",
    "CMD_FILE_CLOSE"                      : "Zavřít",
    "CMD_FILE_CLOSE_ALL"                  : "Zavřít vše",
    "CMD_FILE_SAVE"                       : "Uložit",
    "CMD_FILE_SAVE_ALL"                   : "Uložit vše",
    "CMD_LIVE_FILE_PREVIEW"               : "Živý náhled",
    "CMD_LIVE_HIGHLIGHT"                  : "Živé zvýraznění",
    "CMD_PROJECT_SETTINGS"                : "Nastavení projektu\u2026",
    "CMD_FILE_RENAME"                     : "Přejmenovat",
    "CMD_FILE_DELETE"                     : "Smazat",
    "CMD_INSTALL_EXTENSION"               : "Instalovat doplňky\u2026",
    "CMD_EXTENSION_MANAGER"               : "Správce doplňků\u2026",
    "CMD_FILE_REFRESH"                    : "Obnovit",
    "CMD_QUIT"                            : "Konec",
    // Použito v souborovém menu Windows
    "CMD_EXIT"                            : "Konec",

    // Příkazy menu Edit
    "EDIT_MENU"                           : "Úpravy",
    "CMD_UNDO"                            : "Zpět",
    "CMD_REDO"                            : "Znovu",
    "CMD_CUT"                             : "Vyjmout",
    "CMD_COPY"                            : "Kopírovat",
    "CMD_PASTE"                           : "Vložit",
    "CMD_SELECT_ALL"                      : "Vybrat vše",
    "CMD_SELECT_LINE"                     : "Vybrat řádek",
    "CMD_FIND"                            : "Najít",
    "CMD_FIND_IN_FILES"                   : "Najít v souborech",
    "CMD_FIND_IN_SUBTREE"                 : "Najít v\u2026",
    "CMD_FIND_NEXT"                       : "Najít další",
    "CMD_FIND_PREVIOUS"                   : "Najít předchozí",
    "CMD_REPLACE"                         : "Nahradit",
    "CMD_INDENT"                          : "Odsadit",
    "CMD_UNINDENT"                        : "Vrátit odsazení",
    "CMD_DUPLICATE"                       : "Duplikovat",
    "CMD_DELETE_LINES"                    : "Smazat řádek",
    "CMD_COMMENT"                         : "Řádkový komentář",
    "CMD_BLOCK_COMMENT"                   : "Blokový komentář",
    "CMD_LINE_UP"                         : "Posunout řádek nahoru",
    "CMD_LINE_DOWN"                       : "Posunout řádek dolů",
    "CMD_OPEN_LINE_ABOVE"                 : "O řádek výše",
    "CMD_OPEN_LINE_BELOW"                 : "O řádek níže",
    "CMD_TOGGLE_CLOSE_BRACKETS"           : "Uzavírat závorky",
    "CMD_SHOW_CODE_HINTS"                 : "Zobrazit nápovědu",

    // Příkazy menu Zobrazit
    "VIEW_MENU"                           : "Zobrazit",
    "CMD_HIDE_SIDEBAR"                    : "Skrýt boční menu",
    "CMD_SHOW_SIDEBAR"                    : "Zobrazit boční menu",
    "CMD_INCREASE_FONT_SIZE"              : "Zvětšit velikost písma",
    "CMD_DECREASE_FONT_SIZE"              : "Zmenšit velikost písma",
    "CMD_RESTORE_FONT_SIZE"               : "Obnovit velikost písma",
    "CMD_SCROLL_LINE_UP"                  : "Posunout o řádek nahoru",
    "CMD_SCROLL_LINE_DOWN"                : "Posunout o řádek dolů",
    "CMD_TOGGLE_LINE_NUMBERS"             : "Čísla řádků",
    "CMD_TOGGLE_ACTIVE_LINE"              : "Zvýraznit aktivní řádek",
    "CMD_TOGGLE_WORD_WRAP"                : "Zalomit řádky",
    "CMD_SORT_WORKINGSET_BY_ADDED"        : "Řadit podle data",
    "CMD_SORT_WORKINGSET_BY_NAME"         : "Řadit podle jména",
    "CMD_SORT_WORKINGSET_BY_TYPE"         : "Řadit podle typu",
    "CMD_SORT_WORKINGSET_AUTO"            : "Automatické řazení",

    // Příkazy menu Navigace
    "NAVIGATE_MENU"                       : "Navigace",
    "CMD_QUICK_OPEN"                      : "Rychle otevřít",
    "CMD_GOTO_LINE"                       : "Přejít na řádek",
    "CMD_GOTO_DEFINITION"                 : "Přejít na funkci",
    "CMD_TOGGLE_QUICK_EDIT"               : "Rychlá úprava",
    "CMD_TOGGLE_QUICK_DOCS"               : "Rychlá dokumentace",
    "CMD_QUICK_EDIT_PREV_MATCH"           : "Předchozí shoda",
    "CMD_QUICK_EDIT_NEXT_MATCH"           : "Další shoda",
    "CMD_NEXT_DOC"                        : "Další dokument",
    "CMD_PREV_DOC"                        : "Předchozí dokument",
    "CMD_SHOW_IN_TREE"                    : "Zobrazit stromovou strukturu",
    "CMD_SHOW_IN_OS"                      : "Zobrazit v OS",

    // Příkazy menu nápověda
    "HELP_MENU"                           : "Nápověda",
    "CMD_CHECK_FOR_UPDATE"                : "Zkontrolovat aktualizace",
    "CMD_HOW_TO_USE_BRACKETS"             : "Jak používat {APP_NAME}",
    "CMD_FORUM"                           : "{APP_NAME} fórum",
    "CMD_RELEASE_NOTES"                   : "Poznámky k verzi",
    "CMD_REPORT_AN_ISSUE"                 : "Nahlásit problém",
    "CMD_SHOW_EXTENSIONS_FOLDER"          : "Zobrazit složku s doplňky",
    "CMD_TWITTER"                         : "{TWITTER_NAME} - Twitter",
    "CMD_ABOUT"                           : "O aplikaci {APP_TITLE}",

    // Speciální příkazy spustěné pomocí shell
    "CMD_CLOSE_WINDOW"                    : "Zavřít okno",
    "CMD_ABORT_QUIT"                      : "Zrušit",

    // Řetězce pro main-view.html
    "EXPERIMENTAL_BUILD"                   : "experimentální verze",
    "DEVELOPMENT_BUILD"                    : "vývojová verze",
    "SEARCH_RESULTS"                       : "Výsledky hledání",
    "OK"                                   : "OK",
    "DONT_SAVE"                            : "Neukládat",
    "SAVE"                                 : "Uložit",
    "CANCEL"                               : "Zrušit",
    "RELOAD_FROM_DISK"                     : "Načíst z disku",
    "KEEP_CHANGES_IN_EDITOR"               : "Ponechat změny v editoru",
    "CLOSE_DONT_SAVE"                      : "Zavřít (neukládat)",
    "RELAUNCH_CHROME"                      : "Restartovat Chrome",
    "ABOUT"                                : "O aplikaci",
    "CLOSE"                                : "Zavřít",
    "ABOUT_TEXT_LINE1"                     : "sprint {VERSION_MINOR} {BUILD_TYPE} {VERSION}",
    "ABOUT_TEXT_LINE3"                     : "Oznámení, podmínky týkající se software třetích stran jsou umístěny na http://www.adobe.com/go/thirdparty/ a začleněny prostřednictvím odkazu zde.",
    "ABOUT_TEXT_LINE4"                     : "Dokumentace a zdrojový kód na https://github.com/adobe/brackets/.",
    "ABOUT_TEXT_LINE5"                     : "Vytvořeno s \u2764 a pomocí JavaScript těmito lidmi:",
    "ABOUT_TEXT_LINE6"                     : "Mnoho lidí (ale momentálně máme problém s načítáním dat).",
    "ABOUT_TEXT_WEB_PLATFORM_DOCS"         : "Web Platform Docs a Web Platform logo využívají licenci Creative Commons Attribution, CC-BY 3.0 Unported.",
    "UPDATE_NOTIFICATION_TOOLTIP"          : "Je dostupná nová verze {APP_NAME} ! Klikněte zde pro více informací.",
    "UPDATE_AVAILABLE_TITLE"               : "Dostupná aktualizace",
    "UPDATE_MESSAGE"                       : "Nová verze {APP_NAME} je dostupná. Seznam některých vylepšení:",
    "GET_IT_NOW"                           : "Stáhnout!",
    "PROJECT_SETTINGS_TITLE"               : "Nastavení projektu: {0}",
    "PROJECT_SETTING_BASE_URL"             : "Živý náhled URL",
    "PROJECT_SETTING_BASE_URL_HINT"        : "(nechte prázdné pro URL souboru)",
    "BASEURL_ERROR_INVALID_PROTOCOL"       : "{0} protokol není podporován živým náhledem—prosím, použijte http: nebo https: .",
    "BASEURL_ERROR_SEARCH_DISALLOWED"      : "URL nemůže obsahovat výrazy pro hledání jako \"{0}\".",
    "BASEURL_ERROR_HASH_DISALLOWED"        : "URL nemůže obsahovat znaky jako \"{0}\".",
    "BASEURL_ERROR_INVALID_CHAR"           : "Zvláštní znaky jako '{0}' musí být %-enkódovány.",
    "BASEURL_ERROR_UNKOWN_ERROR"           : "Neznámá chyba při zpracování URL",

    // Řetězce pro správce doplňků
    "INSTALL"                              : "Instalovat",
    "REMOVE"                               : "Odstranit",
    "CANT_REMOVE_DEV"                      : "Doplněk v \"dev\" složce musí být smazán manuálně.",
    "INSTALL_EXTENSION_TITLE"              : "Instalovat doplněk",
    "INSTALL_EXTENSION_LABEL"              : "URL adresa doplňku",
    "INSTALL_EXTENSION_HINT"               : "URL adresa zip archivu nebo GitHub repozitáře",
    "INSTALLING_FROM"                      : "Instalace doplňku z {0}\u2026",
    "INSTALL_SUCCEEDED"                    : "Instalace byla úspěšná!",
    "INSTALL_FAILED"                       : "Instalace se nezdařila.",
    "CANCELING_INSTALL"                    : "Rušení instalace\u2026",
    "CANCELING_HUNG"                       : "Rušení instalace trvá dlouho. Mohlo dojít k interní chybě.",
    "INSTALL_CANCELED"                     : "Instalace zrušena.",
    // Tyto musí odpovídat chybovým hlášením v ExtensionsDomain.Errors.* :
    "INVALID_ZIP_FILE"                     : "Stažený soubor není platný zip soubor.",
    "INVALID_PACKAGE_JSON"                 : "Package.json balíček není platný (chyba byla: {0}).",
    "MISSING_PACKAGE_NAME"                 : "Package.json balíček nespecifikuje jméno souboru.",
    "BAD_PACKAGE_NAME"                     : "{0} je neplatné jméno balíčku.",
    "MISSING_PACKAGE_VERSION"              : "Package.json balíček nespecifikuje verzi souboru.",
    "INVALID_VERSION_NUMBER"               : "Balíček verze ({0}) je neplatný.",
    "INVALID_BRACKETS_VERSION"             : "Řetězec kompatibility {{0}} pro Brackets je neplatný.",
    "DISALLOWED_WORDS"                     : "Slova {{1}} nejsou povolena v {{0}} poli.",
    "API_NOT_COMPATIBLE"                   : "Doplněk není kompatibilní s touto verzi Brackets. Naleznete jej ve složce disabled extensions.",
    "MISSING_MAIN"                         : "Balíček neobsahuje soubor main.js.",
    "ALREADY_INSTALLED"                    : "Doplněk s tímto jménem je již nainstalován. Nový doplněk je nainstalován ve složce disabled extensions.",
    "DOWNLOAD_ID_IN_USE"                   : "Interní chyba: ID stahování se již používá.",
    "NO_SERVER_RESPONSE"                   : "Nelze se připojit na server.",
    "BAD_HTTP_STATUS"                      : "Soubor nebyl nalezen (HTTP {0}).",
    "CANNOT_WRITE_TEMP"                    : "Nelze uložit do dočasných souborů.",
    "ERROR_LOADING"                        : "Při spuštění doplňku došlo k chybě.",
    "MALFORMED_URL"                        : "URL adresa je neplatná. Ujistěte se, že jste adresu zadali správně.",
    "UNSUPPORTED_PROTOCOL"                 : "URL adresa musí být http nebo https.",
    "UNKNOWN_ERROR"                        : "Neznámá chyba.",
    // Pro NOT_FOUND_ERR, vyhledejte obecné řetězce výše
    "EXTENSION_MANAGER_TITLE"              : "Správce doplňků",
    "EXTENSION_MANAGER_ERROR_LOAD"         : "Nelze získat přístup k registru doplňků. Prosím, zkuste to znovu později.",
    "INSTALL_FROM_URL"                     : "Instalovat z URL\u2026",
    "EXTENSION_AUTHOR"                     : "Autor",
    "EXTENSION_DATE"                       : "Datum",
    "EXTENSION_INCOMPATIBLE_NEWER"         : "Tento doplněk požaduje novější verzi {APP_NAME}.",
    "EXTENSION_INCOMPATIBLE_OLDER"         : "Tento doplněk funguje pouze ve starší verzi {APP_NAME}.",
    "EXTENSION_NO_DESCRIPTION"             : "Bez popisu",
    "EXTENSION_MORE_INFO"                  : "Více informací...",
    "EXTENSION_ERROR"                      : "Chyba doplňku",
    "EXTENSION_KEYWORDS"                   : "Klíčová slova",
    "EXTENSION_INSTALLED"                  : "Nainstalováno",
    "EXTENSION_SEARCH_PLACEHOLDER"         : "Hledat",
    "EXTENSION_MORE_INFO_LINK"             : "Více",
    "BROWSE_EXTENSIONS"                    : "Procházet doplňky",
    "EXTENSION_MANAGER_REMOVE"             : "Odstranit doplněk",
    "EXTENSION_MANAGER_REMOVE_ERROR"       : "Chyba při odstraňování jednoho nebo více doplňků: {{0}}. {APP_NAME} bude stále ukončen.",
    "MARKED_FOR_REMOVAL"                   : "Označeno pro odstranění",
    "UNDO_REMOVE"                          : "Zpět",
    "REMOVE_AND_QUIT_TITLE"                : "Odstranit doplňky",
    "REMOVE_AND_QUIT_MESSAGE"              : "Pro odstranění označených doplňků musíte restartovat {APP_NAME}. Budete vyzváni k uložení změn.",
    "REMOVE_AND_QUIT"                      : "Odstranit doplňky a ukončit program",
    "EXTENSION_NOT_INSTALLED"              : "Doplněk {{0}} nemohl být odstraněn, protože nebyl nainstalován.",
    "NO_EXTENSIONS"                        : "Žádný doplněk ještě nebyl nainstalován.
Klikněte na tlačítko Instalovat z URL pro zahájení instalace.",

    /**
     * Jména jednotek
     */

    "UNIT_PIXELS"                          : "pixely",

    // extensions/default/DebugCommands
    "DEBUG_MENU"                          : "Nástroje",
    "CMD_SHOW_DEV_TOOLS"                  : "Zobrazit nástroje pro vývojáře",
    "CMD_REFRESH_WINDOW"                  : "Restartovat {APP_NAME}",
    "CMD_NEW_BRACKETS_WINDOW"             : "Nové okno {APP_NAME}",
    "CMD_SWITCH_LANGUAGE"                 : "Změnit jazyk",
    "CMD_RUN_UNIT_TESTS"                  : "Spustit testy",
    "CMD_SHOW_PERF_DATA"                  : "Zobrazit údaje o výkonnosti",
    "CMD_ENABLE_NODE_DEBUGGER"            : "Povolit Node Debugger",
    "CMD_LOG_NODE_STATE"                  : "Uložit stav Node do konzole",
    "CMD_RESTART_NODE"                    : "Restartovat Node",

    "LANGUAGE_TITLE"                    : "Změnit jazyk",
    "LANGUAGE_MESSAGE"                  : "Prosím, vyberte jazyk ze seznamu:",
    "LANGUAGE_SUBMIT"                   : "Restartovat {APP_NAME}",
    "LANGUAGE_CANCEL"                   : "Zrušit",
    "LANGUAGE_SYSTEM_DEFAULT"           : "Výchozí",

    /**
     * Jazyky
     */
    "LOCALE_CS"                                 : "Česky",
    "LOCALE_DE"                                 : "Německy",
    "LOCALE_EN"                                 : "Anglicky",
    "LOCALE_ES"                                 : "Španělsky",
    "LOCALE_FR"                                 : "Francouzsky",
    "LOCALE_IT"                                 : "Italsky",
    "LOCALE_JA"                                 : "Japonsky",
    "LOCALE_NB"                                 : "Norsky",
    "LOCALE_PL"                                 : "Polsky",
    "LOCALE_PT_BR"                              : "Portugalsky, Brazílie",
    "LOCALE_PT_PT"                              : "Portugalsky",
    "LOCALE_RU"                                 : "Rusky",
    "LOCALE_SV"                                 : "Švédsky",
    "LOCALE_TR"                                 : "Turecky",
    "LOCALE_ZH_CN"                              : "Čínsky",

    // extensions/default/InlineColorEditor
    "COLOR_EDITOR_CURRENT_COLOR_SWATCH_TIP"     : "Současná barva",
    "COLOR_EDITOR_ORIGINAL_COLOR_SWATCH_TIP"    : "Původní barva",
    "COLOR_EDITOR_RGBA_BUTTON_TIP"              : "RGBa formát",
    "COLOR_EDITOR_HEX_BUTTON_TIP"               : "Hex formát",
    "COLOR_EDITOR_HSLA_BUTTON_TIP"              : "HSLa formát",
    "COLOR_EDITOR_USED_COLOR_TIP_SINGULAR"      : "{0} (použito {1} krát)",
    "COLOR_EDITOR_USED_COLOR_TIP_PLURAL"        : "{0} (použito {1} krát)",

    // extensions/default/JavaScriptCodeHints
    "CMD_JUMPTO_DEFINITION"               : "Přejít na definici",

    // extensions/default/JSLint
    "CMD_JSLINT"                           : "Povolit JSLint",
    "CMD_JSLINT_FIRST_ERROR"               : "Přejít na první JSLint chybu",
    "JSLINT_ERRORS"                        : "JSLint chyby",
    "JSLINT_ERROR_INFORMATION"             : "1 JSLint chyba",
    "JSLINT_ERRORS_INFORMATION"            : "{0} JSLint chyb",
    "JSLINT_NO_ERRORS"                     : "Žádné JSLint chyby - výborně!",
    "JSLINT_DISABLED"                      : "JSLint je vypnut nebo nefunguje s tímto souborem.",

    // extensions/default/QuickView
    "CMD_ENABLE_QUICK_VIEW"                : "Rychlý náhled",

    // extensions/default/WebPlatformDocs
    "DOCS_MORE_LINK"                            : "Více"
});


/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */
/*global define, $ */

/**
 * HTMLDocument manages a single HTML source document
 *
 * # EDITING
 *
 * Editing the document will cause the corresponding node to be updated
 * by calling `applyChanges` on the DOMAgent. This will only work for
 * altering text nodes and will break when attempting to change DOM elements
 * or inserting or deleting nodes.
 *
 * # HIGHLIGHTING
 *
 * HTMLDocument supports highlighting nodes from the HighlightAgent and
 * highlighting the DOMNode corresponding to the cursor position in the
 * editor.
 */
define(function HTMLDocumentModule(require, exports, module) {
    "use strict";

    var DocumentManager     = require("document/DocumentManager"),
        DOMAgent            = require("LiveDevelopment/Agents/DOMAgent"),
        HighlightAgent      = require("LiveDevelopment/Agents/HighlightAgent"),
        HTMLInstrumentation = require("language/HTMLInstrumentation"),
        Inspector           = require("LiveDevelopment/Inspector/Inspector"),
        LiveDevelopment     = require("LiveDevelopment/LiveDevelopment"),
        RemoteAgent         = require("LiveDevelopment/Agents/RemoteAgent"),
        StringUtils         = require("utils/StringUtils");

    /** Constructor
     *
     * @param Document the source document from Brackets
     */
    var HTMLDocument = function HTMLDocument(doc, editor) {
        this.doc = doc;
        if (!editor) {
            return;
        }
        this.editor = editor;
        this._instrumentationEnabled = false;

        this.onCursorActivity = this.onCursorActivity.bind(this);
        this.onDocumentSaved = this.onDocumentSaved.bind(this);

        $(this.editor).on("cursorActivity", this.onCursorActivity);
        $(DocumentManager).on("documentSaved", this.onDocumentSaved);

        // Experimental code
        if (LiveDevelopment.config.experimental) {
            // Used by highlight agent to highlight editor text as selected in browser
            this.onHighlight = this.onHighlight.bind(this);
            $(HighlightAgent).on("highlight", this.onHighlight);
        }

        this.onChange = this.onChange.bind(this);
        $(this.editor).on("change", this.onChange);
    };

    /**
     * Enable instrumented HTML
     * @param enabled {boolean}
     */
    HTMLDocument.prototype.setInstrumentationEnabled = function setInstrumentationEnabled(enabled) {
        if (enabled && !this._instrumentationEnabled) {
            HTMLInstrumentation.scanDocument(this.doc);
            HTMLInstrumentation._markText(this.editor);
        }

        this._instrumentationEnabled = enabled;
    };

    /**
     * Returns a JSON object with HTTP response overrides
     * @returns {{body: string}}
     */
    HTMLDocument.prototype.getResponseData = function getResponseData(enabled) {
        var body = (this._instrumentationEnabled) ?
                HTMLInstrumentation.generateInstrumentedHTML(this.doc) : this.doc.getText();

        return {
            body: body
        };
    };

    /** Close the document */
    HTMLDocument.prototype.close = function close() {
        if (!this.editor) {
            return;
        }

        $(this.editor).off("cursorActivity", this.onCursorActivity);
        $(DocumentManager).off("documentSaved", this.onDocumentSaved);

        // Experimental code
        if (LiveDevelopment.config.experimental) {
            $(HighlightAgent).off("highlight", this.onHighlight);
            this.onHighlight();

            $(this.editor).off("change", this.onChange);
        }
    };

    /** Event Handlers *******************************************************/

    /** Triggered on cursor activity by the editor */
    HTMLDocument.prototype.onCursorActivity = function onCursorActivity(event, editor) {
        if (!this.editor) {
            return;
        }
        var codeMirror = this.editor._codeMirror;
        if (Inspector.config.highlight) {
            var tagID = HTMLInstrumentation._getTagIDAtDocumentPos(
                this.editor,
                codeMirror.getCursor()
            );

            if (tagID === -1) {
                HighlightAgent.hide();
            } else {
                HighlightAgent.domElement(tagID);
            }
        }
    };

    /** Triggered on change by the editor */
    HTMLDocument.prototype.onChange = function onChange(event, editor, change) {
        // Only handles attribute changes currently.
        // TODO: text changes should be easy to add
        // TODO: if new tags are added, need to instrument them
        var edits = HTMLInstrumentation.getUnappliedEditList(editor, change);
        edits.forEach(function (edit) {
            // Silly naming convention: $$ = remote $
            var html,
                $$target = RemoteAgent.remoteElement(edit.type === "textReplace" || edit.type === "textDelete" || edit.type === "elementInsert" ? edit.parentID : edit.tagID);
            switch (edit.type) {
            case "attrChange":
            case "attrAdd":
                $$target.attr(edit.attribute, edit.value);
                break;
            case "attrDel":
                $$target.removeAttr(edit.attribute);
                break;
            case "elementDelete":
                $$target.remove();
                break;
            case "elementInsert":
                html = "<" + edit.tag;
                Object.keys(edit.attributes).forEach(function (attr) {
                    html += " " + attr + "='" + StringUtils.htmlEscape(edit.attributes[attr]) + "'";
                });
                html += " data-brackets-id='" + edit.tagID + "'/>";
                $$target.insertChild(edit.child, html, false);
                break;
            case "textInsert":
                $$target.insertChild(edit.child, edit.content, true);
            case "textReplace":
                $$target.replaceChildText(edit.afterID, edit.content);
                break;
            case "textDelete":
                $$target.deleteChildText(edit.afterID);
                break;
            }
        });

//        var marker = HTMLInstrumentation._getMarkerAtDocumentPos(
//            this.editor,
//            editor.getCursorPos()
//        );
//
//        if (marker && marker.tagID) {
//            var range   = marker.find(),
//                text    = marker.doc.getRange(range.from, range.to);
//
//            // HACK maintain ID
//            text = text.replace(">", " data-brackets-id='" + marker.tagID + "'>");
//
//            // FIXME incorrectly replaces body elements with content only, missing body element
//            RemoteAgent.remoteElement(marker.tagID).replaceWith(text);
//        }

        // if (!this.editor) {
        //     return;
        // }
        // var codeMirror = this.editor._codeMirror;
        // while (change) {
        //     var from = codeMirror.indexFromPos(change.from);
        //     var to = codeMirror.indexFromPos(change.to);
        //     var text = change.text.join("\n");
        //     DOMAgent.applyChange(from, to, text);
        //     change = change.next;
        // }
    };

    /** Triggered by the HighlightAgent to highlight a node in the editor */
    HTMLDocument.prototype.onHighlight = function onHighlight(event, node) {
        if (!node || !node.location || !this.editor) {
            if (this._highlight) {
                this._highlight.clear();
                delete this._highlight;
            }
            return;
        }
        var codeMirror = this.editor._codeMirror;
        var to, from = codeMirror.posFromIndex(node.location);
        if (node.closeLocation) {
            to = node.closeLocation + node.closeLength;
        } else {
            to = node.location + node.length;
        }
        to = codeMirror.posFromIndex(to);
        if (this._highlight) {
            this._highlight.clear();
        }
        this._highlight = codeMirror.markText(from, to, { className: "highlight" });
    };

    /** Triggered when a document is saved */
    HTMLDocument.prototype.onDocumentSaved = function onDocumentSaved(event, doc) {
        if (doc === this.doc) {
            HTMLInstrumentation.scanDocument(this.doc);
            HTMLInstrumentation._markText(this.editor);
        }
    };

    // Export the class
    module.exports = HTMLDocument;
});


Copyright (c) 2012 Johannes Ewald

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, window */

define(function (require, exports, module) {
    "use strict";

    var CollectionUtils = require("utils/CollectionUtils");

    /**
     * Convert between URL querystring and name/value pairs. Decodes and encodes URL parameters.
     */
    function UrlParams() {
        this._store = {};
    }

    /**
     * Parse the window location by default. Optionally specify a URL to parse.
     * @param {string} url
     */
    UrlParams.prototype.parse = function (url) {
        if (url) {
            url = url.substring(url.indexOf("?") + 1);
        } else {
            url = window.document.location.search.substring(1);
        }

        var urlParams = url.split("&"),
            p,
            self = this;

        urlParams.forEach(function (param) {
            p = param.split("=");
            self._store[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
        });
    };

    /**
     * Store a name/value string pair
     * @param {!string} name
     * @param {!string} value
     */
    UrlParams.prototype.put = function (name, value) {
        this._store[name] = value;
    };

    /**
     * Retreive a value by name
     * @param {!string} name
     */
    UrlParams.prototype.get = function (name) {
        return this._store[name];
    };

    /**
     * Encode name/value pairs as URI components.
     */
    UrlParams.prototype.toString = function () {
        var strs = [],
            self = this;

        CollectionUtils.forEach(self._store, function (value, key) {
            strs.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
        });

        return strs.join("&");
    };

    // Define public API
    exports.UrlParams = UrlParams;
});


keep keep keep keep keep replace replace replace replace replace replace keep keep keep keep


// A reader for when we don't yet know what kind of thing
// the thing is.

module.exports = ProxyReader

var Reader = require("./reader.js")
  , getType = require("./get-type.js")
  , inherits = require("inherits")
  , fs = require("graceful-fs")

inherits(ProxyReader, Reader)

function ProxyReader (props) {
  var me = this
  if (!(me instanceof ProxyReader)) throw new Error(
    "ProxyReader must be called as constructor.")

  me.props = props
  me._buffer = []
  me.ready = false

  Reader.call(me, props)
}

ProxyReader.prototype._stat = function () {
  var me = this
    , props = me.props
    // stat the thing to see what the proxy should be.
    , stat = props.follow ? "stat" : "lstat"

  fs[stat](props.path, function (er, current) {
    var type
    if (er || !current) {
      type = "File"
    } else {
      type = getType(current)
    }

    props[type] = true
    props.type = me.type = type

    me._old = current
    me._addProxy(Reader(props, current))
  })
}

ProxyReader.prototype._addProxy = function (proxy) {
  var me = this
  if (me._proxyTarget) {
    return me.error("proxy already set")
  }

  me._proxyTarget = proxy
  proxy._proxy = me

  ; [ "error"
    , "data"
    , "end"
    , "close"
    , "linkpath"
    , "entry"
    , "entryEnd"
    , "child"
    , "childEnd"
    , "warn"
    , "stat"
    ].forEach(function (ev) {
      // console.error("~~ proxy event", ev, me.path)
      proxy.on(ev, me.emit.bind(me, ev))
    })

  me.emit("proxy", proxy)

  proxy.on("ready", function () {
    // console.error("~~ proxy is ready!", me.path)
    me.ready = true
    me.emit("ready")
  })

  var calls = me._buffer
  me._buffer.length = 0
  calls.forEach(function (c) {
    proxy[c[0]].apply(proxy, c[1])
  })
}

ProxyReader.prototype.pause = function () {
  return this._proxyTarget ? this._proxyTarget.pause() : false
}

ProxyReader.prototype.resume = function () {
  return this._proxyTarget ? this._proxyTarget.resume() : false
}


TestHelpers.commonWidgetTests('resizable', {
	defaults: {
		alsoResize: false,
		animate: false,
		animateDuration: 'slow',
		animateEasing: 'swing',
		aspectRatio: false,
		autoHide: false,
		cancel: 'input,textarea,button,select,option',
		containment: false,
		delay: 0,
		disabled: false,
		distance: 1,
		ghost: false,
		grid: false,
		handles: 'e,s,se',
		helper: false,
		maxHeight: null,
		maxWidth: null,
		minHeight: 10,
		minWidth: 10,
		zIndex: 1000,

		// callbacks
		create: null
	}
});


/*
 * jsTree apple theme 1.0
 * Supported features: dots/no-dots, icons/no-icons, focused, loading
 * Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search
 */

.jstree-apple > ul { background:url("bg.jpg") left top repeat; }
.jstree-apple li,
.jstree-apple ins { background-image:url("d.png"); background-repeat:no-repeat; background-color:transparent; }
.jstree-apple li { background-position:-90px 0; background-repeat:repeat-y;  }
.jstree-apple li.jstree-last { background:transparent; }
.jstree-apple .jstree-open > ins { background-position:-72px 0; }
.jstree-apple .jstree-closed > ins { background-position:-54px 0; }
.jstree-apple .jstree-leaf > ins { background-position:-36px 0; }

.jstree-apple a { border-radius:4px; -moz-border-radius:4px; -webkit-border-radius:4px; text-shadow:1px 1px 1px white; }
.jstree-apple .jstree-hovered { background:#e7f4f9; border:1px solid #d8f0fa; padding:0 3px 0 1px; text-shadow:1px 1px 1px silver; }
.jstree-apple .jstree-clicked { background:#beebff; border:1px solid #99defd; padding:0 3px 0 1px; }
.jstree-apple a .jstree-icon { background-position:-56px -20px; }
.jstree-apple a.jstree-loading .jstree-icon { background:url("throbber.gif") center center no-repeat !important; }

.jstree-apple.jstree-focused { background:white; }

.jstree-apple .jstree-no-dots li,
.jstree-apple .jstree-no-dots .jstree-leaf > ins { background:transparent; }
.jstree-apple .jstree-no-dots .jstree-open > ins { background-position:-18px 0; }
.jstree-apple .jstree-no-dots .jstree-closed > ins { background-position:0 0; }

.jstree-apple .jstree-no-icons a .jstree-icon { display:none; }

.jstree-apple .jstree-search { font-style:italic; }

.jstree-apple .jstree-no-icons .jstree-checkbox { display:inline-block; }
.jstree-apple .jstree-no-checkboxes .jstree-checkbox { display:none !important; }
.jstree-apple .jstree-checked > a > .jstree-checkbox { background-position:-38px -19px; }
.jstree-apple .jstree-unchecked > a > .jstree-checkbox { background-position:-2px -19px; }
.jstree-apple .jstree-undetermined > a > .jstree-checkbox { background-position:-20px -19px; }
.jstree-apple .jstree-checked > a > .checkbox:hover { background-position:-38px -37px; }
.jstree-apple .jstree-unchecked > a > .jstree-checkbox:hover { background-position:-2px -37px; }
.jstree-apple .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-20px -37px; }

#vakata-dragged.jstree-apple ins { background:transparent !important; }
#vakata-dragged.jstree-apple .jstree-ok { background:url("d.png") -2px -53px no-repeat !important; }
#vakata-dragged.jstree-apple .jstree-invalid { background:url("d.png") -18px -53px no-repeat !important; }
#jstree-marker.jstree-apple { background:url("d.png") -41px -57px no-repeat !important; text-indent:-100px; }

.jstree-apple a.jstree-search { color:aqua; }
.jstree-apple .jstree-locked a { color:silver; cursor:default; }

#vakata-contextmenu.jstree-apple-context,
#vakata-contextmenu.jstree-apple-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; }
#vakata-contextmenu.jstree-apple-context li { }
#vakata-contextmenu.jstree-apple-context a { color:black; }
#vakata-contextmenu.jstree-apple-context a:hover,
#vakata-contextmenu.jstree-apple-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; }
#vakata-contextmenu.jstree-apple-context li.jstree-contextmenu-disabled a,
#vakata-contextmenu.jstree-apple-context li.jstree-contextmenu-disabled a:hover { color:silver; background:transparent; border:0; padding:1px 4px; }
#vakata-contextmenu.jstree-apple-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; }
#vakata-contextmenu.jstree-apple-context li ul { margin-left:-4px; }

/* TODO: IE6 support - the `>` selectors */


// Walk through the dom nodes and insert the 'data-brackets-id' attribute at the

keep keep keep add keep keep keep keep keep