keep keep keep replace keep keep keep keep keep

/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

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

/**
 * DocumentManager is the model for the set of currently 'open' files and their contents. It controls
 * which file is currently shown in the editor, the dirty bit for all files, and the list of documents
 * in the working set.
 *
 * Each Document also encapsulates the editor widget for that file, which in turn controls each file's
 * contents and undo history. (So this is not a perfectly clean, headless model, but that's unavoidable
 * because the document state is tied up in the CodeMirror UI). We share ownership of the editor objects
 * with EditorManager, which creates, shows/hides, resizes, and disposes the editors.
 *
 * This module dispatches several events:
 *    - dirtyFlagChange -- When any Document's isDirty flag changes. The 2nd arg to the listener is the
 *      Document whose flag changed.
 *    - documentSaved -- When a Document's changes have been saved. The 2nd arg to the listener is the
 *      Document that has been saved.
 *    - currentDocumentChange -- When the value of getCurrentDocument() changes.
 *    - workingSetAdd -- When a Document is added to the working set (see getWorkingSet()). The 2nd arg
 *      to the listener is the added Document.
 *    - workingSetRemove -- When a Document is removed from the working set (see getWorkingSet()). The
 *      2nd arg to the listener is the removed Document.
 *
 * These are jQuery events, so to listen for them you do something like this:
 *    $(DocumentManager).on("eventname", handler);
 */
define(function (require, exports, module) {
    'use strict';

    var NativeFileSystem    = require("NativeFileSystem").NativeFileSystem,
        ProjectManager      = require("ProjectManager"),
        PreferencesManager  = require("PreferencesManager"),
        CommandManager      = require("CommandManager"),
        Commands            = require("Commands");

    /**
     * Unique PreferencesManager clientID
     */
    var PREFERENCES_CLIENT_ID = "com.adobe.brackets.DocumentManager";

    /**
     * @private
     * @see DocumentManager.getCurrentDocument()
     */
    var _currentDocument = null;

    /**
     * Returns the Document that is currently open in the editor UI. May be null.
     * When this changes, DocumentManager dispatches a "currentDocumentChange" event.
     * @return {?Document}
     */
    function getCurrentDocument() {
        return _currentDocument;
    }

    /**
     * @private
     * @see DocumentManager.getWorkingSet()
     */
    var _workingSet = [];

    /**
     * Returns an ordered list of items in the working set. May be 0-length, but never null.
     *
     * When a Document is added this list, DocumentManager dispatches a "workingSetAdd" event.
     * When a Document is removed from list, DocumentManager dispatches a "workingSetRemove" event.
     * To listen for ALL changes to this list, you must listen for both events.
     *
     * Which items belong in the working set is managed entirely by DocumentManager. Callers cannot
     * (yet) change this collection on their own.
     *
     * @return {Array.<Document>}
     */
    function getWorkingSet() {
        return _workingSet;
        // TODO: return a clone to prevent meddling?
    }

    /**
      * Returns the index of the file matching fullPath in the working set.
      * Returns -1 if not found.
      * @param {!string} fullPath
      * @returns {number} index
     */
    function findInWorkingSet(fullPath) {
        var ret = -1;
        var found = _workingSet.some(function findByPath(ele, i, arr) {
                ret = i;
                return ele.file.fullPath === fullPath;
            });

        return (found ? ret : -1);
    }

    /**
     * Returns all documents that are open in editors: the union of the working set and the current
     * document (which may not be in the working set if it is unmodified).
     * @return {Array.<Document>}
     */
    function getAllOpenDocuments() {
        var allDocs = _workingSet.slice(0);  //slice() to clone

        if (_currentDocument && allDocs.indexOf(_currentDocument) === -1) {
            allDocs.push(_currentDocument);
        }

        return allDocs;
    }

    /**
     * Adds the given document to the end of the working set list, if it is not already in the list.
     * Does not change which document is currently open in the editor.
     * @param {!Document} document
     */
    function addToWorkingSet(document) {
        // If doc is already in working set, don't add it again
        if (findInWorkingSet(document.file.fullPath) !== -1) {
            return;
        }

        // Add
        _workingSet.push(document);

        // Dispatch event
        $(exports).triggerHandler("workingSetAdd", document);
    }

    /**
     * Removes the given document from the working set list, if it was in the list. Does not change
     * the editor even if this document is the one currently open;.
     * @param {!Document} document
     */
    function _removeFromWorkingSet(document) {
        // If doc isn't in working set, do nothing
        var index = findInWorkingSet(document.file.fullPath);
        if (index === -1) {
            return;
        }

        // Remove
        _workingSet.splice(index, 1);

        // Dispatch event
        $(exports).triggerHandler("workingSetRemove", document);
    }

    /**
     * If the given file is 'open' for editing, returns its Document. Else returns null. "Open for
     * editing" means either the file is in the working set, and/or the file is currently open in
     * the editor UI.
     * @param {!FileEntry} fileEntry
     * @return {?Document}
     */
    function getDocumentForFile(fileEntry) {
        if (_currentDocument && _currentDocument.file.fullPath === fileEntry.fullPath) {
            return _currentDocument;
        }

        var wsIndex = findInWorkingSet(fileEntry.fullPath);
        if (wsIndex !== -1) {
            return _workingSet[wsIndex];
        }

        return null;
    }

    /** If the given file is 'open' for editing, returns its Document. Else returns null. "Open for
     * editing" means either the file is in the working set, and/or the file is currently open in
     * the editor UI.
     * @param {!fullPath}
     * @return {?Document}
    */
    function getDocumentForPath(fullPath) {
        // TODO: we should implement something like NativeFileSystem.resolveNativeFileSystemURL() (similar
        // to what's in the standard file API) to get a FileEntry, rather than manually constructing it
        var fileEntry = new NativeFileSystem.FileEntry(fullPath);

        return getDocumentForFile(fileEntry);
    }

    /**
     * Displays the given file in the editor pane. May also add the item to the working set list.
     * This changes the value of getCurrentDocument(), which will trigger listeners elsewhere in the
     * UI that in turn do things like update the selection in the file tree / working set UI.
     *
     * @param {!Document} document  The document whose editor should be shown. May or may not
     *      already be in the working set.
     */
    function showInEditor(document) {

        // If this file is already in editor, do nothing
        if (_currentDocument === document) {
            return;
        }

        // If file not within project tree, add it to working set right now (don't wait for it to
        // become dirty)
        if (!ProjectManager.isWithinProject(document.file.fullPath)) {
            addToWorkingSet(document);
        }

        // Make it the current document
        _currentDocument = document;
        $(exports).triggerHandler("currentDocumentChange");
        // (this event triggers EditorManager to actually switch editors in the UI)
    }

    /** Closes the currently visible document, if any */
    function _clearEditor() {
        // If editor already blank, do nothing
        if (!_currentDocument) {
            return;
        }

        // Change model & dispatch event
        _currentDocument = null;
        $(exports).triggerHandler("currentDocumentChange");
        // (this event triggers EditorManager to actually clear the editor UI)
    }

    /**
     * Asynchronously reads a file as UTF-8 encoded text.
     * @return {Deferred} a jQuery Deferred that will be resolved with the
     *  file's text content plus its timestamp, or rejected with a FileError if
     *  the file can not be read.
     */
    function readAsText(fileEntry) {
        var result = new $.Deferred(),
            reader = new NativeFileSystem.FileReader();

        fileEntry.file(function (file) {
            reader.onload = function (event) {
                var text = event.target.result;

                fileEntry.getMetadata(
                    function (metadata) {
                        result.resolve(text, metadata.modificationTime);
                    },
                    function (error) {
                        result.reject(error);
                    }
                );
            };

            reader.onerror = function (event) {
                result.reject(event.target.error);
            };

            reader.readAsText(file, "utf8");
        });

        return result;
    }

    /**
     * Closes the given document (which may or may not be the current document in the editor UI, and
     * may or may not be in the working set). Discards any unsaved changes if isDirty - it is
     * expected that the UI has already confirmed with the user before calling us.
     *
     * This will select a different file for editing if the document was the current one (if possible;
     * in some cases, the editor may be left blank instead). This will also remove the doc from the
     * working set if it was in the set.
     *
     * @param {!Document} document
     */
    function closeDocument(document) {
        // If this was the current document shown in the editor UI, we're going to switch to a
        // different document (or none if working set has no other options)
        if (_currentDocument === document) {
            var wsIndex = findInWorkingSet(document.file.fullPath);

            // Decide which doc to show in editor after this one
            var nextDocument;
            if (wsIndex === -1) {
                // If doc wasn't in working set, use bottommost working set item
                if (_workingSet.length > 0) {
                    nextDocument = _workingSet[_workingSet.length  - 1];
                }
                // else: leave nextDocument null; editor area will be blank
            } else {
                // If doc was in working set, use item next to it (below if possible)
                if (wsIndex < _workingSet.length - 1) {
                    nextDocument = _workingSet[wsIndex + 1];
                } else if (wsIndex > 0) {
                    nextDocument = _workingSet[wsIndex - 1];
                }
                // else: leave nextDocument null; editor area will be blank
            }

            // Switch editor to next document (or blank it out)
            if (nextDocument) {
                showInEditor(nextDocument);
            } else {
                _clearEditor();
            }
        }

        // (Now we're guaranteed that the current document is not the one we're closing)
        console.assert(_currentDocument !== document);

        // Remove closed doc from working set, if it was in there
        // This happens regardless of whether the document being closed was the current one or not
        _removeFromWorkingSet(document);

        // Note: EditorManager will dispose the closed document's now-unneeded editor either in
        // response to the editor-swap call above, or the _removeFromWorkingSet() call, depending on
        // circumstances. See notes in EditorManager for more.
    }

    /**
     * Equivalent to calling closeDocument() for all Documents. Same caveat: this discards any
     * unsaved changes, so the UI should confirm with the user before calling this.
     */
    function closeAll() {
        // TODO: could be more efficient by clearing working set in bulk instead of via
        // individual notifications, and ensuring we don't switch editors while closing...

        var allDocs = getAllOpenDocuments();

        allDocs.forEach(closeDocument);
    }

    /**
     * @private
     * Preferences callback. Saves the document file paths for the working set.
     */
    function _savePreferences(storage) {
        // save the working set file paths
        var files       = [],
            isActive    = false,
            workingSet  = getWorkingSet(),
            currentDoc  = getCurrentDocument();

        workingSet.forEach(function (value, index) {
            // flag the currently active editor
            isActive = (value === currentDoc);

            files.push({
                file: value.file.fullPath,
                active: isActive
            });
        });

        storage.files = files;
    }

    /**
     * @constructor
     * A single editable document, e.g. an entry in the working set list. Documents are unique per
     * file, so it IS safe to compare them with '==' or '==='.
     * @param {!FileEntry} file  The file being edited. Need not lie within the project.
     * @param {?CodeMirror} editor  Optional. The editor that will maintain the document state (current text
     *          and undo stack). It is assumed that the editor text has already been initialized
     *          with the file's contents. The editor may be null when the working set is restored
     *          at initialization.
     * @param {?Date} initialTimestamp  Timestamp of file at the time we read its contents from disk.
     *          Required if editor is passed.
     */
    function Document(file, editor, initialTimestamp) {
        if (!(this instanceof Document)) {  // error if constructor called without 'new'
            throw new Error("Document constructor must be called with 'new'");
        }
        if (getDocumentForFile(file) !== null) {
            throw new Error("Creating a document + editor when one already exists, for: " + file);
        }

        this.file = file;

        if (editor) {
            this._setEditor(editor, initialTimestamp);
        }
    }
    /**
     * The FileEntry for the document being edited.
     * @type {!FileEntry}
     */
    Document.prototype.file = 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;

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

    /**
     * @private
     * NOTE: this is actually "semi-private"; EditorManager also accesses this field. But no one
     * other than DocumentManager and EditorManager should access it.
     * The editor may be null in the case that the document working set was
     * restored from storage but an editor was not yet created.
     * TODO: we should close on whether private fields are declared on the prototype like this (vs.
     * just set in the constructor).
     * @type {?CodeMirror}
     */
    Document.prototype._editor = null;

    /**
     * @private
     * Initialize the editor instance for this file.
     * @param {!CodeMirror} editor
     * @param {!Date} initialTimestamp
     */
    Document.prototype._setEditor = function (editor, initialTimestamp) {
        // Editor can only be assigned once per Document
        console.assert(!this._editor);

        this._editor = editor;
        this.diskTimestamp = initialTimestamp;

        // Dirty-bit tracking
        editor.setOption("onChange", this._handleEditorChange.bind(this));
        this.isDirty = false;
    };

    /**
     * @return {string} The document's current contents; may not be saved to disk
     *  yet. Returns null if the file was not yet read and no editor was
     *  created.
     */
    Document.prototype.getText = function () {
        return this._editor.getValue();
    };

    /**
     * Sets the contents of the document. Treated as an edit.
     * @param {!string} text The text to replace the contents of the document with.
     */
    Document.prototype.setText = function (text) {
        this._editor.setValue(text);
    };

    /**
     * Sets the contents of the document. Treated as reloading the document from disk: the document
     * will be marked clean with a new timestamp, and the undo/redo history is cleared.
     * @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.
     */
    Document.prototype.refreshText = function (text, newTimestamp) {
        this._editor.setValue(text);
        this._editor.clearHistory();

        this._markClean();
        this.diskTimestamp = newTimestamp;
    };

    /**
     * Sets the cursor position in the document.
     * @param {number} line The 0 based line number.
     * @param {number} char The 0 based character position.
     */
    Document.prototype.setCursor = function (line, char) {
        this._editor.setCursor(line, char);
    };

    /**
     * @private
     */
    Document.prototype._handleEditorChange = function () {
        if (!this._editor) {
            return;
        }

        // On any change, mark the file dirty. In the future, we should make it so that if you
        // undo back to the last saved state, we mark the file clean.
        var wasDirty = this.isDirty;
        this.isDirty = true;

        // If file just became dirty, notify listeners, and add it to working set (if not already there)
        if (!wasDirty) {
            $(exports).triggerHandler("dirtyFlagChange", this);
            addToWorkingSet(this);
        }
    };

    /** Marks the document not dirty. Should be called after the document is saved to disk. */
    Document.prototype._markClean = function () {
        if (!this._editor) {
            return;
        }

        this.isDirty = false;
        $(exports).triggerHandler("dirtyFlagChange", this);
    };

    /**
     * Called when the document is saved (which currently happens in FileCommandHandlers). Updates the
     * dirty bit and notifies listeners of the save.
     */
    Document.prototype.notifySaved = function () {
        this._markClean();
        $(exports).triggerHandler("documentSaved", this);

        // TODO: this introduces two race conditions: (a) if user leaves app & returns before this
        // async op finishes, we'll think file was modified externally when it wasn't; (b) if ext
        // app overwrites file in between when we saved and when this op finishes, we'll miss the
        // fact that it was modified externally
        var thisDoc = this;
        this.file.getMetadata(
            function (metadata) {
                thisDoc.diskTimestamp = metadata.modificationTime;
            },
            function (error) {
                console.log("Error updating timestamp after saving file: " + this.file.fullPath);
            }
        );
    };

    /* (pretty toString(), to aid debugging) */
    Document.prototype.toString = function () {
        return "[Document " + this.file.fullPath + " " + (this.isDirty ? "(dirty!)" : "(clean)") + "]";
    };

    /**
     * @private
     * Initializes the working set.
     */
    function _init() {
        var prefs       = PreferencesManager.getPreferences(PREFERENCES_CLIENT_ID);

        if (prefs.files === undefined) {
            return;
        }

        var projectRoot = ProjectManager.getProjectRoot(),
            filesToOpen = [],
            activeFile;

        // in parallel, check if files exist
        // TODO (jasonsj): delay validation until user requests the editor (like Eclipse)?
        //                 e.g. A file to restore no longer exists. Should we silently ignore
        //                 it or let the user be notified when they attempt to open the Document?
        var result = (function () {
            var deferred        = new $.Deferred();
            var fileCount       = prefs.files.length,
                responseCount   = 0;

            function next() {
                responseCount++;

                if (responseCount === fileCount) {
                    deferred.resolve();
                }
            }

            prefs.files.forEach(function (value, index) {
                // check if the file still exists
                projectRoot.getFile(value.file, {},
                    function (fileEntry) {
                        // maintain original sequence
                        filesToOpen[index] = fileEntry;

                        if (value.active) {
                            activeFile = fileEntry;
                        }

                        next();
                    },
                    function (error) {
                        filesToOpen[index] = null;
                        next();
                    });
            });

            return deferred;
        }());

        result.done(function () {
            var activeDoc,
                doc;

            // Add all existing files to the working set
            filesToOpen.forEach(function (value, index) {
                if (value) {
                    doc = new Document(value);
                    addToWorkingSet(doc);

                    if (value === activeFile) {
                        activeDoc = doc;
                    }
                }
            });

            // Initialize the active editor
            if (!activeDoc && _workingSet.length > 0) {
                activeDoc = _workingSet[0];
            }

            if (activeDoc) {
                CommandManager.execute(Commands.FILE_OPEN, { fullPath: activeDoc.file.fullPath });
            }
        });
    }

    // Define public API
    exports.Document = Document;
    exports.getCurrentDocument = getCurrentDocument;
    exports.getDocumentForPath = getDocumentForPath;
    exports.getDocumentForFile = getDocumentForFile;
    exports.getWorkingSet = getWorkingSet;
    exports.findInWorkingSet = findInWorkingSet;
    exports.getAllOpenDocuments = getAllOpenDocuments;
    exports.showInEditor = showInEditor;
    exports.addToWorkingSet = addToWorkingSet;
    exports.closeDocument = closeDocument;
    exports.readAsText = readAsText;
    exports.closeAll = closeAll;

    // Register preferences callback
    PreferencesManager.addPreferencesClient(PREFERENCES_CLIENT_ID, _savePreferences, this);

    // Initialize after ProjectManager is loaded
    $(ProjectManager).on("initializeComplete", function (event, projectRoot) {
        _init();
    });
});


