/* Mixins.less
 * Snippets of reusable CSS to develop faster and keep code readable
 * ----------------------------------------------------------------- */

// Clearfix for clearing floats like a boss h5bp.com/q
.clearfix() {
  zoom: 1;
  &:before,
  &:after {
    display: table;
    content: "";
    zoom: 1;
  }
  &:after {
    clear: both;
  }
}

// Center-align a block level element
.center-block() {
  display: block;
  margin-left: auto;
  margin-right: auto;
}

// Sizing shortcuts
.size(@height: 5px, @width: 5px) {
  height: @height;
  width: @width;
}
.square(@size: 5px) {
  .size(@size, @size);
}

// Input placeholder text
.placeholder(@color: @grayLight) {
  :-moz-placeholder {
    color: @color;
  }
  ::-webkit-input-placeholder {
    color: @color;
  }
}

// Font Stacks
#font {
  .shorthand(@weight: normal, @size: 14px, @lineHeight: 20px) {
    font-size: @size;
    font-weight: @weight;
    line-height: @lineHeight;
  }
  .sans-serif(@weight: normal, @size: 14px, @lineHeight: 20px) {
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: @size;
    font-weight: @weight;
    line-height: @lineHeight;
  }
  .serif(@weight: normal, @size: 14px, @lineHeight: 20px) {
    font-family: "Georgia", Times New Roman, Times, serif;
    font-size: @size;
    font-weight: @weight;
    line-height: @lineHeight;
  }
  .monospace(@weight: normal, @size: 12px, @lineHeight: 20px) {
    font-family: "Monaco", Courier New, monospace;
    font-size: @size;
    font-weight: @weight;
    line-height: @lineHeight;
  }
}

// Grid System
.fixed-container() {
  width: @siteWidth;
  margin-left: auto;
  margin-right: auto;
  .clearfix();
}
.columns(@columnSpan: 1) {
  width: (@gridColumnWidth * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1));
}
.offset(@columnOffset: 1) {
  margin-left: (@gridColumnWidth * @columnOffset) + (@gridGutterWidth * (@columnOffset - 1)) + @extraSpace;
}
// Necessary grid styles for every column to make them appear next to each other horizontally
.gridColumn() {
  display: inline;
  float: left;
  margin-left: @gridGutterWidth;
}
// makeColumn can be used to mark any element (e.g., .content-primary) as a column without changing markup to .span something
.makeColumn(@columnSpan: 1) {
  .gridColumn();
  .columns(@columnSpan);
}

// Border Radius
.border-radius(@radius: 5px) {
  -webkit-border-radius: @radius;
     -moz-border-radius: @radius;
          border-radius: @radius;
}

// Drop shadows
.box-shadow(@shadow: 0 1px 3px rgba(0,0,0,.25)) {
  -webkit-box-shadow: @shadow;
     -moz-box-shadow: @shadow;
          box-shadow: @shadow;
}

// Transitions
.transition(@transition) {
-webkit-transform-style: e(%("preserve-3d"));
     -webkit-transition: @transition;
        -moz-transition: @transition;
         -ms-transition: @transition;
          -o-transition: @transition;
             transition: @transition;
}

// Background clipping
.background-clip(@clip) {
  -webkit-background-clip: @clip;
     -moz-background-clip: @clip;
          background-clip: @clip;
}

// CSS3 Content Columns
.content-columns(@columnCount, @columnGap: 20px) {
  -webkit-column-count: @columnCount;
     -moz-column-count: @columnCount;
          column-count: @columnCount;
  -webkit-column-gap: @columnGap;
     -moz-column-gap: @columnGap;
          column-gap: @columnGap;
}

// Make any element resizable for prototyping
.resizable(@direction: both) {
  resize: @direction; // Options are horizontal, vertical, both
  overflow: auto; // Safari fix
}

// Add an alphatransparency value to any background or border color (via Elyse Holladay)
#translucent {
  .background(@color: @white, @alpha: 1) {
    background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha);
  }
  .border(@color: @white, @alpha: 1) {
    border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha);
    background-clip: padding-box;
  }
}

// Gradient Bar Colors for buttons and allerts
.gradientBar(@primaryColor, @secondaryColor) {
  #gradient > .vertical(@primaryColor, @secondaryColor);
  text-shadow: 0 -1px 0 rgba(0,0,0,.25);
  border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%);
  border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%);
}

