Modules (188)

ScopeManager

Description

Dependencies

Functions

TernModule

Encapsulate all the logic to talk to the tern module. This will create a new instance of a TernModule, which the rest of the hinting code can use to talk to the tern node domain, without worrying about initialization, priming the pump, etc.

    function TernModule() {
        var ternPromise         = null,
            addFilesPromise     = null,
            rootTernDir         = null,
            projectRoot         = null,
            stopAddingFiles     = false,
            resolvedFiles       = {},       // file -> resolved file
            numInitialFiles     = 0,
            numResolvedFiles    = 0,
            numAddedFiles       = 0,
            _ternNodeDomain     = null;
Private Public API

_maybeReset

reset the tern module, if necessary.

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 module for each test to start with a clean environment.

session Session
document Document
force boolean
true to force a reset regardless of how long since the last one
Returns: Promise
Promise resolved when the module is ready. The new (or current, if there was no reset) module is passed to the callback.
    function _maybeReset(session, document, force) {
        var newTernModule;
        // if we're in the middle of a reset, don't have to check
        // the new module will be online soon
        if (!resettingDeferred) {

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

                resettingDeferred = new $.Deferred();
                newTernModule = new TernModule();
                newTernModule.handleEditorChange(session, document, null);
                newTernModule.whenReady(function () {
                    // reset the old module
                    currentModule.resetModule();
                    currentModule = newTernModule;
                    resettingDeferred.resolve(currentModule);
                    // all done reseting
                    resettingDeferred = null;
                });
                _hintCount = 0;
            } else {
                var d = new $.Deferred();
                d.resolve(currentModule);
                return d.promise();
            }
        }

        return resettingDeferred.promise();
    }
Private

_postMessageByPass

Send a message to the tern node domain - this is only for messages that need to be sent before and while the addFilesPromise is being resolved.

        function _postMessageByPass(msg) {
            ternPromise.done(function (ternModule) {
                if (config.debug) {
                    console.debug("Sending message", msg);
                }
                _ternNodeDomain.exec("invokeTernCommand", msg);
            });
        }
Private Public API

_readyPromise

Used to avoid timing bugs in unit tests

    function _readyPromise() {
        return deferredPreferences;
    }
Private Public API

_setConfig

    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;
    exports.filterText = filterText;
    exports.postMessage = postMessage;
    exports.addPendingRequest = addPendingRequest;
});

addAllFilesAndSubdirectories

Add the files in the directory and subdirectories of a given directory to tern.

dir string
the root directory to add.
doneCallback function ()
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);
            });
        }

addFilesToTern

Add new files to tern, keeping any previous files. The tern server must be initialized before making this call.

files Array.<string>
array of file to add to tern.
Returns: 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 (ternModule) {
                    var msg = {
                        type        : MessageIds.TERN_ADD_FILES_MSG,
                        files       : files
                    };

                    if (config.debug) {
                        console.debug("Sending message", msg);
                    }
                    _ternNodeDomain.exec("invokeTernCommand", msg);
                });

            } else {
                stopAddingFiles = true;
            }

            return stopAddingFiles;
        }
Public API

addPendingRequest

Add a pending request waiting for the tern-module to complete. If file is a detected exclusion, then reject request.

file string
the name of the file
offset {line: number,ch: number}
the offset into the file the request is for
type string
the type of request
Returns: 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();
    }

canSkipTernInitialization

We can skip tern initialization if we are opening a file that has already been added to tern.

newFile string
full path of new file being opened in the editor.
Returns: boolean
- true if tern initialization should be skipped, false otherwise.
        function canSkipTernInitialization(newFile) {
            return resolvedFiles[newFile] !== undefined;
        }

doEditorChange

Do the work to initialize a code hinting session.

session Session
the active hinting session (TODO: currently unused)
document non-nullable Document
the document the editor has changed to
previousDocument nullable Document
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, document.isUntitled());
                        addFilesDeferred.resolveWith(null, [_ternNodeDomain]);
                    });
                } else {
                    addFilesDeferred.resolveWith(null, [_ternNodeDomain]);
                }

                isDocumentDirty = false;
                return;
            }
            
            if (previousDocument && previousDocument.isDirty) {
                updateTernFile(previousDocument);
            }
    
            isDocumentDirty = false;
            resolvedFiles = {};
            projectRoot = pr;

            ensurePreferences();
            deferredPreferences.done(function () {
                if (file instanceof InMemoryFile) {
                    initTernServer(pr, []);
                    var hintsPromise = primePump(path, true);
                    hintsPromise.done(function () {
                        addFilesDeferred.resolveWith(null, [_ternNodeDomain]);
                    });
                    return;
                }

                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, false);
                        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, false);
                                            addFilesDeferred.resolveWith(null, [_ternNodeDomain]);
                                        });
                                    } else {
                                        addFilesDeferred.resolveWith(null, [_ternNodeDomain]);
                                    }
                                });
                            } else {
                                addFilesDeferred.resolveWith(null, [_ternNodeDomain]);
                            }
                        });
                    });
                });
            });
        }

