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

/*
 * Throughout this file, the term "outer scope" is used to refer to the outer-
 * most/global/root Scope objects for particular file. The term "inner scope"
 * is used to refer to a Scope object that is reachable via the child relation
 * from an outer scope.
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, brackets, $, Worker, setTimeout */

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

    var _ = brackets.getModule("thirdparty/lodash");

    var CodeMirror          = brackets.getModule("thirdparty/CodeMirror2/lib/codemirror"),
        DefaultDialogs      = brackets.getModule("widgets/DefaultDialogs"),
        Dialogs             = brackets.getModule("widgets/Dialogs"),
        DocumentManager     = brackets.getModule("document/DocumentManager"),
        EditorManager       = brackets.getModule("editor/EditorManager"),
        ExtensionUtils      = brackets.getModule("utils/ExtensionUtils"),
        FileSystem          = brackets.getModule("filesystem/FileSystem"),
        FileUtils           = brackets.getModule("file/FileUtils"),
        LanguageManager     = brackets.getModule("language/LanguageManager"),
        PreferencesManager  = brackets.getModule("preferences/PreferencesManager"),
        ProjectManager      = brackets.getModule("project/ProjectManager"),
        Strings             = brackets.getModule("strings"),
        StringUtils         = brackets.getModule("utils/StringUtils");

    var HintUtils           = require("HintUtils"),
        MessageIds          = require("MessageIds"),
        Preferences         = require("Preferences");

    var ternEnvironment     = [],
        pendingTernRequests = {},
        builtinFiles        = ["ecma5.json", "browser.json", "jquery.json"],
        builtinLibraryNames = [],
        isDocumentDirty     = false,
        _hintCount          = 0,
        currentWorker       = null,
        documentChanges     = null,     // bounds of document changes
        preferences         = null,
        deferredPreferences = null;

    var MAX_HINTS           = 30,  // how often to reset the tern server
        LARGE_LINE_CHANGE   = 100,
        LARGE_LINE_COUNT    = 10000,
        OFFSET_ZERO         = {line: 0, ch: 0};

    var config = {};

    /**
     *  An array of library names that contain JavaScript builtins definitions.
     *
     * @return {Array.<string>} - array of library  names.
     */
    function getBuiltins() {
        return builtinLibraryNames;
    }

    /**
     * Read in the json files that have type information for the builtins, dom,etc
     */
    function initTernEnv() {
        var path = ExtensionUtils.getModulePath(module, "thirdparty/tern/defs/"),
            files = builtinFiles,
            library;

        files.forEach(function (i) {
            FileSystem.resolve(path + i, function (err, file) {
                if (!err) {
                    FileUtils.readAsText(file).done(function (text) {
                        library = JSON.parse(text);
                        builtinLibraryNames.push(library["!name"]);
                        ternEnvironment.push(library);
                    }).fail(function (error) {
                        console.log("failed to read tern config file " + i);
                    });
                } else {
                    console.log("failed to read tern config file " + i);
                }
            });
        });
    }

    initTernEnv();

    /**
     *  Init preferences from a file in the project root or builtin
     *  defaults if no file is found;
     *
     *  @param {string=} projectRootPath - new project root path. Only needed
     *  for unit tests.
     */
    function initPreferences(projectRootPath) {

        // Reject the old preferences if they have not completed.
        if (deferredPreferences && deferredPreferences.state() === "pending") {
            deferredPreferences.reject();
        }

        deferredPreferences = $.Deferred();
        var pr = ProjectManager.getProjectRoot();

        // Open preferences relative to the project root
        // Normally there is a project root, but for unit tests we need to
        // pass in a project root.
        if (pr) {
            projectRootPath = pr.fullPath;
        } else if (!projectRootPath) {
            console.log("initPreferences: projectRootPath has no value");
        }

        var path = projectRootPath + Preferences.FILE_NAME;

        FileSystem.resolve(path, function (err, file) {
            if (!err) {
                FileUtils.readAsText(file).done(function (text) {
                    var configObj = null;
                    try {
                        configObj = JSON.parse(text);
                    } catch (e) {
                        // continue with null configObj which will result in
                        // default settings.
                        console.log("Error parsing preference file: " + path);
                        if (e instanceof SyntaxError) {
                            console.log(e.message);
                        }
                    }
                    preferences = new Preferences(configObj);
                    deferredPreferences.resolve();
                }).fail(function (error) {
                    preferences = new Preferences();
                    deferredPreferences.resolve();
                });
            } else {
                preferences = new Preferences();
                deferredPreferences.resolve();
            }
        });
    }

    /**
     * Will initialize preferences only if they do not exist.
     *
     */
    function ensurePreferences() {
        if (!deferredPreferences) {
            initPreferences();
        }
    }

    /**
     * Send a message to the tern worker - if the worker is being initialized,
     * the message will not be posted until initialization is complete
     */
    function postMessage(msg) {
        if (currentWorker) {
            currentWorker.postMessage(msg);
        }
    }

    /**
     * Test if the directory should be excluded from analysis.
     *
     * @param {!string} path - full directory path.
     * @return {boolean} true if excluded, false otherwise.
     */
    function isDirectoryExcluded(path) {
        var excludes = preferences.getExcludedDirectories();

        if (!excludes) {
            return false;
        }

        var testPath = ProjectManager.makeProjectRelativeIfPossible(path);
        testPath = FileUtils.stripTrailingSlash(testPath);

        return excludes.test(testPath);
    }

    /**
     * Test if the file path is in current editor
     *
     * @param {string} filePath file path to test for exclusion.
     * @return {boolean} true if in editor, false otherwise.
     */
    function isFileBeingEdited(filePath) {
        var currentEditor   = EditorManager.getActiveEditor(),
            currentDoc      = currentEditor && currentEditor.document;

        return (currentDoc && currentDoc.file.fullPath === filePath);
    }

    /**
     * Test if the file path is an internal exclusion.
     *
     * @param {string} path file path to test for exclusion.
     * @return {boolean} true if excluded, false otherwise.
     */
    function isFileExcludedInternal(path) {
        // The detectedExclusions are files detected to be troublesome with current versions of Tern.
        // detectedExclusions is an array of full paths.
        var detectedExclusions = PreferencesManager.get("jscodehints.detectedExclusions") || [];
        if (detectedExclusions && detectedExclusions.indexOf(path) !== -1) {
            return true;
        }

        return false;
    }

    /**
     * Test if the file should be excluded from analysis.
     *
     * @param {!File} file - file to test for exclusion.
     * @return {boolean} true if excluded, false otherwise.
     */
    function isFileExcluded(file) {
        if (file.name[0] === ".") {
            return true;
        }

        var languageID = LanguageManager.getLanguageForPath(file.fullPath).getId();
        if (languageID !== HintUtils.LANGUAGE_ID) {
            return true;
        }

        var excludes = preferences.getExcludedFiles();
        if (excludes && excludes.test(file.name)) {
            return true;
        }

        if (isFileExcludedInternal(file.fullPath)) {
            return true;
        }

        return false;
    }

    /**
     * Add a pending request waiting for the tern-worker to complete.
     * If file is a detected exclusion, then reject request.
     *
     * @param {string} file - the name of the file
     * @param {{line: number, ch: number}} offset - the offset into the file the request is for
     * @param {string} type - the type of request
     * @return {jQuery.Promise} - the promise for the request
     */
    function addPendingRequest(file, offset, type) {
        var requests,
            key = file + "@" + offset.line + "@" + offset.ch,
            $deferredRequest;

        // Reject detected exclusions
        if (isFileExcludedInternal(file)) {
            return (new $.Deferred()).reject().promise();
        }

        if (_.has(pendingTernRequests, key)) {
            requests = pendingTernRequests[key];
        } else {
            requests = {};
            pendingTernRequests[key] = requests;
        }

        if (_.has(requests, type)) {
            $deferredRequest = requests[type];
        } else {
            requests[type] = $deferredRequest = new $.Deferred();
        }
        return $deferredRequest.promise();
    }

    /**
     * Get any pending $.Deferred object waiting on the specified file and request type
     * @param {string} file - the file
     * @param {{line: number, ch: number}} offset - the offset into the file the request is for
     * @param {string} type - the type of request
     * @return {jQuery.Deferred} - the $.Deferred for the request
     */
    function getPendingRequest(file, offset, type) {
        var key = file + "@" + offset.line + "@" + offset.ch;
        if (_.has(pendingTernRequests, key)) {
            var requests = pendingTernRequests[key],
                requestType = requests[type];

            delete pendingTernRequests[key][type];

            if (!Object.keys(requests).length) {
                delete pendingTernRequests[key];
            }

            return requestType;
        }
    }

    /**
     * @param {string} file a relative path
     * @return {string} returns the path we resolved when we tried to parse the file, or undefined
     */
    function getResolvedPath(file) {
        return currentWorker.getResolvedPath(file);
    }

    /**
     * Get a Promise for the definition from TernJS, for the file & offset passed in.
     * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo
     * - type of update, name of file, and the text of the update.
     * For "full" updates, the whole text of the file is present. For "part" updates,
     * the changed portion of the text. For "empty" updates, the file has not been modified
     * and the text is empty.
     * @param {{line: number, ch: number}} offset - the offset in the file the hints should be calculate at
     * @return {jQuery.Promise} - a promise that will resolve to definition when
     *      it is done
     */
    function getJumptoDef(fileInfo, offset) {
        postMessage({
            type: MessageIds.TERN_JUMPTODEF_MSG,
            fileInfo: fileInfo,
            offset: offset
        });

        return addPendingRequest(fileInfo.name, offset, MessageIds.TERN_JUMPTODEF_MSG);
    }

    /**
     * check to see if the text we are sending to Tern is too long.
     * @param {string} the text to check
     * @return {string} the text, or the empty text if the original was too long
     */
    function filterText(text) {
        var newText = text;
        if (text.length > preferences.getMaxFileSize()) {
            newText = "";
        }
        return newText;
    }

    /**
     * Get the text of a document, applying any size restrictions
     * if necessary
     * @param {Document} document - the document to get the text from
     * @return {string} the text, or the empty text if the original was too long
     */
    function getTextFromDocument(document) {
        var text = document.getText();
        text = filterText(text);
        return text;
    }

    /**
     * Request Jump-To-Definition from Tern.
     *
     * @param {session} session - the session
     * @param {Document} document - the document
     * @param {{line: number, ch: number}} offset - the offset into the document
     * @return {jQuery.Promise} - The promise will not complete until tern
     *      has completed.
     */
    function requestJumptoDef(session, document, offset) {
        var path    = document.file.fullPath,
            fileInfo = {type: MessageIds.TERN_FILE_INFO_TYPE_FULL,
                name: path,
                offsetLines: 0,
                text: filterText(session.getJavascriptText())};

        var ternPromise = getJumptoDef(fileInfo, offset);

        return {promise: ternPromise};
    }

    /**
     * Handle the response from the tern web worker when
     * it responds with the definition
     *
     * @param response - the response from the worker
     */
    function handleJumptoDef(response) {

        var file = response.file,
            offset = response.offset;

        var $deferredJump = getPendingRequest(file, offset, MessageIds.TERN_JUMPTODEF_MSG);

        if ($deferredJump) {
            response.fullPath = getResolvedPath(response.resultFile);
            $deferredJump.resolveWith(null, [response]);
        }
    }

    /**
     * Get a Promise for the completions from TernJS, for the file & offset passed in.
     *
     * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo
     * - type of update, name of file, and the text of the update.
     * For "full" updates, the whole text of the file is present. For "part" updates,
     * the changed portion of the text. For "empty" updates, the file has not been modified
     * and the text is empty.
     * @param {{line: number, ch: number}} offset - the offset in the file the hints should be calculate at
     * @param {boolean} isProperty - true if getting a property hint,
     * otherwise getting an identifier hint.
     * @return {jQuery.Promise} - a promise that will resolve to an array of completions when
     *      it is done
     */
    function getTernHints(fileInfo, offset, isProperty) {

        /**
         *  If the document is large and we have modified a small portions of it that
         *  we are asking hints for, then send a partial document.
         */
        postMessage({
            type: MessageIds.TERN_COMPLETIONS_MSG,
            fileInfo: fileInfo,
            offset: offset,
            isProperty: isProperty
        });

        return addPendingRequest(fileInfo.name, offset, MessageIds.TERN_COMPLETIONS_MSG);
    }

    /**
     * Get a Promise for the function type from TernJS.
     * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo
     * - type of update, name of file, and the text of the update.
     * For "full" updates, the whole text of the file is present. For "part" updates,
     * the changed portion of the text. For "empty" updates, the file has not been modified
     * and the text is empty.
     * @param {{line:number, ch:number}} offset - the line, column info for what we want the function type of.
     * @return {jQuery.Promise} - a promise that will resolve to the function type of the function being called.
     */
    function getTernFunctionType(fileInfo, offset) {
        postMessage({
            type: MessageIds.TERN_CALLED_FUNC_TYPE_MSG,
            fileInfo: fileInfo,
            offset: offset
        });

        return addPendingRequest(fileInfo.name, offset, MessageIds.TERN_CALLED_FUNC_TYPE_MSG);
    }

    /**
     *  Given a starting and ending position, get a code fragment that is self contained
     *  enough to be compiled.
     *
     * @param {!Session} session - the current session
     * @param {{line: number, ch: number}} start - the starting position of the changes
     * @return {{type: string, name: string, offsetLines: number, text: string}}
     */
    function getFragmentAround(session, start) {
        var minIndent = null,
            minLine   = null,
            endLine,
            cm        = session.editor._codeMirror,
            tabSize   = cm.getOption("tabSize"),
            document  = session.editor.document,
            p,
            min,
            indent,
            line;

        // expand range backwards
        for (p = start.line - 1, min = Math.max(0, p - 100); p >= min; --p) {
            line = session.getLine(p);
            var fn = line.search(/\bfunction\b/);

            if (fn >= 0) {
                indent = CodeMirror.countColumn(line, null, tabSize);
                if (minIndent === null || minIndent > indent) {
                    if (session.getToken({line: p, ch: fn + 1}).type === "keyword") {
                        minIndent = indent;
                        minLine = p;
                    }
                }
            }
        }

        if (minIndent === null) {
            minIndent = 0;
        }

        if (minLine === null) {
            minLine = min;
        }

        var max = Math.min(cm.lastLine(), start.line + 100),
            endCh = 0;

        for (endLine = start.line + 1; endLine < max; ++endLine) {
            line = cm.getLine(endLine);

            if (line.length > 0) {
                indent = CodeMirror.countColumn(line, null, tabSize);
                if (indent <= minIndent) {
                    endCh = line.length;
                    break;
                }
            }
        }

        var from = {line: minLine, ch: 0},
            to   = {line: endLine, ch: endCh};

        return {type: MessageIds.TERN_FILE_INFO_TYPE_PART,
            name: document.file.fullPath,
            offsetLines: from.line,
            text: document.getRange(from, to)};
    }

    /**
     * Get an object that describes what tern needs to know about the updated
     * file to produce a hint. As a side-effect of this calls the document
     * changes are reset.
     *
     * @param {!Session} session - the current session
     * @param {boolean=} preventPartialUpdates - if true, disallow partial updates.
     * Optional, defaults to false.
     * @return {{type: string, name: string, offsetLines: number, text: string}}
     */
    function getFileInfo(session, preventPartialUpdates) {
        var start = session.getCursor(),
            end = start,
            document = session.editor.document,
            path = document.file.fullPath,
            isHtmlFile = LanguageManager.getLanguageForPath(path).getId() === "html",
            result;

        if (isHtmlFile) {
            result = {type: MessageIds.TERN_FILE_INFO_TYPE_FULL,
                name: path,
                text: session.getJavascriptText()};
        } else if (!documentChanges) {
            result = {type: MessageIds.TERN_FILE_INFO_TYPE_EMPTY,
                name: path,
                text: ""};
        } else if (!preventPartialUpdates && session.editor.lineCount() > LARGE_LINE_COUNT &&
                (documentChanges.to - documentChanges.from < LARGE_LINE_CHANGE) &&
                documentChanges.from <= start.line &&
                documentChanges.to > end.line) {
            result = getFragmentAround(session, start);
        } else {
            result = {type: MessageIds.TERN_FILE_INFO_TYPE_FULL,
                name: path,
                text: getTextFromDocument(document)};
        }

        documentChanges = null;
        return result;
    }

    /**
     *  Get the current offset. The offset is adjusted for "part" updates.
     *
     * @param {!Session} session - the current session
     * @param {{type: string, name: string, offsetLines: number, text: string}} fileInfo
     * - type of update, name of file, and the text of the update.
     * For "full" updates, the whole text of the file is present. For "part" updates,
     * the changed portion of the text. For "empty" updates, the file has not been modified
     * and the text is empty.
     * @param {{line: number, ch: number}=} offset - the default offset (optional). Will
     * use the cursor if not provided.
     * @return {{line: number, ch: number}}
     */
    function getOffset(session, fileInfo, offset) {
        var newOffset;

        if (offset) {
            newOffset = {line: offset.line, ch: offset.ch};
        } else {
            newOffset = session.getCursor();
        }

        if (fileInfo.type === MessageIds.TERN_FILE_INFO_TYPE_PART) {
            newOffset.line = Math.max(0, newOffset.line - fileInfo.offsetLines);
        }

        return newOffset;
    }

    /**
     * Get a Promise for all of the known properties from TernJS, for the directory and file.
     * The properties will be used as guesses in tern.
     * @param {Session} session - the active hinting session
     * @param {Document} document - the document for which scope info is
     *      desired
     * @return {jQuery.Promise} - The promise will not complete until the tern
     *      request has completed.
     */
    function requestGuesses(session, document) {
        var $deferred = $.Deferred(),
            fileInfo = getFileInfo(session),
            offset = getOffset(session, fileInfo);

        postMessage({
            type: MessageIds.TERN_GET_GUESSES_MSG,
            fileInfo: fileInfo,
            offset: offset
        });

        var promise = addPendingRequest(fileInfo.name, offset, MessageIds.TERN_GET_GUESSES_MSG);
        promise.done(function (guesses) {
            session.setGuesses(guesses);
            $deferred.resolve();
        }).fail(function () {
            $deferred.reject();
        });

        return $deferred.promise();
    }

    /**
     * Handle the response from the tern web worker when
     * it responds with the list of completions
     *
     * @param {{file: string, offset: {line: number, ch: number}, completions:Array.<string>,
     *          properties:Array.<string>}} response - the response from the worker
     */
    function handleTernCompletions(response) {

        var file = response.file,
            offset = response.offset,
            completions = response.completions,
            properties = response.properties,
            fnType  = response.fnType,
            type = response.type,
            error = response.error,
            $deferredHints = getPendingRequest(file, offset, type);

        if ($deferredHints) {
            if (error) {
                $deferredHints.reject();
            } else if (completions) {
                $deferredHints.resolveWith(null, [{completions: completions}]);
            } else if (properties) {
                $deferredHints.resolveWith(null, [{properties: properties}]);
            } else if (fnType) {
                $deferredHints.resolveWith(null, [fnType]);
            }
        }
    }

    /**
     * Handle the response from the tern web worker when
     * it responds to the get guesses message.
     *
     * @param {{file: string, type: string, offset: {line: number, ch: number},
     *      properties: Array.<string>}} response -
     *      the response from the worker contains the guesses for a
     *      property lookup.
     */
    function handleGetGuesses(response) {
        var path = response.file,
            type = response.type,
            offset = response.offset,
            $deferredHints = getPendingRequest(path, offset, type);

        if ($deferredHints) {
            $deferredHints.resolveWith(null, [response.properties]);
        }
    }

    /**
     * Handle the response from the tern web worker when
     * it responds to the update file message.
     *
     * @param {{path: string, type: string}} response - the response from the worker
     */
    function handleUpdateFile(response) {

        var path = response.path,
            type = response.type,
            $deferredHints = getPendingRequest(path, OFFSET_ZERO, type);

        if ($deferredHints) {
            $deferredHints.resolve();
        }
    }

    /**
     * Handle timed out inference
     *
     * @param {{path: string, type: string}} response - the response from the worker
     */
    function handleTimedOut(response) {

        var detectedExclusions  = PreferencesManager.get("jscodehints.detectedExclusions") || [],
            filePath            = response.file;

        // Don't exclude the file currently being edited
        if (isFileBeingEdited(filePath)) {
            return;
        }

        // Handle file that is already excluded
        if (detectedExclusions.indexOf(filePath) !== -1) {
            console.log("JavaScriptCodeHints.handleTimedOut: file already in detectedExclusions array timed out: " + filePath);
            return;
        }

        // Save detected exclusion in project prefs so no further time is wasted on it
        detectedExclusions.push(filePath);
        PreferencesManager.set("jscodehints.detectedExclusions", detectedExclusions, { location: { scope: "project" } });

        // Show informational dialog
        Dialogs.showModalDialog(
            DefaultDialogs.DIALOG_ID_INFO,
            Strings.DETECTED_EXCLUSION_TITLE,
            StringUtils.format(
                Strings.DETECTED_EXCLUSION_INFO,
                StringUtils.breakableUrl(filePath)
            ),
            [
                {
                    className : Dialogs.DIALOG_BTN_CLASS_PRIMARY,
                    id        : Dialogs.DIALOG_BTN_OK,
                    text      : Strings.OK
                }
            ]
        );
    }

    /**
     * Encapsulate all the logic to talk to the worker thread.  This will create
     * a new instance of a TernWorker, which the rest of the hinting code can use to talk
     * to the worker, without worrying about initialization, priming the pump, etc.
     *
     */
    function TernWorker() {
        var ternPromise         = null,
            addFilesPromise     = null,
            rootTernDir         = null,
            projectRoot         = null,
            stopAddingFiles     = false,
            resolvedFiles       = {},       // file -> resolved file
            numInitialFiles     = 0,
            numResolvedFiles    = 0,
            numAddedFiles       = 0,
            _ternWorker         = null;

        /**
         * @param {string} file a relative path
         * @return {string} returns the path we resolved when we tried to parse the file, or undefined
         */
        function getResolvedPath(file) {
            return resolvedFiles[file];
        }

        /**
         *  Determine whether the current set of files are using modules to pull in
         *  additional files.
         *
         * @return {boolean} - true if more files than the current directory have
         * been read in.
         */
        function usingModules() {
            return numInitialFiles !== numResolvedFiles;
        }

        /**
         * Send a message to the tern worker - if the worker is being initialized,
         * the message will not be posted until initialization is complete
         */
        function postMessage(msg) {
            addFilesPromise.done(function (ternWorker) {
                // If an error came up during file handling, bail out now
                if (!ternWorker) {
                    return;
                }

                if (config.debug) {
                    console.debug("Sending message", msg);
                }
                ternWorker.postMessage(msg);
            });
        }

        /**
         * Send a message to the tern worker - this is only for messages that
         * need to be sent before and while the addFilesPromise is being resolved.
         */
        function _postMessageByPass(msg) {
            ternPromise.done(function (ternWorker) {
                if (config.debug) {
                    console.debug("Sending message", msg);
                }
                ternWorker.postMessage(msg);
            });
        }

        /**
         *  Update tern with the new contents of a given file.
         *
         * @param {Document} document - the document to update
         * @return {jQuery.Promise} - the promise for the request
         */
        function updateTernFile(document) {
            var path  = document.file.fullPath;

            _postMessageByPass({
                type       : MessageIds.TERN_UPDATE_FILE_MSG,
                path       : path,
                text       : getTextFromDocument(document)
            });

            return addPendingRequest(path, OFFSET_ZERO, MessageIds.TERN_UPDATE_FILE_MSG);
        }

        /**
         * Handle a request from the worker for text of a file
         *
         * @param {{file:string}} request - the request from the worker.  Should be an Object containing the name
         *      of the file tern wants the contents of
         */
        function handleTernGetFile(request) {

            function replyWith(name, txt) {
                _postMessageByPass({
                    type: MessageIds.TERN_GET_FILE_MSG,
                    file: name,
                    text: txt
                });
            }

            var name = request.file;

            /**
             * Helper function to get the text of a given document and send it to tern.
             * If DocumentManager successfully gets the file's text then we'll send it to the tern worker.
             * The Promise for getDocumentText() is returned so that custom fail functions can be used.
             *
             * @param {string} filePath - the path of the file to get the text of
             * @return {jQuery.Promise} - the Promise returned from DocumentMangaer.getDocumentText()
             */
            function getDocText(filePath) {
                if (!FileSystem.isAbsolutePath(filePath)) {
                    return (new $.Deferred()).reject().promise();
                }

                var file = FileSystem.getFileForPath(filePath),
                    promise = DocumentManager.getDocumentText(file);

                promise.done(function (docText) {
                    resolvedFiles[name] = filePath;
                    numResolvedFiles++;
                    replyWith(name, filterText(docText));
                });
                return promise;
            }

            /**
             * Helper function to find any files in the project that end with the
             * name we are looking for.  This is so we can find requirejs modules
             * when the baseUrl is unknown, or when the project root is not the same
             * as the script root (e.g. if you open the 'brackets' dir instead of 'brackets/src' dir).
             */
            function findNameInProject() {
                // check for any files in project that end with the right path.
                var fileName = name.substring(name.lastIndexOf("/") + 1);

                function _fileFilter(entry) {
                    return entry.name === fileName;
                }

                ProjectManager.getAllFiles(_fileFilter).done(function (files) {
                    var file;
                    files = files.filter(function (file) {
                        var pos = file.fullPath.length - name.length;
                        return pos === file.fullPath.lastIndexOf(name);
                    });

                    if (files.length === 1) {
                        file = files[0];
                    }
                    if (file) {
                        getDocText(file.fullPath).fail(function () {
                            replyWith(name, "");
                        });
                    } else {
                        replyWith(name, "");
                    }
                });
            }

            if (!isFileExcludedInternal(name)) {
                getDocText(name).fail(function () {
                    getDocText(rootTernDir + name).fail(function () {
                        // check relative to project root
                        getDocText(projectRoot + name)
                            // last look for any files that end with the right path
                            // in the project
                            .fail(findNameInProject);
                    });
                });
            }
        }

        /**
         *  Prime the pump for a fast first lookup.
         *
         * @param {string} path - full path of file
         * @return {jQuery.Promise} - the promise for the request
         */
        function primePump(path) {
            _postMessageByPass({
                type        : MessageIds.TERN_PRIME_PUMP_MSG,
                path        : path
            });

            return addPendingRequest(path, OFFSET_ZERO, MessageIds.TERN_PRIME_PUMP_MSG);
        }

        /**
         * Handle the response from the tern web worker when
         * it responds to the prime pump message.
         *
         * @param {{path: string, type: string}} response - the response from the worker
         */
        function handlePrimePumpCompletion(response) {

            var path = response.path,
                type = response.type,
                $deferredHints = getPendingRequest(path, OFFSET_ZERO, type);

            if ($deferredHints) {
                $deferredHints.resolve();
            }
        }

        /**
         *  Add new files to tern, keeping any previous files.
         *  The tern server must be initialized before making
         *  this call.
         *
         * @param {Array.<string>} files - array of file to add to tern.
         * @return {boolean} - true if more files may be added, false if maximum has been reached.
         */
        function addFilesToTern(files) {
            // limit the number of files added to tern.
            var maxFileCount = preferences.getMaxFileCount();
            if (numResolvedFiles + numAddedFiles < maxFileCount) {
                var available = maxFileCount - numResolvedFiles - numAddedFiles;

                if (available < files.length) {
                    files = files.slice(0, available);
                }

                numAddedFiles += files.length;
                ternPromise.done(function (worker) {
                    var msg = {
                        type        : MessageIds.TERN_ADD_FILES_MSG,
                        files       : files
                    };

                    if (config.debug) {
                        console.debug("Sending message", msg);
                    }
                    worker.postMessage(msg);
                });

            } else {
                stopAddingFiles = true;
            }

            return stopAddingFiles;
        }

        /**
         *  Add the files in the directory and subdirectories of a given directory
         *  to tern.
         *
         * @param {string} dir - the root directory to add.
         * @param {function ()} doneCallback - called when all files have been
         * added to tern.
         */
        function addAllFilesAndSubdirectories(dir, doneCallback) {
            FileSystem.resolve(dir, function (err, directory) {
                function visitor(entry) {
                    if (entry.isFile) {
                        if (!isFileExcluded(entry)) { // ignore .dotfiles and non-.js files
                            addFilesToTern([entry.fullPath]);
                        }
                    } else {
                        return !isDirectoryExcluded(entry.fullPath) &&
                            entry.name.indexOf(".") !== 0 &&
                            !stopAddingFiles;
                    }
                }

                if (err) {
                    return;
                }

                if (dir === FileSystem.getDirectoryForPath(rootTernDir)) {
                    doneCallback();
                    return;
                }

                directory.visit(visitor, doneCallback);
            });
        }

        /**
         * Init the web worker that does all the code hinting work.
         *
         * If a worker already exists, then this will terminate that worker and
         * start a new worker - this helps alleviate leaks that may be ocurring in
         * the code that the worker runs.
         */
        function initTernWorker() {
            if (_ternWorker) {
                _ternWorker.terminate();
            }
            var workerDeferred = $.Deferred();
            ternPromise = workerDeferred.promise();
            var path = ExtensionUtils.getModulePath(module, "tern-worker.js");
            _ternWorker = new Worker(path);

            _ternWorker.addEventListener("message", function (e) {
                if (config.debug) {
                    console.debug("Message received", e);
                }

                var response = e.data,
                    type = response.type;

                if (type === MessageIds.TERN_COMPLETIONS_MSG ||
                        type === MessageIds.TERN_CALLED_FUNC_TYPE_MSG) {
                    // handle any completions the worker calculated
                    handleTernCompletions(response);
                } else if (type === MessageIds.TERN_GET_FILE_MSG) {
                    // handle a request for the contents of a file
                    handleTernGetFile(response);
                } else if (type === MessageIds.TERN_JUMPTODEF_MSG) {
                    handleJumptoDef(response);
                } else if (type === MessageIds.TERN_PRIME_PUMP_MSG) {
                    handlePrimePumpCompletion(response);
                } else if (type === MessageIds.TERN_GET_GUESSES_MSG) {
                    handleGetGuesses(response);
                } else if (type === MessageIds.TERN_UPDATE_FILE_MSG) {
                    handleUpdateFile(response);
                } else if (type === MessageIds.TERN_INFERENCE_TIMEDOUT) {
                    handleTimedOut(response);
                } else if (type === MessageIds.TERN_WORKER_READY) {
                    workerDeferred.resolveWith(null, [_ternWorker]);
                } else {
                    console.log("Worker: " + (response.log || response));
                }
            });

            // Set the initial configuration for the worker
            _ternWorker.postMessage({
                type: MessageIds.SET_CONFIG,
                config: config
            });
        }
        /**
         * Create a new tern server.
         */
        function initTernServer(dir, files) {
            initTernWorker();
            numResolvedFiles = 0;
            numAddedFiles = 0;
            stopAddingFiles = false;
            numInitialFiles = files.length;

            ternPromise.done(function (worker) {
                var msg = {
                    type        : MessageIds.TERN_INIT_MSG,
                    dir         : dir,
                    files       : files,
                    env         : ternEnvironment,
                    timeout     : PreferencesManager.get("jscodehints.inferenceTimeout")
                };

                if (worker) {
                    if (config.debug) {
                        console.debug("Sending message", msg);
                    }
                    worker.postMessage(msg);
                } else {
                    if (config.debug) {
                        console.debug("Worker null. Cannot send message", msg);
                    }
                }
            });
            rootTernDir = dir + "/";
        }

        /**
         *  We can skip tern initialization if we are opening a file that has
         *  already been added to tern.
         *
         * @param {string} newFile - full path of new file being opened in the editor.
         * @return {boolean} - true if tern initialization should be skipped,
         * false otherwise.
         */
        function canSkipTernInitialization(newFile) {
            return resolvedFiles[newFile] !== undefined;
        }

        /**
         *  Do the work to initialize a code hinting session.
         *
         * @param {Session} session - the active hinting session (TODO: currently unused)
         * @param {!Document} document - the document the editor has changed to
         * @param {?Document} previousDocument - the document the editor has changed from
         */
        function doEditorChange(session, document, previousDocument) {
            var file        = document.file,
                path        = file.fullPath,
                dir         = file.parentPath,
                pr;

            var addFilesDeferred = $.Deferred();

            documentChanges = null;
            addFilesPromise = addFilesDeferred.promise();
            pr = ProjectManager.getProjectRoot() ? ProjectManager.getProjectRoot().fullPath : null;

            // avoid re-initializing tern if possible.
            if (canSkipTernInitialization(path)) {

                // update the previous document in tern to prevent stale files.
                if (isDocumentDirty && previousDocument) {
                    var updateFilePromise = updateTernFile(previousDocument);
                    updateFilePromise.done(function () {
                        primePump(path);
                        addFilesDeferred.resolveWith(null, [_ternWorker]);
                    });
                } else {
                    addFilesDeferred.resolveWith(null, [_ternWorker]);
                }

                isDocumentDirty = false;
                return;
            }

            isDocumentDirty = false;
            resolvedFiles = {};
            projectRoot = pr;

            ensurePreferences();
            deferredPreferences.done(function () {
                FileSystem.resolve(dir, function (err, directory) {
                    if (err) {
                        console.error("Error resolving", dir);
                        addFilesDeferred.resolveWith(null);
                        return;
                    }

                    directory.getContents(function (err, contents) {
                        if (err) {
                            console.error("Error getting contents for", directory);
                            addFilesDeferred.resolveWith(null);
                            return;
                        }

                        var files = contents
                            .filter(function (entry) {
                                return entry.isFile && !isFileExcluded(entry);
                            })
                            .map(function (entry) {
                                return entry.fullPath;
                            });

                        initTernServer(dir, files);

                        var hintsPromise = primePump(path);
                        hintsPromise.done(function () {
                            if (!usingModules()) {
                                // Read the subdirectories of the new file's directory.
                                // Read them first in case there are too many files to
                                // read in the project.
                                addAllFilesAndSubdirectories(dir, function () {
                                    // If the file is in the project root, then read
                                    // all the files under the project root.
                                    var currentDir = (dir + "/");
                                    if (projectRoot && currentDir !== projectRoot &&
                                            currentDir.indexOf(projectRoot) === 0) {
                                        addAllFilesAndSubdirectories(projectRoot, function () {
                                            // prime the pump again but this time don't wait
                                            // for completion.
                                            primePump(path);

                                            addFilesDeferred.resolveWith(null, [_ternWorker]);
                                        });
                                    } else {
                                        addFilesDeferred.resolveWith(null, [_ternWorker]);
                                    }
                                });
                            } else {
                                addFilesDeferred.resolveWith(null, [_ternWorker]);
                            }
                        });
                    });
                });
            });
        }

        /**
         * Called each time a new editor becomes active.
         *
         * @param {Session} session - the active hinting session (TODO: currently unused by doEditorChange())
         * @param {!Document} document - the document of the editor that has changed
         * @param {?Document} previousDocument - the document of the editor is changing from
         */
        function handleEditorChange(session, document, previousDocument) {
            if (addFilesPromise === null) {
                doEditorChange(session, document, previousDocument);
            } else {
                addFilesPromise.done(function () {
                    doEditorChange(session, document, previousDocument);
                });
            }
        }

        /**
         * Do some cleanup when a project is closed.
         *
         * We can clean up the web worker we use to calculate hints now, since
         * we know we will need to re-init it in any new project that is opened.
         */
        function closeWorker() {
            function terminateWorker() {
                var worker = _ternWorker;

                // Worker can be null if an error condition came up previously
                if (!worker) {
                    return;
                }
                setTimeout(function () {
                    // give pending requests a chance to finish
                    worker.terminate();
                    worker = null;
                }, 1000);
                _ternWorker = null;
                resolvedFiles = {};
            }

            if (_ternWorker) {
                if (addFilesPromise) {
                    // If we're in the middle of added files, don't terminate
                    // until we're done or we might get NPEs
                    addFilesPromise.done(terminateWorker).fail(terminateWorker);
                } else {
                    terminateWorker();
                }
            }
        }

        function whenReady(func) {
            addFilesPromise.done(func);
        }

        this.closeWorker = closeWorker;
        this.handleEditorChange = handleEditorChange;
        this.postMessage = postMessage;
        this.getResolvedPath = getResolvedPath;
        this.whenReady = whenReady;

        return this;
    }

    var resettingDeferred = null;

    /**
     * reset the tern worker thread, if necessary.
     *
     * To avoid memory leaks in the worker thread we periodically kill
     * the web worker instance, and start a new one.  To avoid a performance
     * hit when we do this we start up a new worker, and don't kill the old
     * one unitl the new one is initialized.
     *
     * During debugging, you can turn this automatic resetting behavior off
     * by running this in the console:
     * brackets._configureJSCodeHints({ noReset: true })
     *
     * This function is also used in unit testing with the "force" flag to
     * reset the worker for each test to start with a clean environment.
     *
     * @param {Session} session
     * @param {Document} document
     * @param {boolean} force true to force a reset regardless of how long since the last one
     * @return {Promise} Promise resolved when the worker is ready.
     *                   The new (or current, if there was no reset) worker is passed to the callback.
     */
    function _maybeReset(session, document, force) {
        var newWorker;
        // if we're in the middle of a reset, don't have to check
        // the new worker will be online soon
        if (!resettingDeferred) {

            // We don't reset if the debugging flag is set
            // because it's easier to debug if the worker isn't
            // getting shut down all the time.
            if (force || (!config.noReset && ++_hintCount > MAX_HINTS)) {
                if (config.debug) {
                    console.debug("Resetting tern worker");
                }

                resettingDeferred = new $.Deferred();
                newWorker = new TernWorker();
                newWorker.handleEditorChange(session, document, null);
                newWorker.whenReady(function () {
                    // tell the old worker to shut down
                    currentWorker.closeWorker();
                    currentWorker = newWorker;
                    resettingDeferred.resolve(currentWorker);
                    // all done reseting
                    resettingDeferred = null;
                });
                _hintCount = 0;
            } else {
                var d = new $.Deferred();
                d.resolve(currentWorker);
                return d.promise();
            }
        }

        return resettingDeferred.promise();
    }

    /**
     * Request a parameter hint from Tern.
     *
     * @param {Session} session - the active hinting session
     * @param {{line: number, ch: number}} functionOffset - the offset of the function call.
     * @return {jQuery.Promise} - The promise will not complete until the
     *      hint has completed.
     */
    function requestParameterHint(session, functionOffset) {
        var $deferredHints = $.Deferred(),
            fileInfo = getFileInfo(session, true),
            offset = getOffset(session, fileInfo, functionOffset),
            fnTypePromise = getTernFunctionType(fileInfo, offset);

        $.when(fnTypePromise).done(
            function (fnType) {
                session.setFnType(fnType);
                session.setFunctionCallPos(functionOffset);
                $deferredHints.resolveWith(null, [fnType]);
            }
        ).fail(function () {
            $deferredHints.reject();
        });

        return $deferredHints.promise();
    }

    /**
     * Request hints from Tern.
     *
     * Note that successive calls to getScope may return the same objects, so
     * clients that wish to modify those objects (e.g., by annotating them based
     * on some temporary context) should copy them first. See, e.g.,
     * Session.getHints().
     *
     * @param {Session} session - the active hinting session
     * @param {Document} document - the document for which scope info is
     *      desired
     * @return {jQuery.Promise} - The promise will not complete until the tern
     *      hints have completed.
     */
    function requestHints(session, document) {
        var $deferredHints = $.Deferred(),
            hintPromise,
            sessionType = session.getType(),
            fileInfo = getFileInfo(session),
            offset = getOffset(session, fileInfo, null);

        _maybeReset(session, document);

        hintPromise = getTernHints(fileInfo, offset, sessionType.property);

        $.when(hintPromise).done(
            function (completions, fnType) {
                if (completions.completions) {
                    session.setTernHints(completions.completions);
                    session.setGuesses(null);
                } else {
                    session.setTernHints([]);
                    session.setGuesses(completions.properties);
                }

                $deferredHints.resolveWith(null);
            }
        ).fail(function () {
            $deferredHints.reject();
        });

        return $deferredHints.promise();
    }

    /**
     *  Track the update area of the current document so we can tell if we can send
     *  partial updates to tern or not.
     *
     * @param {Array.<{from: {line:number, ch: number}, to: {line:number, ch: number},
     *     text: Array<string>}>} changeList - the document changes from the current change event
     */
    function trackChange(changeList) {
        var changed = documentChanges, i;
        if (changed === null) {
            documentChanges = changed = {from: changeList[0].from.line, to: changeList[0].from.line};
            if (config.debug) {
                console.debug("ScopeManager: document has changed");
            }
        }

        for (i = 0; i < changeList.length; i++) {
            var thisChange = changeList[i],
                end = thisChange.from.line + (thisChange.text.length - 1);
            if (thisChange.from.line < changed.to) {
                changed.to = changed.to - (thisChange.to.line - end);
            }

            if (end >= changed.to) {
                changed.to = end + 1;
            }

            if (changed.from > thisChange.from.line) {
                changed.from = thisChange.from.line;
            }
        }
    }

    /*
     * Called each time the file associated with the active editor changes.
     * Marks the file as being dirty.
     *
     * @param {from: {line:number, ch: number}, to: {line:number, ch: number}}
     */
    function handleFileChange(changeList) {
        isDocumentDirty = true;
        trackChange(changeList);
    }

    /**
     * Called each time a new editor becomes active.
     *
     * @param {Session} session - the active hinting session
     * @param {Document} document - the document of the editor that has changed
     * @param {?Document} previousDocument - the document of the editor is changing from
     */
    function handleEditorChange(session, document, previousDocument) {

        if (!currentWorker) {
            currentWorker = new TernWorker();
        }
        return currentWorker.handleEditorChange(session, document, previousDocument);
    }

    /**
     * Do some cleanup when a project is closed.
     *
     * We can clean up the web worker we use to calculate hints now, since
     * we know we will need to re-init it in any new project that is opened.
     */
    function handleProjectClose() {
        if (currentWorker) {
            currentWorker.closeWorker();
            currentWorker = null;
        }
    }

    /**
     *  Read in project preferences when a new project is opened.
     *  Look in the project root directory for a preference file.
     *
     *  @param {string=} projectRootPath - new project root path(optional).
     *  Only needed for unit tests.
     */
    function handleProjectOpen(projectRootPath) {
        initPreferences(projectRootPath);
    }

    /** Used to avoid timing bugs in unit tests */
    function _readyPromise() {
        return deferredPreferences;
    }

    /**
     * @private
     *
     * Update the configuration in the worker.
     */
    function _setConfig(configUpdate) {
        config = brackets._configureJSCodeHints.config;
        postMessage({
            type: MessageIds.SET_CONFIG,
            config: configUpdate
        });
    }

    exports._setConfig = _setConfig;
    exports._maybeReset = _maybeReset;
    exports.getBuiltins = getBuiltins;
    exports.getResolvedPath = getResolvedPath;
    exports.getTernHints = getTernHints;
    exports.handleEditorChange = handleEditorChange;
    exports.requestGuesses = requestGuesses;
    exports.handleFileChange = handleFileChange;
    exports.requestHints = requestHints;
    exports.requestJumptoDef = requestJumptoDef;
    exports.requestParameterHint = requestParameterHint;
    exports.handleProjectClose = handleProjectClose;
    exports.handleProjectOpen = handleProjectOpen;
    exports._readyPromise = _readyPromise;

});