// Gradients
#gradient {
  .horizontal (@startColor: #555, @endColor: #333) {
    background-color: @endColor;
    background-repeat: repeat-x;
    background-image: -khtml-gradient(linear, left top, right top, from(@startColor), to(@endColor)); // Konqueror
    background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+
    background-image: -ms-linear-gradient(left, @startColor, @endColor); // IE10
    background-image: -webkit-gradient(linear, left top, right top, color-stop(0%, @startColor), color-stop(100%, @endColor)); // Safari 4+, Chrome 2+
    background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+
    background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10
    background-image: linear-gradient(left, @startColor, @endColor); // Le standard
    filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",@startColor,@endColor)); // IE9 and down
  }
  .vertical (@startColor: #555, @endColor: #333) {
    background-color: @endColor;
    background-repeat: repeat-x;
    background-image: -khtml-gradient(linear, left top, left bottom, from(@startColor), to(@endColor)); // Konqueror
    background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+
    background-image: -ms-linear-gradient(top, @startColor, @endColor); // IE10
    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, @startColor), color-stop(100%, @endColor)); // Safari 4+, Chrome 2+
    background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+
    background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10
    background-image: linear-gradient(top, @startColor, @endColor); // The standard
    filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",@startColor,@endColor)); // IE9 and down
  }
  .directional (@startColor: #555, @endColor: #333, @deg: 45deg) {
    background-color: @endColor;
    background-repeat: repeat-x;
    background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+
    background-image: -ms-linear-gradient(@deg, @startColor, @endColor); // IE10
    background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+
    background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10
    background-image: linear-gradient(@deg, @startColor, @endColor); // The standard
  }
  .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) {
    background-color: @endColor;
    background-repeat: no-repeat;
    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor));
    background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor);
    background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor);
    background-image: -ms-linear-gradient(@startColor, @midColor @colorStop, @endColor);
    background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor);
    background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor);
    filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",@startColor,@endColor)); // IE9 and down, gets no color-stop at all for proper fallback
  }
}

// Reset filters for IE
.reset-filter() {
  filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)"));
}

// Opacity
.opacity(@opacity: 100) {
  filter: e(%("alpha(opacity=%d)", @opacity));
  -khtml-opacity: @opacity / 100;
    -moz-opacity: @opacity / 100;
         opacity: @opacity / 100;
}