/* Variables.less
 * Variables to customize the look and feel of Bootstrap
 * ----------------------------------------------------- */

// Links
@linkColor:         #0069d6;
@linkColorHover:    darken(@linkColor, 15);

// Grays
@black:             #000;
@grayDark:          lighten(@black, 25%);
@gray:              lighten(@black, 50%);
@grayLight:         lighten(@black, 75%);
@grayLighter:       lighten(@black, 90%);
@white:             #fff;

// Accent Colors
@blue:              #049CDB;
@blueDark:          #0064CD;
@green:             #46a546;
@red:               #9d261d;
@yellow:            #ffc40d;
@orange:            #f89406;
@pink:              #c3325f;
@purple:            #7a43b6;

// Baseline grid
@basefont:          13px;
@baseline:          18px;

// Griditude
// Modify the grid styles in mixins.less
@gridColumns:       16;
@gridColumnWidth:   40px;
@gridGutterWidth:   20px;
@extraSpace:        (@gridGutterWidth * 2); // For our grid calculations
@siteWidth:         (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1));

// Color Scheme
// Use this to roll your own color schemes if you like (unused by Bootstrap by default)
@baseColor:         @blue;                  // Set a base color
@complement:        spin(@baseColor, 180);  // Determine a complementary color
@split1:            spin(@baseColor, 158);  // Split complements
@split2:            spin(@baseColor, -158);
@triad1:            spin(@baseColor, 135);  // Triads colors
@triad2:            spin(@baseColor, -135);
@tetra1:            spin(@baseColor, 90);   // Tetra colors
@tetra2:            spin(@baseColor, -90);
@analog1:           spin(@baseColor, 22);   // Analogs colors
@analog2:           spin(@baseColor, -22);

// More variables coming soon:
// - @basefont to @baseFontSize
// - @baseline to @baseLineHeight
// - @baseFontFamily
// - @primaryButtonColor
// - anything else? File an issue on GitHub


/* =============================================================
 * bootstrap-scrollspy.js v1.4.0
 * http://twitter.github.com/bootstrap/javascript.html#scrollspy
 * =============================================================
 * Copyright 2011 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ============================================================== */

!function ( $ ) {

  "use strict"

  var $window = $(window)

  function ScrollSpy( topbar, selector ) {
    var processScroll = $.proxy(this.processScroll, this)
    this.$topbar = $(topbar)
    this.selector = selector || 'li > a'
    this.refresh()
    this.$topbar.delegate(this.selector, 'click', processScroll)
    $window.scroll(processScroll)
    this.processScroll()
  }

  ScrollSpy.prototype = {

      refresh: function () {
        this.targets = this.$topbar.find(this.selector).map(function () {
          var href = $(this).attr('href')
          return /^#\w/.test(href) && $(href).length ? href : null
        })

        this.offsets = $.map(this.targets, function (id) {
          return $(id).offset().top
        })
      }

    , processScroll: function () {
        var scrollTop = $window.scrollTop() + 10
          , offsets = this.offsets
          , targets = this.targets
          , activeTarget = this.activeTarget
          , i

        for (i = offsets.length; i--;) {
          activeTarget != targets[i]
            && scrollTop >= offsets[i]
            && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
            && this.activateButton( targets[i] )
        }
      }

    , activateButton: function (target) {
        this.activeTarget = target

        this.$topbar
          .find(this.selector).parent('.active')
          .removeClass('active')

        this.$topbar
          .find(this.selector + '[href="' + target + '"]')
          .parent('li')
          .addClass('active')
      }

  }

  /* SCROLLSPY PLUGIN DEFINITION
   * =========================== */

  $.fn.scrollSpy = function( options ) {
    var scrollspy = this.data('scrollspy')

    if (!scrollspy) {
      return this.each(function () {
        $(this).data('scrollspy', new ScrollSpy( this, options ))
      })
    }

    if ( options === true ) {
      return scrollspy
    }

    if ( typeof options == 'string' ) {
      scrollspy[options]()
    }

    return this
  }

  $(document).ready(function () {
    $('body').scrollSpy('[data-scrollspy] li > a')
  })

}( window.jQuery || window.ender );


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

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