*  A non-negative number used to break ties among providers for a

*  particular url. Providers that register with a higher priority will

*  have the opportunity to provide a given url before those with a

*  lower priority. The higher the number, the higher the priority.

* @return {{create: function():BaseServer, priority: number}}

function registerServer(provider, priority) {

keep keep keep keep keep replace keep keep keep

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

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

define({

    /**
     * Errors
     */

    // General file io error strings
	"GENERIC_ERROR": "(Erreur {0})",
	"NOT_FOUND_ERR": "Fichier/répertoire introuvable.",
	"NOT_READABLE_ERR": "Impossible de lire le fichier/répertoire.",
	"EXCEEDS_MAX_FILE_SIZE": "Les fichiers dont la taille excède {0} Mo ne peuvent pas être ouverts dans {APP_NAME}.",
	"NO_MODIFICATION_ALLOWED_ERR": "Le répertoire cible ne peut pas être modifié.",
	"NO_MODIFICATION_ALLOWED_ERR_FILE": "Vous n’êtes pas autorisé à effectuer des modifications.",
	"CONTENTS_MODIFIED_ERR": "Le fichier a été modifié dans une application autre que {APP_NAME}.",
	"UNSUPPORTED_ENCODING_ERR": "Pour le moment, {APP_NAME} ne prend en charge que les fichiers texte avec encodage UTF-8.",
	"FILE_EXISTS_ERR": "Le fichier ou le répertoire existe déjà.",
	"FILE": "fichier",
	"FILE_TITLE": "Fichier",
	"DIRECTORY": "répertoire",
	"DIRECTORY_TITLE": "Répertoire",
	"DIRECTORY_NAMES_LEDE": "noms de répertoires",
	"FILENAMES_LEDE": "noms de fichiers",
	"FILENAME": "Nom de fichier",
	"DIRECTORY_NAME": "Nom du répertoire",

    // Project error strings
	"ERROR_LOADING_PROJECT": "Erreur lors du chargement du projet",
	"OPEN_DIALOG_ERROR": "Une erreur s’est produite lors de l’affichage de la boîte de dialogue Ouvrir. (Erreur {0})",
	"REQUEST_NATIVE_FILE_SYSTEM_ERROR": "Une erreur s’est produite lors de la tentative de chargement du répertoire {0}. (Erreur {1})",
	"READ_DIRECTORY_ENTRIES_ERROR": "Une erreur s’est produite lors de la lecture du contenu du répertoire {0}. (Erreur {1})",

    // File open/save error string
	"ERROR_OPENING_FILE_TITLE": "Erreur lors de l’ouverture du fichier",
	"ERROR_OPENING_FILE": "Une erreur s’est produite lors de la tentative d’ouverture du fichier {0}. {1}",
	"ERROR_OPENING_FILES": "Une erreur s’est produite en tentant d’ouvrir les fichiers suivants :",
	"ERROR_RELOADING_FILE_TITLE": "Erreur lors du rechargement des modifications à partir du disque",
	"ERROR_RELOADING_FILE": "Une erreur s’est produite lors de la tentative de rechargement du fichier {0}. {1}",
	"ERROR_SAVING_FILE_TITLE": "Erreur lors de l’enregistrement du fichier",
	"ERROR_SAVING_FILE": "Une erreur s’est produite lors de la tentative d’enregistrement du fichier {0}. {1}",
	"ERROR_RENAMING_FILE_TITLE": "Erreur lors du changement de nom du {0}",
	"ERROR_RENAMING_FILE": "Une erreur s’est produite lors de la tentative de changement de nom du {2} {0}. {1}",
	"ERROR_DELETING_FILE_TITLE": "Erreur lors de la suppression du {0}",
	"ERROR_DELETING_FILE": "Une erreur s’est produite lors de la tentative de suppression du {2} {0}. {1}",
	"INVALID_FILENAME_TITLE": "{0} non valide",
	"INVALID_FILENAME_MESSAGE": "Les {0} ne peuvent pas utiliser de termes réservés au système, finir par un point (.) ou contenir l’un des caractères suivants : {1}",
	"ENTRY_WITH_SAME_NAME_EXISTS": "Il existe déjà un fichier ou un répertoire portant le nom {0}.",
	"ERROR_CREATING_FILE_TITLE": "Erreur lors de la création de l’élément {0}",
	"ERROR_CREATING_FILE": "Une erreur s’est produite lors de la tentative de création du {0} {1}. {2}",
	"ERROR_MIXED_DRAGDROP": "Impossible d’ouvrir un dossier en même temps que d’autres fichiers.",

    // User key map error strings
	"ERROR_KEYMAP_TITLE": "Erreur lors de la lecture du mappage de touches utilisateur",
	"ERROR_KEYMAP_CORRUPT": "Votre fichier de mappage de touches n’est pas un fichier JSON valide. Il va s’ouvrir afin que vous puissiez corriger le format.",
	"ERROR_LOADING_KEYMAP": "Votre fichier de mappage de touches n’étant pas un fichier texte codé en UTF-8 valide, il ne peut pas être chargé.",
	"ERROR_RESTRICTED_COMMANDS": "Vous ne pouvez pas réaffecter de raccourcis à ces commandes : {0}",
	"ERROR_RESTRICTED_SHORTCUTS": "Vous ne pouvez pas réaffecter ces raccourcis : {0}",
	"ERROR_MULTIPLE_SHORTCUTS": "Vous réaffectez plusieurs raccourcis à ces commandes : {0}",
	"ERROR_DUPLICATE_SHORTCUTS": "Plusieurs liaisons ont été définies pour les raccourcis suivants : {0}",
	"ERROR_INVALID_SHORTCUTS": "Ces raccourcis ne sont pas valides : {0}",
	"ERROR_NONEXISTENT_COMMANDS": "Vous affectez des raccourcis à des commandes qui n'existent pas : {0}",

    // Application preferences corrupt error strings
	"ERROR_PREFS_CORRUPT_TITLE": "Erreur lors de la lecture des préférences",
	"ERROR_PREFS_CORRUPT": "Votre fichier de préférences n’est pas un fichier JSON valide, il va donc être ouvert afin que vous puissiez corriger le format. Vous devrez ensuite redémarrer {APP_NAME} pour que les modifications prennent effet.",

    // Application error strings
	"ERROR_IN_BROWSER_TITLE": "Malheureusement, {APP_NAME} n’est pas encore compatible avec les navigateurs.",
	"ERROR_IN_BROWSER": "{APP_NAME} est défini en HTML, mais il s’exécute actuellement en tant qu’application de bureau, vous pouvez donc l’utiliser afin de modifier des fichiers locaux. Utilisez l’interpréteur de commandes d’application situé dans le référentiel github.com/adobe/brackets-shell afin d’exécuter {APP_NAME}.",

    // ProjectManager max files error string
	"ERROR_MAX_FILES_TITLE": "Erreur lors de l’indexation des fichiers",
	"ERROR_MAX_FILES": "Ce projet contient plus de 30 000 fichiers. Les fonctionnalités qui s’appliquent de manière globale à plusieurs fichiers peuvent être désactivées ou se comporter comme si le projet était vide. En savoir plus sur l’utilisation de projets volumineux.",

    // Live Preview error strings
	"ERROR_LAUNCHING_BROWSER_TITLE": "Erreur lors du lancement du navigateur",
	"ERROR_CANT_FIND_CHROME": "Le navigateur Google Chrome est introuvable. Assurez-vous qu’il est installé.",
	"ERROR_LAUNCHING_BROWSER": "Une erreur s’est produite lors du lancement du navigateur. (Erreur {0})",

	"LIVE_DEVELOPMENT_ERROR_TITLE": "Erreur du module Aperçu en direct",
	"LIVE_DEVELOPMENT_RELAUNCH_TITLE": "Connexion au navigateur",
	"LIVE_DEVELOPMENT_ERROR_MESSAGE": "Pour que le module Aperçu en direct puisse se connecter, vous devez relancer Chrome en activant la fonction de débogage à distance.

Voulez-vous relancer Chrome et activer le débogage à distance ?

",
	"LIVE_DEV_LOADING_ERROR_MESSAGE": "Impossible de charger la page Aperçu en direct.",
	"LIVE_DEV_NEED_HTML_MESSAGE": "Ouvrez un fichier HTML ou vérifiez qu’il y a un fichier index.html dans votre projet pour pouvoir lancer l’aperçu en direct.",
	"LIVE_DEV_NEED_BASEURL_MESSAGE": "Pour lancer l’Aperçu en direct avec un fichier de serveur, vous devez indiquer une URL de base pour ce projet.",
	"LIVE_DEV_SERVER_NOT_READY_MESSAGE": "Une erreur s’est produite au démarrage du serveur HTTP pour les fichiers d’aperçu en direct. Veuillez réessayer.",
	"LIVE_DEVELOPMENT_INFO_TITLE": "Bienvenue dans le module Aperçu en direct !",
	"LIVE_DEVELOPMENT_INFO_MESSAGE": "Le module Aperçu en direct connecte {APP_NAME} à votre navigateur. Il ouvre un aperçu de votre fichier HTML dans le navigateur, puis le met à jour instantanément dès que vous modifiez le code.

Dans cette première version du logiciel {APP_NAME}, le module Aperçu en direct ne fonctionne qu’avec Google Chrome et affiche les mises à jour en direct, dès que vous modifiez des fichiers CSS ou HTML. Les modifications apportées aux fichiers JavaScript sont automatiquement rechargées lorsque vous enregistrez.

(Ce message ne s’affichera qu’une seule fois.)",
	"LIVE_DEVELOPMENT_TROUBLESHOOTING": "Pour plus d’informations, consultez la page Résolution des erreurs de connexion à l’Aperçu en direct.",

	"LIVE_DEV_STATUS_TIP_NOT_CONNECTED": "Aperçu en direct",
	"LIVE_DEV_STATUS_TIP_PROGRESS1": "Aperçu en direct : Connexion\u2026",
	"LIVE_DEV_STATUS_TIP_PROGRESS2": "Aperçu en direct : Initialisation\u2026",
	"LIVE_DEV_STATUS_TIP_CONNECTED": "Déconnecter le module Aperçu en direct",
	"LIVE_DEV_STATUS_TIP_OUT_OF_SYNC": "Aperçu en direct (enregistrez le fichier pour actualiser)",
	"LIVE_DEV_STATUS_TIP_SYNC_ERROR": "Aperçu en direct (échec de la mise à jour en raison d’une erreur de syntaxe)",

	"LIVE_DEV_DETACHED_REPLACED_WITH_DEVTOOLS": "Aperçu en direct a été annulé car les outils de développeur du navigateur étaient ouverts",
	"LIVE_DEV_DETACHED_TARGET_CLOSED": "Aperçu en direct a été annulé car la page était fermée dans le navigateur",
	"LIVE_DEV_NAVIGATED_AWAY": "Aperçu en direct a été annulé car le navigateur a accédé à une page qui ne fait pas partie du projet actuel",
	"LIVE_DEV_CLOSED_UNKNOWN_REASON": "Aperçu en direct a été annulé pour une raison inconnue ({0})",

	"SAVE_CLOSE_TITLE": "Enregistrer les modifications",
	"SAVE_CLOSE_MESSAGE": "Souhaitez-vous enregistrer les modifications apportées au document {0} ?",
	"SAVE_CLOSE_MULTI_MESSAGE": "Souhaitez-vous enregistrer les modifications apportées aux fichiers suivants ?",
	"EXT_MODIFIED_TITLE": "Modifications externes",
	"CONFIRM_FOLDER_DELETE_TITLE": "Confirmer la suppression",
	"CONFIRM_FOLDER_DELETE": "Voulez-vous vraiment supprimer le dossier {0} ?",
	"FILE_DELETED_TITLE": "Fichier supprimé",
	"EXT_MODIFIED_WARNING": "{0} a été modifié sur le disque.

Voulez-vous enregistrer le fichier et remplacer ces modifications ?",
	"EXT_MODIFIED_MESSAGE": "Le fichier {0} a été modifié sur le disque mais présente également des modifications non enregistrées dans {APP_NAME}.

Quelle version souhaitez-vous conserver ?",
	"EXT_DELETED_MESSAGE": "Le fichier {0} a été supprimé sur le disque mais présente des modifications non enregistrées dans {APP_NAME}.

Souhaitez-vous conserver vos modifications ?",

    // Generic dialog/button labels
	"DONE": "Terminé",
	"OK": "OK",
	"CANCEL": "Annuler",
	"DONT_SAVE": "Ne pas enregistrer",
	"SAVE": "Enregistrer",
	"SAVE_AS": "Enregistrer sous\u2026",
	"SAVE_AND_OVERWRITE": "Remplacer",
	"DELETE": "Supprimer",
	"BUTTON_YES": "Oui",
	"BUTTON_NO": "Non",

    // Find, Replace, Find in Files
	"FIND_MATCH_INDEX": "{0} sur {1}",
	"FIND_NO_RESULTS": "Aucun résultat",
	"FIND_QUERY_PLACEHOLDER": "Rechercher\u2026",
	"REPLACE_PLACEHOLDER": "Remplacer par\u2026",
	"BUTTON_REPLACE_ALL": "Traiter par lots\u2026",
	"BUTTON_REPLACE_ALL_IN_FILES": "Remplacer\u2026",
	"BUTTON_REPLACE": "Remplacer",
	"BUTTON_NEXT": "\u25B6",
	"BUTTON_PREV": "\u25C0",
	"BUTTON_NEXT_HINT": "Résultat suivant",
	"BUTTON_PREV_HINT": "Résultat précédent",
	"BUTTON_CASESENSITIVE_HINT": "Respecter la casse",
	"BUTTON_REGEXP_HINT": "Expression régulière",
	"REPLACE_WITHOUT_UNDO_WARNING_TITLE": "Remplacer sans possibilité d’annuler",
	"REPLACE_WITHOUT_UNDO_WARNING": "Dans la mesure où le nombre de fichiers à modifier excède {0}, {APP_NAME} va modifier des fichiers non ouverts sur le disque.
Vous ne pourrez pas annuler les remplacements effectués dans ces fichiers.",
	"BUTTON_REPLACE_WITHOUT_UNDO": "Remplacer sans possibilité d’annuler",

	"OPEN_FILE": "Ouvrir le fichier",
	"SAVE_FILE_AS": "Enregistrer le fichier",
	"CHOOSE_FOLDER": "Choisir un dossier",

	"RELEASE_NOTES": "Notes de mise à jour",
	"NO_UPDATE_TITLE": "Vos logiciels sont à jour !",
	"NO_UPDATE_MESSAGE": "Vous utilisez la dernière version de {APP_NAME}.",

    // Find and Replace
	"FIND_REPLACE_TITLE_LABEL": "Remplacer",
	"FIND_REPLACE_TITLE_WITH": "par",
	"FIND_TITLE_LABEL": "Occurrences trouvées pour",
	"FIND_TITLE_SUMMARY": "— {0} {1} {2} dans {3}",

    // Find in Files
	"FIND_NUM_FILES": "{0} {1}",
	"FIND_IN_FILES_SCOPED": "dans {0}",
	"FIND_IN_FILES_NO_SCOPE": "dans le projet",
	"FIND_IN_FILES_ZERO_FILES": "Le filtre exclut tous les fichiers {0}",
	"FIND_IN_FILES_FILE": "fichier",
	"FIND_IN_FILES_FILES": "fichiers",
	"FIND_IN_FILES_MATCH": "résultat",
	"FIND_IN_FILES_MATCHES": "résultats",
	"FIND_IN_FILES_MORE_THAN": "Plus de ",
	"FIND_IN_FILES_PAGING": "{0}—{1}",
	"FIND_IN_FILES_FILE_PATH": "{0} {2} {1}",
	"FIND_IN_FILES_EXPAND_COLLAPSE": "Cliquer tout en appuyant sur Ctrl/Cmd pour tout développer/tout réduire",
	"REPLACE_IN_FILES_ERRORS_TITLE": "Erreurs de remplacement",
	"REPLACE_IN_FILES_ERRORS": "Les fichiers suivants n’ont pas été traités car ils ont été modifiés après l’opération de recherche ou ne sont pas accessibles en écriture.",

	"ERROR_FETCHING_UPDATE_INFO_TITLE": "Erreur de récupération des informations de mise à jour",
	"ERROR_FETCHING_UPDATE_INFO_MSG": "Un problème est survenu lors de la récupération des dernières informations de mise à jour sur le serveur. Vérifiez que vous êtes connecté à Internet et réessayez.",

    // File exclusion filters
	"NEW_FILE_FILTER": "Nouveau jeu d’exclusion\u2026",
	"CLEAR_FILE_FILTER": "Ne pas exclure les fichiers",
	"NO_FILE_FILTER": "Aucun fichier exclu",
	"EXCLUDE_FILE_FILTER": "Exclure {0}",
	"EDIT_FILE_FILTER": "Modifier\u2026",
	"FILE_FILTER_DIALOG": "Modifier le jeu d’exclusion",
	"FILE_FILTER_INSTRUCTIONS": "Exclure les fichiers et dossiers correspondant à l’une des chaînes/sous-chaînes ou aux caractères génériques suivants. Entrez chaque chaîne sur une ligne différente.",
	"FILTER_NAME_PLACEHOLDER": "Nommer ce jeu d’exclusion (facultatif)",
	"FILE_FILTER_CLIPPED_SUFFIX": "et {0} autre(s)",
	"FILTER_COUNTING_FILES": "Comptage des fichiers\u2026",
	"FILTER_FILE_COUNT": "Autorise {0} fichier(s) sur les {1} {2}",
	"FILTER_FILE_COUNT_ALL": "Autorise les {0} fichiers {1}",

    // Quick Edit
	"ERROR_QUICK_EDIT_PROVIDER_NOT_FOUND": "Fonction Edition rapide non disponible pour la position actuelle du curseur",
	"ERROR_CSSQUICKEDIT_BETWEENCLASSES": "Edition rapide CSS : placez le curseur sur un seul nom de classe",
	"ERROR_CSSQUICKEDIT_CLASSNOTFOUND": "Edition rapide CSS : attribut de classe incomplet",
	"ERROR_CSSQUICKEDIT_IDNOTFOUND": "Edition rapide CSS : attribut d’ID incomplet",
	"ERROR_CSSQUICKEDIT_UNSUPPORTEDATTR": "Edition rapide CSS : placez le curseur dans la balise, la classe ou l’ID",
	"ERROR_TIMINGQUICKEDIT_INVALIDSYNTAX": "Edition rapide des fonctions de temporisation CSS : syntaxe incorrecte",
	"ERROR_JSQUICKEDIT_FUNCTIONNOTFOUND": "Edition rapide JS : placez le curseur dans le nom de fonction",

    // Quick Docs
	"ERROR_QUICK_DOCS_PROVIDER_NOT_FOUND": "Fonction Documentation rapide non disponible pour la position actuelle du curseur",

    /**
     * ProjectManager
     */
	"PROJECT_LOADING": "Chargement\u2026",
	"UNTITLED": "Sans titre",
	"WORKING_FILES": "Fichiers de travail",

    /**
     * MainViewManager
     */
	"TOP": "Haut",
	"BOTTOM": "Bas",
	"LEFT": "Gauche",
	"RIGHT": "Droite",

	"CMD_SPLITVIEW_NONE": "Pas de fractionnement",
	"CMD_SPLITVIEW_VERTICAL": "Fractionnement vertical",
	"CMD_SPLITVIEW_HORIZONTAL": "Fractionnement horizontal",
	"SPLITVIEW_MENU_TOOLTIP": "Fractionner l’éditeur horizontalement ou verticalement",
	"GEAR_MENU_TOOLTIP": "Configurer l’ensemble de travail",

	"SPLITVIEW_INFO_TITLE": "Déjà ouvert",
	"SPLITVIEW_MULTIPANE_WARNING": "Ce fichier est déjà ouvert dans un autre volet. Il sera bientôt possible d’ouvrir un même fichier dans plusieurs volets de l’application {APP_NAME}, mais en attendant, vous ne pouvez consulter le fichier que dans le volet dans lequel il est déjà ouvert.

(Ce message ne s’affichera qu’une fois.)",

    /**
     * Keyboard modifier names
     */
	"KEYBOARD_CTRL": "Ctrl",
	"KEYBOARD_SHIFT": "Maj",
	"KEYBOARD_SPACE": "Espace",

    /**
     * StatusBar strings
     */
	"STATUSBAR_CURSOR_POSITION": "Ligne {0}, colonne {1}",
	"STATUSBAR_SELECTION_CH_SINGULAR": " \u2014 {0} colonne sélectionnée",
	"STATUSBAR_SELECTION_CH_PLURAL": " \u2014 {0} colonnes sélectionnées",
	"STATUSBAR_SELECTION_LINE_SINGULAR": " \u2014 {0} ligne sélectionnée",
	"STATUSBAR_SELECTION_LINE_PLURAL": " \u2014 {0} lignes sélectionnées",
	"STATUSBAR_SELECTION_MULTIPLE": " \u2014 {0} sélections",
	"STATUSBAR_INDENT_TOOLTIP_SPACES": "Cliquez ici pour remplacer la mise en retrait par des espaces.",
	"STATUSBAR_INDENT_TOOLTIP_TABS": "Cliquez ici pour remplacer la mise en retrait par des tabulations.",
	"STATUSBAR_INDENT_SIZE_TOOLTIP_SPACES": "Cliquez ici pour changer le nombre d’espaces utilisés lors de la mise en retrait.",
	"STATUSBAR_INDENT_SIZE_TOOLTIP_TABS": "Cliquez ici pour modifier la largeur du caractère de tabulation.",
	"STATUSBAR_SPACES": "Espaces :",
	"STATUSBAR_TAB_SIZE": "Taille de tabulation :",
	"STATUSBAR_LINE_COUNT_SINGULAR": "\u2014 {0} ligne",
	"STATUSBAR_LINE_COUNT_PLURAL": "\u2014 {0} lignes",
	"STATUSBAR_USER_EXTENSIONS_DISABLED": "Extensions désactivées",
	"STATUSBAR_INSERT": "INS",
	"STATUSBAR_OVERWRITE": "RFP",
	"STATUSBAR_INSOVR_TOOLTIP": "Cliquez pour faire passer le curseur du mode Insertion (INS) au mode Remplacement (RFP) ou inversement",
	"STATUSBAR_LANG_TOOLTIP": "Cliquez pour modifier le type de fichier",
	"STATUSBAR_CODE_INSPECTION_TOOLTIP": "{0}. Cliquez pour afficher/masquer le panneau des rapports.",
	"STATUSBAR_DEFAULT_LANG": "(par défaut)",
	"STATUSBAR_SET_DEFAULT_LANG": "Utiliser par défaut pour les fichiers .{0}",

    // CodeInspection: errors/warnings
	"ERRORS_PANEL_TITLE_MULTIPLE": "{0} problèmes",
	"SINGLE_ERROR": "1 problème {0}",
	"MULTIPLE_ERRORS": "{1} problèmes {0}",
	"NO_ERRORS": "Aucun problème {0} détecté, félicitations !",
	"NO_ERRORS_MULTIPLE_PROVIDER": "Aucun problème détecté, félicitations !",
	"LINT_DISABLED": "L’analyse lint est désactivée",
	"NO_LINT_AVAILABLE": "Aucun programme lint disponible pour {0}",
	"NOTHING_TO_LINT": "Rien à analyser",
	"LINTER_TIMED_OUT": "Délai dépassé pour {0} après une attente de {1} ms",
	"LINTER_FAILED": "{0} arrêté avec l’erreur suivante : {1}",

    /**
     * Command Name Constants
     */

    // File menu commands
	"FILE_MENU": "Fichier",
	"CMD_FILE_NEW_UNTITLED": "Nouveau",
	"CMD_FILE_NEW": "Nouveau fichier",
	"CMD_FILE_NEW_FOLDER": "Nouveau dossier",
	"CMD_FILE_OPEN": "Ouvrir\u2026",
	"CMD_ADD_TO_WORKING_SET": "Ouvrir dans l’ensemble de travail",
	"CMD_OPEN_DROPPED_FILES": "Ouvrir les fichiers déposés",
	"CMD_OPEN_FOLDER": "Ouvrir un dossier\u2026",
	"CMD_FILE_CLOSE": "Fermer",
	"CMD_FILE_CLOSE_ALL": "Tout fermer",
	"CMD_FILE_CLOSE_LIST": "Fermer la liste",
	"CMD_FILE_CLOSE_OTHERS": "Fermer tous les autres",
	"CMD_FILE_CLOSE_ABOVE": "Fermer les autres au-dessus",
	"CMD_FILE_CLOSE_BELOW": "Fermer les autres en dessous",
	"CMD_FILE_SAVE": "Enregistrer",
	"CMD_FILE_SAVE_ALL": "Enregistrer tout",
	"CMD_FILE_SAVE_AS": "Enregistrer sous\u2026",
	"CMD_LIVE_FILE_PREVIEW": "Aperçu en direct",
	"CMD_TOGGLE_LIVE_PREVIEW_MB_MODE": "Activer la version expérimentale de l’Aperçu en direct",
	"CMD_RELOAD_LIVE_PREVIEW": "Forcer le rechargement de l’aperçu en direct",
	"CMD_PROJECT_SETTINGS": "Paramètres du projet\u2026",
	"CMD_FILE_RENAME": "Renommer",
	"CMD_FILE_DELETE": "Supprimer",
	"CMD_INSTALL_EXTENSION": "Installer une extension\u2026",
	"CMD_EXTENSION_MANAGER": "Gestionnaire d'extensions\u2026",
	"CMD_FILE_REFRESH": "Actualiser l’arborescence de fichiers",
	"CMD_QUIT": "Quitter",
    // Used in native File menu on Windows
	"CMD_EXIT": "Quitter",

    // Edit menu commands
	"EDIT_MENU": "Modifier",
	"CMD_UNDO": "Annuler",
	"CMD_REDO": "Répéter",
	"CMD_CUT": "Couper",
	"CMD_COPY": "Copier",
	"CMD_PASTE": "Coller",
	"CMD_SELECT_ALL": "Sélectionner tout",
	"CMD_SELECT_LINE": "Sélectionner la ligne",
	"CMD_SPLIT_SEL_INTO_LINES": "Scinder la sélection en lignes",
	"CMD_ADD_CUR_TO_NEXT_LINE": "Ajouter un curseur à la ligne suivante",
	"CMD_ADD_CUR_TO_PREV_LINE": "Ajouter un curseur à la ligne précédente",
	"CMD_INDENT": "Retrait",
	"CMD_UNINDENT": "Annuler le retrait",
	"CMD_DUPLICATE": "Dupliquer",
	"CMD_DELETE_LINES": "Supprimer la ligne",
	"CMD_COMMENT": "Activer/Désactiver le commentaire de ligne",
	"CMD_BLOCK_COMMENT": "Commenter les blocs/Annuler les commentaires",
	"CMD_LINE_UP": "Déplacer la ligne vers le haut",
	"CMD_LINE_DOWN": "Déplacer la ligne vers le bas",
	"CMD_OPEN_LINE_ABOVE": "Ouvrir une ligne au-dessus",
	"CMD_OPEN_LINE_BELOW": "Ouvrir une ligne en dessous",
	"CMD_TOGGLE_CLOSE_BRACKETS": "Fermeture automatique des accolades",
	"CMD_SHOW_CODE_HINTS": "Afficher les indicateurs de code",

    // Search menu commands
	"FIND_MENU": "Rechercher",
	"CMD_FIND": "Rechercher",
	"CMD_FIND_NEXT": "Rechercher suivant",
	"CMD_FIND_PREVIOUS": "Rechercher précédent",
	"CMD_FIND_ALL_AND_SELECT": "Rechercher et sélectionner tout",
	"CMD_ADD_NEXT_MATCH": "Ajouter l’occurrence suivante à la sélection",
	"CMD_SKIP_CURRENT_MATCH": "Ignorer et ajouter l’occurrence suivante",
	"CMD_FIND_IN_FILES": "Rechercher dans les fichiers",
	"CMD_FIND_IN_SELECTED": "Rechercher dans le fichier/dossier sélectionné",
	"CMD_FIND_IN_SUBTREE": "Rechercher dans\u2026",
	"CMD_REPLACE": "Remplacer",
	"CMD_REPLACE_IN_FILES": "Remplacer dans les fichiers",
	"CMD_REPLACE_IN_SELECTED": "Remplacer dans le fichier/dossier sélectionné",
	"CMD_REPLACE_IN_SUBTREE": "Remplacer dans\u2026",

    // View menu commands
	"VIEW_MENU": "Affichage",
	"CMD_HIDE_SIDEBAR": "Masquer la barre latérale",
	"CMD_SHOW_SIDEBAR": "Afficher la barre latérale",
	"CMD_INCREASE_FONT_SIZE": "Augmenter la taille de la police",
	"CMD_DECREASE_FONT_SIZE": "Diminuer la taille de la police",
	"CMD_RESTORE_FONT_SIZE": "Restaurer la taille de la police",
	"CMD_SCROLL_LINE_UP": "Faire défiler d’une ligne vers le haut",
	"CMD_SCROLL_LINE_DOWN": "Faire défiler d’une ligne vers le bas",
	"CMD_TOGGLE_LINE_NUMBERS": "Numéros de ligne",
	"CMD_TOGGLE_ACTIVE_LINE": "Surligner la ligne active",
	"CMD_TOGGLE_WORD_WRAP": "Renvoi à la ligne",
	"CMD_LIVE_HIGHLIGHT": "Surlignement dans l’Aperçu en direct",
	"CMD_VIEW_TOGGLE_INSPECTION": "Effectuer une analyse lint des fichiers à l’enregistrement",
	"CMD_WORKINGSET_SORT_BY_ADDED": "Trier par date d’ajout",
	"CMD_WORKINGSET_SORT_BY_NAME": "Trier par nom",
	"CMD_WORKINGSET_SORT_BY_TYPE": "Trier par type",
	"CMD_WORKING_SORT_TOGGLE_AUTO": "Tri automatique",
	"CMD_THEMES": "Thèmes\u2026",

    // Navigate menu Commands
	"NAVIGATE_MENU": "Naviguer",
	"CMD_QUICK_OPEN": "Ouverture rapide",
	"CMD_GOTO_LINE": "Atteindre la ligne",
	"CMD_GOTO_DEFINITION": "Accès rapide à la définition",
	"CMD_GOTO_FIRST_PROBLEM": "Accéder au premier problème",
	"CMD_TOGGLE_QUICK_EDIT": "Edition rapide",
	"CMD_TOGGLE_QUICK_DOCS": "Documentation rapide",
	"CMD_QUICK_EDIT_PREV_MATCH": "Correspondance précédente",
	"CMD_QUICK_EDIT_NEXT_MATCH": "Correspondance suivante",
	"CMD_CSS_QUICK_EDIT_NEW_RULE": "Nouvelle règle",
	"CMD_NEXT_DOC": "Document suivant",
	"CMD_PREV_DOC": "Document précédent",
	"CMD_SHOW_IN_TREE": "Afficher dans l’arborescence de fichiers",
	"CMD_SHOW_IN_EXPLORER": "Afficher dans l’Explorateur",
	"CMD_SHOW_IN_FINDER": "Afficher dans le Finder",
	"CMD_SHOW_IN_OS": "Afficher dans le SE",

    // Help menu commands
	"HELP_MENU": "Aide",
	"CMD_CHECK_FOR_UPDATE": "Rechercher les mises à jour",
	"CMD_HOW_TO_USE_BRACKETS": "Comment utiliser {APP_NAME}",
	"CMD_SUPPORT": "Assistance {APP_NAME}",
	"CMD_SUGGEST": "Suggérer une fonctionnalité",
	"CMD_RELEASE_NOTES": "Notes de mise à jour",
	"CMD_GET_INVOLVED": "Participer",
	"CMD_SHOW_EXTENSIONS_FOLDER": "Afficher le dossier d’extensions",
	"CMD_HOMEPAGE": "Page d’accueil de {APP_TITLE}",
	"CMD_TWITTER": "{TWITTER_NAME} sur Twitter",
	"CMD_ABOUT": "A propos de {APP_TITLE}",
	"CMD_OPEN_PREFERENCES": "Ouvrir le fichier de préférences",
	"CMD_OPEN_KEYMAP": "Ouvrir le mappage de touches utilisateur",

    // Strings for main-view.html
	"EXPERIMENTAL_BUILD": "version expérimentale",
	"RELEASE_BUILD": "édition",
	"DEVELOPMENT_BUILD": "version de développement",
	"RELOAD_FROM_DISK": "Recharger à partir du disque",
	"KEEP_CHANGES_IN_EDITOR": "Conserver les modifications dans l’éditeur",
	"CLOSE_DONT_SAVE": "Fermer (sans enregistrer)",
	"RELAUNCH_CHROME": "Relancer Chrome",
	"ABOUT": "A propos",
	"CLOSE": "Fermer",
	"ABOUT_TEXT_LINE1": "Version {VERSION_MAJOR}.{VERSION_MINOR} {BUILD_TYPE} {VERSION}",
	"ABOUT_TEXT_BUILD_TIMESTAMP": "Horodatage de la version : ",
	"ABOUT_TEXT_LINE3": "Les mentions légales et conditions générales relatives aux logiciels tiers sont disponibles à l’adresse {ADOBE_THIRD_PARTY} et sont incluses dans le présent document à titre de référence.",
	"ABOUT_TEXT_LINE4": "La documentation et le code source sont disponibles à l’adresse https://github.com/adobe/brackets/",
	"ABOUT_TEXT_LINE5": "Développé avec \u2764 et JavaScript par :",
	"ABOUT_TEXT_LINE6": "De nombreux contributeurs (information indisponible pour le moment).",
	"ABOUT_TEXT_WEB_PLATFORM_DOCS": "Web Platform Docs et le logo Web Platform font l’objet d’une licence Creative Commons Attribution, CC-BY 3.0 Unported.",
	"UPDATE_NOTIFICATION_TOOLTIP": "Une nouvelle version de {APP_NAME} est disponible. Cliquez ici pour plus de détails.",
	"UPDATE_AVAILABLE_TITLE": "Mise à jour disponible",
	"UPDATE_MESSAGE": "Une nouvelle version de {APP_NAME} est disponible. Voici quelques-unes des nouvelles fonctionnalités proposées :",
	"GET_IT_NOW": "Télécharger",
	"PROJECT_SETTINGS_TITLE": "Paramètres de projet pour : {0}",
	"PROJECT_SETTING_BASE_URL": "URL de base de l’Aperçu en direct ",
	"PROJECT_SETTING_BASE_URL_HINT": "Serveur local : entrez une URL du type http://localhost:8000/",
	"BASEURL_ERROR_INVALID_PROTOCOL": "Le protocole {0} n’est pas pris en charge par l’Aperçu en direct. Veuillez utiliser une adresse de type http: ou https: .",
	"BASEURL_ERROR_SEARCH_DISALLOWED": "L’URL de base ne peut pas contenir de paramètres de recherche tels que \"{0}\".",
	"BASEURL_ERROR_HASH_DISALLOWED": "L’URL de base ne peut pas contenir de signe dièse (\"{0}\").",
	"BASEURL_ERROR_INVALID_CHAR": "Les caractères spéciaux tels que '{0}' doivent être codés en %.",
	"BASEURL_ERROR_UNKNOWN_ERROR": "Erreur inconnue lors de l’analyse de l’URL de base",
	"EMPTY_VIEW_HEADER": "Ouvrir un fichier quand ce panneau est actif",

    // Strings for themes-settings.html and themes-general.html
	"CURRENT_THEME": "Thème actuel ",
	"USE_THEME_SCROLLBARS": "Utiliser les barres de défilement du thème ",
	"FONT_SIZE": "Taille de la police ",
	"FONT_FAMILY": "Famille de polices ",
	"THEMES_SETTINGS": "Paramètres des thèmes",

    // CSS Quick Edit
	"BUTTON_NEW_RULE": "Nouvelle règle",

    // Extension Management strings
	"INSTALL": "Installer",
	"UPDATE": "Mettre à jour",
	"REMOVE": "Supprimer",
	"OVERWRITE": "Remplacer",
	"CANT_REMOVE_DEV": "Les extensions du dossier \"dev\" doivent être supprimées manuellement.",
	"CANT_UPDATE": "La mise à jour n’est pas disponible avec cette version de l’application {APP_NAME}.",
	"CANT_UPDATE_DEV": "Les extensions du dossier « dev » ne peuvent pas être mises à jour automatiquement.",
	"INSTALL_EXTENSION_TITLE": "Installer l’extension",
	"UPDATE_EXTENSION_TITLE": "Mettre à jour l’extension",
	"INSTALL_EXTENSION_LABEL": "URL de l’extension ",
	"INSTALL_EXTENSION_HINT": "URL du fichier zip de l’extension ou du référentiel GitHub",
	"INSTALLING_FROM": "Installation de l’extension depuis·{0}\u2026",
	"INSTALL_SUCCEEDED": "Installation réussie !",
	"INSTALL_FAILED": "Echec de l’installation.",
	"CANCELING_INSTALL": "Annulation en cours\u2026",
	"CANCELING_HUNG": "L’annulation de l’installation prend beaucoup de temps. Il est possible qu’une erreur interne se soit produite.",
	"INSTALL_CANCELED": "Installation annulée.",
	"VIEW_COMPLETE_DESCRIPTION": "Voir la description complète",
	"VIEW_TRUNCATED_DESCRIPTION": "Voir la description tronquée",
    // These must match the error codes in ExtensionsDomain.Errors.* :
	"INVALID_ZIP_FILE": "Le contenu téléchargé n’est pas un fichier zip valide.",
	"INVALID_PACKAGE_JSON": "Le fichier package.json n’est pas valide (erreur : {0}).",
	"MISSING_PACKAGE_NAME": "Le fichier package.json n’indique pas le nom du pack.",
	"BAD_PACKAGE_NAME": "{0} n’est pas un nom de pack valide.",
	"MISSING_PACKAGE_VERSION": "Le fichier package.json n’indique pas la version du pack.",
	"INVALID_VERSION_NUMBER": "Le numéro de version du pack ({0}) n’est pas valide.",
	"INVALID_BRACKETS_VERSION": "La chaîne de compatibilité {APP_NAME} ({0}) n’est pas valide.",
	"DISALLOWED_WORDS": "Le champ {0} ne peut pas contenir les mots ({1}).",
	"API_NOT_COMPATIBLE": "L’extension n’est pas compatible avec cette version de l’application {APP_NAME}. Elle a été installée dans le dossier contenant les extensions désactivées.",
	"MISSING_MAIN": "Le pack ne contient pas de fichier main.js.",
	"EXTENSION_ALREADY_INSTALLED": "L’installation de ce pack remplacera une extension installée précédemment. Voulez-vous remplacer l’extension installée ?",
	"EXTENSION_SAME_VERSION": "Ce pack correspond à la même version que celle installée actuellement. Voulez-vous remplacer l’installation actuelle ?",
	"EXTENSION_OLDER_VERSION": "Ce pack correspond à la version {0}, qui est antérieure à la version actuellement installée ({1}). Voulez-vous remplacer l’installation actuelle ?",
	"DOWNLOAD_ID_IN_USE": "Erreur interne : l’ID de téléchargement est déjà utilisé.",
	"NO_SERVER_RESPONSE": "Impossible de se connecter au serveur.",
	"BAD_HTTP_STATUS": "Fichier introuvable sur le serveur (HTTP {0}).",
	"CANNOT_WRITE_TEMP": "Impossible d’enregistrer le téléchargement vers le fichier temporaire.",
	"ERROR_LOADING": "Une erreur s’est produite au démarrage de l’extension.",
	"MALFORMED_URL": "URL non valide. Veuillez vérifier l’URL saisie.",
	"UNSUPPORTED_PROTOCOL": "L’URL doit commencer par http ou https.",
	"UNKNOWN_ERROR": "Erreur interne inconnue.",
    // For NOT_FOUND_ERR, see generic strings above
	"EXTENSION_MANAGER_TITLE": "Le gestionnaire d'extensions",
	"EXTENSION_MANAGER_ERROR_LOAD": "Impossible d’accéder au registre de l’extension. Réessayez ultérieurement.",
	"INSTALL_EXTENSION_DRAG": "Faire glisser le fichier .zip ici ou",
	"INSTALL_EXTENSION_DROP": "Déposer le fichier .zip pour lancer l’installation",
	"INSTALL_EXTENSION_DROP_ERROR": "Installation/Mise à jour interrompue en raison des erreurs suivantes :",
	"INSTALL_FROM_URL": "Installer à partir de l’URL\u2026",
	"INSTALL_EXTENSION_VALIDATING": "Validation en cours\u2026",
	"EXTENSION_AUTHOR": "Auteur",
	"EXTENSION_DATE": "Date",
	"EXTENSION_INCOMPATIBLE_NEWER": "Cette extension nécessite une version plus récente de l’application {APP_NAME}.",
	"EXTENSION_INCOMPATIBLE_OLDER": "Cette extension n’est actuellement compatible qu’avec les versions antérieures de l’application {APP_NAME}.",
	"EXTENSION_LATEST_INCOMPATIBLE_NEWER": "La version {0} de cette extension nécessite une version plus récente de {APP_NAME}. Mais vous pouvez installer la version antérieure {1}.",
	"EXTENSION_LATEST_INCOMPATIBLE_OLDER": "La version {0} de cette extension n’est compatible qu’avec les anciennes versions de {APP_NAME}. Mais vous pouvez installer la version antérieure {1}.",
	"EXTENSION_NO_DESCRIPTION": "Aucune description",
	"EXTENSION_MORE_INFO": "Plus d’infos...",
	"EXTENSION_ERROR": "Erreur d’extension",
	"EXTENSION_KEYWORDS": "Mots-clés",
	"EXTENSION_TRANSLATED_USER_LANG": "Traduit en {0} langues, y compris la vôtre",
	"EXTENSION_TRANSLATED_GENERAL": "Traduit en {0} langues",
	"EXTENSION_TRANSLATED_LANGS": "Cette extension a été traduite dans les langues suivantes : {0}",
	"EXTENSION_INSTALLED": "Installée",
	"EXTENSION_UPDATE_INSTALLED": "Cette mise à jour d’extension a été téléchargée et va être installée une fois le rechargement de {APP_NAME} effectué.",
	"EXTENSION_SEARCH_PLACEHOLDER": "Rechercher",
	"EXTENSION_MORE_INFO_LINK": "Plus",
	"BROWSE_EXTENSIONS": "Parcourir les extensions",
	"EXTENSION_MANAGER_REMOVE": "Supprimer l’extension",
	"EXTENSION_MANAGER_REMOVE_ERROR": "Impossible de supprimer une ou plusieurs extensions : {0}. {APP_NAME} va être rechargé malgré tout.",
	"EXTENSION_MANAGER_UPDATE": "Mettre à jour l’extension",
	"EXTENSION_MANAGER_UPDATE_ERROR": "Impossible de mettre à jour une ou plusieurs extensions : {0}. {APP_NAME} va être rechargé malgré tout.",
	"MARKED_FOR_REMOVAL": "Marquée pour suppression",
	"UNDO_REMOVE": "Annuler",
	"MARKED_FOR_UPDATE": "Marquée pour mise à jour",
	"UNDO_UPDATE": "Annuler",
	"CHANGE_AND_RELOAD_TITLE": "Modifier les extensions",
	"CHANGE_AND_RELOAD_MESSAGE": "Pour mettre à jour ou supprimer les extensions marquées, {APP_NAME} va devoir être rechargé. Vous serez invité à enregistrer vos modifications.",
	"REMOVE_AND_RELOAD": "Supprimer les extensions et recharger",
	"CHANGE_AND_RELOAD": "Modifier les extensions et recharger",
	"UPDATE_AND_RELOAD": "Mettre à jour les extensions et recharger",
	"PROCESSING_EXTENSIONS": "Traitement des changements d’extension\u2026",
	"EXTENSION_NOT_INSTALLED": "Impossible de supprimer l’extension {0} car elle n’est pas installée.",
	"NO_EXTENSIONS": "Aucune extension installée pour le moment.
Cliquez sur l’onglet Disponibles ci-dessus pour vous lancer.",
	"NO_EXTENSION_MATCHES": "Aucune extension ne correspond à votre recherche.",
	"REGISTRY_SANITY_CHECK_WARNING": "REMARQUE : ces extensions peuvent provenir d’autres sources que l’application {APP_NAME} elle-même. Les extensions ne sont pas contrôlées et disposent de privilèges locaux complets. Soyez prudents lorsque vous installez des extensions provenant d’une source inconnue.",
	"EXTENSIONS_INSTALLED_TITLE": "Installées",
	"EXTENSIONS_AVAILABLE_TITLE": "Disponibles",
	"EXTENSIONS_THEMES_TITLE": "Thèmes",
	"EXTENSIONS_UPDATES_TITLE": "Mises à jour",

	"INLINE_EDITOR_NO_MATCHES": "Aucun résultat.",
	"INLINE_EDITOR_HIDDEN_MATCHES": "Tous les résultats sont réduits. Développez les fichiers dans la liste de droite pour voir le détail.",
	"CSS_QUICK_EDIT_NO_MATCHES": "Aucune règle CSS existante ne correspond à votre sélection.
 Cliquez sur « Nouvelle règle » pour en créer une.",
	"CSS_QUICK_EDIT_NO_STYLESHEETS": "Votre projet ne contient aucune feuille de style.
Créez-en une pour pouvoir ajouter des règles CSS.",

    // Custom Viewers
	"IMAGE_VIEWER_LARGEST_ICON": "maximum",

    /**
     * Unit names
     */

	"UNIT_PIXELS": "pixels",

    // extensions/default/DebugCommands
	"DEBUG_MENU": "Déboguer",
	"ERRORS": "Erreurs",
	"CMD_SHOW_DEV_TOOLS": "Afficher les outils de développement",
	"CMD_REFRESH_WINDOW": "Recharger avec les extensions",
	"CMD_RELOAD_WITHOUT_USER_EXTS": "Recharger sans les extensions",
	"CMD_NEW_BRACKETS_WINDOW": "Nouvelle fenêtre {APP_NAME}",
	"CMD_SWITCH_LANGUAGE": "Changer de langue",
	"CMD_RUN_UNIT_TESTS": "Exécuter des tests",
	"CMD_SHOW_PERF_DATA": "Afficher les données de performances",
	"CMD_ENABLE_NODE_DEBUGGER": "Activer le débogage de nœud",
	"CMD_LOG_NODE_STATE": "Noter l’état du nœud dans la console",
	"CMD_RESTART_NODE": "Redémarrer le nœud",
	"CMD_SHOW_ERRORS_IN_STATUS_BAR": "Afficher les erreurs dans la barre d’état",
	"CMD_OPEN_BRACKETS_SOURCE": "Ouvrir la source Brackets",

	"LANGUAGE_TITLE": "Changer de langue",
	"LANGUAGE_MESSAGE": "Langue :",
	"LANGUAGE_SUBMIT": "Recharger {APP_NAME}",
	"LANGUAGE_CANCEL": "Annuler",
	"LANGUAGE_SYSTEM_DEFAULT": "Langue par défaut du système",

    // extensions/default/InlineTimingFunctionEditor
	"INLINE_TIMING_EDITOR_TIME": "Temps",
	"INLINE_TIMING_EDITOR_PROGRESSION": "Progression",
	"BEZIER_EDITOR_INFO": "↑↓←→ Déplacer le point sélectionné
Maj Déplacer de dix unités
Tabulation Permuter les points",
	"STEPS_EDITOR_INFO": "↑↓ Augmenter ou réduire les pas
←→ 'Démarrer' ou 'Arrêter'",
	"INLINE_TIMING_EDITOR_INVALID": "L’ancienne valeur {0} n’est pas valide et a donc été remplacée par {1} pour la fonction affichée. Le document sera mis à jour à la première modification.",

    // extensions/default/InlineColorEditor
	"COLOR_EDITOR_CURRENT_COLOR_SWATCH_TIP": "Couleur actuelle",
	"COLOR_EDITOR_ORIGINAL_COLOR_SWATCH_TIP": "Couleur d’origine",
	"COLOR_EDITOR_RGBA_BUTTON_TIP": "Format RGBa",
	"COLOR_EDITOR_HEX_BUTTON_TIP": "Format Hex",
	"COLOR_EDITOR_HSLA_BUTTON_TIP": "Format HSLa",
	"COLOR_EDITOR_USED_COLOR_TIP_SINGULAR": "{0} (utilisé {1} fois)",
	"COLOR_EDITOR_USED_COLOR_TIP_PLURAL": "{0} (utilisé {1} fois)",

    // extensions/default/JavaScriptCodeHints
	"CMD_JUMPTO_DEFINITION": "Accéder à la définition",
	"CMD_SHOW_PARAMETER_HINT": "Afficher l’indicateur de paramètre",
	"NO_ARGUMENTS": "<aucun paramètre>",
	"DETECTED_EXCLUSION_TITLE": "Problème d’inférence de fichier Javascript",
	"DETECTED_EXCLUSION_INFO": "Brackets a rencontré des problèmes lors du traitement du fichier {0}.

La fonction d’accès aux définitions, les indicateurs de code et la fonction Edition rapide de ce fichier ne seront plus traités. Pour réactiver ce fichier, ouvrez .brackets.json dans votre projet et éditez la section jscodehints.detectedExclusions.

Il s’agit vraisemblablement d’un bug au niveau de l’application Brackets. Si vous pouvez nous transmettre une copie de ce fichier, merci de consigner un bug en fournissant un lien vers le fichier en question.",

    // extensions/default/JSLint
	"JSLINT_NAME": "JSLint",

    // extensions/default/QuickView
	"CMD_ENABLE_QUICK_VIEW": "Affichage rapide au survol",

    // extensions/default/RecentProjects
	"CMD_TOGGLE_RECENT_PROJECTS": "Projets récents",

    // extensions/default/WebPlatformDocs
	"DOCS_MORE_LINK": "En savoir plus"
});


<!DOCTYPE html>

	<meta charset="utf-8">
	<title>jQuery UI Datepicker Demos</title>
	<link rel="stylesheet" href="../demos.css">

Examples

Default functionality

Format date

Restrict date range

Localize calendar

Populate alternate field

Display inline

Display button bar

Display month & year menus

Dates in other months

Show week of the year

Display multiple months

Icon trigger

Animations

Date Range