/** Closes the specified document. Assumes the current document if doc is null.

* Prompts user about saving file if document is dirty

// utility function for handleFileClose: closes document & removes from working set

keep keep keep keep replace keep keep keep

function handleFileClose( doc, promptOnly ) {


keep keep keep replace replace replace replace keep keep keep keep

/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */
define(function(require, exports, module) {
    require("thirdparty/path-utils/path-utils.min");

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

    /**
     * Handlers for commands related to file handling (opening, saving, etc.)
     */

    /** @type {jQueryObject} */
    var _title;
    /** @type {string} */
    var _currentFilePath;  // TODO: eliminate this and just use getCurrentDocument().file.fullPath
    /** @type {string} */
    var _currentTitlePath;

    function init(title) {
        _title = title;

        // Register global commands
        CommandManager.register(Commands.FILE_OPEN, handleFileOpen);
        CommandManager.register(Commands.FILE_ADD_TO_WORKING_SET, handleFileAddToWorkingSet);
        // TODO: For now, hook up File > New to the "new in project" handler. Eventually
        // File > New should open a new blank tab, and handleFileNewInProject should
        // be called from a "+" button in the project
        CommandManager.register(Commands.FILE_NEW, handleFileNewInProject);
        CommandManager.register(Commands.FILE_SAVE, handleFileSave);
        CommandManager.register(Commands.FILE_CLOSE, handleFileClose);
        CommandManager.register(Commands.FILE_CLOSE_ALL, handleFileCloseAll);

        $(DocumentManager).on("dirtyFlagChange", handleDirtyChange);
        $(DocumentManager).on("currentDocumentChange", handleCurrentDocumentChange);
    };

    function handleCurrentDocumentChange(event) {
        var newDocument = DocumentManager.getCurrentDocument();

        if (newDocument != null) {
            var fullPath = newDocument.file.fullPath;

            _currentFilePath = _currentTitlePath = fullPath;

            // In the main toolbar, show the project-relative path (if the file is inside the current project)
            // or the full absolute path (if it's not in the project).
            _currentTitlePath = ProjectManager.makeProjectRelativeIfPossible(fullPath);

        } else {
            _currentFilePath = _currentTitlePath = null;
        }

        // Update title text & "dirty dot" display
        updateTitle();
    }

    function handleDirtyChange(event, changedDoc) {
        if (changedDoc.file.fullPath == _currentFilePath) {
            updateTitle();
        }
    }

    function updateTitle() {
        var currentDoc = DocumentManager.getCurrentDocument();
        if (currentDoc) {
            _title.text( _currentTitlePath + (currentDoc.isDirty ? " \u2022" : "") );
        } else {
            _title.text("");
        }
    }

    function handleFileAddToWorkingSet(fullPath){
        handleFileOpen(fullPath);
        DocumentManager.addToWorkingSet(DocumentManager.getCurrentDocument());
    }

    function handleFileOpen(fullPath) {
        var result = doOpenWithOptionalPath(fullPath);
        result.always(function() {
            EditorManager.focusEditor();
        });
        return result;
    }

    function doOpenWithOptionalPath(fullPath) {
        var result;
        if (!fullPath) {
            // Prompt the user with a dialog
            // TODO: we're relying on this to not be asynchronous--is that safe?
            NativeFileSystem.showOpenDialog(false, false, Strings.OPEN_FILE, ProjectManager.getProjectRoot().fullPath,
                ["htm", "html", "js", "css"], function(files) {
                    if (files.length > 0) {
                        result = doOpen(files[0]);
                        return;
                    }
                });
        }
        else {
            result = doOpen(fullPath);
        }
        if (!result)
            result = (new $.Deferred()).reject();
        return result;
    }

    function doOpen(fullPath) {
        var result = new $.Deferred();
        if (!fullPath) {
            console.log("doOpen() called without fullPath");
            return result.reject();
        }

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

        var document = DocumentManager.getDocumentForFile(fileEntry);
        if (document != null) {
            // File already open - don't need to load it, just switch to it in the UI
            DocumentManager.showInEditor(document);
            result.resolve();

        } else {
            // File wasn't open before, so we must load its contents into a new document
            var reader = new NativeFileSystem.FileReader();

            fileEntry.file(function(file) {
                reader.onload = function(event) {
                    // Create a new editor initialized with the file's content, and bind it to a Document
                    document = EditorManager.createDocumentAndEditor(fileEntry, event.target.result);

                    // Switch to new document in the UI
                    DocumentManager.showInEditor(document);
                    result.resolve();
                };

                reader.onerror = function(event) {
                    showFileOpenError(event.target.error.code, fullPath);
                    result.reject();
                }

                reader.readAsText(file, "utf8");
            },
            function fileEntry_onerror(event) {
                showFileOpenError(event.target.error.code, fullPath);
                result.reject();
            });
        }

        return result;
    }

    function handleFileNewInProject() {
        // Determine the directory to put the new file
        // If a file is currently selected, put it next to it.
        // If a directory is currently selected, put it in it.
        // If nothing is selected, put it at the root of the project
        var baseDir,
            selected = ProjectManager.getSelectedItem() || ProjectManager.getProjectRoot();

        baseDir = selected.fullPath;
        if (selected.isFile)
            baseDir = baseDir.substr(0, baseDir.lastIndexOf("/"));

        // Create the new node. The createNewItem function does all the heavy work
        // of validating file name, creating the new file and selecting.
        // TODO: Use a unique name like Untitled-1, Untitled-2, etc.
        return ProjectManager.createNewItem(baseDir, "Untitled.js", false);
    }

    function handleFileSave() {
        return doSave( DocumentManager.getCurrentDocument() );
    }
    function doSave(docToSave) {
        var result = new $.Deferred();

        if (docToSave && docToSave.isDirty) {
            var fileEntry = docToSave.file;

            //setup our resolve and reject handlers
            result.done( function fileSaved() {
                docToSave.markClean();
            });

            result.fail( function fileError(error) {
                showSaveFileError(error.code, fileEntry.fullPath);
            });

            fileEntry.createWriter(
                function(writer) {
                    writer.onwriteend = function() {
                        result.resolve();
                    }
                    writer.onerror = function(error) {
                        result.reject(error);
                    }

                    // TODO (jasonsj): Blob instead of string
                    writer.write( docToSave.getText() );
                },
                function(error) {
                    result.reject(error);
                }
            );
        }
        else {
            result.resolve();
        }
        result.always(function() {
            EditorManager.focusEditor();
        });
        return result;
    }

    function saveAll() {
        var saveResults = [];

        DocumentManager.getWorkingSet().forEach(function(doc) {  //TODO: or just use for..in?
            saveResults.push( doSave(doc) );
        });

        // $.when() is sort of crappy: it won't accept an array as an arg, and it seems to insist
        // on receiving Deferreds even though it should work fine given Promises too... ugh
        var overallResult = $.when.apply($, saveResults);

        return overallResult; //NOTE: this returns a Promise, NOT a Deferred (which is actually more correct)
    }

    /** Closes the specified document. Assumes the current document if doc is null.
     * Prompts user about saving file if document is dirty
     * @param {?Document} doc
     */
    function handleFileClose( doc ) {

        // utility function for handleFileClose: closes document & removes from working set
        function doClose(doc) {
            // This selects a different document if the working set has any other options
            DocumentManager.closeDocument(doc);

            EditorManager.focusEditor();
        }

        var result = new $.Deferred();

        // Default to current document if doc is null
        if (!doc)
            doc =  DocumentManager.getCurrentDocument();
        // No-op if called when nothing is open; TODO: should command be grayed out instead?
        if (!doc)
            return;

        if (doc.isDirty) {
            var filename = PathUtils.parseUrl(doc.file.fullPath).filename;

            brackets.showModalDialog(
                  brackets.DIALOG_ID_SAVE_CLOSE
                , Strings.SAVE_CLOSE_TITLE
                , Strings.format(Strings.SAVE_CLOSE_MESSAGE, filename )
            ).done(function(id) {
                if (id === brackets.DIALOG_BTN_CANCEL) {
                    result.reject();
                }
                else {
                    if (id === brackets.DIALOG_BTN_OK) {
                        doSave(doc)
                            .done(function() {
                                doClose(doc);
                                result.resolve();
                            })
                            .fail(function() {
                                result.reject();
                            });
                    }
                    else {
                        // This is the "Don't Save" case--we can just go ahead and close the file.
                        doClose(doc);
                        result.resolve();
                    }
                }
            });
            result.always(function() {
                EditorManager.focusEditor();
            });
        }
        else {
            // Doc is not dirty, just close
            doClose(doc);
            EditorManager.focusEditor();
            result.resolve();
        }
        return result;
    }

    function handleFileCloseAll() {
        var result = new $.Deferred();

        var unsavedDocs = DocumentManager.getWorkingSet().filter( function(doc) {
            return doc.isDirty;
        } );

        if (unsavedDocs.length == 0) {
            console.log("ZERO UNSAVED...");

            DocumentManager.closeAll();
            result.resolve();

        } else if (unsavedDocs.length == 1) {
            console.log("SINGLE UNSAVED...");

            handleFileClose( unsavedDocs[0] ).done( function() {
                DocumentManager.closeAll();
                result.resolve();
            }).fail( function() {
                result.reject();
            });

        } else {
            console.log("MULTIPLE UNSAVED...");
            var message = Strings.SAVE_CLOSE_MULTI_MESSAGE;

            message += "
";
            unsavedDocs.forEach(function(doc) {  //TODO: or just use for..in?
                message += "
" + ProjectManager.makeProjectRelativeIfPossible(doc.file.fullPath) + "
";
            });
            message += "
";

            brackets.showModalDialog(
                  brackets.DIALOG_ID_SAVE_CLOSE
                , Strings.SAVE_CLOSE_TITLE
                , message
            ).done(function(id) {
                if (id === brackets.DIALOG_BTN_CANCEL) {
                    result.reject();
                }
                else {
                    if (id === brackets.DIALOG_BTN_OK) {
                        console.log("SAVE ALL, then CLOSE ALL");
                        saveAll().done( function() {
                            DocumentManager.closeAll();
                            result.resolve();
                        }).fail( function() {
                            result.reject();
                        });
                    }
                    else {
                        // This is the "Don't Save" case--we can just go ahead and close the file.
                        console.log("DISCARD ALL, then CLOSE ALL");
                        DocumentManager.closeAll();
                        result.resolve();
                    }
                }
            });
        }

        return result;
    }

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

    function showSaveFileError(code, path) {
        brackets.showModalDialog(
              brackets.DIALOG_ID_ERROR
            , Strings.ERROR_SAVING_FILE_TITLE
            , Strings.format(
                    Strings.ERROR_SAVING_FILE
                  , path
                  , getErrorString(code))
        );
    }

    function getErrorString(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;
    }

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


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


This directory is marked write only when tests are run. Attempts to read files here should fail.


var unsavedDocs = DocumentManager.getWorkingSet().filter( function(doc) {

// utility function: if we're not in promptOnly mode, close all open documents

/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */
define(function(require, exports, module) {
    // 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")
    ,   CommandManager      = require("CommandManager")
    ,   Commands            = require("Commands")
    ,   Strings             = require("strings")
    ;

    /**
     * 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;
    }
    /**
     * @private
     * @see getProjectRoot()
     */
    var _projectRoot = null;

    /**
     * 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
     * Reference to the tree control
     * @type {jQueryObject}
     */
    var _projectTree = null;

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

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

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

    /**
     * Displays a browser dialog where the user can choose a folder to load.
     * (If the user cancels the dialog, nothing more happens).
     */
    function openProject() {
        if (!brackets.inBrowser) {
            // 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)
                        loadProject( files[0] );
                },
                function(error) {
                    brackets.showModalDialog(
                          brackets.DIALOG_ID_ERROR
                        , Strings.ERROR_LOADING_PROJECT
                        , Strings.format(Strings.OPEN_DIALOG_ERROR, 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.
     */
    function loadProject(rootPath) {
        // reset tree node id's
        _projectInitialLoad.id = 0;

        var prefs = PreferencesManager.getPreferences(PREFERENCES_CLIENT_ID);

        if (rootPath === null || rootPath === undefined) {
            // Load the last known project into the tree
            rootPath = prefs.projectPath;
            _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
        if (brackets.inBrowser) {
            // Hardcoded dummy data for local testing, in jsTree JSON format
            // (we leave _projectRoot null)
            var subfolderInner = { data:"Folder_inner", children:[
                { data: "subsubfile_1" }, { data: "subsubfile_2" }
            ] };
            var treeJSONData = [
                { data: "Dummy tree content:" },
                { data:"Folder_1", children:[
                    { data: "subfile_1" }, { data: "subfile_2" }, { data: "subfile_3" }
                ] },
                { data:"Folder_2", children:[
                    { data: "subfile_4" }, subfolderInner, { data: "subfile_5" }
                ] },
                { data: "file_1" },
                { data: "file_2" }
            ];

            // Show file list in UI synchronously
            _renderTree(treeJSONData);

        } else {
            // 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.
                    _renderTree(_treeDataProvider);
                },
                function(error) {
                    brackets.showModalDialog(
                          brackets.DIALOG_ID_ERROR
                        , Strings.ERROR_LOADING_PROJECT
                        , Strings.format(Strings.REQUEST_NATIVE_FILE_SYSTEM_ERROR, rootPath, error.code)
                    );
                }
            );
        }
    }

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

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

    }

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

        for (var entryI in entries) {
            entry = entries[entryI];

            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
     * 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");

        // 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)
                CommandManager.execute(Commands.FILE_OPEN, entry.fullPath);
        })
        .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);
            }
        })
        .bind("dblclick.jstree", function(event) {
            var entry = $(event.target).closest("li").data("entry");
            if (entry.isFile)
                CommandManager.execute(Commands.FILE_ADD_TO_WORKING_SET, entry.fullPath);
        });
    };

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

    // Register save callback
    var loadedPath = window.location.pathname;
    var bracketsSrc = loadedPath.substr(0, loadedPath.lastIndexOf("/"));
    var defaults =
        { projectPath:      bracketsSrc /* initialze to brackets source */
        , projectTreeState: ""          /* TODO (jasonsj): jstree state */
        };
    PreferencesManager.addPreferencesClient(PREFERENCES_CLIENT_ID, savePreferences, this, defaults);
});


