function getTagInfo(editor, constPos, isHtmlMode) {
        // We're going to be changing pos a lot, but we don't want to mess up
        // the pos the caller passed in so we use extend to make a safe copy of it.
        var pos = $.extend({}, constPos),
            ctx = TokenUtils.getInitialContext(editor._codeMirror, pos),
            offset = TokenUtils.offsetInToken(ctx),
            tagInfo,
            tokenType;

        // Check if this is not known to be in html mode and inside a style block.
        if (!isHtmlMode && editor.getModeForSelection() !== "html") {
            return createTagInfo();
        }

        // Check and see where we are in the tag
        if (ctx.token.string.length > 0 && !/\S/.test(ctx.token.string)) {

            // token at (i.e. before) pos is whitespace, so test token at next pos
            //
            // note: getTokenAt() does range checking for ch. If it detects that ch is past
            // EOL, it uses EOL, same token is returned, and the following condition fails,
            // so we don't need to worry about testPos being valid.
            var testPos = {ch: ctx.pos.ch + 1, line: ctx.pos.line},
                testToken = editor._codeMirror.getTokenAt(testPos, true);

            if (testToken.string.length > 0 && /\S/.test(testToken.string) &&
                    testToken.string.charAt(0) !== ">") {
                // pos has whitespace before it and non-whitespace after it, so use token after
                ctx.token = testToken;

                // Check whether the token type is one of the types prefixed with "tag"
                // (e.g. "tag", "tag error", "tag brackets")
                if (tagPrefixedRegExp.test(ctx.token.type)) {
                    // Check to see if the cursor is just before a "<" but not in any tag.
                    if (ctx.token.string.charAt(0) === "<") {
                        return createTagInfo();
                    }
                } else if (ctx.token.type === "attribute") {
                    // Check to see if the user is going to add a new attr before an existing one
                    return _getTagInfoStartingFromAttrName(ctx, false);
                } else if (ctx.token.string === "=") {
                    // We're between a whitespace and  "=", so return an empty tag info.
                    return createTagInfo();
                }
            } else {
                // We get here if ">" or white spaces after testPos.
                // Check if there is an equal sign after testPos by creating a new ctx
                // with the original pos. We can't use the current ctx since we need to
                // use it to scan backwards if we don't find an equal sign here.
                // Comment out this block to fix issue #1510.
//                if (testToken.string.length > 0 && testToken.string.charAt(0) !== ">") {
//                    tempCtx = TokenUtils.getInitialContext(editor._codeMirror, pos);
//                    if (TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, tempCtx) && tempCtx.token.string === "=") {
//                        // Return an empty tag info since we're between an atribute name and the equal sign.
//                        return createTagInfo();
//                    }
//                }

                // next, see what's before pos
                if (!TokenUtils.movePrevToken(ctx)) {
                    return createTagInfo();
                }

                if (ctx.token.type === "comment") {
                    return createTagInfo();
                } else if (!tagPrefixedRegExp.test(ctx.token.type) && ctx.token.string !== "=") {
                    // If it wasn't the tag name, assume it was an attr value
                    // Also we don't handle the "=" here.
                    tagInfo = _getTagInfoStartingFromAttrValue(ctx);

                    // Check to see if this is the closing of a tag (either the start or end)
                    // or a comment tag.
                    if (ctx.token.type === "comment" ||
                            (tagPrefixedRegExp.test(ctx.token.type) &&
                            (ctx.token.string === ">" || ctx.token.string === "/>" ||
                                ctx.token.string === "</"))) {
                        return createTagInfo();
                    }

                    // If it wasn't an attr value, assume it was an empty attr (ie. attr with no value)
                    if (!tagInfo.tagName) {
                        tagInfo = _getTagInfoStartingFromAttrName(ctx, true);
                    }

                    // We don't want to give context for the previous attr
                    // and we want it to look like the user is going to add a new attr
                    if (tagInfo.tagName) {
                        return createTagInfo(ATTR_NAME, 0, tagInfo.tagName);
                    }
                    return createTagInfo();
                }

                // We know the tag was here, so the user is adding an attr name
                tokenType = ATTR_NAME;
                offset = 0;
            }
        }

        if (tagPrefixedRegExp.test(ctx.token.type)) {
            if (ctx.token.type !== "tag bracket") {
                // Check if the user just typed a white space after "<" that made an existing tag invalid.
                if (TokenUtils.movePrevToken(ctx) && !/\S/.test(ctx.token.string)) {
                    return createTagInfo();
                }

                // Check to see if this is a closing tag
                if (ctx.token.type === "tag bracket" && ctx.token.string === "</") {
                    tokenType = CLOSING_TAG;
                }

                // Restore the original ctx by moving back to next context since we call
                // movePrevToken above to detect "<" or "</".
                TokenUtils.moveNextToken(ctx);
            }

            // Check to see if this is the closing of a start tag or a self closing tag
            if (ctx.token.string === ">" || ctx.token.string === "/>") {
                return createTagInfo();
            }

            // Make sure the cursor is not after an equal sign or a quote before we report the context as a tag.
            if (ctx.token.string !== "=" && ctx.token.string.match(/^["']/) === null) {
                if (!tokenType) {
                    tokenType = TAG_NAME;
                    if (ctx.token.type === "tag bracket") {
                        // Check to see if this is a closing tag
                        if (ctx.token.string === "</") {
                            tokenType = CLOSING_TAG;
                            offset -= 2;
                        } else {
                            offset = 0;
                        }
                        // If the cursor is right after the "<" or "</", then
                        // move context to next one so that _extractTagName
                        // call below can get the tag name if there is one.
                        if (offset === 0) {
                            TokenUtils.moveNextToken(ctx);
                        }
                    }
                }

                // We're actually in the tag, just return that as we have no relevant
                // info about what attr is selected
                return createTagInfo(tokenType, offset, _extractTagName(ctx));
            }
        }

        if (ctx.token.string === "=") {
            // We could be between the attr and the value
            // Step back and check
            if (!TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx) || ctx.token.type !== "attribute") {
                return createTagInfo();
            }

            // The "=" is added, time to hint for values
            tokenType = ATTR_VALUE;
            offset = 0;
        }

        if (ctx.token.type === "attribute") {
            tagInfo = _getTagInfoStartingFromAttrName(ctx, false);

            // If we're in attr value, then we may need to calculate the correct offset
            // from the beginning of the attribute value. If the cursor position is to
            // the left of attr value, then the offset is negative.
            // e.g. if the cursor is just to the right of the "=" in <a rel= "rtl", then
            // the offset will be -2.
            if (tagInfo.attr.quoteChar) {
                offset = constPos.ch - ctx.pos.ch;
            } else if (tokenType === ATTR_VALUE && (constPos.ch + 1) < ctx.pos.ch) {
                // The cursor may be right before an unquoted attribute or another attribute name.
                // Since we can't distinguish between them, we will empty the value so that the
                // caller can just insert a new attribute value.
                tagInfo.attr.value = "";
            }
        } else {
            // if we're not at a tag, "=", or attribute name, assume we're in the value
            tagInfo = _getTagInfoStartingFromAttrValue(ctx);
        }

        if (tokenType && tagInfo.tagName) {
            tagInfo.position.tokenType = tokenType;
            tagInfo.position.offset = offset;
        }

        return tagInfo;
    }
Figure out if we're in a tag, and if we are return info about it
An example token stream for this tag is <span id="open-files-disclosure-arrow"></span> :
     className:tag       string:"<span"
     className:          string:" "
     className:attribute string:"id"
     className:          string:"="
     className:string    string:""open-files-disclosure-arrow""
     className:tag       string:"></span>"
@param {Editor} editor An instance of a Brackets editor
@param {{ch: number, line: number}} constPos  A CM pos (likely from editor.getCursorPos())
@param {isHtmlMode:boolean} let the module know we are in html mode
@return {{tagName:string,
          attr:{name:string, value:string, valueAssigned:boolean, quoteChar:string, hasEndQuote:boolean},
          position:{tokenType:string, offset:number}
        }}
        A tagInfo object with some context about the current tag hint.