keep keep keep replace replace keep keep keep

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

/*jslint vars: true, plusplus: true, devel: true, nomen: true, 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.
 *
 * The FileSystem must be initialized very early during application startup.
 *
 * 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.
 *
 * FileSystem dispatches the following events:
 *    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".
 *          *  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");

    /**
     * @constructor
     * The FileSystem is not usable until init() signals its callback.
     */
    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 = [];
    }

    /**
     * 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) {
            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 {
            // The impl can't handle recursive watch requests, so it's up to the
            // filesystem to recursively watch or unwatch all subdirectories.
            this._enqueueWatchRequest(function (requestCb) {
                // First construct a list of entries to watch or unwatch
                var entriesToWatchOrUnwatch = [],
                    watchOrUnwatch = impl[commandName].bind(impl);

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

                entry.visit(visitor, function (err) {
                    if (err) {
                        // Unwatching a file/folder that doesn't exist will
                        // trigger an expected error
                        if (!shouldWatch && (err === FileSystemError.NOT_FOUND) &&
                                (entriesToWatchOrUnwatch.length === 0)) {
                            impl.unwatchPathsWithPrefix(entry.fullPath, requestCb);
                            return;
                        } else {
                            // Unexpected error
                            requestCb(err);
                            return;
                        }
                    }

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

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

                    entriesToWatchOrUnwatch.forEach(function (entry) {
                        watchOrUnwatch(entry.fullPath, watchOrUnwatchCallback);
                    });
                });
            }, 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) {
                    child._clearCachedData();
                }
            }.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] === ":");
    };

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

    /**
     * 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) {
                        entry._clearCachedData();
                    }
                }.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) {
                entry._clearCachedData();
            });

            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 (!(added && added.length === 0 && removed && removed.length === 0)) {
                        this._fireChangeEvent(entry, added, removed);
                    }
                }.bind(this));
            }
        }
    };

    /**
     * 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("Root is 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.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);

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


/*!
 * jQuery UI CSS Framework @VERSION
 *
 * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * http://docs.jquery.com/UI/Theming
 */
@import url("jquery.ui.core.css");

@import url("jquery.ui.accordion.css");
@import url("jquery.ui.autocomplete.css");
@import url("jquery.ui.button.css");
@import url("jquery.ui.datepicker.css");
@import url("jquery.ui.dialog.css");
@import url("jquery.ui.menu.css");
@import url("jquery.ui.progressbar.css");
@import url("jquery.ui.resizable.css");
@import url("jquery.ui.selectable.css");
@import url("jquery.ui.slider.css");
@import url("jquery.ui.spinner.css");
@import url("jquery.ui.tabs.css");
@import url("jquery.ui.tooltip.css");


<!doctype html>

<meta charset="UTF-8">
<title>Simple Test</title>
<link rel="stylesheet" href="simpleShared.css">
<link rel="stylesheet" href="simple1.css">

Brackets is awesome!

Red is bad. Green is good.


/*!
 * Connect - limit
 * Copyright(c) 2011 TJ Holowaychuk
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var utils = require('../utils'),
  brokenPause = utils.brokenPause;

/**
 * Limit:
 *
 *   Limit request bodies to the given size in `bytes`.
 *
 *   A string representation of the bytesize may also be passed,
 *   for example "5mb", "200kb", "1gb", etc.
 *
 *     connect()
 *       .use(connect.limit('5.5mb'))
 *       .use(handleImageUpload)
 *
 * @param {Number|String} bytes
 * @return {Function}
 * @api public
 */

module.exports = function limit(bytes){
  if ('string' == typeof bytes) bytes = utils.parseBytes(bytes);
  if ('number' != typeof bytes) throw new Error('limit() bytes required');
  return function limit(req, res, next){
    var received = 0
      , len = req.headers['content-length']
        ? parseInt(req.headers['content-length'], 10)
        : null;

    // self-awareness
    if (req._limit) return next();
    req._limit = true;

    // limit by content-length
    if (len && len > bytes) return next(utils.error(413));

    // limit
    if (brokenPause) {
      listen();
    } else {
      req.on('newListener', function handler(event) {
        if (event !== 'data') return;

        req.removeListener('newListener', handler);
        // Start listening at the end of the current loop
        // otherwise the request will be consumed too early.
        // Sideaffect is `limit` will miss the first chunk,
        // but that's not a big deal.
        // Unfortunately, the tests don't have large enough
        // request bodies to test this.
        process.nextTick(listen);
      });
    };

    next();

    function listen() {
      req.on('data', function(chunk) {
        received += Buffer.isBuffer(chunk)
          ? chunk.length :
          Buffer.byteLength(chunk);

        if (received > bytes) req.destroy();
      });
    };
  };
};