/* Shared LESS for all versions of Brackets (e.g. App, In-Browser, etc.)
 *
 * IMPORTANT: Importing this file should be the first step in creating a
 * version-specific LESS file.
 *
 * ALSO IMPORTANT: All CSS imports should occur first in this file. This is
 * because LESS puts all CSS imports at the top of the file anyway (because
 * this is required by CSS2). So, we don't want to be misleading about the
 * processing order.
 */

/* CSS imports */

// CodeMirror
@import "thirdparty/CodeMirror2/lib/codemirror.css";

/* LESS imports */

// Bootstrap @ v.1.4.0
@import "bootstrap/bootstrap.less";

// Brackets-specific variables, mixins and overrides
@import "brackets_variables.less";
@import "brackets_mixins.less";
@import "brackets_patterns_override.less";

// Brackets-specific default font and color definitions
@import "brackets_fonts.less";
@import "brackets_colors.less";

// Default theme -- all UI styling comes from variables in a theme
// Themes can rely on variables defined above
@import "brackets_theme_default.less";

// Codemirror styling overrides
@import "brackets_codemirror_override.less";

// styling for file tree
@import "jsTreeTheme.less";


#!/bin/bash
BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

echo $BASEDIR

# open the Jasmine SpecRunner
open ${BASEDIR}/../../bin/mac/Brackets.app --args file:///${BASEDIR}/SpecRunner.html


