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

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, describe, it, expect, beforeEach, afterEach, waitsFor, runs, window, $, jasmine */
/*unittests: FindReplace*/

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

    var Editor                = require("editor/Editor").Editor,
        FindReplace           = require("search/FindReplace"),
        Commands              = require("command/Commands"),
        CommandManager        = require("command/CommandManager"),
        KeyEvent              = require("utils/KeyEvent"),
        SpecRunnerUtils       = require("spec/SpecRunnerUtils");

    describe("FindReplace", function () {

        var defaultContent = "/* Test comment */\n" +
                             "define(function (require, exports, module) {\n" +
                             "    var Foo = require(\"modules/Foo\"),\n" +
                             "        Bar = require(\"modules/Bar\"),\n" +
                             "        Baz = require(\"modules/Baz\");\n" +
                             "    \n" +
                             "    function callFoo() {\n" +
                             "        \n" +
                             "        foo();\n" +
                             "        \n" +
                             "    }\n" +
                             "\n" +
                             "}";

        var LINE_FIRST_REQUIRE = 2;
        var LINE_AFTER_REQUIRES = 5;
        var CH_REQUIRE_START = 14;
        var CH_REQUIRE_PAREN = CH_REQUIRE_START + "require".length;

        var fooExpectedMatches = [
            {start: {line: LINE_FIRST_REQUIRE, ch: 8}, end: {line: LINE_FIRST_REQUIRE, ch: 11}},
            {start: {line: LINE_FIRST_REQUIRE, ch: 31}, end: {line: LINE_FIRST_REQUIRE, ch: 34}},
            {start: {line: 6, ch: 17}, end: {line: 6, ch: 20}},
            {start: {line: 8, ch: 8}, end: {line: 8, ch: 11}}
        ];
        var barExpectedMatches = [
            {start: {line: LINE_FIRST_REQUIRE + 1, ch: 8}, end: {line: LINE_FIRST_REQUIRE + 1, ch: 11}},
            {start: {line: LINE_FIRST_REQUIRE + 1, ch: 31}, end: {line: LINE_FIRST_REQUIRE + 1, ch: 34}}
        ];

        var myDocument, myEditor, $myToolbar;

        function setupFullEditor() {
            // create dummy Document and Editor
            var mocks = SpecRunnerUtils.createMockEditor(defaultContent, "javascript");
            myDocument = mocks.doc;
            myEditor = mocks.editor;
            myEditor.centerOnCursor = jasmine.createSpy("centering");

            myEditor.focus();
        }

        beforeEach(function () {
            // Dummy version of anchor that ModalBar inserts itself relative to. Must exist in DOM somewhere.
            $myToolbar = $("<div id='editor-holder'/>").appendTo(window.document.body);
        });

        afterEach(function () {
            SpecRunnerUtils.destroyMockEditor(myDocument);
            $myToolbar.remove();
            $myToolbar = null;
            myEditor = null;
            myDocument = null;
        });

        // Helper functions for testing cursor position / selection range
        // TODO: duplicated from EditorCommandHandlers-test
        function expectSelection(sel) {
            expect(myEditor.getSelection()).toEqual(sel);
        }
        function expectHighlightedMatches(selections, expectedDOMHighlightCount) {
            var cm = myEditor._codeMirror;
            var searchState = cm._searchState;

            expect(searchState).toBeDefined();
            expect(searchState.marked).toBeDefined();
            expect(searchState.marked.length).toEqual(selections.length);

            // Verify that searchState's marked ranges match expected ranges
            if (selections) {
                selections.forEach(function (location, index) {
                    var textMarker = searchState.marked[index];
                    var markerLocation = textMarker.find();
                    expect(markerLocation.from).toEqual(location.start);
                    expect(markerLocation.to).toEqual(location.end);
                });
            }

            // Verify that editor UI doesn't have extra ranges left highlighted from earlier
            // (note: this only works for text that's short enough to not get virtualized)
            var lineDiv = $(".CodeMirror-lines > div", myEditor.getRootElement()).children()[2];
            var actualHighlights = $(".CodeMirror-searching", lineDiv);
            if (expectedDOMHighlightCount === undefined) {
                expectedDOMHighlightCount = selections.length;
            }
            expect(actualHighlights.length).toEqual(expectedDOMHighlightCount);
        }

        function getSearchBar() {
            return $(".modal-bar");
        }
        function getSearchField() {
            return $(".modal-bar input[type='text']");
        }

        function expectSearchBarOpen() {
            expect(getSearchBar()[0]).toBeDefined();
        }
        function expectSearchBarClosed() {
            expect(getSearchBar()[0]).not.toBeDefined();
        }

        function enterSearchText(str) {
            expectSearchBarOpen();
            var $input = getSearchField();
            $input.val(str);
            $input.trigger("input");
        }

        function pressEnter() {
            expectSearchBarOpen();
            SpecRunnerUtils.simulateKeyEvent(KeyEvent.DOM_VK_RETURN, "keydown", getSearchField()[0]);
        }
        function pressEscape() {
            expectSearchBarOpen();
            SpecRunnerUtils.simulateKeyEvent(KeyEvent.DOM_VK_ESCAPE, "keydown", getSearchField()[0]);
        }

        describe("Search", function () {
            beforeEach(setupFullEditor);

            it("should find all case-insensitive matches", function () {
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("foo");
                expectHighlightedMatches(fooExpectedMatches);
                expectSelection(fooExpectedMatches[0]);
                expect(myEditor.centerOnCursor.calls.length).toEqual(1);

                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection(fooExpectedMatches[1]);
                expect(myEditor.centerOnCursor.calls.length).toEqual(2);
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection(fooExpectedMatches[2]);
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection(fooExpectedMatches[3]);
                expectHighlightedMatches(fooExpectedMatches);  // no change in highlights

                // wraparound
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection(fooExpectedMatches[0]);
                expect(myEditor.centerOnCursor.calls.length).toEqual(5);
            });

            it("should find all case-sensitive matches", function () {
                var expectedSelections = [
                    {start: {line: LINE_FIRST_REQUIRE, ch: 8}, end: {line: LINE_FIRST_REQUIRE, ch: 11}},
                    {start: {line: LINE_FIRST_REQUIRE, ch: 31}, end: {line: LINE_FIRST_REQUIRE, ch: 34}},
                    {start: {line: 6, ch: 17}, end: {line: 6, ch: 20}}
                ];
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("Foo");
                expectHighlightedMatches(expectedSelections);
                expectSelection(expectedSelections[0]);

                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection(expectedSelections[1]);
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection(expectedSelections[2]);
                // note the lowercase "foo()" is NOT matched

                // wraparound
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection(expectedSelections[0]);
            });

            it("should Find Next after search bar closed, including wraparound", function () {
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("foo");
                pressEnter();
                expectHighlightedMatches([]);
                expectSearchBarClosed();

                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: 8}, end: {line: LINE_FIRST_REQUIRE, ch: 11}});
                expect(myEditor.centerOnCursor.calls.length).toEqual(1);

                // Simple linear Find Next
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: 31}, end: {line: LINE_FIRST_REQUIRE, ch: 34}});
                expect(myEditor.centerOnCursor.calls.length).toEqual(2);
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection({start: {line: 6, ch: 17}, end: {line: 6, ch: 20}});
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection({start: {line: 8, ch: 8}, end: {line: 8, ch: 11}});

                // Wrap around to first result
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: 8}, end: {line: LINE_FIRST_REQUIRE, ch: 11}});
            });

            it("should Find Previous after search bar closed, including wraparound", function () {
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("foo");
                pressEnter();
                expectSearchBarClosed();

                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: 8}, end: {line: LINE_FIRST_REQUIRE, ch: 11}});

                // Wrap around to last result
                CommandManager.execute(Commands.EDIT_FIND_PREVIOUS);
                expectSelection({start: {line: 8, ch: 8}, end: {line: 8, ch: 11}});

                // Simple linear Find Previous
                CommandManager.execute(Commands.EDIT_FIND_PREVIOUS);
                expectSelection({start: {line: 6, ch: 17}, end: {line: 6, ch: 20}});
                CommandManager.execute(Commands.EDIT_FIND_PREVIOUS);
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: 31}, end: {line: LINE_FIRST_REQUIRE, ch: 34}});
                CommandManager.execute(Commands.EDIT_FIND_PREVIOUS);
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: 8}, end: {line: LINE_FIRST_REQUIRE, ch: 11}});
            });

            it("shouldn't Find Next after search bar reopened", function () {
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("foo");
                pressEnter();
                expectSearchBarClosed();

                // Open search bar a second time
                myEditor.setCursorPos(0, 0);
                CommandManager.execute(Commands.EDIT_FIND);

                expectSearchBarOpen();
                expect(myEditor).toHaveCursorPosition(0, 0);

                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expect(myEditor).toHaveCursorPosition(0, 0);
            });

            it("should open search bar on Find Next with no previous search", function () {
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND_NEXT);

                expectSearchBarOpen();
                expect(myEditor).toHaveCursorPosition(0, 0);
            });

            it("should select-all without affecting search state if Find invoked while search bar open", function () {  // #2478
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("foo");  // position cursor first

                // Search for something that doesn't exist; otherwise we can't tell whether search state is cleared or bar is reopened,
                // since reopening the bar will just prepopulate it with selected text from first search's result
                enterSearchText("foobar");

                expect(myEditor).toHaveCursorPosition(LINE_FIRST_REQUIRE, 11);  // cursor left at end of last good match ("foo")

                // Invoke Find a 2nd time - this time while search bar is open
                CommandManager.execute(Commands.EDIT_FIND);

                expectSearchBarOpen();
                expect(getSearchField().val()).toEqual("foobar");
                expect(getSearchField()[0].selectionStart).toBe(0);
                expect(getSearchField()[0].selectionEnd).toBe(6);
                expect(myEditor).toHaveCursorPosition(LINE_FIRST_REQUIRE, 11);
            });

        });

        describe("Incremental search", function () {
            beforeEach(setupFullEditor);

            it("should re-search from original position when text changes", function () {
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("baz");

                var expectedSelections = [
                    {start: {line: LINE_FIRST_REQUIRE + 2, ch: 8}, end: {line: LINE_FIRST_REQUIRE + 2, ch: 11}},
                    {start: {line: LINE_FIRST_REQUIRE + 2, ch: 31}, end: {line: LINE_FIRST_REQUIRE + 2, ch: 34}}
                ];
                expectSelection(expectedSelections[0]);
                expectHighlightedMatches(expectedSelections);

                enterSearchText("bar");

                expectSelection(barExpectedMatches[0]);  // selection one line earlier than previous selection
                expectHighlightedMatches(barExpectedMatches);
            });

            it("should re-search from original position when text changes, even after Find Next", function () {
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("foo");
                expectSelection(fooExpectedMatches[0]);

                // get search highlight down below where the "bar" match will be
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection(fooExpectedMatches[2]);

                enterSearchText("bar");
                expectSelection(barExpectedMatches[0]);
            });

            it("should extend original selection when appending to prepopulated text", function () {
                myEditor.setSelection({line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN});

                CommandManager.execute(Commands.EDIT_FIND);
                expect(getSearchField().val()).toEqual("require");

                var requireExpectedMatches = [
                    {start: {line: 1, ch: 17}, end: {line: 1, ch: 24}},
                    {start: {line: LINE_FIRST_REQUIRE,     ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE,     ch: CH_REQUIRE_PAREN}},
                    {start: {line: LINE_FIRST_REQUIRE + 1, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE + 1, ch: CH_REQUIRE_PAREN}},
                    {start: {line: LINE_FIRST_REQUIRE + 2, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE + 2, ch: CH_REQUIRE_PAREN}}
                ];
                expectHighlightedMatches(requireExpectedMatches);
                expectSelection(requireExpectedMatches[1]);  // cursor was below 1st match, so 2nd match is selected

                enterSearchText("require(");
                requireExpectedMatches.shift();  // first result no longer matches
                requireExpectedMatches[0].end.ch++;  // other results now include one more char
                requireExpectedMatches[1].end.ch++;
                requireExpectedMatches[2].end.ch++;
                expectHighlightedMatches(requireExpectedMatches, 6);  // expect 2x DOM highlights because each match spans 2 tokens
                expectSelection(requireExpectedMatches[0]);
            });

            it("should collapse selection when appending to prepopulated text causes no result", function () {
                myEditor.setSelection({line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN});

                CommandManager.execute(Commands.EDIT_FIND);
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN}});

                enterSearchText("requireX");
                expectHighlightedMatches([]);
                expect(myEditor).toHaveCursorPosition(LINE_FIRST_REQUIRE, CH_REQUIRE_PAREN);
            });

            it("should clear selection, return cursor to start after backspacing to empty query", function () {
                myEditor.setCursorPos(2, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("require");
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN}});

                enterSearchText("");
                expect(myEditor).toHaveCursorPosition(2, 0);
            });
        });

        describe("Terminating search", function () {
            beforeEach(setupFullEditor);

            it("should go to next on Enter with prepopulated text & no Find Nexts", function () {
                myEditor.setSelection({line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN});

                CommandManager.execute(Commands.EDIT_FIND);
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN}});

                pressEnter();

                expectSearchBarClosed();
                expectHighlightedMatches([]);
                expectSelection({start: {line: LINE_FIRST_REQUIRE + 1, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE + 1, ch: CH_REQUIRE_PAREN}});
            });

            it("shouldn't change selection on Esc with prepopulated text & no Find Nexts", function () {
                myEditor.setSelection({line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN});

                CommandManager.execute(Commands.EDIT_FIND);
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN}});

                pressEscape();

                expectSearchBarClosed();
                expectHighlightedMatches([]);
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN}});
            });

            it("shouldn't change selection on Enter with prepopulated text & after Find Next", function () {
                myEditor.setSelection({line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN});

                CommandManager.execute(Commands.EDIT_FIND);
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN}});

                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection({start: {line: LINE_FIRST_REQUIRE + 1, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE + 1, ch: CH_REQUIRE_PAREN}});

                pressEnter();

                expectSearchBarClosed();
                expectSelection({start: {line: LINE_FIRST_REQUIRE + 1, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE + 1, ch: CH_REQUIRE_PAREN}});
            });

            it("shouldn't change selection on Enter after typing text, no Find Nexts", function () {
                myEditor.setCursorPos(LINE_FIRST_REQUIRE, 0);

                CommandManager.execute(Commands.EDIT_FIND);
                expect(myEditor).toHaveCursorPosition(LINE_FIRST_REQUIRE, 0);

                enterSearchText("require");
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN}});

                pressEnter();

                expectSearchBarClosed();
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN}});
            });

            it("shouldn't change selection on Enter after typing text & Find Next", function () {
                myEditor.setCursorPos(LINE_FIRST_REQUIRE, 0);

                CommandManager.execute(Commands.EDIT_FIND);
                expect(myEditor).toHaveCursorPosition(LINE_FIRST_REQUIRE, 0);

                enterSearchText("require");
                expectSelection({start: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE, ch: CH_REQUIRE_PAREN}});

                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection({start: {line: LINE_FIRST_REQUIRE + 1, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE + 1, ch: CH_REQUIRE_PAREN}});

                pressEnter();

                expectSearchBarClosed();
                expectSelection({start: {line: LINE_FIRST_REQUIRE + 1, ch: CH_REQUIRE_START}, end: {line: LINE_FIRST_REQUIRE + 1, ch: CH_REQUIRE_PAREN}});
            });

            it("should no-op on Find Next with blank search", function () {
                myEditor.setCursorPos(LINE_FIRST_REQUIRE, 0);

                CommandManager.execute(Commands.EDIT_FIND);
                expect(myEditor).toHaveCursorPosition(LINE_FIRST_REQUIRE, 0);

                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expect(myEditor).toHaveCursorPosition(LINE_FIRST_REQUIRE, 0); // no change

            });

            it("should no-op on Enter with blank search", function () {
                myEditor.setCursorPos(LINE_FIRST_REQUIRE, 0);

                CommandManager.execute(Commands.EDIT_FIND);
                expect(myEditor).toHaveCursorPosition(LINE_FIRST_REQUIRE, 0);

                pressEnter();

                expectSearchBarClosed();
                expect(myEditor).toHaveCursorPosition(LINE_FIRST_REQUIRE, 0); // no change
            });
        });

        describe("RegExp Search", function () {
            beforeEach(setupFullEditor);

            it("should find based on regexp", function () {
                var expectedSelections = [
                    {start: {line: LINE_FIRST_REQUIRE + 1, ch: 8}, end: {line: LINE_FIRST_REQUIRE + 1, ch: 11}},
                    {start: {line: LINE_FIRST_REQUIRE + 1, ch: 31}, end: {line: LINE_FIRST_REQUIRE + 1, ch: 34}},
                    {start: {line: LINE_FIRST_REQUIRE + 2, ch: 8}, end: {line: LINE_FIRST_REQUIRE + 2, ch: 11}},
                    {start: {line: LINE_FIRST_REQUIRE + 2, ch: 31}, end: {line: LINE_FIRST_REQUIRE + 2, ch: 34}}
                ];
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("/Ba./");
                expectHighlightedMatches(expectedSelections);
                expectSelection(expectedSelections[0]);

                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection(expectedSelections[1]);
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection(expectedSelections[2]);
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection(expectedSelections[3]);

                // wraparound
                CommandManager.execute(Commands.EDIT_FIND_NEXT);
                expectSelection(expectedSelections[0]);
            });

            it("should be a case-sensitive regexp by default", function () {
                var expectedSelections = [
                    {start: {line: 8, ch: 8}, end: {line: 8, ch: 11}}
                ];
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("/foo/");
                expectHighlightedMatches(expectedSelections);
                expectSelection(expectedSelections[0]);
            });

            it("should support case-insensitive regexps via /.../i", function () {
                var expectedSelections = [
                    {start: {line: LINE_FIRST_REQUIRE, ch: 8}, end: {line: LINE_FIRST_REQUIRE, ch: 11}},
                    {start: {line: LINE_FIRST_REQUIRE, ch: 31}, end: {line: LINE_FIRST_REQUIRE, ch: 34}},
                    {start: {line: 6, ch: 17}, end: {line: 6, ch: 20}},
                    {start: {line: 8, ch: 8}, end: {line: 8, ch: 11}}
                ];
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("/foo/i");
                expectHighlightedMatches(expectedSelections);
                expectSelection(expectedSelections[0]);
            });

            it("shouldn't choke on partial regexp", function () {
                var expectedSelections = [
                    {start: {line: LINE_FIRST_REQUIRE + 1, ch: 30}, end: {line: LINE_FIRST_REQUIRE + 1, ch: 33}},
                    {start: {line: LINE_FIRST_REQUIRE + 2, ch: 30}, end: {line: LINE_FIRST_REQUIRE + 2, ch: 33}}
                ];
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                // This should be interpreted as a non-RegExp search, which actually does have a result thanks to "modules/Bar"
                enterSearchText("/Ba");
                expectHighlightedMatches(expectedSelections);
                expectSelection(expectedSelections[0]);
            });

            it("shouldn't choke on invalid regexp", function () {
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                // This is interpreted as a regexp (has both "/"es) but is invalid; should show error message
                enterSearchText("/+/");
                expect($(".modal-bar .error").length).toBe(1);
                expectHighlightedMatches([]);
                expect(myEditor).toHaveCursorPosition(0, 0); // no change
            });

            it("shouldn't choke on empty regexp", function () {
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("//");
                expectHighlightedMatches([]);
                expect(myEditor).toHaveCursorPosition(0, 0); // no change
            });

            it("shouldn't freeze on /.*/ regexp", function () {
                myEditor.setCursorPos(0, 0);

                CommandManager.execute(Commands.EDIT_FIND);

                enterSearchText("/.*/");
                pressEnter();
                expectSelection({start: {line: 0, ch: 0}, end: {line: 0, ch: 18}});
            });
        });
    });

});