// Unwatch a directory and all it's descendants

/*
 * 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, appshell, $, window */

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

    var FileUtils           = require("file/FileUtils"),
        FileSystemStats     = require("filesystem/FileSystemStats"),
        FileSystemError     = require("filesystem/FileSystemError"),
        NodeDomain          = require("utils/NodeDomain");

    var FILE_WATCHER_BATCH_TIMEOUT = 200;   // 200ms - granularity of file watcher changes

    var _changeCallback,            // Callback to notify FileSystem of watcher changes
        _offlineCallback,           // Callback to notify FileSystem that watchers are offline
        _changeTimeout,             // Timeout used to batch up file watcher changes
        _pendingChanges = {};       // Pending file watcher changes

    var _bracketsPath   = FileUtils.getNativeBracketsDirectoryPath(),
        _modulePath     = FileUtils.getNativeModuleDirectoryPath(module),
        _nodePath       = "node/FileWatcherDomain",
        _domainPath     = [_bracketsPath, _modulePath, _nodePath].join("/"),
        _nodeDomain     = new NodeDomain("fileWatcher", _domainPath);

    // If the connection closes, notify the FileSystem that watchers have gone offline.
    $(_nodeDomain.connection).on("close", function (event, promise) {
        if (_offlineCallback) {
            _offlineCallback();
        }
    });

    /**
     * Enqueue a file change event for eventual reporting back to the FileSystem.
     *
     * @param {string} changedPath The path that was changed
     * @param {boolean} needsStats Whether or not the eventual change event should include stats
     * @private
     */
    function _enqueueChange(changedPath, needsStats) {
        _pendingChanges[changedPath] = _pendingChanges[changedPath] || needsStats;

        if (!_changeTimeout) {
            _changeTimeout = window.setTimeout(function () {
                if (_changeCallback) {
                    Object.keys(_pendingChanges).forEach(function (path) {
                        var needsStats = _pendingChanges[path];
                        if (needsStats) {
                            exports.stat(path, function (err, stats) {
                                if (err) {
                                    console.warn("Unable to stat changed path: ", path, err);
                                    return;
                                }
                                _changeCallback(path, stats);
                            });
                        } else {
                            _changeCallback(path);
                        }
                    });
                }

                _changeTimeout = null;
                _pendingChanges = {};
            }, FILE_WATCHER_BATCH_TIMEOUT);
        }
    }

    /**
     * Event handler for the Node fileWatcher domain's change event.
     *
     * @param {jQuery.Event} The underlying change event
     * @param {string} path The path that is reported to have changed
     * @param {string} event The type of the event: either "change" or "rename"
     * @param {string=} filename The name of the file that changed.
     * @private
     */
    function _fileWatcherChange(evt, path, event, filename) {
        var change;

        if (event === "change") {
            // Only register change events if filename is passed
            if (filename) {
                // an existing file was created; stats are needed
                change = path + filename;
                _enqueueChange(change, true);
            }
        } else if (event === "rename") {
            // a new file was created; no stats are needed
            change = path;
            _enqueueChange(change, false);
        }
    }

    // Setup the change handler. This only needs to happen once.
    $(_nodeDomain).on("change", _fileWatcherChange);

    /**
     * Convert appshell error codes to FileSystemError values.
     *
     * @param {?number} err An appshell error code
     * @return {?string} A FileSystemError string, or null if there was no error code.
     * @private
     */
    function _mapError(err) {
        if (!err) {
            return null;
        }

        switch (err) {
        case appshell.fs.ERR_INVALID_PARAMS:
            return FileSystemError.INVALID_PARAMS;
        case appshell.fs.ERR_NOT_FOUND:
            return FileSystemError.NOT_FOUND;
        case appshell.fs.ERR_CANT_READ:
            return FileSystemError.NOT_READABLE;
        case appshell.fs.ERR_CANT_WRITE:
            return FileSystemError.NOT_WRITABLE;
        case appshell.fs.ERR_UNSUPPORTED_ENCODING:
            return FileSystemError.NOT_READABLE;
        case appshell.fs.ERR_OUT_OF_SPACE:
            return FileSystemError.OUT_OF_SPACE;
        case appshell.fs.ERR_FILE_EXISTS:
            return FileSystemError.ALREADY_EXISTS;
        }
        return FileSystemError.UNKNOWN;
    }

    /**
     * Convert a callback to one that transforms its first parameter from an
     * appshell error code to a FileSystemError string.
     *
     * @param {function(?number)} cb A callback that expects an appshell error code
     * @return {function(?string)} A callback that expects a FileSystemError string
     * @private
     */
    function _wrap(cb) {
        return function (err) {
            var args = Array.prototype.slice.call(arguments);
            args[0] = _mapError(args[0]);
            cb.apply(null, args);
        };
    }

    /**
     * Display an open-files dialog to the user and call back asynchronously with
     * either a FileSystmError string or an array of path strings, which indicate
     * the entry or entries selected.
     *
     * @param {boolean} allowMultipleSelection
     * @param {boolean} chooseDirectories
     * @param {string} title
     * @param {string} initialPath
     * @param {Array.<string>=} fileTypes
     * @param {function(?string, Array.<string>=)} callback
     */
    function showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, callback) {
        appshell.fs.showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, _wrap(callback));
    }

    /**
     * Display a save-file dialog and call back asynchronously with either a
     * FileSystemError string or the path to which the user has chosen to save
     * the file. If the dialog is cancelled, the path string will be empty.
     *
     * @param {string} title
     * @param {string} initialPath
     * @param {string} proposedNewFilename
     * @param {function(?string, string=)} callback
     */
    function showSaveDialog(title, initialPath, proposedNewFilename, callback) {
        appshell.fs.showSaveDialog(title, initialPath, proposedNewFilename, _wrap(callback));
    }

    /**
     * Stat the file or directory at the given path, calling back
     * asynchronously with either a FileSystemError string or the entry's
     * associated FileSystemStats object.
     *
     * @param {string} path
     * @param {function(?string, FileSystemStats=)} callback
     */
    function stat(path, callback) {
        appshell.fs.stat(path, function (err, stats) {
            if (err) {
                callback(_mapError(err));
            } else {
                var options = {
                    isFile: stats.isFile(),
                    mtime: stats.mtime,
                    size: stats.size,
                    realPath: stats.realPath,
                    hash: stats.mtime.getTime()
                };

                var fsStats = new FileSystemStats(options);

                callback(null, fsStats);
            }
        });
    }

    /**
     * Determine whether a file or directory exists at the given path by calling
     * back asynchronously with either a FileSystemError string or a boolean,
     * which is true if the file exists and false otherwise. The error will never
     * be FileSystemError.NOT_FOUND; in that case, there will be no error and the
     * boolean parameter will be false.
     *
     * @param {string} path
     * @param {function(?string, boolean)} callback
     */
    function exists(path, callback) {
        stat(path, function (err) {
            if (err) {
                if (err === FileSystemError.NOT_FOUND) {
                    callback(null, false);
                } else {
                    callback(err);
                }
                return;
            }

            callback(null, true);
        });
    }

    /**
     * Read the contents of the directory at the given path, calling back
     * asynchronously either with a FileSystemError string or an array of
     * FileSystemEntry objects along with another consistent array, each index
     * of which either contains a FileSystemStats object for the corresponding
     * FileSystemEntry object in the second parameter or a FileSystemError
     * string describing a stat error.
     *
     * @param {string} path
     * @param {function(?string, Array.<FileSystemEntry>=, Array.<string|FileSystemStats>=)} callback
     */
    function readdir(path, callback) {
        appshell.fs.readdir(path, function (err, contents) {
            if (err) {
                callback(_mapError(err));
                return;
            }

            var count = contents.length;
            if (!count) {
                callback(null, [], []);
                return;
            }

            var stats = [];
            contents.forEach(function (val, idx) {
                stat(path + "/" + val, function (err, stat) {
                    stats[idx] = err || stat;
                    count--;
                    if (count <= 0) {
                        callback(null, contents, stats);
                    }
                });
            });
        });
    }

    /**
     * Create a directory at the given path, and call back asynchronously with
     * either a FileSystemError string or a stats object for the newly created
     * directory. The octal mode parameter is optional; if unspecified, the mode
     * of the created directory is implementation dependent.
     *
     * @param {string} path
     * @param {number=} mode The base-eight mode of the newly created directory.
     * @param {function(?string, FileSystemStats=)=} callback
     */
    function mkdir(path, mode, callback) {
        if (typeof mode === "function") {
            callback = mode;
            mode = parseInt("0755", 8);
        }
        appshell.fs.makedir(path, mode, function (err) {
            if (err) {
                callback(_mapError(err));
            } else {
                stat(path, function (err, stat) {
                    callback(err, stat);
                });
            }
        });
    }

    /**
     * Rename the file or directory at oldPath to newPath, and call back
     * asynchronously with a possibly null FileSystemError string.
     *
     * @param {string} oldPath
     * @param {string} newPath
     * @param {function(?string)=} callback
     */
    function rename(oldPath, newPath, callback) {
        appshell.fs.rename(oldPath, newPath, _wrap(callback));
    }

    /**
     * Read the contents of the file at the given path, calling back
     * asynchronously with either a FileSystemError string, or with the data and
     * the FileSystemStats object associated with the read file. The options
     * parameter can be used to specify an encoding (default "utf8"), and also
     * a cached stats object that the implementation is free to use in order
     * to avoid an additional stat call.
     *
     * Note: if either the read or the stat call fails then neither the read data
     * nor stat will be passed back, and the call should be considered to have failed.
     * If both calls fail, the error from the read call is passed back.
     *
     * @param {string} path
     * @param {{encoding: string=, stat: FileSystemStats=}} options
     * @param {function(?string, string=, FileSystemStats=)} callback
     */
    function readFile(path, options, callback) {
        var encoding = options.encoding || "utf8";

        // Execute the read and stat calls in parallel. Callback early if the
        // read call completes first with an error; otherwise wait for both
        // to finish.
        var done = false, data, stat, err;

        if (options.stat) {
            done = true;
            stat = options.stat;
        } else {
            exports.stat(path, function (_err, _stat) {
                if (done) {
                    callback(_err, _err ? null : data, _stat);
                } else {
                    done = true;
                    stat = _stat;
                    err = _err;
                }
            });
        }

        appshell.fs.readFile(path, encoding, function (_err, _data) {
            if (_err) {
                callback(_mapError(_err));
                return;
            }

            if (done) {
                callback(err, err ? null : _data, stat);
            } else {
                done = true;
                data = _data;
            }
        });
    }

    /**
     * Write data to the file at the given path, calling back asynchronously with
     * either a FileSystemError string or the FileSystemStats object associated
     * with the written file and a boolean that indicates whether the file was
     * created by the write (true) or not (false). If no file exists at the
     * given path, a new file will be created. The options parameter can be used
     * to specify an encoding (default "utf8"), an octal mode (default
     * unspecified and implementation dependent), and a consistency hash, which
     * is used to the current state of the file before overwriting it. If a
     * consistency hash is provided but does not match the hash of the file on
     * disk, a FileSystemError.CONTENTS_MODIFIED error is passed to the callback.
     *
     * @param {string} path
     * @param {string} data
     * @param {{encoding : string=, mode : number=, expectedHash : object=}} options
     * @param {function(?string, FileSystemStats=, boolean)} callback
     */
    function writeFile(path, data, options, callback) {
        var encoding = options.encoding || "utf8";

        function _finishWrite(created) {
            appshell.fs.writeFile(path, data, encoding, function (err) {
                if (err) {
                    callback(_mapError(err));
                } else {
                    stat(path, function (err, stat) {
                        callback(err, stat, created);
                    });
                }
            });
        }

        stat(path, function (err, stats) {
            if (err) {
                switch (err) {
                case FileSystemError.NOT_FOUND:
                    _finishWrite(true);
                    break;
                default:
                    callback(err);
                }
                return;
            }

            if (options.hasOwnProperty("expectedHash") && options.expectedHash !== stats._hash) {
                console.error("Blind write attempted: ", path, stats._hash, options.expectedHash);
                callback(FileSystemError.CONTENTS_MODIFIED);
                return;
            }

            _finishWrite(false);
        });
    }

    /**
     * Unlink (i.e., permanently delete) the file or directory at the given path,
     * calling back asynchronously with a possibly null FileSystemError string.
     * Directories will be unlinked even when non-empty.
     *
     * @param {string} path
     * @param {function(string)=} callback
     */
    function unlink(path, callback) {
        appshell.fs.unlink(path, function (err) {
            callback(_mapError(err));
        });
    }

    /**
     * Move the file or directory at the given path to a system dependent trash
     * location, calling back asynchronously with a possibly null FileSystemError
     * string. Directories will be moved even when non-empty.
     *
     * @param {string} path
     * @param {function(string)=} callback
     */
    function moveToTrash(path, callback) {
        appshell.fs.moveToTrash(path, function (err) {
            callback(_mapError(err));
        });
    }

    /**
     * Initialize file watching for this filesystem, using the supplied
     * changeCallback to provide change notifications. The first parameter of
     * changeCallback specifies the changed path (either a file or a directory);
     * if this parameter is null, it indicates that the implementation cannot
     * specify a particular changed path, and so the callers should consider all
     * paths to have changed and to update their state accordingly. The second
     * parameter to changeCallback is an optional FileSystemStats object that
     * may be provided in case the changed path already exists and stats are
     * readily available. The offlineCallback will be called in case watchers
     * are no longer expected to function properly. All watched paths are
     * cleared when the offlineCallback is called.
     *
     * @param {function(?string, FileSystemStats=)} changeCallback
     * @param {function()=} callback
     */
    function initWatchers(changeCallback, offlineCallback) {
        _changeCallback = changeCallback;
        _offlineCallback = offlineCallback;
    }

    /**
     * Start providing change notifications for the file or directory at the
     * given path, calling back asynchronously with a possibly null FileSystemError
     * string when the initialization is complete. Notifications are provided
     * using the changeCallback function provided by the initWatchers method.
     * Note that change notifications are only provided recursively for directories
     * when the recursiveWatch property of this module is true.
     *
     * @param {string} path
     * @param {function(?string)=} callback
     */
    function watchPath(path, callback) {
        appshell.fs.isNetworkDrive(path, function (err, isNetworkDrive) {
            if (err || isNetworkDrive) {
                callback(FileSystemError.UNKNOWN);
                return;
            }

            _nodeDomain.exec("watchPath", path)
                .then(callback, callback);
        });
    }

    /**
     * Stop providing change notifications for the file or directory at the
     * given path, calling back asynchronously with a possibly null FileSystemError
     * string when the operation is complete.
     *
     * @param {string} path
     * @param {function(?string)=} callback
     */
    function unwatchPath(path, callback) {
        _nodeDomain.exec("unwatchPath", path)
            .then(callback, callback);
    }

    /**
     * Stop providing change notifications for all previously watched files and
     * directories that start with a path prefix, optionally calling back
     * asynchronously with a possibly null FileSystemError string when the \
     * operation is complete.
     *
     * @param {string} path
     * @param {function(?string)=} callback
     */
    function unwatchPathsWithPrefix(path, callback) {
        _nodeDomain.exec("unwatchPathsWithPrefix", path)
            .then(callback, callback);
    }

    /**
     * Stop providing change notifications for all previously watched files and
     * directories, optionally calling back asynchronously with a possibly null
     * FileSystemError string when the operation is complete.
     *
     * @param {function(?string)=} callback
     */
    function unwatchAll(callback) {
        _nodeDomain.exec("unwatchAll")
            .then(callback, callback);
    }

    // Export public API
    exports.showOpenDialog          = showOpenDialog;
    exports.showSaveDialog          = showSaveDialog;
    exports.exists                  = exists;
    exports.readdir                 = readdir;
    exports.mkdir                   = mkdir;
    exports.rename                  = rename;
    exports.stat                    = stat;
    exports.readFile                = readFile;
    exports.writeFile               = writeFile;
    exports.unlink                  = unlink;
    exports.moveToTrash             = moveToTrash;
    exports.initWatchers            = initWatchers;
    exports.watchPath               = watchPath;
    exports.unwatchPath             = unwatchPath;
    exports.unwatchPathsWithPrefix  = unwatchPathsWithPrefix
    exports.unwatchAll              = unwatchAll;

    /**
     * Indicates whether or not recursive watching notifications are supported
     * by the watchPath call. Currently, only Darwin supports recursive watching.
     *
     * @type {boolean}
     */
    exports.recursiveWatch = appshell.platform === "mac";

    /**
     * Indicates whether or not the filesystem should expect and normalize UNC
     * paths. If set, then //server/directory/ is a normalized path; otherwise the
     * filesystem will normalize it to /server/directory. Currently, UNC path
     * normalization only occurs on Windows.
     *
     * @type {boolean}
     */
    exports.normalizeUNCPaths = appshell.platform === "win";
});