define(function(require, exports, module) {
    // Load dependent modules
    var ProjectManager      // Load from brackets.test
    ,   PreferencesManager  // Load from brackets.test
    ,   SpecRunnerUtils     = require("./SpecRunnerUtils.js")
    ;

    // FIXME (jasonsj): these tests are ommitted when launching in the main app window
    if (window.opener) { // (function(){

    describe("ProjectManager", function() {

        beforeEach(function() {
            this.app = window.opener;

            // Load module instances from brackets.test
            ProjectManager = this.app.brackets.test.ProjectManager;
            PreferencesManager = this.app.brackets.test.PreferencesManager;

            // Temporarily use test key in the main app window
            this.oldKey = PreferencesManager._setStorageKey( SpecRunnerUtils.TEST_PREFERENCES_KEY );

            this.app.location.reload();
            this.testPath = SpecRunnerUtils.getTestPath("/spec/ProjectManager-test-files");
            var isReady = false;
            $(this.app.document).ready(function() {
                isReady = true;
            });
            waitsFor(function() { return isReady; }, 5000);
        });

        afterEach(function() {
            // restore main app window preferences key
            PreferencesManager._setStorageKey( this.oldKey );
        });

        describe("createNewItem", function() {
            // TODO (jasonsj): test Commands.FILE_NEW
            it("should create a new file with a given name", function() {
                var didCreate = false, gotError = false;

                runs(function() {
                    ProjectManager.loadProject(this.testPath);
                });
                waitsFor(function() { return ProjectManager.getProjectRoot() }, "loadProject() timeout", 1000);

                runs(function() {
                    // skip rename
                    ProjectManager.createNewItem(this.testPath, "Untitled.js", true)
                        .done(function() { didCreate = true; })
                        .fail(function() { gotError = true; });
                });
                waitsFor(function() { return didCreate && !gotError; }, "ProjectManager.createNewItem() timeout", 1000);

                var error, stat, complete = false;
                var filePath = this.testPath + "/Untitled.js";
                runs(function() {
                    brackets.fs.stat(filePath, function(err, _stat) {
                        error = err;
                        stat = _stat;
                        complete = true;
                    });
                });

                waitsFor(function() { return complete; }, 1000);

                var unlinkError = brackets.fs.NO_ERROR;
                runs(function() {
                    expect(error).toBeFalsy();
                    expect(stat.isFile()).toBe(true);

                    // delete the new file
                    complete = false;
                    brackets.fs.unlink(filePath, function(err) {
                        unlinkError = err;
                        complete = true;
                    });
                });
                waitsFor(function() {
                        return complete && unlinkError == brackets.fs.NO_ERROR;
                    }
                    , "unlink() failed to cleanup Untitled.js, err=" + unlinkError
                    , 1000
                );
            });

            it("should fail when a file already exists", function() {
                var didCreate = false, gotError = false;

                runs(function() {
                    ProjectManager.loadProject(this.testPath);
                });
                waitsFor(function() { return ProjectManager.getProjectRoot() }, "loadProject() timeout", 1000);

                runs(function() {
                    // skip rename
                    ProjectManager.createNewItem(this.testPath, "file.js", true)
                        .done(function() { didCreate = true; })
                        .fail(function() { gotError = true; });
                });
                waitsFor(function() { return !didCreate && gotError; }, "ProjectManager.createNewItem() timeout", 1000);

                runs(function() {
                    expect(gotError).toBeTruthy();
                    expect(didCreate).toBeFalsy();
                });
            });

            it("should fail when a file name matches a directory that already exists", function() {
                var didCreate = false, gotError = false;

                runs(function() {
                    ProjectManager.loadProject(this.testPath);
                });
                waitsFor(function() { return ProjectManager.getProjectRoot() }, "loadProject() timeout", 1000);

                runs(function() {
                    // skip rename
                    ProjectManager.createNewItem(this.testPath, "directory", true)
                        .done(function() { didCreate = true; })
                        .fail(function() { gotError = true; });
                });
                waitsFor(function() { return !didCreate && gotError; }, "ProjectManager.createNewItem() timeout", 1000);

                runs(function() {
                    expect(gotError).toBeTruthy();
                    expect(didCreate).toBeFalsy();
                });
            });

            it("should fail when file name contains special characters", function() {
                var chars = "/?*:;{}<>\\";
                var i = 0;
                var len = chars.length;
                var charAt, didCreate, gotError;

                runs(function() {
                    ProjectManager.loadProject(this.testPath);
                });
                waitsFor(function() { return ProjectManager.getProjectRoot() }, "loadProject() timeout", 1000);

                for (i = 0; i < len; i++) {
                    didCreate = false;
                    gotError = false;
                    charAt = chars.charAt(i);

                    runs(function() {
                        // skip rename
                        ProjectManager.createNewItem(this.testPath, "file" + charAt + ".js", true)
                            .done(function() { didCreate = true; })
                            .fail(function() { gotError = true; });
                    });
                    waitsFor(function() { return !didCreate && gotError; }, "ProjectManager.createNewItem() timeout", 1000);

                    runs(function() {
                        expect(gotError).toBeTruthy();
                        expect(didCreate).toBeFalsy();
                    });
                }
            });
        });

    });

    }
});


