* Detach the backing Editor from the Document, disallowing setText(). The text content is

* stored back onto _text so other Document clients continue to have read-only access. ONLY

keep keep keep keep replace keep keep keep keep keep

// Keep the change listner bound to this doc instance so that we can call off while toggling master editor

<!DOCTYPE html>

	<meta charset="utf-8">
	<title>jQuery UI Slider - Slider scrollbar</title>
	<link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">

	<link rel="stylesheet" href="../demos.css">

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

Use a slider to manipulate the positioning of content on the page. In this case, it acts as a scrollbar with the potential to capture values if needed.


if (this._visibleRange) {   // TextRange also refs the Document

/*
 * 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(function (require, exports, module) {
    "use strict";

    var EditorManager       = require("editor/EditorManager"),
        EventDispatcher     = require("utils/EventDispatcher"),
        FileUtils           = require("file/FileUtils"),
        InMemoryFile        = require("document/InMemoryFile"),
        PerfUtils           = require("utils/PerfUtils"),
        LanguageManager     = require("language/LanguageManager"),
        CodeMirror          = require("thirdparty/CodeMirror/lib/codemirror"),
        _                   = require("thirdparty/lodash");

    /**
     * Model for the contents of a single file and its current modification state.
     * See DocumentManager documentation for important usage notes.
     *
     * Document dispatches these events:
     *
     * __change__ -- When the text of the editor changes (including due to undo/redo).
     *
     * Passes ({Document}, {ChangeList}), where ChangeList is an array
     * of change record objects. Each change record looks like:
     *
     *     { from: start of change, expressed as {line: <line number>, ch: <character offset>},
     *       to: end of change, expressed as {line: <line number>, ch: <chracter offset>},
     *       text: array of lines of text to replace existing text }
     *
     * The line and ch offsets are both 0-based.
     *
     * The ch offset in "from" is inclusive, but the ch offset in "to" is exclusive. For example,
     * an insertion of new content (without replacing existing content) is expressed by a range
     * where from and to are the same.
     *
     * If "from" and "to" are undefined, then this is a replacement of the entire text content.
     *
     * IMPORTANT: If you listen for the "change" event, you MUST also addRef() the document
     * (and releaseRef() it whenever you stop listening). You should also listen to the "deleted"
     * event.
     *
     * __deleted__ -- When the file for this document has been deleted. All views onto the document should
     * be closed. The document will no longer be editable or dispatch "change" events.
     *
     * __languageChanged__ -- When the value of getLanguage() has changed. 2nd argument is the old value,
     * 3rd argument is the new value.
     *
     * @constructor
     * @param {!File} file  Need not lie within the project.
     * @param {!Date} initialTimestamp  File's timestamp when we read it off disk.
     * @param {!string} rawText  Text content of the file.
     */
    function Document(file, initialTimestamp, rawText) {
        this.file = file;
        this._updateLanguage();
        this.refreshText(rawText, initialTimestamp, true);
    }

    EventDispatcher.makeEventDispatcher(Document.prototype);

    /**
     * List of editors which were initialized as master editors for this doc.
     */
    Document.prototype._associatedFullEditors = [];

    /**
     * Number of clients who want this Document to stay alive. The Document is listed in
     * DocumentManager._openDocuments whenever refCount > 0.
     */
    Document.prototype._refCount = 0;

    /**
     * The File for this document. Need not lie within the project.
     * If Document is untitled, this is an InMemoryFile object.
     * @type {!File}
     */
    Document.prototype.file = null;

    /**
     * The Language for this document. Will be resolved by file extension in the constructor
     * @type {!Language}
     */
    Document.prototype.language = null;

    /**
     * Whether this document has unsaved changes or not.
     * When this changes on any Document, DocumentManager dispatches a "dirtyFlagChange" event.
     * @type {boolean}
     */
    Document.prototype.isDirty = false;

    /**
     * Whether this document is currently being saved.
     * @type {boolean}
     */
    Document.prototype.isSaving = false;

    /**
     * What we expect the file's timestamp to be on disk. If the timestamp differs from this, then
     * it means the file was modified by an app other than Brackets.
     * @type {!Date}
     */
    Document.prototype.diskTimestamp = null;

    /**
     * The timestamp of the document at the point where the user last said to keep changes that conflict
     * with the current disk version. Can also be -1, indicating that the file was deleted on disk at the
     * last point when the user said to keep changes, or null, indicating that the user has not said to
     * keep changes.
     * Note that this is a time as returned by Date.getTime(), not a Date object.
     * @type {?Number}
     */
    Document.prototype.keepChangesTime = null;

    /**
     * True while refreshText() is in progress and change notifications shouldn't trip the dirty flag.
     * @type {boolean}
     */
    Document.prototype._refreshInProgress = false;

    /**
     * The text contents of the file, or null if our backing model is _masterEditor.
     * @type {?string}
     */
    Document.prototype._text = null;

    /**
     * Editor object representing the full-size editor UI for this document. May be null if Document
     * has not yet been modified or been the currentDocument; in that case, our backing model is the
     * string _text.
     * @type {?Editor}
     */
    Document.prototype._masterEditor = null;

    /**
     * The content's line-endings style. If a Document is created on empty text, or text with
     * inconsistent line endings, defaults to the current platform's standard endings.
     * @type {FileUtils.LINE_ENDINGS_CRLF|FileUtils.LINE_ENDINGS_LF}
     */
    Document.prototype._lineEndings = null;

    /** Add a ref to keep this Document alive */
    Document.prototype.addRef = function () {
        //console.log("+++REF+++ "+this);

        if (this._refCount === 0) {
            //console.log("+++ adding to open list");
            if (exports.trigger("_afterDocumentCreate", this)) {
                return;
            }
        }
        this._refCount++;
    };
    /** Remove a ref that was keeping this Document alive */
    Document.prototype.releaseRef = function () {
        //console.log("---REF--- "+this);

        this._refCount--;
        if (this._refCount < 0) {
            console.error("Document ref count has fallen below zero!");
            return;
        }
        if (this._refCount === 0) {
            //console.log("--- removing from open list");
            if (exports.trigger("_beforeDocumentDelete", this)) {
                return;
            }
        }
    };

    /**
     * Attach a backing Editor to the Document, enabling setText() to be called. Assumes Editor has
     * already been initialized with the value of getText(). ONLY Editor should call this (and only
     * when EditorManager has told it to act as the master editor).
     * @param {!Editor} masterEditor
     */
    Document.prototype._makeEditable = function (masterEditor) {
        if (this._masterEditor) {
            //Already a master editor is associated , so preserve the old editor in list of full editors
            if (this._associatedFullEditors.indexOf(this._masterEditor) < 0) {
                this._associatedFullEditors.push(this._masterEditor);
            }
        }

        this._text = null;
        this._masterEditor = masterEditor;

        masterEditor.on("change", this._handleEditorChange.bind(this));
    };

    /**
     * Detach the backing Editor from the Document, disallowing setText(). The text content is
     * stored back onto _text so other Document clients continue to have read-only access. ONLY
     * Editor.destroy() should call this.
     */
    Document.prototype._makeNonEditable = function () {
        if (!this._masterEditor) {
            console.error("Document is already non-editable");
        } else {
            // _text represents the raw text, so fetch without normalized line endings
            this._text = this.getText(true);
            this._associatedFullEditors.splice(this._associatedFullEditors.indexOf(this._masterEditor), 1);

            // Identify the most recently created full editor before this and set that as new master editor
            if (this._associatedFullEditors.length > 0) {
                this._masterEditor = this._associatedFullEditors[this._associatedFullEditors.length - 1];
            } else {
                this._masterEditor = null;
            }
        }
    };

    /**
     * Toggles the master editor which has gained focus from a pool of full editors
     * To be used internally by Editor only
     */
    Document.prototype._toggleMasterEditor = function (masterEditor) {
        // Do a check before processing the request to ensure inline editors are not being set as master editor
        if (this._associatedFullEditors.indexOf(masterEditor) >= 0) {
            if (this._masterEditor) {
                // Already a master editor is associated , so preserve the old editor in list of editors
                if (this._associatedFullEditors.indexOf(this._masterEditor) < 0) {
                    this._associatedFullEditors.push(this._masterEditor);
                }
            }
            this._masterEditor = masterEditor;
        }
    };

    /**
     * Disassociates an editor from this document if present in the associated editor list
     * To be used internally by Editor only when destroyed and not the current master editor for the document
     */
    Document.prototype._disassociateEditor = function (editor) {
        // Do a check before processing the request to ensure inline editors are not being handled
        if (this._associatedFullEditors.indexOf(editor) >= 0) {
            this._associatedFullEditors.splice(this._associatedFullEditors.indexOf(editor), 1);
        }
    };

    /**
     * Guarantees that _masterEditor is non-null. If needed, asks EditorManager to create a new master
     * editor bound to this Document (which in turn causes Document._makeEditable() to be called).
     * Should ONLY be called by Editor and Document.
     */
    Document.prototype._ensureMasterEditor = function () {
        if (!this._masterEditor) {
            EditorManager._createUnattachedMasterEditor(this);
        }
    };

    /**
     * Returns the document's current contents; may not be saved to disk yet. Whenever this
     * value changes, the Document dispatches a "change" event.
     *
     * @param {boolean=} useOriginalLineEndings If true, line endings in the result depend on the
     *      Document's line endings setting (based on OS & the original text loaded from disk).
     *      If false, line endings are always \n (like all the other Document text getter methods).
     * @return {string}
     */
    Document.prototype.getText = function (useOriginalLineEndings) {
        if (this._masterEditor) {
            // CodeMirror.getValue() always returns text with LF line endings; fix up to match line
            // endings preferred by the document, if necessary
            var codeMirrorText = this._masterEditor._codeMirror.getValue();
            if (useOriginalLineEndings) {
                if (this._lineEndings === FileUtils.LINE_ENDINGS_CRLF) {
                    return codeMirrorText.replace(/\n/g, "\r\n");
                }
            }
            return codeMirrorText;

        } else {
            // Optimized path that doesn't require creating master editor
            if (useOriginalLineEndings) {
                return this._text;
            } else {
                return Document.normalizeText(this._text);
            }
        }
    };

    /** Normalizes line endings the same way CodeMirror would */
    Document.normalizeText = function (text) {
        return text.replace(/\r\n/g, "\n");
    };

    /**
     * Sets the contents of the document. Treated as an edit. Line endings will be rewritten to
     * match the document's current line-ending style.
     * @param {!string} text The text to replace the contents of the document with.
     */
    Document.prototype.setText = function (text) {
        this._ensureMasterEditor();
        this._masterEditor._codeMirror.setValue(text);
        // _handleEditorChange() triggers "change" event
    };

    /**
     * @private
     * Triggers the appropriate events when a change occurs: "change" on the Document instance
     * and "documentChange" on the Document module.
     * @param {Object} changeList Changelist in CodeMirror format
     */
    Document.prototype._notifyDocumentChange = function (changeList) {
        this.trigger("change", this, changeList);
        exports.trigger("documentChange", this, changeList);
    };

    /**
     * Sets the contents of the document. Treated as reloading the document from disk: the document
     * will be marked clean with a new timestamp, the undo/redo history is cleared, and we re-check
     * the text's line-ending style. CAN be called even if there is no backing editor.
     * @param {!string} text The text to replace the contents of the document with.
     * @param {!Date} newTimestamp Timestamp of file at the time we read its new contents from disk.
     * @param {boolean} initial True if this is the initial load of the document. In that case,
     *      we don't send change events.
     */
    Document.prototype.refreshText = function (text, newTimestamp, initial) {
        var perfTimerName = PerfUtils.markStart("refreshText:\t" + (!this.file || this.file.fullPath));

        // If clean, don't transiently mark dirty during refresh
        // (we'll still send change events though, of course)
        this._refreshInProgress = true;

        if (this._masterEditor) {
            this._masterEditor._resetText(text);  // clears undo history too
            // _handleEditorChange() triggers "change" event for us
        } else {
            this._text = text;

            if (!initial) {
                // We fake a change record here that looks like CodeMirror's text change records, but
                // omits "from" and "to", by which we mean the entire text has changed.
                // TODO: Dumb to split it here just to join it again in the change handler, but this is
                // the CodeMirror change format. Should we document our change format to allow this to
                // either be an array of lines or a single string?
                this._notifyDocumentChange([{text: text.split(/\r?\n/)}]);
            }
        }
        this._updateTimestamp(newTimestamp);

        // If Doc was dirty before refresh, reset it to clean now (don't always call, to avoid no-op dirtyFlagChange events) Since
        // _resetText() above already ensures Editor state is clean, it's safe to skip _markClean() as long as our own state is already clean too.
        if (this.isDirty) {
            this._markClean();
        }
        this._refreshInProgress = false;

        // Sniff line-ending style
        this._lineEndings = FileUtils.sniffLineEndings(text);
        if (!this._lineEndings) {
            this._lineEndings = FileUtils.getPlatformLineEndings();
        }

        exports.trigger("_documentRefreshed", this);

        PerfUtils.addMeasurement(perfTimerName);
    };

    /**
     * Adds, replaces, or removes text. If a range is given, the text at that range is replaced with the
     * given new text; if text == "", then the entire range is effectively deleted. If 'end' is omitted,
     * then the new text is inserted at that point and all existing text is preserved. Line endings will
     * be rewritten to match the document's current line-ending style.
     *
     * IMPORTANT NOTE: Because of #1688, do not use this in cases where you might be
     * operating on a linked document (like the main document for an inline editor)
     * during an outer CodeMirror operation (like a key event that's handled by the
     * editor itself). A common case of this is code hints in inline editors. In
     * such cases, use `editor._codeMirror.replaceRange()` instead. This should be
     * fixed when we migrate to use CodeMirror's native document-linking functionality.
     *
     * @param {!string} text  Text to insert or replace the range with
     * @param {!{line:number, ch:number}} start  Start of range, inclusive (if 'to' specified) or insertion point (if not)
     * @param {?{line:number, ch:number}} end  End of range, exclusive; optional
     * @param {?string} origin  Optional string used to batch consecutive edits for undo.
     *     If origin starts with "+", then consecutive edits with the same origin will be batched for undo if
     *     they are close enough together in time.
     *     If origin starts with "*", then all consecutive edit with the same origin will be batched for
     *     undo.
     *     Edits with origins starting with other characters will not be batched.
     *     (Note that this is a higher level of batching than batchOperation(), which already batches all
     *     edits within it for undo. Origin batching works across operations.)
     */
    Document.prototype.replaceRange = function (text, start, end, origin) {
        this._ensureMasterEditor();
        this._masterEditor._codeMirror.replaceRange(text, start, end, origin);
        // _handleEditorChange() triggers "change" event
    };

    /**
     * Returns the characters in the given range. Line endings are normalized to '\n'.
     * @param {!{line:number, ch:number}} start  Start of range, inclusive
     * @param {!{line:number, ch:number}} end  End of range, exclusive
     * @return {!string}
     */
    Document.prototype.getRange = function (start, end) {
        this._ensureMasterEditor();
        return this._masterEditor._codeMirror.getRange(start, end);
    };

    /**
     * Returns the text of the given line (excluding any line ending characters)
     * @param {number} Zero-based line number
     * @return {!string}
     */
    Document.prototype.getLine = function (lineNum) {
        this._ensureMasterEditor();
        return this._masterEditor._codeMirror.getLine(lineNum);
    };

    /**
     * Batches a series of related Document changes. Repeated calls to replaceRange() should be wrapped in a
     * batch for efficiency. Begins the batch, calls doOperation(), ends the batch, and then returns.
     * @param {function()} doOperation
     */
    Document.prototype.batchOperation = function (doOperation) {
        this._ensureMasterEditor();

        var self = this;
        self._masterEditor._codeMirror.operation(doOperation);
    };

    /**
     * Handles changes from the master backing Editor. Changes are triggered either by direct edits
     * to that Editor's UI, OR by our setText()/refreshText() methods.
     * @private
     */
    Document.prototype._handleEditorChange = function (event, editor, changeList) {
        // TODO: This needs to be kept in sync with SpecRunnerUtils.createMockActiveDocument(). In the
        // future, we should fix things so that we either don't need mock documents or that this
        // is factored so it will just run in both.
        if (!this._refreshInProgress) {
            // Sync isDirty from CodeMirror state
            var wasDirty = this.isDirty;
            this.isDirty = !editor._codeMirror.isClean();

            // Notify if isDirty just changed (this also auto-adds us to working set if needed)
            if (wasDirty !== this.isDirty) {
                exports.trigger("_dirtyFlagChange", this);
            }
        }

        // Notify that Document's text has changed
        this._notifyDocumentChange(changeList);
    };

    /**
     * @private
     */
    Document.prototype._markClean = function () {
        this.isDirty = false;
        if (this._masterEditor) {
            this._masterEditor._codeMirror.markClean();
        }
        exports.trigger("_dirtyFlagChange", this);
    };

    /**
     * @private
     */
    Document.prototype._updateTimestamp = function (timestamp) {
        this.diskTimestamp = timestamp;
        // Clear the "keep changes" timestamp since it's no longer relevant.
        this.keepChangesTime = null;
    };

    /**
     * Called when the document is saved (which currently happens in DocumentCommandHandlers). Marks the
     * document not dirty and notifies listeners of the save.
     */
    Document.prototype.notifySaved = function () {
        if (!this._masterEditor) {
            console.log("### Warning: saving a Document that is not modifiable!");
        }

        this._markClean();

        // TODO: (issue #295) fetching timestamp async creates race conditions (albeit unlikely ones)
        var thisDoc = this;
        this.file.stat(function (err, stat) {
            if (!err) {
                thisDoc._updateTimestamp(stat.mtime);
            } else {
                console.log("Error updating timestamp after saving file: " + thisDoc.file.fullPath);
            }
            exports.trigger("_documentSaved", thisDoc);
        });
    };

    /**
     * Adjusts a given position taking a given replaceRange-type edit into account.
     * If the position is within the original edit range (start and end inclusive),
     * it gets pushed to the end of the content that replaced the range. Otherwise,
     * if it's after the edit, it gets adjusted so it refers to the same character
     * it did before the edit.
     * @param {!{line:number, ch: number}} pos The position to adjust.
     * @param {!Array.<string>} textLines The text of the change, split into an array of lines.
     * @param {!{line: number, ch: number}} start The start of the edit.
     * @param {!{line: number, ch: number}} end The end of the edit.
     * @return {{line: number, ch: number}} The adjusted position.
     */
    Document.prototype.adjustPosForChange = function (pos, textLines, start, end) {
        // Same as CodeMirror.adjustForChange(), but that's a private function
        // and Marijn would rather not expose it publicly.
        var change = { text: textLines, from: start, to: end };

        if (CodeMirror.cmpPos(pos, start) < 0) {
            return pos;
        }
        if (CodeMirror.cmpPos(pos, end) <= 0) {
            return CodeMirror.changeEnd(change);
        }

        var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1,
            ch = pos.ch;
        if (pos.line === change.to.line) {
            ch += CodeMirror.changeEnd(change).ch - change.to.ch;
        }
        return {line: line, ch: ch};
    };

    /**
     * Like _.each(), but if given a single item not in an array, acts as
     * if it were an array containing just that item.
     */
    function oneOrEach(itemOrArr, cb) {
        if (Array.isArray(itemOrArr)) {
            _.each(itemOrArr, cb);
        } else {
            cb(itemOrArr, 0);
        }
    }

    /**
     * Helper function for edit operations that operate on multiple selections. Takes an "edit list"
     * that specifies a list of replaceRanges that should occur, but where all the positions are with
     * respect to the document state before all the edits (i.e., you don't have to figure out how to fix
     * up the selections after each sub-edit). Edits must be non-overlapping (in original-document terms).
     * All the edits are done in a single batch.
     *
     * If your edits are structured in such a way that each individual edit would cause its associated
     * selection to be properly updated, then all you need to specify are the edits themselves, and the
     * selections will automatically be updated as the edits are performed. However, for some
     * kinds of edits, you need to fix up the selection afterwards. In that case, you can specify one
     * or more selections to be associated with each edit. Those selections are assumed to be in terms
     * of the document state after the edit, *as if* that edit were the only one being performed (i.e.,
     * you don't have to worry about adjusting for the effect of other edits). If you supply these selections,
     * then this function will adjust them as necessary for the effects of other edits, and then return a
     * flat list of all the selections, suitable for passing to `setSelections()`.
     *
     * @param {!Array.<{edit: {text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}
     *                        | Array.<{text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}>,
     *                  selection: ?{start:{line:number, ch:number}, end:{line:number, ch:number},
     *                              primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}
     *                        | ?Array.<{start:{line:number, ch:number}, end:{line:number, ch:number},
     *                                  primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}>} edits
     *     Specifies the list of edits to perform in a manner similar to CodeMirror's `replaceRange`. This array
     *     will be mutated.
     *
     *     `edit` is the edit to perform:
     *         `text` will replace the current contents of the range between `start` and `end`.
     *         If `end` is unspecified, the text is inserted at `start`.
     *         `start` and `end` should be positions relative to the document *ignoring* all other edit descriptions
     *         (i.e., as if you were only performing this one edit on the document).
     *     If any of the edits overlap, an error will be thrown.
     *
     *     If `selection` is specified, it should be a selection associated with this edit.
     *          If `isBeforeEdit` is set on the selection, the selection will be fixed up for this edit.
     *          If not, it won't be fixed up for this edit, meaning it should be expressed in terms of
     *          the document state after this individual edit is performed (ignoring any other edits).
     *          Note that if you were planning on just specifying `isBeforeEdit` for every selection, you can
     *          accomplish the same thing by simply not passing any selections and letting the editor update
     *          the existing selections automatically.
     *
     *     Note that `edit` and `selection` can each be either an individual edit/selection, or a group of
     *     edits/selections to apply in order. This can be useful if you need to perform multiple edits in a row
     *     and then specify a resulting selection that shouldn't be fixed up for any of those edits (but should be
     *     fixed up for edits related to other selections). It can also be useful if you have several selections
     *     that should ignore the effects of a given edit because you've fixed them up already (this commonly happens
     *     with line-oriented edits where multiple cursors on the same line should be ignored, but still tracked).
     *     Within an edit group, edit positions must be specified relative to previous edits within that group. Also,
     *     the total bounds of edit groups must not overlap (e.g. edits in one group can't surround an edit from another group).
     *
     * @param {?string} origin An optional edit origin that's passed through to each replaceRange().
     * @return {Array<{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed: boolean}>}
     *     The list of passed selections adjusted for the performed edits, if any.
     */
    Document.prototype.doMultipleEdits = function (edits, origin) {
        var self = this;

        // Sort the edits backwards, so we don't have to adjust the edit positions as we go along
        // (though we do have to adjust the selection positions).
        edits.sort(function (editDesc1, editDesc2) {
            var edit1 = (Array.isArray(editDesc1.edit) ? editDesc1.edit[0] : editDesc1.edit),
                edit2 = (Array.isArray(editDesc2.edit) ? editDesc2.edit[0] : editDesc2.edit);
            // Treat all no-op edits as if they should happen before all other edits (the order
            // doesn't really matter, as long as they sort out of the way of the real edits).
            if (!edit1) {
                return -1;
            } else if (!edit2) {
                return 1;
            } else {
                return CodeMirror.cmpPos(edit2.start, edit1.start);
            }
        });

        // Pull out the selections, in the same order as the edits.
        var result = _.cloneDeep(_.pluck(edits, "selection"));

        // Preflight the edits to specify "end" if unspecified and make sure they don't overlap.
        // (We don't want to do it during the actual edits, since we don't want to apply some of
        // the edits before we find out.)
        _.each(edits, function (editDesc, index) {
            oneOrEach(editDesc.edit, function (edit) {
                if (edit) {
                    if (!edit.end) {
                        edit.end = edit.start;
                    }
                    if (index > 0) {
                        var prevEditGroup = edits[index - 1].edit;
                        // The edits are in reverse order, so we want to make sure this edit ends
                        // before any of the previous ones start.
                        oneOrEach(prevEditGroup, function (prevEdit) {
                            if (CodeMirror.cmpPos(edit.end, prevEdit.start) > 0) {
                                throw new Error("Document.doMultipleEdits(): Overlapping edits specified");
                            }
                        });
                    }
                }
            });
        });

        // Perform the edits.
        this.batchOperation(function () {
            _.each(edits, function (editDesc, index) {
                // Perform this group of edits. The edit positions are guaranteed to be okay
                // since all the previous edits we've done have been later in the document. However,
                // we have to fix up any selections that overlap or come after the edit.
                oneOrEach(editDesc.edit, function (edit) {
                    if (edit) {
                        self.replaceRange(edit.text, edit.start, edit.end, origin);

                        // Fix up all the selections *except* the one(s) related to this edit list that
                        // are not "before-edit" selections.
                        var textLines = edit.text.split("\n");
                        _.each(result, function (selections, selIndex) {
                            if (selections) {
                                oneOrEach(selections, function (sel) {
                                    if (sel.isBeforeEdit || selIndex !== index) {
                                        sel.start = self.adjustPosForChange(sel.start, textLines, edit.start, edit.end);
                                        sel.end = self.adjustPosForChange(sel.end, textLines, edit.start, edit.end);
                                    }
                                });
                            }
                        });
                    }
                });
            });
        });

        result = _.chain(result)
            .filter(function (item) {
                return item !== undefined;
            })
            .flatten()
            .sort(function (sel1, sel2) {
                return CodeMirror.cmpPos(sel1.start, sel2.start);
            })
            .value();
        _.each(result, function (item) {
            delete item.isBeforeEdit;
        });
        return result;
    };

    /* (pretty toString(), to aid debugging) */
    Document.prototype.toString = function () {
        var dirtyInfo = (this.isDirty ? " (dirty!)" : " (clean)");
        var editorInfo = (this._masterEditor ? " (Editable)" : " (Non-editable)");
        var refInfo = " refs:" + this._refCount;
        return "[Document " + this.file.fullPath + dirtyInfo + editorInfo + refInfo + "]";
    };

    /**
     * Returns the language this document is written in.
     * The language returned is based on the file extension.
     * @return {Language} An object describing the language used in this document
     */
    Document.prototype.getLanguage = function () {
        return this.language;
    };

    /**
     * Updates the language to match the current mapping given by LanguageManager
     */
    Document.prototype._updateLanguage = function () {
        var oldLanguage = this.language;
        this.language = LanguageManager.getLanguageForPath(this.file.fullPath);
        if (oldLanguage && oldLanguage !== this.language) {
            this.trigger("languageChanged", oldLanguage, this.language);
        }
    };

    /** Called when Document.file has been modified (due to a rename) */
    Document.prototype._notifyFilePathChanged = function () {
        // File extension may have changed
        this._updateLanguage();
    };

    /**
     * Is this an untitled document?
     *
     * @return {boolean} - whether or not the document is untitled
     */
    Document.prototype.isUntitled = function () {
        return this.file instanceof InMemoryFile;
    };

    // We dispatch events from the module level, and the instance level. Instance events are wired up
    // in the Document constructor.
    EventDispatcher.makeEventDispatcher(exports);

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


var mkdirp = require('../').mkdirp;
var path = require('path');
var fs = require('fs');
var test = require('tap').test;

var ps = [ '', 'tmp' ];

for (var i = 0; i < 25; i++) {
    var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
    ps.push(dir);
}

var file = ps.join('/');

test('chmod-pre', function (t) {
    var mode = 0744
    mkdirp(file, mode, function (er) {
        t.ifError(er, 'should not error');
        fs.stat(file, function (er, stat) {
            t.ifError(er, 'should exist');
            t.ok(stat && stat.isDirectory(), 'should be directory');
            t.equal(stat && stat.mode & 0777, mode, 'should be 0744');
            t.end();
        });
    });
});

test('chmod', function (t) {
    var mode = 0755
    mkdirp(file, mode, function (er) {
        t.ifError(er, 'should not error');
        fs.stat(file, function (er, stat) {
            t.ifError(er, 'should exist');
            t.ok(stat && stat.isDirectory(), 'should be directory');
            t.end();
        });
    });
});


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

/**
 * NetworkAgent tracks all resources loaded by the remote debugger. Use
 * `wasURLRequested(url)` to query whether a resource was loaded.
 */
define(function NetworkAgent(require, exports, module) {
    "use strict";

    var Inspector = require("LiveDevelopment/Inspector/Inspector");

    var _urlRequested = {}; // url -> request info

    /** Return the URL without the query string
     * @param {string} URL
     */
    function _urlWithoutQueryString(url) {
        var index = url.search(/[#\?]/);
        if (index >= 0) {
            url = url.substr(0, index);
        }
        return url;
    }

    /** Return the resource information for a given URL
     * @param {string} url
     */
    function wasURLRequested(url) {
        return _urlRequested && _urlRequested[url];
    }

    function _logURL(url) {
        _urlRequested[_urlWithoutQueryString(url)] = true;
    }

    // WebInspector Event: Network.requestWillBeSent
    function _onRequestWillBeSent(event, res) {
        // res = {requestId, frameId, loaderId, documentURL, request, timestamp, initiator, stackTrace, redirectResponse}
        _logURL(res.request.url);
    }

    function _reset() {
        _urlRequested = {};
    }

    // WebInspector Event: Page.frameNavigated
    function _onFrameNavigated(event, res) {
        // res = {frame}
        // Clear log when navigating to a new page, but not if an iframe was loaded
        if (!res.frame.parentId) {
            _reset();
        }
        _logURL(res.frame.url);
    }

    /**
     * Enable the inspector Network domain
     * @return {jQuery.Promise} A promise resolved when the Network.enable() command is successful.
     */
    function enable() {
        return Inspector.Network.enable();
    }

    /** Initialize the agent */
    function load() {
        Inspector.Page.on("frameNavigated.NetworkAgent", _onFrameNavigated);
        Inspector.Network.on("requestWillBeSent.NetworkAgent", _onRequestWillBeSent);
    }

    /** Unload the agent */
    function unload() {
        _reset();
        Inspector.Page.off(".NetworkAgent");
        Inspector.Network.off(".NetworkAgent");
    }

    // Export public functions
    exports.wasURLRequested = wasURLRequested;
    exports.enable = enable;
    exports.load = load;
    exports.unload = unload;
});


/*
 * 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, regexp: true, indent: 4, maxerr: 50 */
/*global define */

/**
 * FileSystem is a model object representing a complete file system. This object creates
 * and manages File and Directory instances, dispatches events when the file system changes,
 * and provides methods for showing 'open' and 'save' dialogs.
 *
 * FileSystem automatically initializes when loaded. It depends on a pluggable "impl" layer, which
 * it loads itself but must be designated in the require.config() that loads FileSystem. For details
 * see: https://github.com/adobe/brackets/wiki/File-System-Implementations
 *
 * There are three ways to get File or Directory instances:
 *    * Use FileSystem.resolve() to convert a path to a File/Directory object. This will only
 *      succeed if the file/directory already exists.
 *    * Use FileSystem.getFileForPath()/FileSystem.getDirectoryForPath() if you know the
 *      file/directory already exists, or if you want to create a new entry.
 *    * Use Directory.getContents() to return all entries for the specified Directory.
 *
 * All paths passed *to* FileSystem APIs must be in the following format:
 *    * The path separator is "/" regardless of platform
 *    * Paths begin with "/" on Mac/Linux and "c:/" (or some other drive letter) on Windows
 *
 * All paths returned *from* FileSystem APIs additionally meet the following guarantees:
 *    * No ".." segments
 *    * No consecutive "/"s
 *    * Paths to a directory always end with a trailing "/"
 * (Because FileSystem normalizes paths automatically, paths passed *to* FileSystem do not need
 * to meet these requirements)
 *
 * FileSystem dispatches the following events:
 * (NOTE: attach to these events via `FileSystem.on()` - not `$(FileSystem).on()`)
 *
 * __change__ - Sent whenever there is a change in the file system. The handler
 *   is passed up to three arguments: the changed entry and, if that changed entry
 *   is a Directory, a list of entries added to the directory and a list of entries
 *   removed from the Directory. The entry argument can be:
 *   *  a File - the contents of the file have changed, and should be reloaded.
 *   *  a Directory - an immediate child of the directory has been added, removed,
 *      or renamed/moved. Not triggered for "grandchildren".
 *      - If the added & removed arguments are null, we don't know what was added/removed:
 *        clients should assume the whole subtree may have changed.
 *      - If the added & removed arguments are 0-length, there's no net change in the set
 *        of files but a file may have been replaced: clients should assume the contents
 *        of any immediate child file may have changed.
 *   *  null - a 'wholesale' change happened, and you should assume everything may
 *      have changed.
 *   For changes made externally, there may be a significant delay before a "change" event
 *   is dispatched.
 *
 * __rename__ - Sent whenever a File or Directory is renamed. All affected File and Directory
 *   objects have been updated to reflect the new path by the time this event is dispatched.
 *   This event should be used to trigger any UI updates that may need to occur when a path
 *   has changed. Note that these events will only be sent for rename operations that happen
 *   within the filesystem. If a file is renamed externally, a change event on the parent
 *   directory will be sent instead.
 *
 * FileSystem may perform caching. But it guarantees:
 *    * File contents & metadata - reads are guaranteed to be up to date (cached data is not used
 *      without first veryifying it is up to date).
 *    * Directory structure / file listing - reads may return cached data immediately, which may not
 *      reflect external changes made recently. (However, changes made via FileSystem itself are always
 *      reflected immediately, as soon as the change operation's callback signals success).
 *
 * The FileSystem doesn't directly read or write contents--this work is done by a low-level
 * implementation object. This allows client code to use the FileSystem API without having to
 * worry about the underlying storage, which could be a local filesystem or a remote server.
 */
define(function (require, exports, module) {
    "use strict";

    var Directory       = require("filesystem/Directory"),
        File            = require("filesystem/File"),
        FileIndex       = require("filesystem/FileIndex"),
        FileSystemError = require("filesystem/FileSystemError"),
        WatchedRoot     = require("filesystem/WatchedRoot"),
        EventDispatcher = require("utils/EventDispatcher");

    /**
     * The FileSystem is not usable until init() signals its callback.
     * @constructor
     */
    function FileSystem() {
        // Create a file index
        this._index = new FileIndex();

        // Initialize the set of watched roots
        this._watchedRoots = {};

        // Initialize the watch/unwatch request queue
        this._watchRequests = [];

        // Initialize the queue of pending external changes
        this._externalChanges = [];
    }
    EventDispatcher.makeEventDispatcher(FileSystem.prototype);

    /**
     * The low-level file system implementation used by this object.
     * This is set in the init() function and cannot be changed.
     */
    FileSystem.prototype._impl = null;

    /**
     * The FileIndex used by this object. This is initialized in the constructor.
     */
    FileSystem.prototype._index = null;

    /**
     * Refcount of any pending filesystem mutation operations (e.g., writes,
     * unlinks, etc.). Used to ensure that external change events aren't processed
     * until after index fixups, operation-specific callbacks, and internal change
     * events are complete. (This is important for distinguishing rename from
     * an unrelated delete-add pair).
     * @type {number}
     */
    FileSystem.prototype._activeChangeCount = 0;

    // For unit testing only
    FileSystem.prototype._getActiveChangeCount = function () {
        return this._activeChangeCount;
    };

    /**
     * Queue of arguments with which to invoke _handleExternalChanges(); triggered
     * once _activeChangeCount drops to zero.
     * @type {!Array.<{path:?string, stat:FileSystemStats=}>}
     */
    FileSystem.prototype._externalChanges = null;

    /** Process all queued watcher results, by calling _handleExternalChange() on each */
    FileSystem.prototype._triggerExternalChangesNow = function () {
        this._externalChanges.forEach(function (info) {
            this._handleExternalChange(info.path, info.stat);
        }, this);
        this._externalChanges.length = 0;
    };

    /**
     * Receives a result from the impl's watcher callback, and either processes it
     * immediately (if _activeChangeCount is 0) or otherwise stores it for later
     * processing.
     * @param {?string} path The fullPath of the changed entry
     * @param {FileSystemStats=} stat An optional stat object for the changed entry
     */
    FileSystem.prototype._enqueueExternalChange = function (path, stat) {
        this._externalChanges.push({path: path, stat: stat});
        if (!this._activeChangeCount) {
            this._triggerExternalChangesNow();
        }
    };

    /**
     * The queue of pending watch/unwatch requests.
     * @type {Array.<{fn: function(), cb: function()}>}
     */
    FileSystem.prototype._watchRequests = null;

    /**
     * Dequeue and process all pending watch/unwatch requests
     */
    FileSystem.prototype._dequeueWatchRequest = function () {
        if (this._watchRequests.length > 0) {
            var request = this._watchRequests[0];

            request.fn.call(null, function () {
                // Apply the given callback
                var callbackArgs = arguments;
                try {
                    request.cb.apply(null, callbackArgs);
                } finally {
                    // Process the remaining watch/unwatch requests
                    this._watchRequests.shift();
                    this._dequeueWatchRequest();
                }
            }.bind(this));
        }
    };

    /**
     * Enqueue a new watch/unwatch request.
     *
     * @param {function()} fn - The watch/unwatch request function.
     * @param {callback()} cb - The callback for the provided watch/unwatch
     *      request function.
     */
    FileSystem.prototype._enqueueWatchRequest = function (fn, cb) {
        // Enqueue the given watch/unwatch request
        this._watchRequests.push({fn: fn, cb: cb});

        // Begin processing the queue if it is not already being processed
        if (this._watchRequests.length === 1) {
            this._dequeueWatchRequest();
        }
    };

    /**
     * The set of watched roots, encoded as a mapping from full paths to WatchedRoot
     * objects which contain a file entry, filter function, and an indication of
     * whether the watched root is inactive, starting up or fully active.
     *
     * @type {Object.<string, WatchedRoot>}
     */
    FileSystem.prototype._watchedRoots = null;

    /**
     * Finds a parent watched root for a given path, or returns null if a parent
     * watched root does not exist.
     *
     * @param {string} fullPath The child path for which a parent watched root is to be found
     * @return {?{entry: FileSystemEntry, filter: function(string) boolean}} The parent
     *      watched root, if it exists, or null.
     */
    FileSystem.prototype._findWatchedRootForPath = function (fullPath) {
        var watchedRoot = null;

        Object.keys(this._watchedRoots).some(function (watchedPath) {
            if (fullPath.indexOf(watchedPath) === 0) {
                watchedRoot = this._watchedRoots[watchedPath];
                return true;
            }
        }, this);

        return watchedRoot;
    };

    /**
     * Helper function to watch or unwatch a filesystem entry beneath a given
     * watchedRoot.
     *
     * @private
     * @param {FileSystemEntry} entry - The FileSystemEntry to watch. Must be a
     *      non-strict descendent of watchedRoot.entry.
     * @param {WatchedRoot} watchedRoot - See FileSystem._watchedRoots.
     * @param {function(?string)} callback - A function that is called once the
     *      watch is complete, possibly with a FileSystemError string.
     * @param {boolean} shouldWatch - Whether the entry should be watched (true)
     *      or unwatched (false).
     */
    FileSystem.prototype._watchOrUnwatchEntry = function (entry, watchedRoot, callback, shouldWatch) {
        var impl = this._impl,
            recursiveWatch = impl.recursiveWatch,
            commandName = shouldWatch ? "watchPath" : "unwatchPath";

        if (recursiveWatch) {
            // The impl can watch the entire subtree with one call on the root (we also fall into this case for
            // unwatch, although that never requires us to do the recursion - see similar final case below)
            if (entry !== watchedRoot.entry) {
                // Watch and unwatch calls to children of the watched root are
                // no-ops if the impl supports recursiveWatch
                callback(null);
            } else {
                // The impl will handle finding all subdirectories to watch.
                this._enqueueWatchRequest(function (requestCb) {
                    impl[commandName].call(impl, entry.fullPath, requestCb);
                }.bind(this), callback);
            }
        } else if (shouldWatch) {
            // The impl can't handle recursive watch requests, so it's up to the
            // filesystem to recursively watch all subdirectories.
            this._enqueueWatchRequest(function (requestCb) {
                // First construct a list of entries to watch or unwatch
                var entriesToWatch = [];

                var visitor = function (child) {
                    if (watchedRoot.filter(child.name, child.parentPath)) {
                        if (child.isDirectory || child === watchedRoot.entry) {
                            entriesToWatch.push(child);
                        }
                        return true;
                    }
                    return false;
                };

                entry.visit(visitor, function (err) {
                    if (err) {
                        // Unexpected error
                        requestCb(err);
                        return;
                    }

                    // Then watch or unwatched all these entries
                    var count = entriesToWatch.length;
                    if (count === 0) {
                        requestCb(null);
                        return;
                    }

                    var watchCallback = function () {
                        if (--count === 0) {
                            requestCb(null);
                        }
                    };

                    entriesToWatch.forEach(function (entry) {
                        impl.watchPath(entry.fullPath, watchCallback);
                    });
                });
            }, callback);
        } else {
            // Unwatching never requires enumerating the subfolders (which is good, since after a
            // delete/rename we may be unable to do so anyway)
            this._enqueueWatchRequest(function (requestCb) {
                impl.unwatchPath(entry.fullPath, requestCb);
            }, callback);
        }
    };

    /**
     * Watch a filesystem entry beneath a given watchedRoot.
     *
     * @private
     * @param {FileSystemEntry} entry - The FileSystemEntry to watch. Must be a
     *      non-strict descendent of watchedRoot.entry.
     * @param {WatchedRoot} watchedRoot - See FileSystem._watchedRoots.
     * @param {function(?string)} callback - A function that is called once the
     *      watch is complete, possibly with a FileSystemError string.
     */
    FileSystem.prototype._watchEntry = function (entry, watchedRoot, callback) {
        this._watchOrUnwatchEntry(entry, watchedRoot, callback, true);
    };

    /**
     * Unwatch a filesystem entry beneath a given watchedRoot.
     *
     * @private
     * @param {FileSystemEntry} entry - The FileSystemEntry to watch. Must be a
     *      non-strict descendent of watchedRoot.entry.
     * @param {WatchedRoot} watchedRoot - See FileSystem._watchedRoots.
     * @param {function(?string)} callback - A function that is called once the
     *      watch is complete, possibly with a FileSystemError string.
     */
    FileSystem.prototype._unwatchEntry = function (entry, watchedRoot, callback) {
        this._watchOrUnwatchEntry(entry, watchedRoot, function (err) {
            // Make sure to clear cached data for all unwatched entries because
            // entries always return cached data if it exists!
            this._index.visitAll(function (child) {
                if (child.fullPath.indexOf(entry.fullPath) === 0) {
                    // 'true' so entry doesn't try to clear its immediate childrens' caches too. That would be redundant
                    // with the visitAll() here, and could be slow if we've already cleared its parent (#7150).
                    child._clearCachedData(true);
                }
            }.bind(this));

            callback(err);
        }.bind(this), false);
    };

    /**
     * Initialize this FileSystem instance.
     *
     * @param {FileSystemImpl} impl The back-end implementation for this
     *      FileSystem instance.
     */
    FileSystem.prototype.init = function (impl) {
        console.assert(!this._impl, "This FileSystem has already been initialized!");

        var changeCallback = this._enqueueExternalChange.bind(this),
            offlineCallback = this._unwatchAll.bind(this);

        this._impl = impl;
        this._impl.initWatchers(changeCallback, offlineCallback);
    };

    /**
     * Close a file system. Clear all caches, indexes, and file watchers.
     */
    FileSystem.prototype.close = function () {
        this._impl.unwatchAll();
        this._index.clear();
    };

    /**
     * Returns true if the given path should be automatically added to the index & watch list when one of its ancestors
     * is a watch-root. (Files are added automatically when the watch-root is first established, or later when a new
     * directory is created and its children enumerated).
     *
     * Entries explicitly created via FileSystem.getFile/DirectoryForPath() are *always* added to the index regardless
     * of this filtering - but they will not be watched if the watch-root's filter excludes them.
     *
     * @param {string} path Full path
     * @param {string} name Name portion of the path
     */
    FileSystem.prototype._indexFilter = function (path, name) {
        var parentRoot = this._findWatchedRootForPath(path);

        if (parentRoot) {
            return parentRoot.filter(name, path);
        }

        // It might seem more sensible to return false (exclude) for files outside the watch roots, but
        // that would break usage of appFileSystem for 'system'-level things like enumerating extensions.
        // (Or in general, Directory.getContents() for any Directory outside the watch roots).
        return true;
    };

    /**
     * Indicates that a filesystem-mutating operation has begun. As long as there
     * are changes taking place, change events from the external watchers are
     * blocked and queued, to be handled once changes have finished. This is done
     * because for mutating operations that originate from within the filesystem,
     * synthetic change events are fired that do not depend on external file
     * watchers, and we prefer the former over the latter for the following
     * reasons: 1) there is no delay; and 2) they may have higher fidelity ---
     * e.g., a rename operation can be detected as such, instead of as a nearly
     * simultaneous addition and deletion.
     *
     * All operations that mutate the file system MUST begin with a call to
     * _beginChange and must end with a call to _endChange.
     */
    FileSystem.prototype._beginChange = function () {
        this._activeChangeCount++;
        //console.log("> beginChange  -> " + this._activeChangeCount);
    };

    /**
     * Indicates that a filesystem-mutating operation has completed. See
     * FileSystem._beginChange above.
     */
    FileSystem.prototype._endChange = function () {
        this._activeChangeCount--;
        //console.log("< endChange    -> " + this._activeChangeCount);

        if (this._activeChangeCount < 0) {
            console.error("FileSystem _activeChangeCount has fallen below zero!");
        }

        if (!this._activeChangeCount) {
            this._triggerExternalChangesNow();
        }
    };

    /**
     * Determines whether or not the supplied path is absolute, as opposed to relative.
     *
     * @param {!string} fullPath
     * @return {boolean} True if the fullPath is absolute and false otherwise.
     */
    FileSystem.isAbsolutePath = function (fullPath) {
        return (fullPath[0] === "/" || (fullPath[1] === ":" && fullPath[2] === "/"));
    };

    function _ensureTrailingSlash(path) {
        if (path[path.length - 1] !== "/") {
            path += "/";
        }

        return path;
    }

    /*
     * Matches continguous groups of forward slashes
     * @const
     */
    var _DUPLICATED_SLASH_RE = /\/{2,}/g;

    /**
     * Returns a canonical version of the path: no duplicated "/"es, no ".."s,
     * and directories guaranteed to end in a trailing "/"
     * @param {!string} path  Absolute path, using "/" as path separator
     * @param {boolean=} isDirectory
     * @return {!string}
     */
    FileSystem.prototype._normalizePath = function (path, isDirectory) {

        if (!FileSystem.isAbsolutePath(path)) {
            throw new Error("Paths must be absolute: '" + path + "'");  // expect only absolute paths
        }

        var isUNCPath = this._impl.normalizeUNCPaths && path.search(_DUPLICATED_SLASH_RE) === 0;

        // Remove duplicated "/"es
        path = path.replace(_DUPLICATED_SLASH_RE, "/");

        // Remove ".." segments
        if (path.indexOf("..") !== -1) {
            var segments = path.split("/"),
                i;
            for (i = 1; i < segments.length; i++) {
                if (segments[i] === "..") {
                    if (i < 2) {
                        throw new Error("Invalid absolute path: '" + path + "'");
                    }
                    segments.splice(i - 1, 2);
                    i -= 2; // compensate so we start on the right index next iteration
                }
            }
            path = segments.join("/");
        }

        if (isDirectory) {
            // Make sure path DOES include trailing slash
            path = _ensureTrailingSlash(path);
        }

        if (isUNCPath) {
            // Restore the leading double slash that was removed previously
            path = "/" + path;
        }

        return path;
    };

    /**
     * This method adds an entry for a file in the file Index. Files on disk are added
     * to the file index either on load or on open. This method is primarily needed to add
     * in memory files to the index
     *
     * @param {File} The fileEntry which needs to be added
     * @param {String} The full path to the file
     */
    FileSystem.prototype.addEntryForPathIfRequired = function (fileEntry, path) {
        var entry = this._index.getEntry(path);

        if (!entry) {
            this._index.addEntry(fileEntry);
        }
    };

    /**
     * Return a (strict subclass of a) FileSystemEntry object for the specified
     * path using the provided constuctor. For now, the provided constructor
     * should be either File or Directory.
     *
     * @private
     * @param {function(string, FileSystem)} EntryConstructor Constructor with
     *      which to initialize new FileSystemEntry objects.
     * @param {string} path Absolute path of file.
     * @return {File|Directory} The File or Directory object. This file may not
     *      yet exist on disk.
     */
    FileSystem.prototype._getEntryForPath = function (EntryConstructor, path) {
        var isDirectory = EntryConstructor === Directory;
        path = this._normalizePath(path, isDirectory);
        var entry = this._index.getEntry(path);

        if (!entry) {
            entry = new EntryConstructor(path, this);
            this._index.addEntry(entry);
        }

        return entry;
    };

    /**
     * Return a File object for the specified path.
     *
     * @param {string} path Absolute path of file.
     *
     * @return {File} The File object. This file may not yet exist on disk.
     */
    FileSystem.prototype.getFileForPath = function (path) {
        return this._getEntryForPath(File, path);
    };

    /**
     * Return a Directory object for the specified path.
     *
     * @param {string} path Absolute path of directory.
     *
     * @return {Directory} The Directory object. This directory may not yet exist on disk.
     */
    FileSystem.prototype.getDirectoryForPath = function (path) {
        return this._getEntryForPath(Directory, path);
    };

    /**
     * Resolve a path.
     *
     * @param {string} path The path to resolve
     * @param {function (?string, FileSystemEntry=, FileSystemStats=)} callback Callback resolved
     *      with a FileSystemError string or with the entry for the provided path.
     */
    FileSystem.prototype.resolve = function (path, callback) {
        var normalizedPath = this._normalizePath(path, false),
            item = this._index.getEntry(normalizedPath);

        if (!item) {
            normalizedPath = _ensureTrailingSlash(normalizedPath);
            item = this._index.getEntry(normalizedPath);
        }

        if (item) {
            item.stat(function (err, stat) {
                if (err) {
                    callback(err);
                    return;
                }

                callback(null, item, stat);
            });
        } else {
            this._impl.stat(path, function (err, stat) {
                if (err) {
                    callback(err);
                    return;
                }

                if (stat.isFile) {
                    item = this.getFileForPath(path);
                } else {
                    item = this.getDirectoryForPath(path);
                }

                if (item._isWatched()) {
                    item._stat = stat;
                }

                callback(null, item, stat);
            }.bind(this));
        }
    };

    /**
     * Show an "Open" dialog and return the file(s)/directories selected by the user.
     *
     * @param {boolean} allowMultipleSelection Allows selecting more than one file at a time
     * @param {boolean} chooseDirectories Allows directories to be opened
     * @param {string} title The title of the dialog
     * @param {string} initialPath The folder opened inside the window initially. If initialPath
     *                          is not set, or it doesn't exist, the window would show the last
     *                          browsed folder depending on the OS preferences
     * @param {?Array.<string>} fileTypes (Currently *ignored* except on Mac - https://trello.com/c/430aXkpq)
     *                          List of extensions that are allowed to be opened, without leading ".".
     *                          Null or empty array allows all files to be selected. Not applicable
     *                          when chooseDirectories = true.
     * @param {function (?string, Array.<string>=)} callback Callback resolved with a FileSystemError
     *                          string or the selected file(s)/directories. If the user cancels the
     *                          open dialog, the error will be falsy and the file/directory array will
     *                          be empty.
     */
    FileSystem.prototype.showOpenDialog = function (allowMultipleSelection,
                            chooseDirectories,
                            title,
                            initialPath,
                            fileTypes,
                            callback) {

        this._impl.showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, callback);
    };

    /**
     * Show a "Save" dialog and return the path of the file to save.
     *
     * @param {string} title The title of the dialog.
     * @param {string} initialPath The folder opened inside the window initially. If initialPath
     *                          is not set, or it doesn't exist, the window would show the last
     *                          browsed folder depending on the OS preferences.
     * @param {string} proposedNewFilename Provide a new file name for the user. This could be based on
     *                          on the current file name plus an additional suffix
     * @param {function (?string, string=)} callback Callback that is resolved with a FileSystemError
     *                          string or the name of the file to save. If the user cancels the save,
     *                          the error will be falsy and the name will be empty.
     */
    FileSystem.prototype.showSaveDialog = function (title, initialPath, proposedNewFilename, callback) {
        this._impl.showSaveDialog(title, initialPath, proposedNewFilename, callback);
    };

    /**
     * Fire a rename event. Clients listen for these events using FileSystem.on.
     *
     * @param {string} oldPath The entry's previous fullPath
     * @param {string} newPath The entry's current fullPath
     */
    FileSystem.prototype._fireRenameEvent = function (oldPath, newPath) {
        this.trigger("rename", oldPath, newPath);
    };

    /**
     * Fire a change event. Clients listen for these events using FileSystem.on.
     *
     * @param {File|Directory} entry The entry that has changed
     * @param {Array<File|Directory>=} added If the entry is a directory, this
     *      is a set of new entries in the directory.
     * @param {Array<File|Directory>=} removed If the entry is a directory, this
     *      is a set of removed entries from the directory.
     */
    FileSystem.prototype._fireChangeEvent = function (entry, added, removed) {
        this.trigger("change", entry, added, removed);
    };

    /**
     * @private
     * Notify the system when an entry name has changed.
     *
     * @param {string} oldFullPath
     * @param {string} newFullPath
     * @param {boolean} isDirectory
     */
    FileSystem.prototype._handleRename = function (oldFullPath, newFullPath, isDirectory) {
        // Update all affected entries in the index
        this._index.entryRenamed(oldFullPath, newFullPath, isDirectory);
    };

    /**
     * Notify the filesystem that the given directory has changed. Updates the filesystem's
     * internal state as a result of the change, and calls back with the set of added and
     * removed entries. Mutating FileSystemEntry operations should call this method before
     * applying the operation's callback, and pass along the resulting change sets in the
     * internal change event.
     *
     * @param {Directory} directory The directory that has changed.
     * @param {function(Array<File|Directory>=, Array<File|Directory>=)} callback
     *      The callback that will be applied to a set of added and a set of removed
     *      FileSystemEntry objects.
     */
    FileSystem.prototype._handleDirectoryChange = function (directory, callback) {
        var oldContents = directory._contents;

        directory._clearCachedData();
        directory.getContents(function (err, contents) {
            var addedEntries = oldContents && contents.filter(function (entry) {
                return oldContents.indexOf(entry) === -1;
            });

            var removedEntries = oldContents && oldContents.filter(function (entry) {
                return contents.indexOf(entry) === -1;
            });

            // If directory is not watched, clear children's caches manually.
            var watchedRoot = this._findWatchedRootForPath(directory.fullPath);
            if (!watchedRoot || !watchedRoot.filter(directory.name, directory.parentPath)) {
                this._index.visitAll(function (entry) {
                    if (entry.fullPath.indexOf(directory.fullPath) === 0) {
                        // Passing 'true' for a similar reason as in _unwatchEntry() - see #7150
                        entry._clearCachedData(true);
                    }
                }.bind(this));

                callback(addedEntries, removedEntries);
                return;
            }

            var addedCounter = addedEntries ? addedEntries.length : 0,
                removedCounter = removedEntries ? removedEntries.length : 0,
                counter = addedCounter + removedCounter;

            if (counter === 0) {
                callback(addedEntries, removedEntries);
                return;
            }

            var watchOrUnwatchCallback = function (err) {
                if (err) {
                    console.error("FileSystem error in _handleDirectoryChange after watch/unwatch entries: " + err);
                }

                if (--counter === 0) {
                    callback(addedEntries, removedEntries);
                }
            };

            if (addedEntries) {
                addedEntries.forEach(function (entry) {
                    this._watchEntry(entry, watchedRoot, watchOrUnwatchCallback);
                }, this);
            }

            if (removedEntries) {
                removedEntries.forEach(function (entry) {
                    this._unwatchEntry(entry, watchedRoot, watchOrUnwatchCallback);
                }, this);
            }
        }.bind(this));
    };

    /**
     * @private
     * Processes a result from the file/directory watchers. Watch results are sent from the low-level implementation
     * whenever a directory or file is changed.
     *
     * @param {string} path The path that changed. This could be a file or a directory.
     * @param {FileSystemStats=} stat Optional stat for the item that changed. This param is not always
     *         passed.
     */
    FileSystem.prototype._handleExternalChange = function (path, stat) {

        if (!path) {
            // This is a "wholesale" change event; clear all caches
            this._index.visitAll(function (entry) {
                // Passing 'true' for a similar reason as in _unwatchEntry() - see #7150
                entry._clearCachedData(true);
            });

            this._fireChangeEvent(null);
            return;
        }

        path = this._normalizePath(path, false);

        var entry = this._index.getEntry(path);
        if (entry) {
            var oldStat = entry._stat;
            if (entry.isFile) {
                // Update stat and clear contents, but only if out of date
                if (!(stat && oldStat && stat.mtime.getTime() === oldStat.mtime.getTime())) {
                    entry._clearCachedData();
                    entry._stat = stat;
                    this._fireChangeEvent(entry);
                }
            } else {
                this._handleDirectoryChange(entry, function (added, removed) {
                    entry._stat = stat;

                    if (entry._isWatched()) {
                        // We send a change even if added & removed are both zero-length. Something may still have changed,
                        // e.g. a file may have been quickly removed & re-added before we got a chance to reread the directory
                        // listing.
                        this._fireChangeEvent(entry, added, removed);
                    }
                }.bind(this));
            }
        }
    };

    /**
     * Clears all cached content. Because of the performance implications of this, this should only be used if
     * there is a suspicion that the file system has not been updated through the normal file watchers
     * mechanism.
     */
    FileSystem.prototype.clearAllCaches = function () {
        this._handleExternalChange(null);
    };

    /**
     * Start watching a filesystem root entry.
     *
     * @param {FileSystemEntry} entry - The root entry to watch. If entry is a directory,
     *      all subdirectories that aren't explicitly filtered will also be watched.
     * @param {function(string): boolean} filter - Returns true if a particular item should
     *      be watched, given its name (not full path). Items that are ignored are also
     *      filtered from Directory.getContents() results within this subtree.
     * @param {function(?string)=} callback - A function that is called when the watch has
     *      completed. If the watch fails, the function will have a non-null FileSystemError
     *      string parametr.
     */
    FileSystem.prototype.watch = function (entry, filter, callback) {
        var fullPath = entry.fullPath;

        callback = callback || function () {};

        var watchingParentRoot = this._findWatchedRootForPath(fullPath);
        if (watchingParentRoot &&
                (watchingParentRoot.status === WatchedRoot.STARTING ||
                 watchingParentRoot.status === WatchedRoot.ACTIVE)) {
            callback("A parent of this root is already watched");
            return;
        }

        var watchingChildRoot = Object.keys(this._watchedRoots).some(function (path) {
            var watchedRoot = this._watchedRoots[path],
                watchedPath = watchedRoot.entry.fullPath;

            return watchedPath.indexOf(fullPath) === 0;
        }, this);

        if (watchingChildRoot &&
                (watchingChildRoot.status === WatchedRoot.STARTING ||
                 watchingChildRoot.status === WatchedRoot.ACTIVE)) {
            callback("A child of this root is already watched");
            return;
        }

        var watchedRoot = new WatchedRoot(entry, filter);

        this._watchedRoots[fullPath] = watchedRoot;

        // Enter the STARTING state early to indiate that watched Directory
        // objects may cache their contents. See FileSystemEntry._isWatched.
        watchedRoot.status = WatchedRoot.STARTING;

        this._watchEntry(entry, watchedRoot, function (err) {
            if (err) {
                console.warn("Failed to watch root: ", entry.fullPath, err);
                delete this._watchedRoots[fullPath];
                callback(err);
                return;
            }

            watchedRoot.status = WatchedRoot.ACTIVE;

            callback(null);
        }.bind(this));
    };

    /**
     * Stop watching a filesystem root entry.
     *
     * @param {FileSystemEntry} entry - The root entry to stop watching. The unwatch will
     *      if the entry is not currently being watched.
     * @param {function(?string)=} callback - A function that is called when the unwatch has
     *      completed. If the unwatch fails, the function will have a non-null FileSystemError
     *      string parameter.
     */
    FileSystem.prototype.unwatch = function (entry, callback) {
        var fullPath = entry.fullPath,
            watchedRoot = this._watchedRoots[fullPath];

        callback = callback || function () {};

        if (!watchedRoot) {
            callback(FileSystemError.ROOT_NOT_WATCHED);
            return;
        }

        // Mark this as inactive, but don't delete the entry until the unwatch is complete.
        // This is useful for making sure we don't try to concurrently watch overlapping roots.
        watchedRoot.status = WatchedRoot.INACTIVE;

        this._unwatchEntry(entry, watchedRoot, function (err) {
            delete this._watchedRoots[fullPath];

            this._index.visitAll(function (child) {
                if (child.fullPath.indexOf(entry.fullPath) === 0) {
                    this._index.removeEntry(child);
                }
            }.bind(this));

            if (err) {
                console.warn("Failed to unwatch root: ", entry.fullPath, err);
                callback(err);
                return;
            }

            callback(null);
        }.bind(this));
    };

    /**
     * Unwatch all watched roots. Calls unwatch on the underlying impl for each
     * watched root and ignores errors.
     * @private
     */
    FileSystem.prototype._unwatchAll = function () {
        console.warn("File watchers went offline!");

        Object.keys(this._watchedRoots).forEach(function (path) {
            var watchedRoot = this._watchedRoots[path];

            watchedRoot.status = WatchedRoot.INACTIVE;
            delete this._watchedRoots[path];
            this._unwatchEntry(watchedRoot.entry, watchedRoot, function () {
                console.warn("Watching disabled for", watchedRoot.entry.fullPath);
            });
        }, this);

        // Fire a wholesale change event, clearing all caches and request that
        // clients manually update their state.
        this._handleExternalChange(null);
    };

    // The singleton instance
    var _instance;

    function _wrap(func) {
        return function () {
            return func.apply(_instance, arguments);
        };
    }

    // Export public methods as proxies to the singleton instance
    exports.init = _wrap(FileSystem.prototype.init);
    exports.close = _wrap(FileSystem.prototype.close);
    exports.shouldShow = _wrap(FileSystem.prototype.shouldShow);
    exports.getFileForPath = _wrap(FileSystem.prototype.getFileForPath);
    exports.addEntryForPathIfRequired = _wrap(FileSystem.prototype.addEntryForPathIfRequired);
    exports.getDirectoryForPath = _wrap(FileSystem.prototype.getDirectoryForPath);
    exports.resolve = _wrap(FileSystem.prototype.resolve);
    exports.showOpenDialog = _wrap(FileSystem.prototype.showOpenDialog);
    exports.showSaveDialog = _wrap(FileSystem.prototype.showSaveDialog);
    exports.watch = _wrap(FileSystem.prototype.watch);
    exports.unwatch = _wrap(FileSystem.prototype.unwatch);
    exports.clearAllCaches = _wrap(FileSystem.prototype.clearAllCaches);

    // Static public utility methods
    exports.isAbsolutePath = FileSystem.isAbsolutePath;

    // For testing only
    exports._getActiveChangeCount = _wrap(FileSystem.prototype._getActiveChangeCount);

    /**
     * Add an event listener for a FileSystem event.
     *
     * @param {string} event The name of the event
     * @param {function} handler The handler for the event
     */
    exports.on = function (event, handler) {
        _instance.on(event, handler);
    };

    /**
     * Remove an event listener for a FileSystem event.
     *
     * @param {string} event The name of the event
     * @param {function} handler The handler for the event
     */
    exports.off = function (event, handler) {
        _instance.off(event, handler);
    };

    // Export the FileSystem class as "private" for unit testing only.
    exports._FileSystem = FileSystem;

    // Create the singleton instance
    _instance = new FileSystem();

    // Initialize the singleton instance
    _instance.init(require("fileSystemImpl"));
});


Component.prototype.forceUpdate = function(callback) {

// Set render mode so that we can differantiate where the render request

// is coming from. We need this because forceUpdate should never call

if (this._force==null) this._force = true;

keep keep keep replace keep keep keep keep keep

let vnode = this._vnode, dom = this._vnode._dom, parentDom = this._parentDom;

import { diff, unmount } from './index';
import { coerceToVNode, Fragment } from '../create-element';
import { EMPTY_OBJ, EMPTY_ARR } from '../constants';

/**
 * Diff the children of a virtual node
 * @param {import('../internal').PreactElement} parentDom The DOM element whose
 * children are being diffed
 * @param {import('../internal').VNode} newParentVNode The new virtual
 * node whose children should be diff'ed against oldParentVNode
 * @param {import('../internal').VNode} oldParentVNode The old virtual
 * node whose children should be diff'ed against newParentVNode
 * @param {object} context The current context object
 * @param {boolean} isSvg Whether or not this DOM node is an SVG node
 * @param {Array<import('../internal').PreactElement>} excessDomChildren
 * @param {Array<import('../internal').Component>} mounts The list of components
 * which have mounted
 * @param {import('../internal').Component} ancestorComponent The direct parent
 * component to the ones being diffed
 */
export function diffChildren(parentDom, newParentVNode, oldParentVNode, context, isSvg, excessDomChildren, mounts, ancestorComponent) {
	let childVNode, i, j, p, index, oldVNode, newDom,
		nextDom, sibDom, focus,
		childDom;

	// TODO: Ideally be consistent with the oldParentVNode. We should only check for null or EMPTY_OBJ

	// 3117 B
	let newChildren = getVNodeChildren(newParentVNode);
	let oldChildren = oldParentVNode==null || oldParentVNode==EMPTY_OBJ ? EMPTY_ARR : getVNodeChildren(oldParentVNode);

	// 3117 B
	// let newChildren = newParentVNode._children == null ? newParentVNode._children = toChildArray(newParentVNode.props.children) : newParentVNode._children;
	// let oldChildren = oldParentVNode==null || oldParentVNode==EMPTY_OBJ ? EMPTY_ARR : oldParentVNode._children == null ? oldParentVNode._children = toChildArray(oldParentVNode.props.children) : oldParentVNode._children;

	// 3119 B
	// let newChildren = newParentVNode._children == null ? toChildArray(newParentVNode.props.children, newParentVNode._children = []) : newParentVNode._children;
	// let oldChildren = oldParentVNode==null || oldParentVNode==EMPTY_OBJ ? EMPTY_ARR : oldParentVNode._children == null ? toChildArray(oldParentVNode.props.children, oldParentVNode._children = []) : oldParentVNode._children;

	let oldChildrenLength = oldChildren.length;

	// 3163 B
	// if (parentVNode.type === Fragment) {
	// 	if (excessDomChildren!=null) {
	// 		for (i = 0; i < excessDomChildren.length; i++) {
	// 			if (excessDomChildren[i]!=null) {
	// 				childDom = excessDomChildren[i];
	// 				break;
	// 			}
	// 		}
	// 	}
	// 	else if (oldChildren[0] != null) {
	// 		childDom = oldChildren[0]._dom;
	// 	}
	// 	else {
	// 		childDom = null;
	// 	}
	// }
	// else {
	// 	childDom = dom.firstChild;
	// }

	// 3161 B
	// if (parentVNode.type !== Fragment) {
	// 	childDom = dom.firstChild;
	// }
	// else if (oldChildren[0] != null) {
	// 	childDom = oldChildren[0]._dom;
	// }
	// else if (excessDomChildren!=null) {
	// 	for (i = 0; i < excessDomChildren.length; i++) {
	// 		if (excessDomChildren[i]!=null) {
	// 			childDom = excessDomChildren[i];
	// 			break;
	// 		}
	// 	}
	// }

	// 3149 B
	childDom = oldChildren.length ? oldChildren[0] && oldChildren[0]._dom : null;
	if (excessDomChildren!=null) {
		for (i = 0; i < excessDomChildren.length; i++) {
			if (excessDomChildren[i]!=null) {
				childDom = excessDomChildren[i];
				break;
			}
		}
	}

	for (i=0; i<newChildren.length; i++) {
		childVNode = newChildren[i] = coerceToVNode(newChildren[i]);
		oldVNode = index = null;

		// Check if we find a corresponding element in oldChildren and store the
		// index where the element was found.
		p = oldChildren[i];
		if (p != null && (childVNode.key==null && p.key==null ? (childVNode.type === p.type) : (childVNode.key === p.key))) {
			index = i;
		}
		else {
			for (j=0; j<oldChildrenLength; j++) {
				p = oldChildren[j];
				if (p!=null) {
					if (childVNode.key==null && p.key==null ? (childVNode.type === p.type) : (childVNode.key === p.key)) {
						index = j;
						break;
					}
				}
			}
		}

		// If we have found a corresponding old element we store it in a variable
		// and delete it from the array. That way the next iteration can skip this
		// element.
		if (index!=null) {
			oldVNode = oldChildren[index];
			oldChildren[index] = null;
		}

		nextDom = childDom!=null && childDom.nextSibling;

		// Morph the old element into the new one, but don't append it to the dom yet
		newDom = diff(oldVNode==null ? null : oldVNode._dom, parentDom, childVNode, oldVNode, context, isSvg, false, excessDomChildren, mounts, ancestorComponent);

		// Only proceed if the vnode has not been unmounted by `diff()` above.
		if (childVNode!=null && newDom !=null) {
			// Store focus in case moving children around changes it. Note that we
			// can't just check once for every tree, because we have no way to
			// differentiate wether the focus was reset by the user in a lifecycle
			// hook or by reordering dom nodes.
			focus = document.activeElement;

			// Fragments or similar components have already been diffed at this point.
			if (childVNode._lastDomChild != null) {
				newDom = childVNode._lastDomChild;
			}
			else if (excessDomChildren==oldVNode || newDom!=childDom || newDom.parentNode==null) {
				// NOTE: excessDomChildren==oldVNode above:
				// This is a compression of excessDomChildren==null && oldVNode==null!
				// The values only have the same type when `null`.

				outer: if (childDom==null || childDom.parentNode!==parentDom) {
					parentDom.appendChild(newDom);
				}
				else {
					sibDom = childDom;
					j = 0;
					while ((sibDom=sibDom.nextSibling) && j++<oldChildrenLength/2) {
						if (sibDom===newDom) {
							break outer;
						}
					}
					parentDom.insertBefore(newDom, childDom);
				}
			}

			// Restore focus if it was changed
			if (focus!==document.activeElement) {
				focus.focus();
			}

			childDom = newDom!=null ? newDom.nextSibling : nextDom;
		}
	}

	// Remove children that are not part of any vnode. Only used by `hydrate`
	if (excessDomChildren!=null && newParentVNode.type!==Fragment) for (i=excessDomChildren.length; i--; ) if (excessDomChildren[i]!=null) excessDomChildren[i].remove();

	// Remove remaining oldChildren if there are any.
	for (i=oldChildren.length; i--; ) if (oldChildren[i]!=null) unmount(oldChildren[i], ancestorComponent);
}

/**
 * Get the children of a virtual node as a flat array
 * @param {import('../internal').VNode} vnode The virtual node to get the
 * children of
 * @returns {Array<import('../internal').VNode>} The virtual node's children
 */
function getVNodeChildren(vnode) {
	if (vnode._children==null) {
		toChildArray(vnode.props.children, vnode._children=[]);
	}
	return vnode._children;
}

/**
 * Flatten a virtual nodes children to a single dimensional array
 * @param {import('../index').ComponentChildren} children The unflattened
 * children of a virtual node
 * @param {Array<import('../internal').VNode | null>} [flattened] An flat array of children to modify
 */
export function toChildArray(children, flattened) {
	if (flattened===undefined) flattened = [];
	if (children==null || typeof children === 'boolean') {}
	else if (Array.isArray(children)) {
		for (let i=0; i < children.length; i++) {
			toChildArray(children[i], flattened);
		}
	}
	else {
		flattened.push(coerceToVNode(children));
	}

	return flattened;
}


import { createElement, Component, Fragment } from 'ceviche';

export default class extends Component {

  state = { number: 0 }

  componentDidMount() {
    setInterval(_ => this.updateChildren(), 1000);
  }

  updateChildren() {
    this.setState(state => ({ number: state.number + 1 }))
  }

  render(props, state) {
    return (

{state.number}

        <>

one

{state.number}

three

        </>

    )
  }
}


import { expect } from "chai";
import {
	createElement,
	ComponentFactory,
	ComponentConstructor,
	Component
} from "../../src";
import {
	SimpleComponent,
	SimpleComponentProps
} from "./Component-test";

export interface highlightedProps {
	isHighlighted: boolean;
}

export function highlighted<T>(Wrappable: ComponentFactory<T>): ComponentConstructor<T & highlightedProps> {
	return class extends Component<T & highlightedProps> {

		constructor(props: T & highlightedProps) {
			super(props);
		}

		render() {
			let className = this.props.isHighlighted ? "highlighted" : "";
			return (
				<div className={className}>
					<Wrappable {...this.props}/>
				</div>
			);
		}

		toString() {
			return `Highlighted ${Wrappable.name}`;
		}
	}
}

const HighlightedSimpleComponent = highlighted<SimpleComponentProps>(SimpleComponent);

describe("hoc", () => {
	it("wraps the given component", () => {
		const highlight = new HighlightedSimpleComponent({
			initialName: "inital name",
			isHighlighted: true
		});

		expect(highlight.toString()).to.eq("Highlighted SimpleComponent");
	});
});


// diff(this._vnode._dom, this._parentDom, this._vnode, this._vnode, this.context, this._parentDom.ownerSVGElement!==undefined, true, null, mounts, this._ancestorComponent);

// diff(this._vnode._dom, this._parentDom, this._vnode, this._vnode, this.context, this._parentDom.ownerSVGElement!==undefined, null, mounts, this._ancestorComponent);

// if (this._vnode._dom!=null && this._vnode._dom.parentNode!==this._parentDom) {


import { createElement, cloneElement } from '../../src/index';

/** @jsx createElement */

describe('cloneElement', () => {
	it('should clone components', () => {
		function Comp () {}
		const instance = <Comp prop1={1}>hello</Comp>;
		const clone = cloneElement(instance);

		expect(clone.prototype).to.equal(instance.prototype);
		expect(clone.type).to.equal(instance.type);
		expect(clone.props).not.to.equal(instance.props); // Should be a different object...
		expect(clone.props).to.deep.equal(instance.props); // with the same properties
	});

	it('should merge new props', () => {
		function Foo () {}
		const instance = <Foo prop1={1} prop2={2} />;
		const clone = cloneElement(instance, { prop1: -1, newProp: -2 });

		expect(clone.prototype).to.equal(instance.prototype);
		expect(clone.type).to.equal(instance.type);
		expect(clone.props).not.to.equal(instance.props);
		expect(clone.props.prop1).to.equal(-1);
		expect(clone.props.prop2).to.equal(2);
		expect(clone.props.newProp).to.equal(-2);
	});

	it('should override children if specified', () => {
		function Foo() {}
		const instance = <Foo>hello</Foo>;
		const clone = cloneElement(instance, null, 'world', '!');

		expect(clone.prototype).to.equal(instance.prototype);
		expect(clone.type).to.equal(instance.type);
		expect(clone.props).not.to.equal(instance.props);
		expect(clone.props.children).to.deep.equal(['world', '!']);
	});

	it('should override key if specified', () => {
		function Foo() {}
		const instance = <Foo key="1">hello</Foo>;

		let clone = cloneElement(instance);
		expect(clone.key).to.equal('1');

		clone = cloneElement(instance, { key: '2' });
		expect(clone.key).to.equal('2');
	});

	it('should override ref if specified', () => {
		function a() {}
		function b() {}
		function Foo() {}
		const instance = <Foo ref={a}>hello</Foo>;

		let clone = cloneElement(instance);
		expect(clone.ref).to.equal(a);

		clone = cloneElement(instance, { ref: b });
		expect(clone.ref).to.equal(b);
	});
});


export default function logger(logStats, logConsole) {
	if (!logStats && !logConsole) {
		return;
	}

	const consoleBuffer = new ConsoleBuffer();

	let calls = {};
	let lock = true;

	function serialize(obj) {
		if (obj instanceof Text) return '#text';
		if (obj instanceof Element) return `<${obj.localName}>`;
		if (obj===document) return 'document';
		return Object.prototype.toString.call(obj).replace(/(^\[object |\]$)/g, '');
	}

	function count(key) {
		if (lock===true) return;
		calls[key] = (calls[key] || 0) + 1;

		if (logConsole) {
			consoleBuffer.log(key);
		}
	}

	function logCall(obj, method, name) {
		let old = obj[method];
		obj[method] = function() {
			let c = '';
			for (let i=0; i<arguments.length; i++) {
				if (c) c += ', ';
				c += serialize(arguments[i]);
			}
			count(`${serialize(this)}.${method}(${c})`);
			return old.apply(this, arguments);
		};
	}

	logCall(document, 'createElement');
	logCall(document, 'createElementNS');
	logCall(Element.prototype, 'remove');
	logCall(Element.prototype, 'appendChild');
	logCall(Element.prototype, 'removeChild');
	logCall(Element.prototype, 'insertBefore');
	logCall(Element.prototype, 'replaceChild');
	logCall(Element.prototype, 'setAttribute');
	logCall(Element.prototype, 'setAttributeNS');
	logCall(Element.prototype, 'removeAttribute');
	logCall(Element.prototype, 'removeAttributeNS');
	let d = Object.getOwnPropertyDescriptor(CharacterData.prototype, 'data') || Object.getOwnPropertyDescriptor(Node.prototype, 'data');
	Object.defineProperty(Text.prototype, 'data', {
		get() {
			let value = d.get.call(this);
			count(`get #text.data`);
			return value;
		},
		set(v) {
			count(`set #text.data`);
			return d.set.call(this, v);
		}
	});

	let root;
	function setup() {
		if (!logStats) return;

		lock = true;
		root = document.createElement('table');
		root.style.cssText = 'position: fixed; right: 0; top: 0; z-index:999; background: #000; font-size: 12px; color: #FFF; opacity: 0.9; white-space: nowrap; pointer-events: none;';
		let header = document.createElement('thead');
		header.innerHTML = '
Stats
';
		root.tableBody = document.createElement('tbody');
		root.appendChild(root.tableBody);
		root.appendChild(header);
		document.documentElement.appendChild(root);
		lock = false;
	}

	let rows = {};
	function createRow(id) {
		let row = document.createElement('tr');
		row.key = document.createElement('td');
		row.key.textContent = id;
		row.appendChild(row.key);
		row.value = document.createElement('td');
		row.value.textContent = ' ';
		row.appendChild(row.value);
		root.tableBody.appendChild(row);
		return rows[id] = row;
	}

	function insertInto(parent) {
		parent.appendChild(root);
	}

	function remove() {
		clearInterval(updateTimer);
	}

	function update() {
		if (!logStats) return;

		lock = true;
		for (let i in calls) {
			if (calls.hasOwnProperty(i)) {
				let row = rows[i] || createRow(i);
				row.value.firstChild.nodeValue = calls[i];
			}
		}
		lock = false;
	}

	let updateTimer = setInterval(update, 50);

	setup();
	lock = false;
	return { insertInto, update, remove };
}

/**
 * Logging to the console significantly affects performance.
 * Buffer calls to console and replay them at the end of the
 * current stack
 * @extends {Console}
 */
class ConsoleBuffer {
	constructor() {

		/** @type {Array<[string, any[]]>} */
		this.buffer = [];
		this.deferred = null;

		for (let methodName of Object.keys(console)) {
			this[methodName] = this.proxy(methodName);
		}
	}

	proxy(methodName) {
		return (...args) => {
			this.buffer.push([methodName, args]);
			this.deferFlush();
		};
	}

	deferFlush() {
		if (this.deferred == null) {
			this.deferred = Promise.resolve()
				.then(() => this.flush())
				.then(() => (this.deferred = null));
		}
	}

	flush() {
		let method;
		while ((method = this.buffer.shift())) {
			let [name, args] = method;
			console[name](...args);
		}
	}
}


/** @type {import('./index').Options}  */
const options = {};

export default options;