keep keep keep replace keep keep keep keep keep

# readable-stream

    Stability: 1 - Experimental

A new kind of readable streams for Node.js

This is an abstract class designed to be extended.  It also provides a
`wrap` method that you can use to provide the simpler readable API for
streams that have the "readable stream" interface of Node 0.8 and
before.

Note that Duplex, Transform, Writable, and PassThrough streams are also
provided as base classes.  See the full API details below.

## Justification

<!-- misc -->

Writable streams in node are relatively straightforward to use and
extend.  The `write` method either returns `false` if you would like
the user to back off a bit, in which case a `drain` event at some
point in the future will let them continue writing, or anything other
than false if the bytes could be completely handled and another
`write` should be performed, or   The `end()` method lets the user
indicate that no more bytes will be written.  That's pretty much the
entire required interface for writing.

However, readable streams in Node 0.8 and before are rather
complicated.

1. The `data` events start coming right away, no matter what.  There
   is no way to do other actions before consuming data, without
   handling buffering yourself.
2. If you extend the interface in userland programs, then you must
   implement `pause()` and `resume()` methods, and take care of
   buffering yourself.
3. In many streams, `pause()` was purely advisory, so **even while
   paused**, you still have to be careful that you might get some
   data.  This caused a lot of subtle b ugs.