module Jasmine
  module Core
    VERSION = "1.1.0"
  end
end


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

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

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

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

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

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

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

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

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

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

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

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


handleFileClose( unsavedDocs[0] ).done( function() {

keep keep keep keep keep replace replace keep keep keep

handleFileClose( unsavedDocs[0], promptOnly ).done( function() {

// still need to close any other, non-unsaved documents


/* ==========================================================
 * bootstrap-alerts.js v1.4.0
 * http://twitter.github.com/bootstrap/javascript.html#alerts
 * ==========================================================
 * 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"

  /* CSS TRANSITION SUPPORT (https://gist.github.com/373874)
   * ======================================================= */

   var transitionEnd

   $(document).ready(function () {

     $.support.transition = (function () {
       var thisBody = document.body || document.documentElement
         , thisStyle = thisBody.style
         , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined
       return support
     })()

     // set CSS transition event type
     if ( $.support.transition ) {
       transitionEnd = "TransitionEnd"
       if ( $.browser.webkit ) {
        transitionEnd = "webkitTransitionEnd"
       } else if ( $.browser.mozilla ) {
        transitionEnd = "transitionend"
       } else if ( $.browser.opera ) {
        transitionEnd = "oTransitionEnd"
       }
     }

   })

 /* ALERT CLASS DEFINITION
  * ====================== */

  var Alert = function ( content, options ) {
    if (options == 'close') return this.close.call(content)
    this.settings = $.extend({}, $.fn.alert.defaults, options)
    this.$element = $(content)
      .delegate(this.settings.selector, 'click', this.close)
  }

  Alert.prototype = {

    close: function (e) {
      var $element = $(this)
        , className = 'alert-message'

      $element = $element.hasClass(className) ? $element : $element.parent()

      e && e.preventDefault()
      $element.removeClass('in')

      function removeElement () {
        $element.remove()
      }

      $.support.transition && $element.hasClass('fade') ?
        $element.bind(transitionEnd, removeElement) :
        removeElement()
    }

  }

 /* ALERT PLUGIN DEFINITION
  * ======================= */

  $.fn.alert = function ( options ) {

    if ( options === true ) {
      return this.data('alert')
    }

    return this.each(function () {
      var $this = $(this)
        , data

      if ( typeof options == 'string' ) {

        data = $this.data('alert')

        if (typeof data == 'object') {
          return data[options].call( $this )
        }

      }

      $(this).data('alert', new Alert( this, options ))

    })
  }

  $.fn.alert.defaults = {
    selector: '.close'
  }

  $(document).ready(function () {
    new Alert($('body'), {
      selector: '.alert-message[data-alert] .close'
    })
  })

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


console.log("SAVE ALL, then CLOSE ALL");

keep keep keep keep keep replace keep keep keep


describe("Player", function() {
  var player;
  var song;

  beforeEach(function() {
    player = new Player();
    song = new Song();
  });

  it("should be able to play a Song", function() {
    player.play(song);
    expect(player.currentlyPlayingSong).toEqual(song);

    //demonstrates use of custom matcher
    expect(player).toBePlaying(song);
  });

  describe("when song has been paused", function() {
    beforeEach(function() {
      player.play(song);
      player.pause();
    });

    it("should indicate that the song is currently paused", function() {
      expect(player.isPlaying).toBeFalsy();

      // demonstrates use of 'not' with a custom matcher
      expect(player).not.toBePlaying(song);
    });

    it("should be possible to resume", function() {
      player.resume();
      expect(player.isPlaying).toBeTruthy();
      expect(player.currentlyPlayingSong).toEqual(song);
    });
  });

  // demonstrates use of spies to intercept and test method calls
  it("tells the current song if the user has made it a favorite", function() {
    spyOn(song, 'persistFavoriteStatus');

    player.play(song);
    player.makeFavorite();

    expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true);
  });

  //demonstrates use of expected exceptions
  describe("#resume", function() {
    it("should throw an exception if song is already playing", function() {
      player.play(song);

      expect(function() {
        player.resume();
      }).toThrow("song is already playing");
    });
  });
});


/* ============================================================
 * bootstrap-buttons.js v1.4.0
 * http://twitter.github.com/bootstrap/javascript.html#buttons
 * ============================================================
 * 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"

  function setState(el, state) {
    var d = 'disabled'
      , $el = $(el)
      , data = $el.data()

    state = state + 'Text'
    data.resetText || $el.data('resetText', $el.html())

    $el.html( data[state] || $.fn.button.defaults[state] )

    setTimeout(function () {
      state == 'loadingText' ?
        $el.addClass(d).attr(d, d) :
        $el.removeClass(d).removeAttr(d)
    }, 0)
  }

  function toggle(el) {
    $(el).toggleClass('active')
  }

  $.fn.button = function(options) {
    return this.each(function () {
      if (options == 'toggle') {
        return toggle(this)
      }
      options && setState(this, options)
    })
  }

  $.fn.button.defaults = {
    loadingText: 'loading...'
  }

  $(function () {
    $('body').delegate('.btn[data-toggle]', 'click', function () {
      $(this).button('toggle')
    })
  })

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


/*
 * Brackets Colors
 *
 * Based on a de-saturated modification of Solarized
 *  http://ethanschoonover.com/solarized
 *
 * These are general purpose colors that can be used in defining
 * themes or UI elements. We should NEVER use color literals (e.g. #aaa)
 * elsewhere in our LESS. (Using "lighten" and "darken") functions
 * is encouraged however.

 * IMPORTANT: IF we want a UI element to be themeable, these variable
 * names should _not_ be used in its definition.
 *
 * Instead, a new semantically-meaningful variables/mixins should be added
 * to the "brackets_theme_default.less" file, and then these variables/mixins
 * should be used in the definition of the UI element
 *
 * For UI elements we do NOT want to theme, we should use these color names
 *
 * All brackets color variable names (that refer to an actual color)
 * are prefixed with "bc-" for "brackets color". This is to avoid confusion
 * with system and css color names. (We define our own colors because system
 * colors are ugly.)
 */

// use the "lighten" or "darken" function with integer multiples of this
@bc-color-step-size: 10%;

@bc-black:       #000000;
@bc-grey:        #808080;
@bc-gray:        @bc-grey; // because people spell it both ways
@bc-white:       #ffffff;

@bc-yellow:      #b58900;
@bc-orange:      #cb4b16;
@bc-red:         #dc322f;
@bc-magenta:     #d33682;
@bc-violet:      #6c71c4;
@bc-blue:        #268bd2;
@bc-light-blue:  #d8e0e8;
@bc-cyan:        #2aa198;
@bc-green:       #859900;


// This is the "Don't Save" case--we can just go ahead and close the file.

console.log("DISCARD ALL, then CLOSE ALL");