ensurePreferences

Will initialize preferences only if they do not exist.

    function ensurePreferences() {
        if (!deferredPreferences) {
            initPreferences();
        }
    }
Public API

filterText

check to see if the text we are sending to Tern is too long.

the string
text to check
Returns: 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;
    }

findNameInProject

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);
                    });
                });
            }
        }
Public API

getBuiltins

An array of library names that contain JavaScript builtins definitions.

Returns: Array.<string>
- array of library names.
    function getBuiltins() {
        return builtinLibraryNames;
    }

getDocText

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 node domain. The Promise for getDocumentText() is returned so that custom fail functions can be used.

filePath string
the path of the file to get the text of
Returns: jQuery.Promise
- the Promise returned from DocumentMangaer.getDocumentText()
            function getDocText(filePath) {
                if (!FileSystem.isAbsolutePath(filePath) || // don't handle URLs
                        filePath.slice(0, 2) === "//") { // don't handle protocol-relative URLs like //example.com/main.js (see #10566)
                    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;
            }

getFileInfo

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.

session non-nullable Session
the current session
preventPartialUpdates optional boolean
if true, disallow partial updates. Optional, defaults to false.
Returns: {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;
    }

getFragmentAround

Given a starting and ending position, get a code fragment that is self contained enough to be compiled.

session non-nullable Session
the current session
start {line: number,ch: number}
the starting position of the changes
Returns: {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)};
    }

getJumptoDef

Get a Promise for the definition from TernJS, for the file & offset passed in.

fileInfo {type: string,name: string,offsetLines: number,text: string}
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.
offset {line: number,ch: number}
the offset in the file the hints should be calculate at
Returns: 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);
    }

getOffset

Get the current offset. The offset is adjusted for "part" updates.

session non-nullable Session
the current session
fileInfo {type: string,name: string,offsetLines: number,text: string}
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.
offset optional {line: number, ch: number}
the default offset (optional). Will use the cursor if not provided.
Returns: {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;
    }

getPendingRequest

Get any pending $.Deferred object waiting on the specified file and request type

file string
the file
offset {line: number,ch: number}
the offset into the file the request is for
type string
the type of request
Returns: 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;
        }
    }
Public API

getResolvedPath

file string
a relative path
Returns: string
returns the path we resolved when we tried to parse the file, or undefined
    function getResolvedPath(file) {
        return currentModule.getResolvedPath(file);
    }
Public API

getResolvedPath

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

getTernFunctionType

Get a Promise for the function type from TernJS.

fileInfo {type: string,name: string,offsetLines: number,text: string}
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.
offset {line:number,ch:number}
the line, column info for what we want the function type of.
Returns: 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);
    }
Public API

getTernHints

Get a Promise for the completions from TernJS, for the file & offset passed in.

fileInfo {type: string,name: string,offsetLines: number,text: string}
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.
offset {line: number,ch: number}
the offset in the file the hints should be calculate at
isProperty boolean
true if getting a property hint, otherwise getting an identifier hint.
Returns: jQuery.Promise
- a promise that will resolve to an array of completions when it is done
    function getTernHints(fileInfo, offset, isProperty) {

getTextFromDocument

Get the text of a document, applying any size restrictions if necessary

document Document
the document to get the text from
Returns: 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;
    }
Public API

handleEditorChange

Called each time a new editor becomes active.

session Session
the active hinting session (TODO: currently unused by doEditorChange())
document non-nullable Document
the document of the editor that has changed
previousDocument nullable Document
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);
                });
            }
        }
Public API

handleEditorChange

Called each time a new editor becomes active.

session Session
the active hinting session
document Document
the document of the editor that has changed
previousDocument nullable Document
the document of the editor is changing from
    function handleEditorChange(session, document, previousDocument) {

        if (!currentModule) {
            currentModule = new TernModule();
        }
        
        return currentModule.handleEditorChange(session, document, previousDocument);
    }
Public API

handleFileChange

Called each time the file associated with the active editor changes. Marks the file as being dirty.

from: {line:number,ch: number},to: {line:number,ch: number}
    function handleFileChange(changeList) {
        isDocumentDirty = true;
        trackChange(changeList);
    }