var assert = require('assert');
var Traverse = require('traverse');

exports.dateEach = function () {
    var obj = { x : new Date, y : 10, z : 5 };

    var counts = {};

    Traverse(obj).forEach(function (node) {
        var t = (node instanceof Date && 'Date') || typeof node;
        counts[t] = (counts[t] || 0) + 1;
    });

    assert.deepEqual(counts, {
        object : 1,
        Date : 1,
        number : 2,
    });
};

exports.dateMap = function () {
    var obj = { x : new Date, y : 10, z : 5 };

    var res = Traverse(obj).map(function (node) {
        if (typeof node === 'number') this.update(node + 100);
    });

    assert.ok(obj.x !== res.x);
    assert.deepEqual(res, {
        x : obj.x,
        y : 110,
        z : 105,
    });
};


exports.unlink                  = unlink;

exports.moveToTrash             = moveToTrash;

exports.initWatchers            = initWatchers;

exports.watchPath               = watchPath;

exports.unwatchPath             = unwatchPath;

exports.unwatchAll              = unwatchAll;

* Indicates whether or not recursive watching notifications are supported

* by the watchPath call. Currently, only Darwin supports recursive watching.

keep keep keep keep keep replace keep keep keep keep keep

exports.unwatchPathsWithPrefix  = unwatchPathsWithPrefix;