So, while writers only have to implement `write()`, `end()`, and
`drain`, readers have to implement (at minimum):

* `pause()` method
* `resume()` method
* `data` event
* `end` event

And read consumers had to always be prepared for their backpressure
advice to simply be ignored.

If you are using a readable stream, and want to just get the first 10
bytes, make a decision, and then pass the rest off to somewhere else,
then you have to handle buffering, pausing, slicing, and so on.  This
is all rather brittle and easy to get wrong for all but the most
trivial use cases.

Additionally, this all made the `reader.pipe(writer)` method
unnecessarily complicated and difficult to extend without breaking
something.  Backpressure and error handling is especially challenging
and brittle.

### Solution

<!-- misc -->

The reader does not have pause/resume methods.  If you want to consume
the bytes, you call `read()`.  If bytes are not being consumed, then
effectively the stream is in a paused state.  It exerts backpressure
on upstream connections, doesn't read from files, etc.  Any data that
was already in the process of being read will be placed in a buffer.

If `read()` returns `null`, then a future `readable` event will be
fired when there are more bytes ready to be consumed.

This is simpler and conceptually closer to the underlying mechanisms.
The resulting `pipe()` method is much shorter and simpler.  The
problems of data events happening while paused are alleviated.

### Compatibility

<!-- misc -->