handleGetGuesses

Handle the response from the tern node domain when it responds to the get guesses message.

response {file: string,type: string,offset: {line: number,ch: number},properties: Array.<string>}
the response from node domain 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]);
        }
    }

handleJumptoDef

Handle the response from the tern node domain when it responds with the definition

- response
the response from the node domain
    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]);
        }
    }

handlePrimePumpCompletion

Handle the response from the tern node domain when it responds to the prime pump message.

response {path: string,type: string}
the response from node domain
        function handlePrimePumpCompletion(response) {

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

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

handleProjectClose

Do some cleanup when a project is closed. Clean up previous analysis data from the module

    function handleProjectClose() {
        if (currentModule) {
            currentModule.resetModule();
        }
    }
Public API

handleProjectOpen

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

handleRename

Handle the response from the tern node domain when it responds with the references

- response
the response from the node domain
    function handleRename(response) {

        if (response.error) {
            EditorManager.getActiveEditor().displayErrorMessageAtCursor(response.error);
            return;
        }

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

        var $deferredFindRefs = getPendingRequest(file, offset, MessageIds.TERN_REFS);

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

handleScopeData

Handle the response from the tern node domain when it responds with the scope data

- response
the response from the node domain
    function handleScopeData(response) {
        var file = response.file,
            offset = response.offset;

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

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

handleTernCompletions

Handle the response from the tern node domain when it responds with the list of completions

response {file: string,offset: {line: number,ch: number},completions:Array.<string>,properties:Array.<string>}
the response from node domain
    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]);
            }
        }
    }

handleTernGetFile

Handle a request from the tern node domain for text of a file

request {file:string}
the request from the tern node domain. 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;

handleTimedOut

Handle timed out inference

response {path: string,type: string}
the response from node domain
    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
                }
            ]
        );
    }
    
    DocumentManager.on("dirtyFlagChange", function (event, changedDoc) {
        if (changedDoc.file.fullPath) {
            postMessage({
                type: MessageIds.TERN_UPDATE_DIRTY_FILE,
                name: changedDoc.file.fullPath,
                action: changedDoc.isDirty
            });
        }
    });

    // Clear dirty document list in tern node domain
    ProjectManager.on("beforeProjectClose", function () {
        postMessage({
            type: MessageIds.TERN_CLEAR_DIRTY_FILES_LIST
        });
    });

handleUpdateFile

Handle the response from the tern node domain when it responds to the update file message.

response {path: string,type: string}
the response from node domain
    function handleUpdateFile(response) {

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

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

initPreferences

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

initTernEnv

Read in the json files that have type information for the builtins, dom,etc

    function initTernEnv() {
        var path = [_absoluteModulePath, "node/node_modules/tern/defs/"].join("/"),
            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();

initTernModule

Init the Tern module that does all the code hinting work.

        function initTernModule() {
            var moduleDeferred = $.Deferred();
            ternPromise = moduleDeferred.promise();
            
            function prepareTern() {  
                _ternNodeDomain.exec("setInterface", {
                    messageIds : MessageIds
                });

                _ternNodeDomain.exec("invokeTernCommand", {
                    type: MessageIds.SET_CONFIG,
                    config: config
                });
                moduleDeferred.resolveWith(null, [_ternNodeDomain]);
            }
            
            if (_ternNodeDomain) {
                _ternNodeDomain.exec("resetTernServer");
                moduleDeferred.resolveWith(null, [_ternNodeDomain]);
            } else {
                _ternNodeDomain     = new NodeDomain("TernNodeDomain", _domainPath);
                _ternNodeDomain.on("data", function (evt, data) {
                    if (config.debug) {
                        console.log("Message received", data.type);
                    }

                    var response = data,
                        type = response.type;

                    if (type === MessageIds.TERN_COMPLETIONS_MSG ||
                            type === MessageIds.TERN_CALLED_FUNC_TYPE_MSG) {
                        // handle any completions the tern server 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_SCOPEDATA_MSG) {
                        handleScopeData(response);
                    } else if (type === MessageIds.TERN_REFS) {
                        handleRename(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) {
                        moduleDeferred.resolveWith(null, [_ternNodeDomain]);
                    } else if (type === "RE_INIT_TERN") {
                        // Ensure the request is because of a node restart
                        if (currentModule) {
                            prepareTern();
                            // Mark the module with resetForced, then creation of TernModule will 
                            // happen again as part of '_maybeReset' call
                            currentModule.resetForced = true;
                        }
                    } else {
                        console.log("Tern Module: " + (response.log || response));
                    }
                });
            
                _ternNodeDomain.promise().done(prepareTern);
            }
        }

initTernServer

Create a new tern server.

        function initTernServer(dir, files) {
            initTernModule();
            numResolvedFiles = 0;
            numAddedFiles = 0;
            stopAddingFiles = false;
            numInitialFiles = files.length;

            ternPromise.done(function (ternModule) {
                var msg = {
                    type        : MessageIds.TERN_INIT_MSG,
                    dir         : dir,
                    files       : files,
                    env         : ternEnvironment,
                    timeout     : PreferencesManager.get("jscodehints.inferenceTimeout")
                };
                _ternNodeDomain.exec("invokeTernCommand", msg);
            });
            rootTernDir = dir + "/";
        }

isDirectoryExcluded

Test if the directory should be excluded from analysis.

path non-nullable string
full directory path.
Returns: 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);
    }

isFileBeingEdited

Test if the file path is in current editor

filePath string
file path to test for exclusion.
Returns: boolean
true if in editor, false otherwise.
    function isFileBeingEdited(filePath) {
        var currentEditor   = EditorManager.getActiveEditor(),
            currentDoc      = currentEditor && currentEditor.document;

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

isFileExcluded

Test if the file should be excluded from analysis.

file non-nullable File
file to test for exclusion.
Returns: 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;
    }

isFileExcludedInternal

Test if the file path is an internal exclusion.

path string
file path to test for exclusion.
Returns: 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;
    }