.jstree-default-rtl .jstree-hovered { background:#e7f4f9; border:1px solid #d8f0fa; padding:0 2px 0 1px; }
.jstree-default-rtl .jstree-clicked { background:#beebff; border:1px solid #99defd; padding:0 2px 0 1px; }
.jstree-default-rtl a .jstree-icon { background-position:-56px -19px; }
.jstree-default-rtl a.jstree-loading .jstree-icon { background:url("throbber.gif") center center no-repeat !important; }

.jstree-default-rtl.jstree-focused { background:#ffffee; }

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

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

.jstree-default-rtl .jstree-search { font-style:italic; }

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

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

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

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

/* IE6 BEGIN */
.jstree-default-rtl li,
.jstree-default-rtl ins,
#vakata-dragged.jstree-default-rtl .jstree-invalid,
#vakata-dragged.jstree-default-rtl .jstree-ok,
#jstree-marker.jstree-default-rtl { _background-image:url("d.gif"); }
.jstree-default-rtl .jstree-open ins { _background-position:-72px 0; }
.jstree-default-rtl .jstree-closed ins { _background-position:-54px 0; }
.jstree-default-rtl .jstree-leaf ins { _background-position:-36px 0; }
.jstree-default-rtl a ins.jstree-icon { _background-position:-56px -19px; }
#vakata-contextmenu.jstree-default-rtl-context ins { _display:none; }
#vakata-contextmenu.jstree-default-rtl-context li { _zoom:1; }
.jstree-default-rtl .jstree-undetermined a .jstree-checkbox { _background-position:-18px -19px; }
.jstree-default-rtl .jstree-checked a .jstree-checkbox { _background-position:-36px -19px; }
.jstree-default-rtl .jstree-unchecked a .jstree-checkbox { _background-position:0px -19px; }
/* IE6 END */

/* RTL part */
.jstree-default-rtl .jstree-hovered, .jstree-default-rtl .jstree-clicked { padding:0 1px 0 2px; }
.jstree-default-rtl li { background-image:url("dots.gif"); background-position: 100% 0px; }
.jstree-default-rtl .jstree-checked > a > .jstree-checkbox { background-position:-36px -19px; margin-left:2px; }
.jstree-default-rtl .jstree-unchecked > a > .jstree-checkbox { background-position:0px -19px; margin-left:2px; }
.jstree-default-rtl .jstree-undetermined > a > .jstree-checkbox { background-position:-18px -19px; margin-left:2px; }
.jstree-default-rtl .jstree-checked > a > .jstree-checkbox:hover { background-position:-36px -37px; }
.jstree-default-rtl .jstree-unchecked > a > .jstree-checkbox:hover { background-position:0px -37px; }
.jstree-default-rtl .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-18px -37px; }


$(DocumentManager).on("currentDocumentChange", function (event) {

// The the cause of the doc change was not openAndSelectDocument, so pick the best fileSelectionFocus

if (curDoc !== null && DocumentManager.findInWorkingSet(curDoc.file.fullPath) !== -1) {

keep keep keep keep keep replace keep keep keep

if (curDoc && DocumentManager.findInWorkingSet(curDoc.file.fullPath) !== -1) {

/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, $: false, brackets: false, FileError: false */

/**
 * ProjectManager is the model for the set of currently open project. It is responsible for
 * creating and updating the project tree when projects are opened and when changes occur to
 * the file tree.
 *
 * This module dispatches 1 event:
 *    - initializeComplete -- When the ProjectManager initializes the first
 *                            project at application start-up.
 *
 * These are jQuery events, so to listen for them you do something like this:
 *    $(ProjectManager).on("eventname", handler);
 */
define(function (require, exports, module) {
    'use strict';

    // Load dependent non-module scripts
    require("thirdparty/jstree_pre1.0_fix_1/jquery.jstree");

    // Load dependent modules
    var NativeFileSystem    = require("NativeFileSystem").NativeFileSystem,
        PreferencesManager  = require("PreferencesManager"),
        DocumentManager     = require("DocumentManager"),
        EditorManager       = require("EditorManager"),
        CommandManager      = require("CommandManager"),
        Commands            = require("Commands"),
        Strings             = require("strings"),
        FileViewController  = require("FileViewController");

    /**
     * @private
     * Reference to the tree control
     * @type {jQueryObject}
     */
    var _projectTree = null;

    /**
     * @private
     * @see getProjectRoot()
     */
    var _projectRoot = null;

    /**
     * @private
     * Used to initialize jstree state
     */
    var _projectInitialLoad = {
        previous : [],
        id : 0,
        fullPathToIdMap : {}
    };

    $(FileViewController).on("documentSelectionFocusChange", function (event) {
        var curDoc = DocumentManager.getCurrentDocument();
        if ((curDoc !== null)
                && (FileViewController.getFileSelectionFocus() !== FileViewController.WORKING_SET_VIEW)) {
            $("#project-files-container li").is(function (index) {
                var entry = $(this).data("entry");

                if (entry && entry.fullPath === curDoc.file.fullPath && !_projectTree.jstree("is_selected", $(this))) {
                    //we don't want to trigger another selection change event, so manually deselect
                    //and select without sending out notifications
                    _projectTree.jstree("deselect_all");
                    _projectTree.jstree("select_node", $(this), false);
                    return true;
                }
                return false;
            });
        } else {
            _projectTree.jstree("deselect_all");
        }
    });

    /**
     * Unique PreferencesManager clientID
     */
    var PREFERENCES_CLIENT_ID = "com.adobe.brackets.ProjectManager";

    /**
     * Returns the root folder of the currently loaded project, or null if no project is open (during
     * startup, or running outside of app shell).
     * @return {DirectoryEntry}
     */
    function getProjectRoot() {
        return _projectRoot;
    }

    /**
     * Returns true if absPath lies within the project, false otherwise.
     * FIXME: Does not support paths containing ".."
     */
    function isWithinProject(absPath) {
        var rootPath = _projectRoot.fullPath;
        if (rootPath.charAt(rootPath.length - 1) !== "/") {  // TODO: standardize whether DirectoryEntry.fullPath can end in "/"
            rootPath += "/";
        }
        return (absPath.indexOf(rootPath) === 0);
    }
    /**
     * If absPath lies within the project, returns a project-relative path. Else returns absPath
     * unmodified.
     * FIXME: Does not support paths containing ".."
     */
    function makeProjectRelativeIfPossible(absPath) {
        if (isWithinProject(absPath)) {
            var relPath = absPath.slice(_projectRoot.fullPath.length);
            if (relPath.charAt(0) === '/') {  // TODO: standardize whether DirectoryEntry.fullPath can end in "/"
                relPath = relPath.slice(1);
            }
            return relPath;
        }
        return absPath;
    }

    /**
     * @private
     * Preferences callback. Saves current project path.
     */
    function _savePreferences(storage) {
        // save the current project
        storage.projectPath = _projectRoot.fullPath;

        // save jstree state
        var openNodes = [],
            projectPathLength = _projectRoot.fullPath.length + 1,
            entry,
            fullPath,
            shortPath,
            depth;

        // Query open nodes by class selector
        $(".jstree-open").each(function (index) {
            entry = $(this).data("entry");

            if (entry.fullPath) {
                fullPath = entry.fullPath;

                // Truncate project path prefix
                shortPath = fullPath.slice(projectPathLength);

                // Determine depth of the node by counting path separators.
                // Children at the root have depth of zero.
                depth = shortPath.split("/").length - 1;

                // Map tree depth to list of open nodes
                if (openNodes[depth] === undefined) {
                    openNodes[depth] = [];
                }

                openNodes[depth].push(fullPath);
            }
        });

        // Store the open nodes by their full path and persist to storage
        storage.projectTreeState = openNodes;
    }

    /**
     * @private
     * Given an input to jsTree's json_data.data setting, display the data in the file tree UI
     * (replacing any existing file tree that was previously displayed). This input could be
     * raw JSON data, or it could be a dataprovider function. See jsTree docs for details:
     * http://www.jstree.com/documentation/json_data
     */
    function _renderTree(treeDataProvider) {
        var projectTreeContainer = $("#project-files-container"),
            result = new $.Deferred();

        // Instantiate tree widget
        // (jsTree is smart enough to replace the old tree if there's already one there)
        _projectTree = projectTreeContainer
            .jstree(
                {
                    plugins : ["ui", "themes", "json_data", "crrm"],
                    json_data : { data: treeDataProvider, correct_state: false },
                    core : { animation: 0 },
                    themes : { theme: "brackets", url: "styles/jsTreeTheme.css", dots: false, icons: false },
                        //(note: our actual jsTree theme CSS lives in brackets.less; we specify an empty .css
                        // file because jsTree insists on loading one itself)
                    strings : { loading : "Loading ...", new_node : "New node" }    // TODO: localization
                }
            )
            .bind(
                "select_node.jstree",
                function (event, data) {
                    var entry = data.rslt.obj.data("entry");
                    if (entry.isFile) {
                        FileViewController.openAndSelectDocument(entry.fullPath, "ProjectManager");
                    }
                }
            )
            .bind(
                "reopen.jstree",
                function (event, data) {
                    // This handler fires for the initial load and subsequent
                    // reload_nodes events. For each depth level of the tree, we open
                    // the saved nodes by a fullPath lookup.
                    if (_projectInitialLoad.previous.length > 0) {
                        // load previously open nodes by increasing depth
                        var toOpenPaths = _projectInitialLoad.previous.shift(),
                            toOpenIds   = [];

                        // use path to lookup ID
                        $.each(toOpenPaths, function (index, value) {
                            toOpenIds.push(_projectInitialLoad.fullPathToIdMap[value]);
                        });

                        // specify nodes to open and load
                        data.inst.data.core.to_open = toOpenIds;
                        _projectTree.jstree("reload_nodes", false);
                    }
                    if (_projectInitialLoad.previous.length === 0) {
                        result.resolve();
                    }
                }
            );

        // jstree has a default event handler for dblclick that attempts to clear the
        // global window selection (presumably because it doesn't want text within the tree
        // to be selected). This ends up messing up CodeMirror, and we don't need this anyway
        // since we've turned off user selection of UI text globally. So we just unbind it,
        // and add our own double-click handler here.
        // Filed this bug against jstree at https://github.com/vakata/jstree/issues/163
        _projectTree.bind("init.jstree", function () {
            _projectTree
                .unbind("dblclick.jstree")
                .bind("dblclick.jstree", function (event) {
                    var entry = $(event.target).closest("li").data("entry");
                    if (entry.isFile) {
                        FileViewController.addToWorkingSetAndSelect(entry.fullPath);
                    }
                });
        });

        return result;
    }

    /** @param {Entry} entry File or directory to filter */
    function _shouldShowInTree(entry) {
        if (entry.name[0] === ".") {       // "." prefix is always hidden on Mac
            return false;
        }

        if (entry.name === "Thumbs.db") {  // "Thumbs.db" hidden file auto-generated by Win
            return false;
        }

        return true;
    }

    /**
     * @private
     * Given an array of NativeFileSystem entries, returns a JSON array representing them in the format
     * required by jsTree. Saves the corresponding Entry object as metadata (which jsTree will store in
     * the DOM via $.data()).
     *
     * Does NOT recursively traverse the file system: folders are marked as expandable but are given no
     * children initially.
     *
     * @param {Array.<Entry>} entries  Array of NativeFileSystem entry objects.
     * @return {Array} jsTree node data: array of JSON objects
     */
    function _convertEntriesToJSON(entries) {
        var jsonEntryList = [],
            entry,
            entryI;

        for (entryI = 0; entryI < entries.length; entryI++) {
            entry = entries[entryI];

            if (_shouldShowInTree(entry)) {
                var jsonEntry = {
                    data: entry.name,
                    attr: { id: "node" + _projectInitialLoad.id++ },
                    metadata: { entry: entry }
                };
                if (entry.isDirectory) {
                    jsonEntry.children = [];
                    jsonEntry.state = "closed";
                }

                // For more info on jsTree's JSON format see: http://www.jstree.com/documentation/json_data
                jsonEntryList.push(jsonEntry);

                // Map path to ID to initialize loaded and opened states
                _projectInitialLoad.fullPathToIdMap[entry.fullPath] = jsonEntry.attr.id;
            }
        }
        return jsonEntryList;
    }

    /**
     * @private
     * Called by jsTree when the user has expanded a node that has never been expanded before. We call
     * jsTree back asynchronously with the node's immediate children data once the subfolder is done
     * being fetched.
     *
     * @param {jQueryObject} treeNode  jQ object for the DOM node being expanded
     * @param {function(Array)} jsTreeCallback  jsTree callback to provide children to
     */
    function _treeDataProvider(treeNode, jsTreeCallback) {
        var dirEntry;

        if (treeNode === -1) {
            // Special case: root of tree
            dirEntry = _projectRoot;
        } else {
            // All other nodes: the DirectoryEntry is saved as jQ data in the tree (by _convertEntriesToJSON())
            dirEntry = treeNode.data("entry");
        }

        // Fetch dirEntry's contents
        dirEntry.createReader().readEntries(
            function (entries) {
                var subtreeJSON = _convertEntriesToJSON(entries);

                //If the list is empty, add an empty object so the loading message goes away
                if (subtreeJSON.length === 0) {
                    subtreeJSON.push({});
                }

                jsTreeCallback(subtreeJSON);
            },
            function (error) {
                brackets.showModalDialog(
                    brackets.DIALOG_ID_ERROR,
                    Strings.ERROR_LOADING_PROJECT,
                    Strings.format(Strings.READ_DIRECTORY_ENTRIES_ERROR, dirEntry.fullPath, error.code)
                );
            }
        );

    }

    /**
     * Loads the given folder as a project. Normally, you would call openProject() instead to let the
     * user choose a folder.
     *
     * @param {string} rootPath  Absolute path to the root folder of the project.
     *  If rootPath is undefined or null, the last open project will be restored.
     * @return {Deferred} A $.Deferred() object that will be resolved when the
     *  project is loaded and tree is rendered, or rejected if the project path
     *  fails to load.
     */
    function loadProject(rootPath) {
        // reset tree node id's
        _projectInitialLoad.id = 0;

        var prefs = PreferencesManager.getPreferences(PREFERENCES_CLIENT_ID),
            result = new $.Deferred(),
            resultRenderTree,
            isFirstProjectOpen = false;

        if (rootPath === null || rootPath === undefined) {
            // Load the last known project into the tree
            rootPath = prefs.projectPath;
            isFirstProjectOpen = true;

            // TODO (jasonsj): handle missing paths, see issue #100
            _projectInitialLoad.previous = prefs.projectTreeState;

            if (brackets.inBrowser) {
                // In browser: dummy folder tree (hardcoded in ProjectManager)
                rootPath = "DummyProject";
            }
        }

        // Set title
        var projectName = rootPath.substring(rootPath.lastIndexOf("/") + 1);
        $("#project-title").html(projectName);

        // Populate file tree as long as we aren't running in the browser
        if (!brackets.inBrowser) {
            // Point at a real folder structure on local disk
            NativeFileSystem.requestNativeFileSystem(rootPath,
                function (rootEntry) {
                    // Success!
                    _projectRoot = rootEntry;

                    // The tree will invoke our "data provider" function to populate the top-level items, then
                    // go idle until a node is expanded - at which time it'll call us again to fetch the node's
                    // immediate children, and so on.
                    resultRenderTree = _renderTree(_treeDataProvider);

                    resultRenderTree.done(function () {
                        result.resolve();

                        if (isFirstProjectOpen) {
                            $(exports).triggerHandler("initializeComplete", _projectRoot);
                        }
                    });
                    resultRenderTree.fail(function () {
                        result.reject();
                    });
                },
                function (error) {
                    brackets.showModalDialog(
                        brackets.DIALOG_ID_ERROR,
                        Strings.ERROR_LOADING_PROJECT,
                        Strings.format(Strings.REQUEST_NATIVE_FILE_SYSTEM_ERROR, rootPath, error.code,
                            function () { result.reject(); }
                            )
                    );
                }
                );
        }

        return result;
    }

    /**
     * Displays a browser dialog where the user can choose a folder to load.
     * (If the user cancels the dialog, nothing more happens).
     */
    function openProject() {
        // Confirm any unsaved changes first. We run the command in "prompt-only" mode, meaning it won't
        // actually close any documents even on success; we'll do that manually after the user also oks
        //the folder-browse dialog.
        CommandManager.execute(Commands.FILE_CLOSE_ALL, { promptOnly: true })
            .done(function () {
                // Pop up a folder browse dialog
                NativeFileSystem.showOpenDialog(false, true, "Choose a folder", null, null,
                    function (files) {
                        // If length == 0, user canceled the dialog; length should never be > 1
                        if (files.length > 0) {
                            // Actually close all the old files now that we know for sure we're proceeding
                            DocumentManager.closeAll();

                            // Load the new project into the folder tree
                            loadProject(files[0]);
                        }
                    },
                    function (error) {
                        brackets.showModalDialog(
                            brackets.DIALOG_ID_ERROR,
                            Strings.ERROR_LOADING_PROJECT,
                            Strings.format(Strings.OPEN_DIALOG_ERROR, error.code)
                        );
                    }
                    );
            });
        // if fail, don't open new project: user canceled (or we failed to save its unsaved changes)
    }

    /**
     * Returns the FileEntry or DirectoryEntry corresponding to the selected item, or null
     * if no item is selected.
     *
     * @return {?Entry}
     */
    function getSelectedItem() {
        var selected = _projectTree.jstree("get_selected");
        if (selected) {
            return selected.data("entry");
        }
        return null;
    }

    /**
     * Create a new item in the project tree.
     *
     * @param baseDir {string} Full path of the directory where the item should go
     * @param initialName {string} Initial name for the item
     * @param skipRename {boolean} If true, don't allow the user to rename the item
     * @return {Deferred} A $.Deferred() object that will be resolved with the FileEntry
     *  of the created object, or rejected if the user cancelled or entered an illegal
     *  filename.
     */
    function createNewItem(baseDir, initialName, skipRename) {
        // TODO: Need API to get tree node for baseDir.
        // In the meantime, pass null for node so new item is placed
        // relative to the selection
        var node = null,
            selection = _projectTree.jstree("get_selected"),
            selectionEntry = null,
            position = "inside",
            escapeKeyPressed = false,
            result = new $.Deferred();

        // get the FileEntry or DirectoryEntry
        if (selection) {
            selectionEntry = selection.data("entry");
        }

        // move selection to parent DirectoryEntry
        if (selectionEntry && selectionEntry.isFile) {
            position = "after";

            // FIXME (jasonsj): get_parent returns the tree instead of the directory?
            /*
            selection = _projectTree.jstree("get_parent", selection);

            if (typeof(selection.data) == "function") {
                // get Entry from tree node
                // note that the jstree root will return undefined
                selectionEntry = selection.data("entry");
            }
            else {
                // reset here. will be replaced with project root.
                selectionEntry = null;
            }
            */
            // FIXME (jasonsj): hackish way to get parent directory; replace with Entry.getParent() when available
            var filePath = selectionEntry.fullPath;
            selectionEntry = new NativeFileSystem.DirectoryEntry(filePath.substring(0, filePath.lastIndexOf("/")));
        }

        // use the project root DirectoryEntry
        if (!selectionEntry) {
            selectionEntry = getProjectRoot();
        }

        _projectTree.on("create.jstree", function (event, data) {
            $(event.target).off("create.jstree");

            function errorCleanup() {
                // TODO: If an error occurred, we should allow the user to fix the filename.
                // For now we just remove the node so you have to start again.
                _projectTree.jstree("remove", data.rslt.obj);
                result.reject();
            }

            if (!escapeKeyPressed) {
                // Validate file name
                // TODO: There are some filenames like COM1, LPT3, etc. that are not valid on Windows.
                // We may want to add checks for those here.
                // See http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
                if (data.rslt.name.search(/[\/?*:;\{\}<>\\|]+/) !== -1) {
                    brackets.showModalDialog(
                        brackets.DIALOG_ID_ERROR,
                        Strings.INVALID_FILENAME_TITLE,
                        Strings.INVALID_FILENAME_MESSAGE
                    );

                    errorCleanup();
                    return;
                }

                // Use getFile() to create the new file
                selectionEntry.getFile(
                    data.rslt.name,
                    {create: true, exclusive: true},
                    function (entry) {
                        data.rslt.obj.data("entry", entry);
                        _projectTree.jstree("select_node", data.rslt.obj, true);
                        result.resolve(entry);
                    },
                    function (error) {
                        if ((error.code === FileError.PATH_EXISTS_ERR)
                                || (error.code === FileError.TYPE_MISMATCH_ERR)) {
                            brackets.showModalDialog(
                                brackets.DIALOG_ID_ERROR,
                                Strings.INVALID_FILENAME_TITLE,
                                Strings.format(Strings.FILE_ALREADY_EXISTS, data.rslt.name)
                            );
                        } else {
                            var errString = error.code === FileError.NO_MODIFICATION_ALLOWED_ERR ?
                                             Strings.NO_MODIFICATION_ALLOWED_ERR :
                                             Strings.format(String.GENERIC_ERROR, error.code);
                            var errMsg = Strings.format(Strings.ERROR_CREATING_FILE, data.rslt.name, errString);

                            brackets.showModalDialog(
                                brackets.DIALOG_ID_ERROR,
                                Strings.ERROR_CREATING_FILE_TITLE,
                                errMsg
                            );
                        }

                        errorCleanup();
                    }
                );
            } else { //escapeKeyPressed
                errorCleanup();
            }
        });

        // Create the node and open the editor
        _projectTree.jstree("create", node, position, {data: initialName}, null, skipRename);

        var renameInput = _projectTree.find(".jstree-rename-input");

        renameInput.on("keydown", function (event) {
            // Listen for escape key on keydown, so we can remove the node in the create.jstree handler above
            if (event.keyCode === 27) {
                escapeKeyPressed = true;
            }
        });

        // TODO: Figure out better way to style this input. All styles are inlined by jsTree...
        renameInput.css({ left: "17px", height: "24px"})
            .parent().css({ height: "26px"});

        return result;
    }

    // Define public API
    exports.getProjectRoot  = getProjectRoot;
    exports.isWithinProject = isWithinProject;
    exports.makeProjectRelativeIfPossible = makeProjectRelativeIfPossible;
    exports.openProject     = openProject;
    exports.loadProject     = loadProject;
    exports.getSelectedItem = getSelectedItem;
    exports.createNewItem   = createNewItem;

    // Initialize now
    (function () {
        var loadedPath = window.location.pathname;
        var bracketsSrc = loadedPath.substr(0, loadedPath.lastIndexOf("/"));

        // On Windows, when loading from a file, window.location.pathname has
        // a leading '/'. Remove that here.
        // TODO: Figure out a better way to handle this...
        if (bracketsSrc[0] === '/' && bracketsSrc[2] === ":") {
            bracketsSrc = bracketsSrc.substr(1);
        }

        var defaults = {
            projectPath:      bracketsSrc, /* initialze to brackets source */
            projectTreeState: ""           /* TODO (jasonsj): jstree state */
        };
        PreferencesManager.addPreferencesClient(PREFERENCES_CLIENT_ID, _savePreferences, this, defaults);
    }());
});


module Jasmine
  module Core
    VERSION = "1.1.0"
  end
end


/* Patterns.less
 * Repeatable UI elements outside the base styles provided from the scaffolding
 * ---------------------------------------------------------------------------- */

// TOPBAR
// ------

// Topbar for Branding and Nav
.topbar {
  height: 40px;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 10000;
  overflow: visible;

  // Links get text shadow
  a {
    color: @grayLight;
    text-shadow: 0 -1px 0 rgba(0,0,0,.25);
  }

  // Hover and active states
  // h3 for backwards compatibility
  h3 a:hover,
  .brand:hover,
  ul .active > a {
    background-color: #333;
    background-color: rgba(255,255,255,.05);
    color: @white;
    text-decoration: none;
  }

  // Website name
  // h3 left for backwards compatibility
  h3 {
    position: relative;
  }
  h3 a,
  .brand {
    float: left;
    display: block;
    padding: 8px 20px 12px;
    margin-left: -20px; // negative indent to left-align the text down the page
    color: @white;
    font-size: 20px;
    font-weight: 200;
    line-height: 1;
  }

  // Plain text in topbar
  p {
    margin: 0;
    line-height: 40px;
    a:hover {
      background-color: transparent;
      color: @white;
    }
  }

  // Search Form
  form {
    float: left;
    margin: 5px 0 0 0;
    position: relative;
    .opacity(100);
  }
  // Todo: remove from v2.0 when ready, added for legacy
  form.pull-right {
    float: right;
  }
  input {
    background-color: #444;
    background-color: rgba(255,255,255,.3);
    #font > .sans-serif(13px, normal, 1);
    padding: 4px 9px;
    color: @white;
    color: rgba(255,255,255,.75);
    border: 1px solid #111;
    .border-radius(4px);
    @shadow: inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0px rgba(255,255,255,.25);
    .box-shadow(@shadow);
    .transition(none);

    // Placeholder text gets special styles; can't be bundled together though for some reason
    &:-moz-placeholder {
      color: @grayLighter;
    }
    &::-webkit-input-placeholder {
      color: @grayLighter;
    }
    // Hover states
    &:hover {
      background-color: @grayLight;
      background-color: rgba(255,255,255,.5);
      color: @white;
    }
    // Focus states (we use .focused since IE8 and down doesn't support :focus)
    &:focus,
    &.focused {
      outline: 0;
      background-color: @white;
      color: @grayDark;
      text-shadow: 0 1px 0 @white;
      border: 0;
      padding: 5px 10px;
      .box-shadow(0 0 3px rgba(0,0,0,.15));
    }
  }
}

// gradient is applied to it's own element because overflow visible is not honored by ie when filter is present
// For backwards compatibility, include .topbar .fill
.topbar-inner,
.topbar .fill {
  background-color: #222;
  #gradient > .vertical(#333, #222);
  @shadow: 0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1);
  .box-shadow(@shadow);
}

// NAVIGATION
// ----------

// Topbar Nav
// ul.nav for all topbar based navigation to avoid inheritance issues and over-specificity
// For backwards compatibility, leave in .topbar div > ul
.topbar div > ul,
.nav {
  display: block;
  float: left;
  margin: 0 10px 0 0;
  position: relative;
  left: 0;
  > li {
    display: block;
    float: left;
  }
  a {
    display: block;
    float: none;
    padding: 10px 10px 11px;
    line-height: 19px;
    text-decoration: none;
    &:hover {
      color: @white;
      text-decoration: none;
    }
  }
  .active > a {
    background-color: #222;
    background-color: rgba(0,0,0,.5);
  }

  // Secondary (floated right) nav in topbar
  &.secondary-nav {
    float: right;
    margin-left: 10px;
    margin-right: 0;
    // backwards compatibility
    .menu-dropdown,
    .dropdown-menu {
      right: 0;
      border: 0;
    }
  }
  // Dropdowns within the .nav
  // a.menu:hover and li.open .menu for backwards compatibility
  a.menu:hover,
  li.open .menu,
  .dropdown-toggle:hover,
  .dropdown.open .dropdown-toggle {
    background: #444;
    background: rgba(255,255,255,.05);
  }
  // .menu-dropdown for backwards compatibility
  .menu-dropdown,
  .dropdown-menu {
    background-color: #333;
    // a.menu for backwards compatibility
    a.menu,
    .dropdown-toggle {
      color: @white;
      &.open {
        background: #444;
        background: rgba(255,255,255,.05);
      }
    }
    li a {
      color: #999;
      text-shadow: 0 1px 0 rgba(0,0,0,.5);
      &:hover {
        #gradient > .vertical(#292929,#191919);
        color: @white;
      }
    }
    .active a {
      color: @white;
    }
    .divider {
      background-color: #222;
      border-color: #444;
    }
  }
}

// For backwards compatibility with new dropdowns, redeclare dropdown link padding
.topbar ul .menu-dropdown li a,
.topbar ul .dropdown-menu li a {
  padding: 4px 15px;
}

// Dropdown Menus
// Use the .menu class on any <li> element within the topbar or ul.tabs and you'll get some superfancy dropdowns
// li.menu for backwards compatibility
li.menu,
.dropdown {
  position: relative;
}
// The link that is clicked to toggle the dropdown
// a.menu for backwards compatibility
a.menu:after,
.dropdown-toggle:after {
  width: 0;
  height: 0;
  display: inline-block;
  content: "↓";
  text-indent: -99999px;
  vertical-align: top;
  margin-top: 8px;
  margin-left: 4px;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 4px solid @white;
  .opacity(50);
}
// The dropdown menu (ul)
// .menu-dropdown for backwards compatibility
.menu-dropdown,
.dropdown-menu {
  background-color: @white;
  float: left;
  display: none; // None by default, but block on "open" of the menu
  position: absolute;
  top: 40px;
  z-index: 900;
  min-width: 160px;
  max-width: 220px;
  _width: 160px;
  margin-left: 0; // override default ul styles
  margin-right: 0;
  padding: 6px 0;
  zoom: 1; // do we need this?
  border-color: #999;
  border-color: rgba(0,0,0,.2);
  border-style: solid;
  border-width: 0 1px 1px;
  .border-radius(0 0 6px 6px);
  .box-shadow(0 2px 4px rgba(0,0,0,.2));
  .background-clip(padding-box);

  // Unfloat any li's to make them stack
  li {
    float: none;
    display: block;
    background-color: none;
  }
  // Dividers (basically an hr) within the dropdown
  .divider {
    height: 1px;
    margin: 5px 0;
    overflow: hidden;
    background-color: #eee;
    border-bottom: 1px solid @white;
  }
}

.topbar .dropdown-menu,
.dropdown-menu {
  // Links within the dropdown menu
  a {
    display: block;
    padding: 4px 15px;
    clear: both;
    font-weight: normal;
    line-height: 18px;
    color: @gray;
    text-shadow: 0 1px 0 @white;
    // Hover state
    &:hover,
    &.hover {
      #gradient > .vertical(#eeeeee, #dddddd);
      color: @grayDark;
      text-decoration: none;
      @shadow: inset 0 1px 0 rgba(0,0,0,.025), inset 0 -1px rgba(0,0,0,.025);
      .box-shadow(@shadow);
    }
  }
}

// Open state for the dropdown
// .open for backwards compatibility
.open,
.dropdown.open {
  // .menu for backwards compatibility
  .menu,
  .dropdown-toggle {
    color: @white;
    background: #ccc;
    background: rgba(0,0,0,.3);
  }
  // .menu-dropdown for backwards compatibility
  .menu-dropdown,
  .dropdown-menu {
    display: block;
  }
}

// TABS AND PILLS
// --------------

// Common styles
.tabs,
.pills {
  margin: 0 0 @baseline;
  padding: 0;
  list-style: none;
  .clearfix();
  > li {
    float: left;
    > a {
      display: block;
    }
  }
}

// Tabs
.tabs {
  border-color: #ddd;
  border-style: solid;
  border-width: 0 0 1px;
  > li {
    position: relative; // For the dropdowns mostly
    margin-bottom: -1px;
    > a {
      padding: 0 15px;
      margin-right: 2px;
      line-height: (@baseline * 2) - 2;
      border: 1px solid transparent;
      .border-radius(4px 4px 0 0);
      &:hover {
        text-decoration: none;
        background-color: #eee;
        border-color: #eee #eee #ddd;
      }
    }
  }
  // Active state, and it's :hover to override normal :hover
  .active > a,
  .active > a:hover {
    color: @gray;
    background-color: @white;
    border: 1px solid #ddd;
    border-bottom-color: transparent;
    cursor: default;
  }
}

// Dropdowns in tabs
.tabs {
  // first one for backwards compatibility
  .menu-dropdown,
  .dropdown-menu {
    top: 35px;
    border-width: 1px;
    .border-radius(0 6px 6px 6px);
  }
  // first one for backwards compatibility
  a.menu:after,
  .dropdown-toggle:after {
    border-top-color: #999;
    margin-top: 15px;
    margin-left: 5px;
  }
  // first one for backwards compatibility
  li.open.menu .menu,
  .open.dropdown .dropdown-toggle {
    border-color: #999;
  }
  // first one for backwards compatibility
  li.open a.menu:after,
  .dropdown.open .dropdown-toggle:after {
    border-top-color: #555;
  }
}

// Pills
.pills {
  a {
    margin: 5px 3px 5px 0;
    padding: 0 15px;
    line-height: 30px;
    text-shadow: 0 1px 1px @white;
    .border-radius(15px);
    &:hover {
      color: @white;
      text-decoration: none;
      text-shadow: 0 1px 1px rgba(0,0,0,.25);
      background-color: @linkColorHover;
    }
  }
  .active a {
    color: @white;
    text-shadow: 0 1px 1px rgba(0,0,0,.25);
    background-color: @linkColor;
  }
}

// Stacked pills
.pills-vertical > li {
  float: none;
}

// Tabbable areas
.tab-content,
.pill-content {
}
.tab-content > .tab-pane,
.pill-content > .pill-pane {
  display: none;
}
.tab-content > .active,
.pill-content > .active {
  display: block;
}

// BREADCRUMBS
// -----------

.breadcrumb {
  padding: 7px 14px;
  margin: 0 0 @baseline;
  #gradient > .vertical(#ffffff, #f5f5f5);
  border: 1px solid #ddd;
  .border-radius(3px);
  .box-shadow(inset 0 1px 0 @white);
  li {
    display: inline;
    text-shadow: 0 1px 0 @white;
  }
  .divider {
    padding: 0 5px;
    color: @grayLight;
  }
  .active a {
    color: @grayDark;
  }
}

// PAGE HEADERS
// ------------

.hero-unit {
  background-color: #f5f5f5;
  margin-bottom: 30px;
  padding: 60px;
  .border-radius(6px);
  h1 {
    margin-bottom: 0;
    font-size: 60px;
    line-height: 1;
    letter-spacing: -1px;
  }
  p {
    font-size: 18px;
    font-weight: 200;
    line-height: @baseline * 1.5;
  }
}
footer {
  margin-top: @baseline - 1;
  padding-top: @baseline - 1;
  border-top: 1px solid #eee;
}

// PAGE HEADERS
// ------------

.page-header {
  margin-bottom: @baseline - 1;
  border-bottom: 1px solid #ddd;
  .box-shadow(0 1px 0 rgba(255,255,255,.5));
  h1 {
    margin-bottom: (@baseline / 2) - 1px;
  }
}

// BUTTON STYLES
// -------------

// Shared colors for buttons and alerts
.btn,
.alert-message {
  // Set text color
  &.danger,
  &.danger:hover,
  &.error,
  &.error:hover,
  &.success,
  &.success:hover,
  &.info,
  &.info:hover {
    color: @white
  }
  // Sets the close button to the middle of message
  .close{
    font-family: Arial, sans-serif;
    line-height: 18px;
  }
  // Danger and error appear as red
  &.danger,
  &.error {
    .gradientBar(#ee5f5b, #c43c35);
  }
  // Success appears as green
  &.success {
    .gradientBar(#62c462, #57a957);
  }
  // Info appears as a neutral blue
  &.info {
    .gradientBar(#5bc0de, #339bb9);
  }
}

// Base .btn styles
.btn {
  // Button Base
  cursor: pointer;
  display: inline-block;
  #gradient > .vertical-three-colors(#ffffff, #ffffff, 25%, darken(#ffffff, 10%)); // Don't use .gradientbar() here since it does a three-color gradient
  padding: 5px 14px 6px;
  text-shadow: 0 1px 1px rgba(255,255,255,.75);
  color: #333;
  font-size: @basefont;
  line-height: normal;
  border: 1px solid #ccc;
  border-bottom-color: #bbb;
  .border-radius(4px);
  @shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
  .box-shadow(@shadow);

  &:hover {
    background-position: 0 -15px;
    color: #333;
    text-decoration: none;
  }

  // Focus state for keyboard and accessibility
  &:focus {
    outline: 1px dotted #666;
  }

  // Primary Button Type
  &.primary {
    color: @white;
    .gradientBar(@blue, @blueDark)
  }

   // Transitions
  .transition(.1s linear all);

  // Active and Disabled states
  &.active,
  :active {
    @shadow: inset 0 2px 4px rgba(0,0,0,.25), 0 1px 2px rgba(0,0,0,.05);
    .box-shadow(@shadow);
  }
  &.disabled {
    cursor: default;
    background-image: none;
    .reset-filter();
    .opacity(65);
    .box-shadow(none);
  }
  &[disabled] {
    // disabled pseudo can't be included with .disabled
    // def because IE8 and below will drop it ;_;
    cursor: default;
    background-image: none;
    .reset-filter();
    .opacity(65);
    .box-shadow(none);
  }

  // Button Sizes
  &.large {
    font-size: @basefont + 2px;
    line-height: normal;
    padding: 9px 14px 9px;
    .border-radius(6px);
  }
  &.small {
    padding: 7px 9px 7px;
    font-size: @basefont - 2px;
  }
}
// Super jank hack for removing border-radius from IE9 so we can keep filter gradients on alerts and buttons
:root .alert-message,
:root .btn {
  border-radius: 0 \0;
}

// Help Firefox not be a jerk about adding extra padding to buttons
button.btn,
input[type=submit].btn {
  &::-moz-focus-inner {
      padding: 0;
      border: 0;
  }
}

// CLOSE ICONS
// -----------
.close {
  float: right;
  color: @black;
  font-size: 20px;
  font-weight: bold;
  line-height: @baseline * .75;
  text-shadow: 0 1px 0 rgba(255,255,255,1);
  .opacity(25);
  &:hover {
    color: @black;
    text-decoration: none;
    .opacity(40);
  }
}

// ERROR STYLES
// ------------

// Base alert styles
.alert-message {
  position: relative;
  padding: 7px 15px;
  margin-bottom: @baseline;
  color: @grayDark;
  .gradientBar(#fceec1, #eedc94); // warning by default
  text-shadow: 0 1px 0 rgba(255,255,255,.5);
  border-width: 1px;
  border-style: solid;
  .border-radius(4px);
  .box-shadow(inset 0 1px 0 rgba(255,255,255,.25));

  // Adjust close icon
  .close {
    margin-top: 1px;
    *margin-top: 0; // For IE7
  }

  // Make links same color as text and stand out more
  a {
    font-weight: bold;
    color: @grayDark;
  }
  &.danger p a,
  &.error p a,
  &.success p a,
  &.info p a {
    color: @white;
  }

  // Remove extra margin from content
  h5 {
    line-height: @baseline;
  }
  p {
    margin-bottom: 0;
  }
  div {
    margin-top: 5px;
    margin-bottom: 2px;
    line-height: 28px;
  }
  .btn {
    // Provide actions with buttons
    .box-shadow(0 1px 0 rgba(255,255,255,.25));
  }

  &.block-message {
    background-image: none;
    background-color: lighten(#fceec1, 5%);
    .reset-filter();
    padding: 14px;
    border-color: #fceec1;
    .box-shadow(none);
    ul, p {
      margin-right: 30px;
    }
    ul {
      margin-bottom: 0;
    }
    li {
      color: @grayDark;
    }
    .alert-actions {
      margin-top: 5px;
    }
    &.error,
    &.success,
    &.info {
      color: @grayDark;
      text-shadow: 0 1px 0 rgba(255,255,255,.5);
    }
    &.error {
      background-color: lighten(#f56a66, 25%);
      border-color: lighten(#f56a66, 20%);
    }
    &.success {
      background-color: lighten(#62c462, 30%);
      border-color: lighten(#62c462, 25%);
    }
    &.info {
      background-color: lighten(#6bd0ee, 25%);
      border-color: lighten(#6bd0ee, 20%);
    }
    // Change link color back
    &.danger p a,
    &.error p a,
    &.success p a,
    &.info p a {
      color: @grayDark;
    }

  }
}

// PAGINATION
// ----------

.pagination {
  height: @baseline * 2;
  margin: @baseline 0;
  ul {
    float: left;
    margin: 0;
    border: 1px solid #ddd;
    border: 1px solid rgba(0,0,0,.15);
    .border-radius(3px);
    .box-shadow(0 1px 2px rgba(0,0,0,.05));
  }
  li {
    display: inline;
  }
  a {
    float: left;
    padding: 0 14px;
    line-height: (@baseline * 2) - 2;
    border-right: 1px solid;
    border-right-color: #ddd;
    border-right-color: rgba(0,0,0,.15);
    *border-right-color: #ddd; /* IE6-7 */
    text-decoration: none;
  }
  a:hover,
  .active a {
    background-color: lighten(@blue, 45%);
  }
  .disabled a,
  .disabled a:hover {
    background-color: transparent;
    color: @grayLight;
  }
  .next a {
    border: 0;
  }
}

// WELLS
// -----

.well {
  background-color: #f5f5f5;
  margin-bottom: 20px;
  padding: 19px;
  min-height: 20px;
  border: 1px solid #eee;
  border: 1px solid rgba(0,0,0,.05);
  .border-radius(4px);
  .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
  blockquote {
    border-color: #ddd;
    border-color: rgba(0,0,0,.15);
  }
}

// MODALS
// ------

.modal-backdrop {
  background-color: @black;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10000;
  // Fade for backdrop
  &.fade { opacity: 0; }
}

.modal-backdrop,
.modal-backdrop.fade.in {
  .opacity(80);
}

.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 11000;
  width: 560px;
  margin: -250px 0 0 -280px;
  background-color: @white;
  border: 1px solid #999;
  border: 1px solid rgba(0,0,0,.3);
  *border: 1px solid #999; /* IE6-7 */
  .border-radius(6px);
  .box-shadow(0 3px 7px rgba(0,0,0,0.3));
  .background-clip(padding-box);
  .close { margin-top: 7px; }
  &.fade {
    .transition(e('opacity .3s linear, top .3s ease-out'));
    top: -25%;
  }
  &.fade.in { top: 50%; }
}
.modal-header {
  border-bottom: 1px solid #eee;
  padding: 5px 15px;
}
.modal-body {
  padding: 15px;
}
.modal-body form {
  margin-bottom: 0;
}
.modal-footer {
  background-color: #f5f5f5;
  padding: 14px 15px 15px;
  border-top: 1px solid #ddd;
  .border-radius(0 0 6px 6px);
  .box-shadow(inset 0 1px 0 @white);
  .clearfix();
  margin-bottom: 0;
  .btn {
    float: right;
    margin-left: 5px;
  }
}

// Fix the stacking of these components when in modals
.modal .popover,
.modal .twipsy {
  z-index: 12000;
}

// POPOVER ARROWS
// --------------

#popoverArrow {
  .above(@arrowWidth: 5px) {
    bottom: 0;
    left: 50%;
    margin-left: -@arrowWidth;
    border-left: @arrowWidth solid transparent;
    border-right: @arrowWidth solid transparent;
    border-top: @arrowWidth solid @black;
  }
  .left(@arrowWidth: 5px) {
    top: 50%;
    right: 0;
    margin-top: -@arrowWidth;
    border-top: @arrowWidth solid transparent;
    border-bottom: @arrowWidth solid transparent;
    border-left: @arrowWidth solid @black;
  }
  .below(@arrowWidth: 5px) {
    top: 0;
    left: 50%;
    margin-left: -@arrowWidth;
    border-left: @arrowWidth solid transparent;
    border-right: @arrowWidth solid transparent;
    border-bottom: @arrowWidth solid @black;
  }
  .right(@arrowWidth: 5px) {
    top: 50%;
    left: 0;
    margin-top: -@arrowWidth;
    border-top: @arrowWidth solid transparent;
    border-bottom: @arrowWidth solid transparent;
    border-right: @arrowWidth solid @black;
  }
}

// TWIPSY
// ------

.twipsy {
  display: block;
  position: absolute;
  visibility: visible;
  padding: 5px;
  font-size: 11px;
  z-index: 1000;
  .opacity(80);
  &.fade.in {
    .opacity(80);
  }
  &.above .twipsy-arrow   { #popoverArrow > .above(); }
  &.left .twipsy-arrow    { #popoverArrow > .left(); }
  &.below .twipsy-arrow   { #popoverArrow > .below(); }
  &.right .twipsy-arrow   { #popoverArrow > .right(); }
}
.twipsy-inner {
  padding: 3px 8px;
  background-color: @black;
  color: white;
  text-align: center;
  max-width: 200px;
  text-decoration: none;
  .border-radius(4px);
}
.twipsy-arrow {
  position: absolute;
  width: 0;
  height: 0;
}

// POPOVERS
// --------

.popover {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1000;
  padding: 5px;
  display: none;
  &.above .arrow { #popoverArrow > .above(); }
  &.right .arrow { #popoverArrow > .right(); }
  &.below .arrow { #popoverArrow > .below(); }
  &.left .arrow  { #popoverArrow > .left(); }
  .arrow {
    position: absolute;
    width: 0;
    height: 0;
  }
  .inner {
    background: @black;
    background: rgba(0,0,0,.8);
    padding: 3px;
    overflow: hidden;
    width: 280px;
    .border-radius(6px);
    .box-shadow(0 3px 7px rgba(0,0,0,0.3));
  }
  .title {
    background-color: #f5f5f5;
    padding: 9px 15px;
    line-height: 1;
    .border-radius(3px 3px 0 0);
    border-bottom:1px solid #eee;
  }
  .content {
    background-color: @white;
    padding: 14px;
    .border-radius(0 0 3px 3px);
    .background-clip(padding-box);
    p, ul, ol {
      margin-bottom: 0;
    }
  }
}

// PATTERN ANIMATIONS
// ------------------

.fade {
  .transition(opacity .15s linear);
  opacity: 0;
  &.in {
    opacity: 1;
  }
}

// LABELS
// ------

.label {
  padding: 1px 3px 2px;
  font-size: @basefont * .75;
  font-weight: bold;
  color: @white;
  text-transform: uppercase;
  white-space: nowrap;
  background-color: @grayLight;
  .border-radius(3px);
  &.important { background-color: #c43c35; }
  &.warning   { background-color: @orange; }
  &.success   { background-color: @green; }
  &.notice    { background-color: lighten(@blue, 25%); }
}

// MEDIA GRIDS
// -----------

.media-grid {
  margin-left: -@gridGutterWidth;
  margin-bottom: 0;
  .clearfix();
  li {
    display: inline;
  }
  a {
    float: left;
    padding: 4px;
    margin: 0 0 @baseline @gridGutterWidth;
    border: 1px solid #ddd;
    .border-radius(4px);
    .box-shadow(0 1px 1px rgba(0,0,0,.075));
    img {
      display: block;
    }
    &:hover {
      border-color: @linkColor;
      .box-shadow(0 1px 4px rgba(0,105,214,.25));
    }
  }
}


/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, PathUtils: false, FileError: false, brackets: false */

/**
 * Set of utilites for working with the code editor
 */
define(function (require, exports, module) {
    'use strict';

    require("thirdparty/path-utils/path-utils.min");
    require("thirdparty/CodeMirror2/mode/xml/xml");
    require("thirdparty/CodeMirror2/mode/javascript/javascript");
    require("thirdparty/CodeMirror2/mode/css/css");
    require("thirdparty/CodeMirror2/mode/less/less");
    require("thirdparty/CodeMirror2/mode/htmlmixed/htmlmixed");

    var Strings = require("strings");

    /**
     * @private
     * Given a file URL, determines the mode to use based
     * off the files extensions.
     * @param {string} fileUrl  A cannonical file URL to extract the extension from
     */
    function _getModeFromFileExtensions(fileUrl) {
        var ext = PathUtils.filenameExtension(fileUrl);
        //incase the arg is just the ext
        if (!ext) {
            ext = fileUrl;
        }
        if (ext.charAt(0) === '.') {
            ext = ext.substr(1);
        }

        switch (ext) {

        case "js":
            return "javascript";

        case "json":
            return {name: "javascript", json: true};

        case "css":
            return "css";

        case "less":
            return "less";

        case "html":
        case "htm":
            return "htmlmixed";

        default:
            console.log("Called EditorUtils.js _getModeFromFileExtensions with an unhandled file extension: " + ext);
            return "";
        }
    }

    /**
     * Change the current mode of the editor based on file extension
     * @param {object} editor  An instance of a CodeMirror editor
     * @param {string} fileUrl  A cannonical file URL to extract the extension from
     */
    function setModeFromFileExtension(editor, fileUrl) {
        var mode = _getModeFromFileExtensions(fileUrl);
        editor.setOption("mode", mode);
    }

    function getFileErrorString(code) {
        // There are a few error codes that we have specific error messages for. The rest are
        // displayed with a generic "(error N)" message.
        var result;

        if (code === FileError.NOT_FOUND_ERR) {
            result = Strings.NOT_FOUND_ERR;
        } else if (code === FileError.NOT_READABLE_ERR) {
            result = Strings.NOT_READABLE_ERR;
        } else if (code === FileError.NO_MODIFICATION_ALLOWED_ERR) {
            result = Strings.NO_MODIFICATION_ALLOWED_ERR_FILE;
        } else {
            result = Strings.format(Strings.GENERIC_ERROR, code);
        }

        return result;
    }

    function showFileOpenError(code, path) {
        return brackets.showModalDialog(
            brackets.DIALOG_ID_ERROR,
            Strings.ERROR_OPENING_FILE_TITLE,
            Strings.format(
                Strings.ERROR_OPENING_FILE,
                path,
                getFileErrorString(code)
            )
        );
    }

    // Define public API
    exports.setModeFromFileExtension = setModeFromFileExtension;
    exports.showFileOpenError        = showFileOpenError;
    exports.getFileErrorString       = getFileErrorString;
});


$(FileViewController).on("documentSelectionFocusChange", function (event) {

&& (FileViewController.getFileSelectionFocus() !== FileViewController.WORKING_SET_VIEW)) {

$("#project-files-container li").is(function (index) {

if (entry && entry.fullPath === curDoc.file.fullPath && !_projectTree.jstree("is_selected", $(this))) {


[submodule "src/thirdparty/CodeMirror2"]
	path = src/thirdparty/CodeMirror2
	url = git@github.com:adobe/CodeMirror2.git
[submodule "src/thirdparty/path-utils"]
	path = src/thirdparty/path-utils
	url = git@github.com:jblas/path-utils.git
[submodule "src/thirdparty/jslint"]
	path = src/thirdparty/jslint
	url = git@github.com:douglascrockford/JSLint.git


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

// Set the baseUrl to brackets/src
require.config({
    baseUrl: "../src"
});

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

    // Utility dependency
    var SpecRunnerUtils = require("spec/SpecRunnerUtils.js");

    // Unique key for unit testing
    localStorage.setItem("preferencesKey", SpecRunnerUtils.TEST_PREFERENCES_KEY);

     // Load test specs
    require("spec/LowLevelFileIO-test.js");
    require("spec/FileCommandHandlers-test.js");
    require("spec/NativeFileSystem-test.js");
    require("spec/PreferencesManager-test.js");
    require("spec/Editor-test.js");
    require("spec/ProjectManager-test.js");
    require("spec/WorkingSetView-test.js");

    // Clean up preferencesKey
    $(window).unload(function () {
        localStorage.removeItem("preferencesKey");
    });
});


/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

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

define(function (require, exports, module) {

    'use strict';

    /**
     * Format a string by replacing placeholder symbols with passed in arguments.
     *
     * Example: var formatted = Strings.format("Hello {0}", "World");
     *
     * @param {string} str The base string
     * @param {...} Arguments to be substituted into the string
     *
     * @return {string} Formatted string
     */
    function format(str) {
        // arguments[0] is the base string, so we need to adjust index values here
        var args = [].slice.call(arguments, 1);
        return str.replace(/\{(\d+)\}/g, function (match, num) {
            return typeof args[num] !== 'undefined' ? args[num] : match;
        });
    }

    // Define public API
    exports.format                            = format;

    // General file io error strings
    exports.GENERIC_ERROR                     = "(error {0})";
    exports.NOT_FOUND_ERR                     = "The file could not be found.";
    exports.NOT_READABLE_ERR                  = "The file could not be read.";
    exports.NO_MODIFICATION_ALLOWED_ERR       = "The target directory cannot be modified.";
    exports.NO_MODIFICATION_ALLOWED_ERR_FILE  = "The permissions do not allow you to make modifications.";

    // Project error strings
    exports.ERROR_LOADING_PROJECT             = "Error loading project";
    exports.OPEN_DIALOG_ERROR                 = "An error occurred when showing the open file dialog. (error {0})";
    exports.REQUEST_NATIVE_FILE_SYSTEM_ERROR  = "An error occurred when trying to load the directory \"{0}\". (error {1})";
    exports.READ_DIRECTORY_ENTRIES_ERROR      = "An error occurred when reading the contents of the directory \"{0}\". (error {1})";

    // File open/save error string
    exports.ERROR_OPENING_FILE_TITLE          = "Error opening file";
    exports.ERROR_OPENING_FILE                = "An error occurred when trying to open the file \"{0}\". {1}";
    exports.ERROR_RELOADING_FILE_TITLE        = "Error reloading changes from disk";
    exports.ERROR_RELOADING_FILE              = "An error occurred when trying to reload the file \"{0}\". {1}";
    exports.ERROR_SAVING_FILE_TITLE           = "Error saving file";
    exports.ERROR_SAVING_FILE                 = "An error occurred when trying to save the file \"{0}\". {1}";
    exports.INVALID_FILENAME_TITLE            = "Invalid file name";
    exports.INVALID_FILENAME_MESSAGE          = "Filenames cannot contain the following characters: /?*:;{}<>\\|";
    exports.FILE_ALREADY_EXISTS               = "The file \"{0}\" already exists.";
    exports.ERROR_CREATING_FILE_TITLE         = "Error creating file";
    exports.ERROR_CREATING_FILE               = "An error occurred when trying to create the file \"{0}\". {1}";

    exports.SAVE_CLOSE_TITLE                  = "Save Changes";
    exports.SAVE_CLOSE_MESSAGE                = "Do you want to save the changes you made in the document \"{0}\"?";
    exports.SAVE_CLOSE_MULTI_MESSAGE          = "Do you want to save your changes to the following files?";
    exports.EXT_MODIFIED_TITLE                = "External Changes";
    exports.EXT_MODIFIED_MESSAGE              = "{0} has been modified on disk, but also has unsaved changes in Brackets."
                                                + "

"
                                                + "Which version do you want to keep?";
    exports.EXT_DELETED_MESSAGE               = "{0} has been deleted on disk, but has unsaved changes in Brackets."
                                                + "

"
                                                + "Do you want to keep your changes?";

    exports.OPEN_FILE                         = "Open File";
});


keep keep keep replace keep keep keep keep

L.Map.include(!(L.Transition && L.Transition.implemented()) ? {} : {

	setView: function (center, zoom, forceReset) {
		zoom = this._limitZoom(zoom);

		var zoomChanged = (this._zoom !== zoom);

		if (this._loaded && !forceReset && this._layers) {
			var done = (zoomChanged ?
					this._zoomToIfClose && this._zoomToIfClose(center, zoom) :
					this._panByIfClose(center));

			// exit if animated pan or zoom started
			if (done) { return this; }
		}

		// reset the map view
		this._resetView(center, zoom);

		return this;
	},

	panBy: function (offset, options) {
		if (!(offset.x || offset.y)) {
			return this;
		}

		if (!this._panTransition) {
			this._panTransition = new L.Transition(this._mapPane);

			this._panTransition.on({
				'step': this._onPanTransitionStep,
				'end': this._onPanTransitionEnd
			}, this);
		}

		L.Util.setOptions(this._panTransition, L.Util.extend({duration: 0.25}, options));

		this.fire('movestart');

		L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');

		this._panTransition.run({
			position: L.DomUtil.getPosition(this._mapPane).subtract(offset)
		});

		return this;
	},

	_onPanTransitionStep: function () {
		this.fire('move');
	},

	_onPanTransitionEnd: function () {
		L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
		this.fire('moveend');
	},

	_panByIfClose: function (center) {
		// difference between the new and current centers in pixels
		var offset = this._getCenterOffset(center);

		if (this._offsetIsWithinView(offset)) {
			this.panBy(offset);
			return true;
		}
		return false;
	},

	_offsetIsWithinView: function (offset, multiplyFactor) {
		var m = multiplyFactor || 1,
			size = this.getSize();

		return (Math.abs(offset.x) <= size.x * m) &&
				(Math.abs(offset.y) <= size.y * m);
	}
});


<!DOCTYPE html>

	<title>Leaflet debug page</title>

	<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />

	<link rel="stylesheet" href="../../dist/leaflet.css" />

	<input type="button" value="Set blue rectangle bounds as current map extent." onclick="resetBounds();" />


var jshint = require('jshint').JSHINT,
	fs = require('fs'),
	config = require('./hintrc.js').config;

function jshintSrc(path, src) {
	jshint(src, config);

	var errors = jshint.errors,
		i, len, e, line;

	for (i = 0, len = errors.length; i < len; i++) {
		e = errors[i];
		//console.log(e.evidence);
		console.log(path + '\tline ' + e.line + '\tcol ' + e.character + '\t ' + e.reason);
	}

	return len;
}

exports.jshint = function (files) {
	var errorsFound = 0;

	for (var i = 0, len = files.length; i < len; i++) {
		var src = fs.readFileSync(files[i], 'utf8');

		errorsFound += jshintSrc(files[i], src);
	}

	return errorsFound;
};


/*
 * L.Path is a base class for rendering vector paths on a map. It's inherited by Polyline, Circle, etc.
 */

L.Path = L.Class.extend({
	includes: [L.Mixin.Events],

	statics: {
		// how much to extend the clip area around the map view
		// (relative to its size, e.g. 0.5 is half the screen in each direction)
		// set in such way that SVG element doesn't exceed 1280px (vector layers flicker on dragend if it is)
		CLIP_PADDING: L.Browser.mobile ?
			Math.max(0, Math.min(0.5,
				(1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2))
			: 0.5
	},

	options: {
		stroke: true,
		color: '#0033ff',
		dashArray: null,
		weight: 5,
		opacity: 0.5,

		fill: false,
		fillColor: null, //same as color by default
		fillOpacity: 0.2,

		clickable: true
	},

	initialize: function (options) {
		L.Util.setOptions(this, options);
	},

	onAdd: function (map) {
		this._map = map;

		this._initElements();
		this._initEvents();
		this.projectLatlngs();
		this._updatePath();

		map.on({
			'viewreset': this.projectLatlngs,
			'moveend': this._updatePath
		}, this);
	},

	addTo: function (map) {
		map.addLayer(this);
		return this;
	},

	onRemove: function (map) {
		this._map = null;

		map._pathRoot.removeChild(this._container);

		map.off({
			'viewreset': this.projectLatlngs,
			'moveend': this._updatePath
		}, this);
	},

	projectLatlngs: function () {
		// do all projection stuff here
	},

	setStyle: function (style) {
		L.Util.setOptions(this, style);

		if (this._container) {
			this._updateStyle();
		}

		return this;
	},

	redraw: function () {
		if (this._map) {
			this.projectLatlngs();
			this._updatePath();
		}
		return this;
	}
});

L.Map.include({
	_updatePathViewport: function () {
		var p = L.Path.CLIP_PADDING,
			size = this.getSize(),
			panePos = L.DomUtil.getPosition(this._mapPane),
			min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)),
			max = min.add(size.multiplyBy(1 + p * 2));

		this._pathViewport = new L.Bounds(min, max);
	}
});


L.Map.include(!(L.Transition && L.Transition.implemented()) ? {} : {

setView: function (center, zoom, forceReset) {

var zoomChanged = (this._zoom !== zoom);

keep keep replace keep keep keep keep keep


/*
	CM.LatLng represents a geographical point with latitude and longtitude coordinates.
*/

L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean])
	var lat = parseFloat(rawLat),
		lng = parseFloat(rawLng);

	if (isNaN(lat) || isNaN(lng)) {
		throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
	}

	if (noWrap !== true) {
		lat = Math.max(Math.min(lat, 90), -90);					// clamp latitude into -90..90
		lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180);	// wrap longtitude into -180..180
	}

	this.lat = lat;
	this.lng = lng;
};

L.Util.extend(L.LatLng, {
	DEG_TO_RAD: Math.PI / 180,
	RAD_TO_DEG: 180 / Math.PI,
	MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
});

L.LatLng.prototype = {
	equals: function (obj) { // (LatLng) -> Boolean
		if (!obj) { return false; }

		obj = L.latLng(obj);

		var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng));
		return margin <= L.LatLng.MAX_MARGIN;
	},

	toString: function () { // -> String
		return 'LatLng(' +
				L.Util.formatNum(this.lat) + ', ' +
				L.Util.formatNum(this.lng) + ')';
	},

	// Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
	distanceTo: function (other) { // (LatLng) -> Number
		other = L.latLng(other);

		var R = 6378137, // earth radius in meters
			d2r = L.LatLng.DEG_TO_RAD,
			dLat = (other.lat - this.lat) * d2r,
			dLon = (other.lng - this.lng) * d2r,
			lat1 = this.lat * d2r,
			lat2 = other.lat * d2r,
			sin1 = Math.sin(dLat / 2),
			sin2 = Math.sin(dLon / 2);

		var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);

		return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
	}
};

L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Number, Boolean)
	if (a instanceof L.LatLng) {
		return a;
	}
	if (a instanceof Array) {
		return new L.LatLng(a[0], a[1]);
	}
	if (isNaN(a)) {
		return a;
	}
	return new L.LatLng(a, b, c);
};


describe('Util', function() {

	describe('#extend', function() {
		var a;

		beforeEach(function() {
			a = {
				foo: 5,
				bar: 'asd'
			};
		});

		it('should extend the first argument with the properties of the second', function() {
			L.Util.extend(a, {
				bar: 7,
				baz: 3
			});

			expect(a).toEqual({
				foo: 5,
				bar: 7,
				baz: 3
			});
		});

		it('should work with more than 2 arguments', function() {
			L.Util.extend(a, {bar: 7}, {baz: 3});

			expect(a).toEqual({
				foo: 5,
				bar: 7,
				baz: 3
			});
		});
	});

	describe('#bind', function() {
		it('should return the given function with the given context', function() {
			var fn = function() {
				return this;
			};

			var fn2 = L.Util.bind(fn, 5);

			expect(fn2()).toEqual(5);
		});
	});

	describe('#stamp', function() {
		it('should set a unique id on the given object and return it', function() {
			var a = {},
				id = L.Util.stamp(a);

			expect(typeof id).toEqual('number');
			expect(L.Util.stamp(a)).toEqual(id);

			var b = {},
				id2 = L.Util.stamp(b);

			expect(id2).not.toEqual(id);
		});
	});

	// TODO cancel/requestAnimFrame?

	// TODO limitExecByInterval

	// TODO formatNum

	// TODO splitWords

	// TODO setOptions

	// TODO getParamString

	// TODO template
});


this._zoomToIfClose && this._zoomToIfClose(center, zoom) :

if (done) {
clearTimeout(this._sizeTimer);
return this;
}