It's not particularly difficult to wrap old-style streams in this
new interface, or to wrap this type of stream in the old-style
interface.

The `Readable` class provides a `wrap(oldStream)` method that takes an
argument which is an old-style stream with `data` events and `pause()`
and `resume()` methods, and uses that as the data source.  For
example:

```javascript
var r = new Readable();
r.wrap(oldReadableStream);

// now you can use r.read(), and it will emit 'readable' events
// but the data is based on whatever oldReadableStream spits out of
// its 'data' events.
```

In order to work with programs that use the old interface, some
magic is unfortunately required.  At some point in the future, this
magic will be removed.

The `Readable` class will automatically convert into an old-style
`data`-emitting stream if any listeners are added to the `data` event.
So, this works fine, though you of course lose a lot of the benefits of
the new interface:

```javascript
var r = new ReadableThing();

r.on('data', function(chunk) {
  // ...
  // magic is happening!  oh no!  the animals are walking upright!
  // the brooms are sweeping the floors all by themselves!
});

// this will also turn on magic-mode:
r.pause();

// now pause, resume, etc. are patched into place, and r will
// continually call read() until it returns null, emitting the
// returned chunks in 'data' events.

r.on('end', function() {
  // ...
});
```

## Class: Readable

A base class for implementing Readable streams.  Override the
`_read(n,cb)` method to fetch data asynchronously and take advantage
of the buffering built into the Readable class.

### Example

Extend the Readable class, and provide a `_read(n,cb)` implementation
method.

```javascript
var Readable = require('readable-stream');
var util = require('util');

util.inherits(MyReadable, Readable);

function MyReadable(options) {
  Readable.call(this, options);
}

MyReadable.prototype._read = function(n, cb) {
  // your magic goes here.
  // call the cb at some time in the future with either up to n bytes,
  // or an error, like cb(err, resultData)
  //
  // The code in the Readable class will call this to keep an internal
  // buffer at a healthy level, as the user calls var chunk=stream.read(n)
  // to consume chunks.
};

var r = new MyReadable();

r.on('end', function() {
  // no more bytes will be provided.
});

r.on('readable', function() {
  // now is the time to call read() again.
});

// to get some bytes out of it:
var data = r.read(optionalLengthArgument);
// now data is either null, or a buffer of optionalLengthArgument
// length.  If you don't provide an argument, then it returns whatever
// it has.

// typically you'd just r.pipe() into some writable stream, but you
// can of course do stuff like this, as well:
function flow() {
  var chunk;
  while (null !== (chunk = r.read())) {
    doSomethingWithData(chunk);
  }
  r.once('readable', flow);
}
flow();
```

### new Readable(options)

* `options` {Object}
  * `lowWaterMark` {Number} The minimum number of bytes before the
    stream is considered 'readable'.  Default = `0`
  * `bufferSize` {Number} The number of bytes to try to read from the
    underlying `_read` function.  Default = `16 * 1024`

Make sure to call the `Readable` constructor in your extension
classes, or else the stream will not be properly initialized.

### readable.read([size])

* `size` {Number} Optional number of bytes to read.  If not provided,
  then return however many bytes are available.
* Returns: {Buffer | null}

Pulls the requested number of bytes out of the internal buffer.  If
that many bytes are not available, then it returns `null`.

### readable.\_read(size, callback)

* `size` {Number} Number of bytes to read from the underlying
  asynchronous data source.
* `callback` {Function} Callback function
  * `error` {Error Object}
  * `data` {Buffer | null}

**Note: This function is not implemented in the Readable base class.**
Rather, it is up to you to implement `_read` in your extension
classes.

`_read` should fetch the specified number of bytes, and call the
provided callback with `cb(error, data)`, where `error` is any error
encountered, and `data` is the returned data.

This method is prefixed with an underscore because it is internal to
the class that defines it, and should not be called directly by user
programs.  However, you **are** expected to override this method in
your own extension classes.

The `size` argument is purely advisory.  You may call the callback
with more or fewer bytes.  However, if you call the callback with
`null`, or an empty buffer, then it will assume that the end of the
data was reached.

### readable.pipe(destination)

* `destination` {Writable Stream object}

Continually `read()` data out of the readable stream, and `write()` it
into the writable stream.  When the `writable.write(chunk)` call
returns `false`, then it will back off until the next `drain` event,
to do backpressure.

Piping to multiple destinations is supported.  The slowest destination
stream will limit the speed of the `pipe()` flow.

Note that this puts the readable stream into a state where not very
much can be done with it.  You can no longer `read()` from the stream
in other code, without upsetting the pipe() process.  However, since
multiple pipe destinations are supported, you can always create a
`PassThrough` stream, and pipe the reader to that.  For example:

```
var r = new ReadableWhatever();
var pt = new PassThrough();

r.pipe(someWritableThing);
r.pipe(pt);

// now I can call pt.read() to my heart's content.
// note that if I *don't* call pt.read(), then it'll back up and
// prevent the pipe() from flowing!
```

### readable.unpipe([destination])

* `destination` {Writable Stream object} Optional

Remove the provided `destination` stream from the pipe flow.  If no
argument is provided, then it will unhook all piped destinations.

### readable.on('readable')

An event that signals more data is now available to be read from the
stream.  Emitted when more data arrives, after previously calling
`read()` and getting a null result.

### readable.on('end')

An event that signals that no more data will ever be available on this
stream.  It's over.

### readable.\_readableState

* {Object}

An object that tracks the state of the stream.  Buffer information,
whether or not it has reached the end of the underlying data source,
etc., are all tracked on this object.

You are strongly encouraged not to modify this in any way, but it is
often useful to read from.

## Class: Writable

A base class for creating Writable streams.  Similar to Readable, you
can create child classes by overriding the asynchronous
`_write(chunk,cb)` method, and it will take care of buffering,
backpressure, and so on.

### new Writable(options)

* `options` {Object}
  * `highWaterMark` {Number} The number of bytes to store up before it
    starts returning `false` from write() calls.  Default = `16 * 1024`
  * `lowWaterMark` {Number} The number of bytes that the buffer must
    get down to before it emits `drain`.  Default = `1024`

Make sure to call the `Writable` constructor in your extension
classes, or else the stream will not be properly initialized.