Public API

postMessage

Send a message to the tern module - if the module is being initialized, the message will not be posted until initialization is complete

    function postMessage(msg) {
        if (currentModule) {
            currentModule.postMessage(msg);
        }
    }
Public API

postMessage

Send a message to the tern node domain - if the module is being initialized, the message will not be posted until initialization is complete

        function postMessage(msg) {
            addFilesPromise.done(function (ternModule) {
                // If an error came up during file handling, bail out now
                if (!_ternNodeDomain) {
                    return;
                }

                if (config.debug) {
                    console.debug("Sending message", msg);
                }
                _ternNodeDomain.exec("invokeTernCommand", msg);
            });
        }

primePump

Prime the pump for a fast first lookup.

path string
full path of file
Returns: jQuery.Promise
- the promise for the request
        function primePump(path, isUntitledDoc) {
            _postMessageByPass({
                type            : MessageIds.TERN_PRIME_PUMP_MSG,
                path            : path,
                isUntitledDoc   : isUntitledDoc
            });

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

requestGuesses

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.

session Session
the active hinting session
document Document
the document for which scope info is desired
Returns: 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();
    }
Public API

requestHints

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().

session Session
the active hinting session
document Document
the document for which scope info is desired
Returns: 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();
    }
Public API

requestJumptoDef

Request Jump-To-Definition from Tern.

session session
the session
document Document
the document
offset {line: number,ch: number}
the offset into the document
Returns: 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};
    }
Public API

requestParameterHint

Request a parameter hint from Tern.

session Session
the active hinting session
functionOffset {line: number,ch: number}
the offset of the function call.
Returns: 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();
    }

resetModule

Do some cleanup when a project is closed.

We can clean up the node tern server we use to calculate hints now, since we know we will need to re-init it in any new project that is opened.

        function resetModule() {
            function resetTernServer() {
                if (_ternNodeDomain.ready()) {
                    _ternNodeDomain.exec('resetTernServer');
                }
            }
            
            if (_ternNodeDomain) {
                if (addFilesPromise) {
                    // If we're in the middle of added files, don't reset 
                    // until we're done
                    addFilesPromise.done(resetTernServer).fail(resetTernServer);
                } else {
                    resetTernServer();
                }
            }
        }

        function whenReady(func) {
            addFilesPromise.done(func);
        }
        
        this.resetModule = resetModule;
        this.handleEditorChange = handleEditorChange;
        this.postMessage = postMessage;
        this.getResolvedPath = getResolvedPath;
        this.whenReady = whenReady;

        return this;
    }

    var resettingDeferred = null;

trackChange

Track the update area of the current document so we can tell if we can send partial updates to tern or not.

changeList Array.<{from: {line:number,ch: number},to: {line:number,ch: number},text: Array<string>}>
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;
            }
        }
    }

updateTernFile

Update tern with the new contents of a given file.

document Document
the document to update
Returns: 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);
        }

usingModules

Determine whether the current set of files are using modules to pull in additional files.

Returns: boolean
- true if more files than the current directory have been read in.
        function usingModules() {
            return numInitialFiles !== numResolvedFiles;
        }