POST /upload HTTP/1.1
Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, */*
Referer: http://192.168.56.1:8080/
Accept-Language: de
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)
Content-Type: multipart/form-data; boundary=---------------------------7db3a8372017c
Accept-Encoding: gzip, deflate
Host: 192.168.56.1:8080
Content-Length: 368
Connection: Keep-Alive
Cache-Control: no-cache

-----------------------------7db3a8372017c
Content-Disposition: form-data; name="title"

Weird filename
-----------------------------7db3a8372017c
Content-Disposition: form-data; name="upload"; filename=" ? % * | " < > . ☃ ; ' @ # $ ^ & ( ) - _ = + { } [ ] ` ~.txt"
Content-Type: application/octet-stream

-----------------------------7db3a8372017c--


<!DOCTYPE html>

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

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

I can be dragged only by this handle

You can drag me around…

…but you can't drag me by this handle.

Allow dragging only when the cursor is over a specific part of the draggable.  Use the handle option to specify the jQuery selector of an element (or group of elements) used to drag the object.

Or prevent dragging when the cursor is over a specific element (or group of elements) within the draggable.  Use the cancel option to specify a jQuery selector over which to "cancel" draggable functionality.


keep keep keep keep keep replace keep keep keep keep


---
pedantic: true
---

AT&T has an ampersand in their name.

AT&T is another way to write it.

This & that.

4 < 5.

6 > 5.

Here's a [link] [1] with an ampersand in the URL.

Here's a link with an amersand in the link text: [AT&T] [2].

Here's an inline [link](/script?foo=1&bar=2).

Here's an inline [link](</script?foo=1&bar=2>).

[1]: http://example.com/?foo=1&bar=2
[2]: http://att.com/  "AT&T"


---
sanitize: true
---
[URL](javascript:alert)

[URL](vbscript:alert)

[URL](javascript:alert(1))

[URL](javascript&#58document;alert(1))

[URL](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)


> A list within a blockquote:
>
> *	asterisk 1
> *	asterisk 2
> *	asterisk 3


var marked = require('../../../lib/marked.js');
var cmSpec = require('./commonmark.0.28.json');
var HtmlDiffer = require('html-differ').HtmlDiffer,
    htmlDiffer = new HtmlDiffer();
var since = require('jasmine2-custom-message');

var Messenger = function() {}

Messenger.prototype.message = function(spec, expected, actual) {
  return 'CommonMark (' + spec.section + '):\n' + spec.markdown + '\n------\n\nExpected:\n' + expected + '\n------\n\nMarked:\n' + actual;
}

Messenger.prototype.test = function(spec, section, ignore) {
  if (spec.section === section) {
    var shouldFail = ~ignore.indexOf(spec.example);
    it('should ' + (shouldFail ? 'fail' : 'pass') + ' example ' + spec.example, function() {
      var expected = spec.html;
      var actual = marked(spec.markdown, { headerIds: false, xhtml: true });
      since(messenger.message(spec, expected, actual)).expect(
        htmlDiffer.isEqual(expected, actual)
      ).toEqual(!shouldFail);
    });
  }
}

var messenger = new Messenger();
/*
|Section                                 |Count      |Percent |
|:---------------------------------------|:---------:|-------:|
|Tabs                                    |  7 of  11 |     64%|
|Thematic breaks                         | 18 of  19 |     95%|
|ATX headings                            | 14 of  18 |     78%|
|Setext headings                         | 21 of  26 |     81%|
|Indented code blocks                    | 11 of  12 |     92%|
|Fenced code blocks                      | 17 of  28 |     61%|
|Link reference definitions              | 22 of  23 |     96%|
|Paragraphs                              |  6 of   8 |     75%|
|Block quotes                            | 21 of  25 |     84%|
|List items                              | 32 of  48 |     67%|
|Lists                                   | 10 of  24 |     42%|
|Backslash escapes                       |  8 of  13 |     62%|
|Entity and numeric character references |  9 of  12 |     75%|
|Code spans                              | 11 of  17 |     65%|
|Emphasis and strong emphasis            | 71 of 128 |     55%|
|Links                                   | 64 of  84 |     76%|
|Images                                  | 15 of  22 |     68%|
|Autolinks                               | 15 of  19 |     79%|
|Raw HTML                                | 19 of  21 |     90%|
|Hard line breaks                        | 32 of  36 |     89%|
|Soft line breaks                        |  1 of   2 |     50%|
*/