### writable.write(chunk, [encoding])

* `chunk` {Buffer | String}
* `encoding` {String} An encoding argument to turn the string chunk
  into a buffer.  Only relevant if `chunk` is a string.
  Default = `'utf8'`.
* Returns `false` if you should not write until the next `drain`
  event, or some other value otherwise.

The basic write function.

### writable.\_write(chunk, callback)

* `chunk` {Buffer}
* `callback` {Function}
  * `error` {Error | null} Call with an error object as the first
    argument to indicate that the write() failed for unfixable
    reasons.

**Note: This function is not implemented in the Writable base class.**
Rather, it is up to you to implement `_write` in your extension
classes.

`_write` should do whatever has to be done in this specific Writable
class, to handle the bytes being written.  Write to a file, send along
a socket, encrypt as an mp3, whatever needs to be done.  Do your I/O
asynchronously, and call the callback when it's complete.

This method is prefixed with an underscore because it is internal to
the class that defines it, and should not be called directly by user
programs.  However, you **are** expected to override this method in
your own extension classes.

### writable.end([chunk], [encoding])

* `chunk` {Buffer | String}
* `encoding` {String}

If a chunk (and, optionally, an encoding) are provided, then that
chunk is first passed to `this.write(chunk, encoding)`.

This method is a way to signal to the writable stream that you will
not be writing any more data.  It should be called exactly once for
every writable stream.

Calling `write()` *after* calling `end()` will trigger an error.

### writable.on('pipe', source)

Emitted when calling `source.pipe(writable)`.  See above for the
description of the `readable.pipe()` method.

### writable.on('unpipe', source)

Emitted when calling `source.unpipe(writable)`.  See above for the
description of the `readable.unpipe()` method.

### writable.on('drain')

If a call to `writable.write()` returns false, then at some point in
the future, this event will tell you to start writing again.

### writable.on('finish')

When the stream has been ended, and all the data in its internal
buffer has been consumed, then it emits a `finish` event to let you
know that it's completely done.

This is particularly handy if you want to know when it is safe to shut
down a socket or close a file descriptor.  At this time, the writable
stream may be safely disposed.  Its mission in life has been
accomplished.

## Class: Duplex

A base class for Duplex streams (ie, streams that are both readable
and writable).

Since JS doesn't have multiple prototypal inheritance, this class
prototypally inherits from Readable, and then parasitically from
Writable.  It is thus up to the user to implement both the lowlevel
`_read(n,cb)` method as well as the lowlevel `_write(chunk,cb)`
method on extension duplex classes.

For cases where the written data is transformed into the output, it
may be simpler to use the `Transform` class instead.

### new Duplex(options)

* `options` {Object}  Passed to both the Writable and Readable
  constructors.

Make sure to call the `Duplex` constructor in your extension
classes, or else the stream will not be properly initialized.

If `options.allowHalfOpen` is set to the value `false`, then the
stream will automatically end the readable side when the writable
side ends, and vice versa.

### duplex.allowHalfOpen

* {Boolean} Default = `true`

Set this flag to either `true` or `false` to determine whether or not
to automatically close the writable side when the readable side ends,
and vice versa.

## Class: Transform

A duplex (ie, both readable and writable) stream that is designed to
make it easy to implement transform operations such as encryption,
decryption, compression, and so on.

Transform streams are `instanceof` Readable, but they have all of the
methods and properties of both Readable and Writable streams.  See
above for the list of events and methods that Transform inherits from
Writable and Readable.

Override the `_transform(chunk, outputFunction, callback)` method in
your implementation classes to take advantage of it.

### new Transform(options)

* `options` {Object}  Passed to both the Writable and Readable
  constructors.

Make sure to call the `Transform` constructor in your extension
classes, or else the stream will not be properly initialized.

### transform.\_transform(chunk, outputFn, callback)

* `chunk` {Buffer} The chunk to be transformed.
* `outputFn` {Function} Call this function with any output data to be
  passed to the readable interface.
* `callback` {Function} Call this function (optionally with an error
  argument) when you are done processing the supplied chunk.

**Note: This function is not implemented in the Transform base class.**
Rather, it is up to you to implement `_transform` in your extension
classes.

`_transform` should do whatever has to be done in this specific
Transform class, to handle the bytes being written, and pass them off
to the readable portion of the interface.  Do asynchronous I/O,
process things, and so on.

Call the callback function only when the current chunk is completely
consumed.  Note that this may mean that you call the `outputFn` zero
or more times, depending on how much data you want to output as a
result of this chunk.

This method is prefixed with an underscore because it is internal to
the class that defines it, and should not be called directly by user
programs.  However, you **are** expected to override this method in
your own extension classes.

### transform.\_flush(outputFn, callback)

* `outputFn` {Function} Call this function with any output data to be
  passed to the readable interface.
* `callback` {Function} Call this function (optionally with an error
  argument) when you are done flushing any remaining data.

**Note: This function is not implemented in the Transform base class.**
Rather, it is up to you to implement `_flush` in your extension
classes optionally, if it applies to your use case.

In some cases, your transform operation may need to emit a bit more
data at the end of the stream.  For example, a `Zlib` compression
stream will store up some internal state so that it can optimally
compress the output.  At the end, however, it needs to do the best it
can with what is left, so that the data will be complete.

In those cases, you can implement a `_flush` method, which will be
called at the very end, after all the written data is consumed, but
before emitting `end` to signal the end of the readable side.  Just
like with `_transform`, call `outputFn` zero or more times, as
appropriate, and call `callback` when the flush operation is complete.

This method is prefixed with an underscore because it is internal to
the class that defines it, and should not be called directly by user
programs.  However, you **are** expected to override this method in
your own extension classes.

## Class: PassThrough

This is a trivial implementation of a `Transform` stream that simply
passes the input bytes across to the output.  Its purpose is mainly
for examples and testing, but there are occasionally use cases where
it can come in handy.


var assert = require('assert'),
    path = require('path'),
    rimraf = require('rimraf'),
    vows = require('vows'),
    readDirFiles = require('read-dir-files'),
    ncp = require('../').ncp;

var fixtures = path.join(__dirname, 'fixtures'),
    src = path.join(fixtures, 'src'),
    out = path.join(fixtures, 'out');

vows.describe('ncp').addBatch({
  'When copying a directory of files': {
    topic: function () {
      var cb = this.callback;
      rimraf(out, function () {
        ncp(src, out, cb);
      });
    },
    'files should be copied': {
      topic: function () {
        var cb = this.callback;

        readDirFiles(src, 'utf8', function (srcErr, srcFiles) {
          readDirFiles(out, 'utf8', function (outErr, outFiles) {
            cb(outErr, srcFiles, outFiles);
          });
        });
      },
      'and the destination should match the source': function (err, srcFiles, outFiles) {
        assert.isNull(err);
        assert.deepEqual(srcFiles, outFiles);
      }
    }
  }
}).addBatch({
  'When copying files using filter': {
    topic: function() {
      var cb = this.callback;
      var filter = function(name) {
        return name.substr(name.length - 1) != 'a'
      }
      rimraf(out, function () {
        ncp(src, out, {filter: filter}, cb);
      });
    },
    'it should copy files': {
      topic: function () {
        var cb = this.callback;

        readDirFiles(src, 'utf8', function (srcErr, srcFiles) {
          function filter(files) {
            for (var fileName in files) {
              var curFile = files[fileName];
              if (curFile instanceof Object)
                return filter(curFile);
              if (fileName.substr(fileName.length - 1) == 'a')
                delete files[fileName];
            }
          }
          filter(srcFiles);
          readDirFiles(out, 'utf8', function (outErr, outFiles) {
            cb(outErr, srcFiles, outFiles);
          });
        });
      },
      'and destination files should match source files that pass filter': function (err, srcFiles, outFiles) {
        assert.isNull(err);
        assert.deepEqual(srcFiles, outFiles);
      }
    }
  }
}).export(module);


console.error("Skipping command " + eventName + " because it looks like you are " +

"at a breakpoint. If you are NOT at a breakpoint, please " +

"file a bug and mention this comment. Stack depth = " + stackDepth + ".");

return (promise && promise.state() === "rejected") ? false : true;

keep keep keep keep replace keep keep keep keep keep

return (promise && (typeof promise.state === "function") && promise.state() === "rejected") ? false : true;

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

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

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

    require("thirdparty/path-utils/path-utils.min");

    var Dialogs                = require("widgets/Dialogs"),
        PreferencesManager     = require("preferences/PreferencesManager"),
        ProjectManager         = require("project/ProjectManager"),
        StringUtils            = require("utils/StringUtils"),
        Strings                = require("strings"),
        SettingsDialogTemplate = require("text!htmlContent/project-settings-dialog.html");

    /**
     * Validate that text string is a valid base url which should map to a server folder
     * @param {string} url
     * @return {string} Empty string if valid, otherwise error string
     */
    function _validateBaseUrl(url) {
        var result = "";
        // Empty url means "no server mapping; use file directly"
        if (url === "") {
            return result;
        }

        var obj = PathUtils.parseUrl(url);
        if (!obj) {
            result = Strings.BASEURL_ERROR_UNKOWN_ERROR;
        } else if (obj.href.search(/^(http|https):\/\//i) !== 0) {
            result = StringUtils.format(Strings.BASEURL_ERROR_INVALID_PROTOCOL, obj.href.substring(0, obj.href.indexOf("//")));
        } else if (obj.search !== "") {
            result = StringUtils.format(Strings.BASEURL_ERROR_SEARCH_DISALLOWED, obj.search);
        } else if (obj.hash !== "") {
            result = StringUtils.format(Strings.BASEURL_ERROR_HASH_DISALLOWED, obj.hash);
        } else {
            var index = url.search(/[ \^\[\]\{\}<>\\"\?]+/);
            if (index !== -1) {
                result = StringUtils.format(Strings.BASEURL_ERROR_INVALID_CHAR, url[index]);
            }
        }

        return result;
    }

    /**
     * Show a dialog that shows the project preferences
     * @param {string} baseUrl Initial value
     * @param {string} errorMessage Error to display
     * @return {Dialog} A Dialog object with an internal promise that will be resolved with the ID
     *      of the clicked button when the dialog is dismissed. Never rejected.
     */
    function showProjectPreferencesDialog(baseUrl, errorMessage) {
        var $dlg,
            $title,
            $baseUrlControl,
            dialog;

        dialog = Dialogs.showModalDialogUsingTemplate(Mustache.render(SettingsDialogTemplate, Strings))
            .done(function (id) {
                if (id === Dialogs.DIALOG_BTN_OK) {
                    var baseUrlValue = $baseUrlControl.val();
                    var result = _validateBaseUrl(baseUrlValue);
                    if (result === "") {
                        ProjectManager.setBaseUrl(baseUrlValue);
                    } else {
                        // Re-invoke dialog with result (error message)
                        showProjectPreferencesDialog(baseUrlValue, result);
                    }
                }
            });

        // Populate project settings
        $dlg = $(".project-settings-dialog.instance");

        // Title
        $title = $dlg.find(".dialog-title");
        var projectName = "",
            projectRoot = ProjectManager.getProjectRoot(),
            title;
        if (projectRoot) {
            projectName = projectRoot.name;
        }
        title = StringUtils.format(Strings.PROJECT_SETTINGS_TITLE, projectName);
        $title.text(title);

        // Base URL
        $baseUrlControl = $dlg.find(".url");
        if (baseUrl) {
            $baseUrlControl.val(baseUrl);
        }

        // Error message
        if (errorMessage) {
            $dlg.find(".field-container").append("
" + errorMessage + "
");
        }

        // Give focus to first control
        $baseUrlControl.focus();

        return dialog;
    }

    // For unit testing
    exports._validateBaseUrl                = _validateBaseUrl;

    exports.showProjectPreferencesDialog    = showProjectPreferencesDialog;
});


// Load modules

var Fs = require('fs');

// Declare internals

var internals = {};

// Clone object or array

exports.clone = function (obj) {

    if (obj === null ||
        obj === undefined) {

        return null;
    }

    if (typeof obj !== 'object') {
        return obj;
    }

    var newObj = (obj instanceof Array) ? [] : {};

    for (var i in obj) {
        if (obj.hasOwnProperty(i)) {
            if (obj[i] instanceof Date) {
                newObj[i] = new Date(obj[i].getTime());
            }
            else if (obj[i] instanceof RegExp) {
                var flags = '' + (obj[i].global ? 'g' : '') + (obj[i].ignoreCase ? 'i' : '') + (obj[i].multiline ? 'm' : '') + (obj[i].sticky ? 'y' : '');
                newObj[i] = new RegExp(obj[i].source, flags);
            }
            else {
                newObj[i] = exports.clone(obj[i]);
            }
        }
    }

    return newObj;
};

// Merge all the properties of source into target, source wins in conflic, and by default null and undefined from source are applied

exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) {

    if (source) {
        if (source instanceof Array) {
            if (isMergeArrays !== false) {                                      // Defaults to true
                target = (target || []).concat(source);
            }
            else {
                target = source;
            }
        }
        else {
            target = target || {};

            for (var key in source) {
                if (source.hasOwnProperty(key)) {
                    var value = source[key];

                    if (value &&
                        typeof value === 'object') {

                        if (value instanceof Date) {
                            target[key] = new Date(value.getTime());
                        }
                        else if (value instanceof RegExp) {
                            var flags = '' + (value.global ? 'g' : '') + (value.ignoreCase ? 'i' : '') + (value.multiline ? 'm' : '') + (value.sticky ? 'y' : '');
                            target[key] = new RegExp(value.source, flags);
                        }
                        else {
                            target[key] = exports.merge(target[key], source[key], isNullOverride, isMergeArrays);
                        }
                    }
                    else {
                        if (value !== null && value !== undefined) {            // Explicit to preserve empty strings
                            target[key] = value;
                        }
                        else if (isNullOverride !== false) {                    // Defaults to true
                            target[key] = value;
                        }
                    }
                }
            }
        }
    }

    return target;
};

// Apply options to a copy of the defaults

exports.applyToDefaults = function (defaults, options) {

    if (!options) {                                                 // If no options, return null
        return null;
    }

    var copy = exports.clone(defaults);

    if (options === true) {                                         // If options is set to true, use defaults
        return copy;
    }

    return exports.merge(copy, options, false, false);
};

// Remove duplicate items from array

exports.unique = function (array, key) {

    var index = {};
    var result = [];

    for (var i = 0, il = array.length; i < il; ++i) {
        var id = (key ? array[i][key] : array[i]);
        if (index[id] !== true) {

            result.push(array[i]);
            index[id] = true;
        }
    }

    return result;
};

// Convert array into object

exports.mapToObject = function (array, key) {

    if (!array) {
        return null;
    }

    var obj = {};
    for (var i = 0, il = array.length; i < il; ++i) {
        if (key) {
            if (array[i][key]) {
                obj[array[i][key]] = true;
            }
        }
        else {
            obj[array[i]] = true;
        }
    }

    return obj;
};

// Find the common unique items in two arrays

exports.intersect = function (array1, array2) {

    if (!array1 || !array2) {
        return [];
    }

    var common = [];
    var hash = exports.mapToObject(array1);
    var found = {};
    for (var i = 0, il = array2.length; i < il; ++i) {
        if (hash[array2[i]] && !found[array2[i]]) {
            common.push(array2[i]);
            found[array2[i]] = true;
        }
    }

    return common;
};

// Find which keys are present

exports.matchKeys = function (obj, keys) {

    var matched = [];
    for (var i = 0, il = keys.length; i < il; ++i) {
        if (obj.hasOwnProperty(keys[i])) {
            matched.push(keys[i]);
        }
    }
    return matched;
};

// Flatten array

exports.flatten = function (array, target) {

    var result = target || [];

    for (var i = 0, il = array.length; i < il; ++i) {
        if (Array.isArray(array[i])) {
            exports.flatten(array[i], result);
        }
        else {
            result.push(array[i]);
        }
    }

    return result;
};

// Remove keys

exports.removeKeys = function (object, keys) {

    for (var i = 0, il = keys.length; i < il; i++) {
        delete object[keys[i]];
    }
};

// Convert an object key chain string ('a.b.c') to reference (object[a][b][c])

exports.reach = function (obj, chain) {

    var path = chain.split('.');
    var ref = obj;
    path.forEach(function (level) {

        if (ref) {
            ref = ref[level];
        }
    });

    return ref;
};

// Inherits a selected set of methods from an object, wrapping functions in asynchronous syntax and catching errors

exports.inheritAsync = function (self, obj, keys) {

    keys = keys || null;

    for (var i in obj) {
        if (obj.hasOwnProperty(i)) {
            if (keys instanceof Array &&
                keys.indexOf(i) < 0) {

                continue;
            }

            self.prototype[i] = (function (fn) {

                return function (callback) {

                    var result = null;
                    try {
                        result = fn();
                    }
                    catch (err) {
                        return callback(err);
                    }

                    return callback(null, result);
                };
            })(obj[i]);
        }
    }
};

exports.callStack = function (slice) {

    // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi

    var v8 = Error.prepareStackTrace;
    Error.prepareStackTrace = function (err, stack) {

        return stack;
    };

    var capture = {};
    Error.captureStackTrace(capture, arguments.callee);
    var stack = capture.stack;

    Error.prepareStackTrace = v8;

    var trace = [];
    stack.forEach(function (item) {

        trace.push([item.getFileName(), item.getLineNumber(), item.getColumnNumber(), item.getFunctionName(), item.isConstructor()]);
    });

    if (slice) {
        return trace.slice(slice);
    }

    return trace;
};

exports.displayStack = function (slice) {

    var trace = exports.callStack(slice === undefined ? 1 : slice + 1);
    var display = [];
    trace.forEach(function (row) {

        display.push((row[4] ? 'new ' : '') + row[3] + ' (' + row[0] + ':' + row[1] + ':' + row[2] + ')');
    });

    return display;
};

exports.abort = function (message) {

    if (process.env.NODE_ENV === 'test') {
        throw new Error(message || 'Unknown error');
    }
    else {
        console.log('ABORT: ' + message + '\n\t' + exports.displayStack(1).join('\n\t'));
        process.exit(1);
    }
};

exports.assert = function (condition, message) {

    if (!condition) {
        exports.abort(message);
    }
};

exports.loadDirModules = function (path, excludeFiles, target) {      // target(filename, name, capName)

    var exclude = {};
    for (var i = 0, il = excludeFiles.length; i < il; ++i) {
        exclude[excludeFiles[i] + '.js'] = true;
    }

    Fs.readdirSync(path).forEach(function (filename) {

        if (/\.js$/.test(filename) &&
            !exclude[filename]) {

            var name = filename.substr(0, filename.lastIndexOf('.'));
            var capName = name.charAt(0).toUpperCase() + name.substr(1).toLowerCase();

            if (typeof target !== 'function') {
                target[capName] = require(path + '/' + name);
            }
            else {
                target(path + '/' + name, name, capName);
            }
        }
    });
};

exports.rename = function (obj, from, to) {

    obj[to] = obj[from];
    delete obj[from];
};

exports.Timer = function () {

    this.reset();
};

exports.Timer.prototype.reset = function () {

    this.ts = Date.now();
};

exports.Timer.prototype.elapsed = function () {

    return Date.now() - this.ts;
};

// Load and parse package.json process root or given directory

exports.loadPackage = function (dir) {

    var result = {};
    var filepath = (dir || process.env.PWD) + '/package.json';
    if (Fs.existsSync(filepath)) {
        try {
            result = JSON.parse(Fs.readFileSync(filepath));
        }
        catch (e) { }
    }

    return result;
};

// Escape string for Regex construction

exports.escapeRegex = function (string) {

    // Escape ^$.*+-?=!:|\/()[]{},
    return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&');
};

// Return an error as first argument of a callback

exports.toss = function (condition /*, [message], callback */) {

    var message = (arguments.length === 3 ? arguments[1] : '');
    var callback = (arguments.length === 3 ? arguments[2] : arguments[1]);

    var err = (message instanceof Error ? message : (message ? new Error(message) : (condition instanceof Error ? condition : new Error())));

    if (condition instanceof Error ||
        !condition) {

        return callback(err);
    }
};

// Base64url (RFC 4648) encode

exports.base64urlEncode = function (value) {

    return (new Buffer(value, 'binary')).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
};

// Base64url (RFC 4648) decode

exports.base64urlDecode = function (encoded) {

    if (encoded &&
        !encoded.match(/^[\w\-]*$/)) {

        return new Error('Invalid character');
    }

    try {
        return (new Buffer(encoded.replace(/-/g, '+').replace(/:/g, '/'), 'base64')).toString('binary');
    }
    catch (err) {
        return err;
    }
};

// Escape attribute value for use in HTTP header

exports.escapeHeaderAttribute = function (attribute) {

    return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"');
};

// Escape string for inclusion in HTML

internals.htmlEscaped = {
    '&': '&',
    '<': '<',
    '>': '>',
    '"': '"',
    "'": ''',
    '`': '`'
};

exports.escapeHtml = function (string) {

    if (!string) {
        return '';
    }

    if (/[&<>"'`]/.test(string) === false) {
        return string;
    }

    return string.replace(/[&<>"'`]/g, function (chr) {

        return internals.htmlEscaped[chr];
    });
};


[![build status](https://secure.travis-ci.org/jprichardson/node-fs-extra.png)](http://travis-ci.org/jprichardson/node-fs-extra)

Node.js: fs-extra
=================

This module adds a few extra file system methods that aren't included in the native `fs` module. It is a drop in replacement for `fs`.

Why?
----

I got tired of including `mkdirp`, `rimraf`, and `cp -r` in most of my projects.

Installation
------------

    npm install fs-extra

Usage
-----

```javascript
var fs = require('fs-extra');
```

Methods
-------

**NOTE:** You can still use the native Node.js methods. They are copied over to `fs-extra`.

### copy(src, dest, callback)

Copy a file or directory. The directory can have contents. Like `cp -r`. There isn't a synchronous version implemented yet.

Sync: (none)

Examples:

```javascript
var fs = require('fs-extra');

fs.copy('/tmp/myfile', '/tmp/mynewfile', function(err){
  if (err) {
    console.error(err);
  }
  else {
    console.log("success!")
  }
}); //copies file

fs.copy('/tmp/mydir', '/tmp/mynewdir', function(err){
  if (err) {
    console.error(err);
  }
  else {
    console.log("success!")
  }
}); //copies directory, even if it has subdirectories or files
```

### createFile(file, callback)

Creates a file. If the file that is requested to be created is in directories that do not exist, these directories are created. If the file already exists, it is **NOT MODIFIED**.

Sync: `createFileSync()`

Example:

```javascript
var fs = require('fs-extra')
  , file = '/tmp/this/path/does/not/exist/file.txt'

fs.createFile(file, function(err) {
  console.log(err); //null

  //file has now been created, including the directory it is to be placed in
})
```

### exists() / existsSync()

These methods are actually from `path` in v0.6. But in Node v0.8 they are moved from `path` to `fs`. So you can use this module to help make your modules v0.6 and v0.8 compatible.

### mkdirs(dir, callback)

Creates a directory. If the parent hierarchy doesn't exist, it's created. Like `mkdir -p`.

Alias: `mkdirp()`

Sync: `mkdirsSync()` / `mkdirpSync()`

Examples:

```javascript
var fs = require('fs-extra');

fs.mkdirs('/tmp/some/long/path/that/prob/doesnt/exist', function(err){
  if (err) {
    console.error(err);
  }
  else {
    console.log("success!")
  }
});

fs.mkdirsSync('/tmp/another/path');
```

### outputFile(file, data, callback)

Almost the same as `writeFile`, except that if the directory does not exist, it's created.

Sync: `outputFileSync()`

Example:

```javascript
var fs = require('fs-extra')
  , file = '/tmp/this/path/does/not/exist/file.txt'

fs.outputFile(file, 'hello!' function(err) {
  console.log(err); //null

  fs.readFile(file, 'utf8', function(err, data) {
    console.log(data); //hello!
  })
})
```

### readJson(file, callback)

Reads a JSON file and then parses it into an object.

Alias: `readJSON()`

Sync: `readJsonSync()`, `readJSONSync()`

Example:

```javascript
var fs = require('fs-extra');

fs.readJson('./package.json', function(err, packageObj) {
  console.log(packageObj.version); //0.1.3
});
```

### remove(dir, callback)

Removes a file or directory. The directory can have contents. Like `rm -rf`.

Alias: `delete()`

Sync: `removeSync()` / `deleteSync()`

Examples:

```javascript
var fs = require('fs-extra');

fs.remove('/tmp/myfile', function(err){
  if (err) {
    console.error(err);
  }
  else {
    console.log("success!")
  }
});

fs.removeSync('/home/jprichardson'); //I just deleted my entire HOME directory.
```

### writeJson(file, object, callback)

Writes an object to a JSON file.

Alias: `writeJSON()`

Sync: `writeJsonSync()`, `writeJSONSync()`

Example:

```javascript
var fs = require('fs-extra');
fs.writeJson('./package.json', {name: 'fs-extra'}, function(err){
  console.log(err);
});
```

Roadmap
-------

This contains items that I'm considering doing. I'd love community feedback.

* File system walker. I really like this one: https://github.com/daaku/nodejs-walker
* File/directory tree watcher. There are quite a few.
* Method to move files.
* Copy sync.
* Thinking about moving `rimraf`, `ncp`, and `mkdirps` code into this library. I'd like fs-extra to be a stable library that module authors
can depend upon. A bunch of other dependencies kinda sucks for modules/libraries.
* Change documentation to use the `fse` prefix instead of `fs`. This may encourage people to start using `fse` as a prefix and hence make their code clearer that they're not using the native `fs`. I'm very undecided on this one since `fs-extra` is a drop in replacement for the native `fs`.

Naming
------

I put a lot of thought into the naming of these function. Inspired by @coolaj86's request. So he deserves much of the credit for raising the issue. See discussion(s) here:

* https://github.com/jprichardson/node-fs-extra/issues/2
* https://github.com/flatiron/utile/issues/11
* https://github.com/ryanmcgrath/wrench-js/issues/29
* https://github.com/substack/node-mkdirp/issues/17

First, I believe that in as many cases as possible, the [Node.js naming schemes](http://nodejs.org/api/fs.html) should be chosen. However, there are problems with the Node.js own naming schemes.

For example, `fs.readFile()` and `fs.readdir()`: the **F** is capitalized in *File* and the **d** is not capitalized in *dir*. Perhaps a bit pedantic, but they should still be consistent. Also, Node.js has chosen a lot of POSIX naming schemes, which I believe is great. See: `fs.mkdir()`, `fs.rmdir()`, `fs.chown()`, etc.

We have a dilemma though. How do you consistently name methods that perform the following POSIX commands: `cp`, `cp -r`, `mkdir -p`, and `rm -rf`?

My perspective: when in doubt, err on the side of simplicity. Consider that for a moment. A directory is just a hierarchical grouping of directories and files. So when you want to copy it or remove it, in most cases you'll want to copy or remove all of its contents. When you want to create a directory, if the directory that it's suppose to be contained in does not exist, then in most cases you'll want to create that too.

So, if you want to remove a file or a directory regardless of whether it has contents, just call `fs.remove(path)` or its alias `fs.delete(path)`. If you want to copy a file or a directory whether it has contents, just call `fs.copy(source, destination)`. If you want to create a directory regardless of whether its parent directories exist, just call `fs.mkdirs(path)` or `fs.mkdirp(path)`.

Author
------

`node-fs-extra` was written by [JP Richardson][aboutjp]. You should follow him on Twitter [@jprichardson][twitter]. Also read his coding blog [Procbits][procbits]. If you write software with others, you should checkout [Gitpilot][gitpilot] to make collaboration with Git simple.

License
-------

Licensed under MIT

Copyright (c) 2011-2013 JP Richardson

[1]: http://nodejs.org/docs/latest/api/fs.html

[jsonfile]: https://github.com/jprichardson/node-jsonfile

[aboutjp]: http://about.me/jprichardson
[twitter]: http://twitter.com/jprichardson
[procbits]: http://procbits.com
[gitpilot]: http://gitpilot.com


<!DOCTYPE html>

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

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

	Maximum price:
	<input type="text" id="amount" style="border:0; color:#f6931f; font-weight:bold;" />

Fix the minimum value of the range slider so that the user can only select a maximum.  Set the range option to "min."