describe('CommonMark 0.28 Tabs', function() {
  var section = 'Tabs';

  // These examples probably should pass but don't for some reason.
  // This is the easiest way to demonstrate limitations  or defects
  // within Marked. Toggle comments for next two lines to see which examples
  // are known failures. Note: If all arrays are empty, it means Marked is
  // 100% compliant with that section of the given specification.
  //
  // var shouldPassButFails = [];
  var shouldPassButFails = [1, 2, 3, 7];

  // Identifies examples that the Marked core team has determined beyond
  // the ability or desire to correct; thereby, implicitly requesting
  // outside help and assistance.
  var willNotBeAttemptedByCoreTeam = [];

  // Combine known failures and skips.
  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    // Run test.
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Precedence', function() {
  var section = 'Precedence';

  // var shouldPassButFails = [];
  var shouldPassButFails = [];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Thematic breaks', function() {
  var section = 'Thematic breaks';

  // var shouldPassButFails = [];
  var shouldPassButFails = [19];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 ATX headings', function() {
  var section = 'ATX headings';

  // var shouldPassButFails = [];
  var shouldPassButFails = [40, 45, 46, 49];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Setext headings', function() {
  var section = 'Setext headings';

  // var shouldPassButFails = [];
  var shouldPassButFails = [51, 52, 56, 62, 64];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Indented code blocks', function() {
  var section = 'Indented code blocks';

  // var shouldPassButFails = [];
  var shouldPassButFails = [82];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Fenced code blocks', function() {
  var section = 'Fenced code blocks';

  // var shouldPassButFails = [];
  var shouldPassButFails = [93, 95, 96, 97, 101, 102, 106, 108, 112];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 HTML blocks', function() {
  var section = 'HTML blocks';

  var shouldPassButFails = [];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Link reference definitions', function() {
  var section = 'Link reference definitions';

  // var shouldPassButFails = [];
  var shouldPassButFails = [167];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Paragraphs', function() {
  var section = 'Paragraphs';

  // var shouldPassButFails = [];
  var shouldPassButFails = [185, 186];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Blank lines', function() {
  var section = 'Blank lines';

  // var shouldPassButFails = [];
  var shouldPassButFails = [];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Block quotes', function() {
  var section = 'Block quotes';

  // var shouldPassButFails = [];
  var shouldPassButFails = [198, 199, 200, 201];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 List items', function() {
  var section = 'List items';

  // var shouldPassButFails = [];
  var shouldPassButFails = [229, 237, 236, 227, 218, 243, 259, 241, 239, 247, 246, 225, 220, 258, 260, 244];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Lists', function() {
  var section = 'Lists';

  // var shouldPassButFails = [];
  var shouldPassButFails = [282, 270, 280, 278, 273, 274, 264, 265, 276, 279, 267, 269];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Inlines', function() {
  var section = 'Inlines';

  // var shouldPassButFails = [];
  var shouldPassButFails = [];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Backslash escapes', function() {
  var section = 'Backslash escapes';

  // var shouldPassButFails = [];
  var shouldPassButFails = [290, 291, 300, 301];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Entity and numeric character references', function() {
  var section = 'Entity and numeric character references';

  // var shouldPassButFails = [];
  var shouldPassButFails = [311, 309, 310];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Code spans', function() {
  var section = 'Code spans';

  // var shouldPassButFails = [];
  var shouldPassButFails = [330, 316, 328, 320, 323, 322];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Emphasis and strong emphasis', function() {
  var section = 'Emphasis and strong emphasis';

  // var shouldPassButFails = [];
  var shouldPassButFails = [333, 334, 342, 348, 349, 352, 353, 354, 355, 356, 360, 368, 369, 371, 372, 378, 380, 381, 382, 387, 388, 392, 393, 394, 395, 396, 402, 403, 409, 416, 419, 420, 421, 422, 423, 424, 428, 431, 432, 433, 434, 435, 436, 443, 444, 445, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Links', function() {
  var section = 'Links';

  // var shouldPassButFails = [];
  var shouldPassButFails = [474, 478, 483, 489, 490, 491, 492, 495, 496, 497, 499, 503, 504, 505, 507, 508, 509, 523, 535];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Images', function() {
  var section = 'Images';

  // var shouldPassButFails = [];
  var shouldPassButFails = [544, 545, 546, 547, 548, 556, 560];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Autolinks', function() {
  var section = 'Autolinks';

  // var shouldPassButFails = [];
  var shouldPassButFails = [582, 573, 579, 583];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Raw HTML', function() {
  var section = 'Raw HTML';

  // var shouldPassButFails = [];
  var shouldPassButFails = [597, 598];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Hard line breaks', function() {
  var section = 'Hard line breaks';

  // var shouldPassButFails = [];
  var shouldPassButFails = [613];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Soft line breaks', function() {
  var section = 'Soft line breaks';

  // var shouldPassButFails = [];
  var shouldPassButFails = [621];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});

describe('CommonMark 0.28 Textual content', function() {
  var section = 'Textual content';

  // var shouldPassButFails = [];
  var shouldPassButFails = [];

  var willNotBeAttemptedByCoreTeam = [];

  var ignore = shouldPassButFails.concat(willNotBeAttemptedByCoreTeam);

  cmSpec.forEach(function(spec) {
    messenger.test(spec, section, ignore);
  });
});


Simple block on one line:

foo

And nested without indentation:

foo

"/>

bar


describe('CommonMark 0.28 Links', function() {


hello world
    text after spaces
    text after spaces

paragraph before code

text inside block code

paragraph before hr

paragraph before head with hash

how are you

paragraph before head with equals

how are you

paragraph before blockquote

text for blockquote

paragraph before list

text inside list

paragraph before div

text inside div

paragraph with span
text inside span

hello world

hello

hello


Here's a simple block:

	foo

This should be a code block, though:

		foo

As should this:

foo

Now, nested:

			foo

This should just be an HTML comment:

Multiline:

Code block:

Just plain comment, with trailing spaces on the line:

Code:

Hr's: