function extractAllSelectors(text, documentMode) {
        var state, lines, lineCount,
            token, style, stream, line,
            selectors              = [],
            mode                   = CodeMirror.getMode({indentUnit: 2}, documentMode || "css"),
            currentSelector        = "",
            currentLevel           = 0,
            ruleStartChar          = -1,
            ruleStartLine          = -1,
            selectorStartChar      = -1,
            selectorStartLine      = -1,
            selectorGroupStartLine = -1,
            selectorGroupStartChar = -1,
            declListStartLine      = -1,
            declListStartChar      = -1,
            escapePattern          = new RegExp("\\\\[^\\\\]+", "g"),
            validationPattern      = new RegExp("\\\\([a-f0-9]{6}|[a-f0-9]{4}(\\s|\\\\|$)|[a-f0-9]{2}(\\s|\\\\|$)|.)", "i"),
            _parseRuleList;

        // implement _firstToken()/_nextToken() methods to
        // provide a single stream of tokens

        function _hasStream() {
            while (stream.eol()) {
                line++;
                if (line >= lineCount) {
                    return false;
                }
                if (_hasNonWhitespace(currentSelector)) {
                    // If we are in a current selector and starting a newline,
                    // make sure there is whitespace in the selector
                    currentSelector += " ";
                }
                stream = new CodeMirror.StringStream(lines[line]);
            }
            return true;
        }

        function _firstToken() {
            state = CodeMirror.startState(mode);
            lines = CodeMirror.splitLines(text);
            lineCount = lines.length;
            if (lineCount === 0) {
                return false;
            }
            line = 0;
            stream = new CodeMirror.StringStream(lines[line]);
            if (!_hasStream()) {
                return false;
            }
            style = mode.token(stream, state);
            token = stream.current();
            return true;
        }

        function _nextToken() {
            // advance the stream past this token
            stream.start = stream.pos;
            if (!_hasStream()) {
                return false;
            }
            style = mode.token(stream, state);
            token = stream.current();
            return true;
        }

        function _firstTokenSkippingWhitespace() {
            if (!_firstToken()) {
                return false;
            }
            while (!_hasNonWhitespace(token)) {
                if (!_nextToken()) {
                    return false;
                }
            }
            return true;
        }

        function _nextTokenSkippingWhitespace() {
            if (!_nextToken()) {
                return false;
            }
            while (!_hasNonWhitespace(token)) {
                if (!_nextToken()) {
                    return false;
                }
            }
            return true;
        }

        function _isStartComment() {
            // Also check for line comments used in LESS and SASS.
            return (/^\/[\/\*]/.test(token));
        }

        function _parseComment() {
            // If it is a line comment, then do nothing and just return. Unlike block
            // comment, a line comment is just one single token and the caller always
            // has to find the next token by skipping the current token. So leaving
            // it for the caller to skip the current token.
            if (/^\/\//.test(token)) {
                return;
            }
            while (!/\*\/$/.test(token)) {
                if (!_nextToken()) {
                    break;
                }
            }
        }

        function _nextTokenSkippingComments() {
            if (!_nextToken()) {
                return false;
            }
            while (_isStartComment()) {
                _parseComment();
                if (!_nextToken()) {
                    return false;
                }
            }
            return true;
        }

        function _skipToClosingBracket(startChar) {
            var skippedText = "",
                unmatchedBraces = 0;
            if (!startChar) {
                startChar = "{";
            }
            while (true) {
                if (token.indexOf(startChar) !== -1 && token.indexOf(_bracketPairs[startChar]) === -1) {
                    // Found an opening bracket but not the matching closing bracket in the same token
                    unmatchedBraces++;
                } else if (token === _bracketPairs[startChar]) {
                    unmatchedBraces--;
                    if (unmatchedBraces <= 0) {
                        skippedText += token;
                        return skippedText;
                    }
                }
                skippedText += token;

                if (!_nextTokenSkippingComments()) {
                    return skippedText; // eof
                }
            }
        }

        function _maybeProperty() {
            return (/^-(moz|ms|o|webkit)-$/.test(token) ||
                    (state.state !== "top" && state.state !== "block" && state.state !== "pseudo" &&
                    // Has a semicolon as in "rgb(0,0,0);", but not one of those after a LESS
                    // mixin parameter variable as in ".size(@width; @height)"
                    stream.string.indexOf(";") !== -1 && !/\([^)]+;/.test(stream.string)));
        }

        function _skipProperty() {
            var prevToken = "";
            while (token !== ";") {
                // Skip tokens until the closing brace if we find an interpolated variable.
                if (/[#@]\{$/.test(token) || (token === "{" && /[#@]$/.test(prevToken))) {
                    _skipToClosingBracket("{");
                    if (token === "}") {
                        _nextToken();   // Skip the closing brace
                    }
                    if (token === ";") {
                        break;
                    }
                }
                // If there is a '{' or '}' before the ';',
                // then stop skipping.
                if (token === "{" || token === "}") {
                    return false;   // can't tell if the entire property is skipped
                }
                prevToken = token;
                if (!_nextTokenSkippingComments()) {
                    break;
                }
            }
            return true;    // skip the entire property
        }

        function _getParentSelectors() {
            var j;
            for (j = selectors.length - 1; j >= 0; j--) {
                if (selectors[j].declListEndLine === -1 && selectors[j].level < currentLevel) {
                    return getCompleteSelectors(selectors[j], true);
                }
            }
            return "";
        }

        function _parseSelector(start, level) {

            currentSelector = "";
            selectorStartChar = start;
            selectorStartLine = line;

            // Everything until the next ',' or '{' is part of the current selector
            while ((token !== "," && token !== "{") ||
                    (token === "{" && /[#@]$/.test(currentSelector)) ||
                    (token === "," && !_hasNonWhitespace(currentSelector))) {
                if (token === "{") {
                    // Append the interpolated variable to selector
                    currentSelector += _skipToClosingBracket("{");
                    _nextToken();  // skip the closing brace
                } else if (token === "}" &&
                        (!currentSelector || /:\s*\S/.test(currentSelector) || !/[#@]\{.+/.test(currentSelector))) {
                    // Either empty currentSelector or currentSelector is a CSS property
                    // but not a selector that is in the form of #{$class} or @{class}
                    return false;
                }
                // Clear currentSelector if we're in a property, but make sure we don't treat
                // the semicolors inside a parameter as a property separators.
                if ((token === ";" && state.state !== "parens") ||
                        // Make sure that something like `> li > a {` is not identified as a property
                        (state.state === "prop" && !/\{/.test(stream.string))) {
                    currentSelector = "";
                } else if (token === "(") {
                    // Collect everything inside the parentheses as a whole chunk so that
                    // commas inside the parentheses won't be identified as selector separators
                    // by while loop.
                    if (_hasNonWhitespace(currentSelector)) {
                        currentSelector += _skipToClosingBracket("(");
                    } else {
                        // Nothing in currentSelector yet. Skip to the closing parenthesis
                        // without collecting the selector since a selector cannot start with
                        // an opening parenthesis.
                        _skipToClosingBracket("(");
                    }
                } else if (_hasNonWhitespace(token) || _hasNonWhitespace(currentSelector)) {
                    currentSelector += token;
                }
                if (!_nextTokenSkippingComments()) {
                    return false; // eof
                }
            }

            if (!currentSelector) {
                return false;
            }

            // Unicode character replacement as defined in http://www.w3.org/TR/CSS21/syndata.html#characters
            if (/\\/.test(currentSelector)) {
                // Double replace in case of pattern overlapping (regex improvement?)
                currentSelector = currentSelector.replace(escapePattern, function (escapedToken) {
                    return escapedToken.replace(validationPattern, function (unicodeChar) {
                        unicodeChar = unicodeChar.substr(1);
                        if (unicodeChar.length === 1) {
                            return unicodeChar;
                        } else {
                            if (parseInt(unicodeChar, 16) < 0x10FFFF) {
                                return String.fromCharCode(parseInt(unicodeChar, 16));
                            } else { return String.fromCharCode(0xFFFD); }
                        }
                    });
                });
            }

            currentSelector = currentSelector.trim();
            var startChar = (selectorGroupStartLine === -1) ? selectorStartChar : selectorStartChar + 1;
            var selectorStart = (stream.string.indexOf(currentSelector, selectorStartChar) !== -1) ? stream.string.indexOf(currentSelector, selectorStartChar - currentSelector.length) : startChar;

            if (currentSelector !== "") {
                if (currentLevel < level) {
                    currentLevel++;
                }
                if (ruleStartLine === -1) {
                    ruleStartLine = line;
                    ruleStartChar = stream.start - currentSelector.length;
                }
                var parentSelectors = _getParentSelectors();
                selectors.push({selector: currentSelector,
                                ruleStartLine: ruleStartLine,
                                ruleStartChar: ruleStartChar,
                                selectorStartLine: selectorStartLine,
                                selectorStartChar: selectorStart,
                                declListEndLine: -1,
                                selectorEndLine: line,
                                selectorEndChar: selectorStart + currentSelector.length,
                                selectorGroupStartLine: selectorGroupStartLine,
                                selectorGroupStartChar: selectorGroupStartChar,
                                level: currentLevel,
                                parentSelectors: parentSelectors
                               });
                currentSelector = "";
            }
            selectorStartChar = -1;

            return true;
        }

        function _parseSelectorList(level) {
            selectorGroupStartLine = (stream.string.indexOf(",") !== -1) ? line : -1;
            selectorGroupStartChar = stream.start;

            if (!_parseSelector(stream.start, level)) {
                return false;
            }

            while (token === ",") {
                if (!_nextTokenSkippingComments()) {
                    return false; // eof
                }
                if (!_parseSelector(stream.start, level)) {
                    return false;
                }
            }

            return true;
        }

        function _parseDeclarationList(level) {

            var j;
            declListStartLine = Math.min(line, lineCount - 1);
            declListStartChar = stream.start;

            // Extract the entire selector group we just saw.
            var selectorGroup, sgLine;
            if (selectorGroupStartLine !== -1) {
                selectorGroup = "";
                for (sgLine = selectorGroupStartLine; sgLine <= declListStartLine; sgLine++) {
                    var startChar = 0, endChar = lines[sgLine].length;
                    if (sgLine === selectorGroupStartLine) {
                        startChar = selectorGroupStartChar;
                    } else {
                        selectorGroup += " "; // replace the newline with a single space
                    }
                    if (sgLine === declListStartLine) {
                        endChar = declListStartChar;
                    }
                    selectorGroup += lines[sgLine].substring(startChar, endChar).trim();
                }
                selectorGroup = selectorGroup.trim();
            }

            // assign this declaration list position and selector group to every selector on the stack
            // that doesn't have a declaration list start and end line
            for (j = selectors.length - 1; j >= 0; j--) {
                if (selectors[j].level === level) {
                    if (selectors[j].declListEndLine !== -1) {
                        break;
                    } else {
                        selectors[j].declListStartLine = declListStartLine;
                        selectors[j].declListStartChar = declListStartChar;
                        if (selectorGroup) {
                            selectors[j].selectorGroup = selectorGroup;
                        }
                    }
                }
            }

            var nested = true;
            do {
                // Since we're now in a declaration list, that means we also finished
                // parsing the whole selector group. Therefore, reset selectorGroupStartLine
                // so that next time we parse a selector we know it's a new group
                selectorGroupStartLine = -1;
                selectorGroupStartChar = -1;
                ruleStartLine = -1;
                ruleStartChar = -1;

                if (!nested) {
                    if (currentLevel > 0 && currentLevel === level) {
                        currentLevel--;
                        // Skip past '}'
                        if (token === "}") {
                            _nextTokenSkippingWhitespace();
                        }
                    }
                }
                // Skip past '{' before parsing nested rule list.
                if (token === "{") {
                    _nextTokenSkippingWhitespace();
                }
                nested = _parseRuleList(undefined, currentLevel + 1);

                // assign this declaration list position to every selector on the stack
                // that doesn't have a declaration list end line
                for (j = selectors.length - 1; j >= 0; j--) {
                    if (selectors[j].level < currentLevel) {
                        break;
                    }
                    if (selectors[j].declListEndLine === -1) {
                        selectors[j].declListEndLine = line;
                        selectors[j].declListEndChar = stream.pos - 1; // stream.pos actually points to the char after the }
                    }
                }
            } while (currentLevel > 0 && currentLevel === level);
        }

        function includeCommentInNextRule() {
            if (ruleStartChar !== -1) {
                return false;       // already included
            }
            if (stream.start > 0 && lines[line].substr(0, stream.start).indexOf("}") !== -1) {
                return false;       // on same line as '}', so it's for previous rule
            }
            return true;
        }

        function _isStartAtRule() {
            // Exclude @mixin from at-rule so that we can parse it like a normal rule list
            return (/^@/.test(token) && !/^@mixin/i.test(token) && token !== "@");
        }

        function _followedByPseudoSelector() {
            return (/\}:(enabled|disabled|checked|indeterminate|link|visited|hover|active|focus|target|lang|root|nth-|first-|last-|only-|empty|not)/.test(stream.string));
        }

        function _isVariableInterpolatedProperty() {
            return (/[@#]\{\S+\}(\s*:|.*;)/.test(stream.string) && !_followedByPseudoSelector());
        }

        function _parseAtRule(level) {

            // reset these fields to ignore comments preceding @rules
            ruleStartLine = -1;
            ruleStartChar = -1;
            selectorStartLine = -1;
            selectorStartChar = -1;
            selectorGroupStartLine = -1;
            selectorGroupStartChar = -1;

            if (/@media/i.test(token)) {
                // @media rule holds a rule list

                // Skip everything until the opening '{'
                while (token !== "{") {
                    if (!_nextTokenSkippingComments()) {
                        return; // eof
                    }
                }

                // skip past '{', to next non-ws token
                if (!_nextTokenSkippingWhitespace()) {
                    return; // eof
                }

                if (currentLevel <= level) {
                    currentLevel++;
                }

                // Parse rules until we see '}'
                // Treat media rule as one nested level by
                // calling _parseRuleList with next level.
                _parseRuleList("}", currentLevel + 1);

                if (currentLevel > 0) {
                    currentLevel--;
                }

            } else {
                // This code handles @rules in this format:
                //   @rule ... ;
                // Or any less variable that starts with @var ... ;
                // Skip everything until the next ';'
                while (token !== ";") {
                    // This code handle @rules that use this format:
                    //    @rule ... { ... }
                    // such as @page, @keyframes (also -webkit-keyframes, etc.), and @font-face.
                    // Skip everything including nested braces until the next matching '}'
                    if (token === "{") {
                        _skipToClosingBracket("{");
                        return;
                    }
                    if (!_nextTokenSkippingComments()) {
                        return; // eof
                    }
                }
            }
        }

        // parse a style rule
        function _parseRule(level) {
            if (!_parseSelectorList(level)) {
                return false;
            }

            _parseDeclarationList(level);
            return true;
        }

        _parseRuleList = function (escapeToken, level) {
            while ((!escapeToken) || token !== escapeToken) {
                if (_isVariableInterpolatedProperty()) {
                    if (!_skipProperty()) {
                        // We found a "{" or "}" while skipping a property. Return false to handle the
                        // opening or closing of a block properly.
                        return false;
                    }
                } else if (_isStartAtRule()) {
                    // @rule
                    _parseAtRule(level);
                } else if (_isStartComment()) {
                    // comment - make this part of style rule
                    if (includeCommentInNextRule()) {
                        ruleStartChar = stream.start;
                        ruleStartLine = line;
                    }
                    _parseComment();
                } else if (_maybeProperty()) {
                    // Skip the property.
                    if (!_skipProperty()) {
                        // We found a "{" or "}" while skipping a property. Return false to handle the
                        // opening or closing of a block properly.
                        return false;
                    }
                } else {
                    // Otherwise, it's style rule
                    if (!_parseRule(level === undefined ? 0 : level) && level > 0) {
                        return false;
                    }
                    if (level > 0) {
                        return true;
                    }
                    // Clear ruleStartChar and ruleStartLine in case we have a comment
                    // at the end of previous rule in level 0.
                    ruleStartChar = -1;
                    ruleStartLine = -1;
                }

                if (!_nextTokenSkippingWhitespace()) {
                    break;
                }
            }

            return true;
        };

        // Do parsing

        if (_firstTokenSkippingWhitespace()) {

            // Style sheet is a rule list
            _parseRuleList();
        }

        return selectors;
    }
Extracts all CSS selectors from the given text
Returns an array of SelectorInfo. Each SelectorInfo is an object with the following properties:
         selector:                 the text of the selector (note: comma separated selector groups like
                                   "h1, h2" are broken into separate selectors)
         ruleStartLine:            line in the text where the rule (including preceding comment) appears
         ruleStartChar:            column in the line where the rule (including preceding comment) starts
         selectorStartLine:        line in the text where the selector appears
         selectorStartChar:        column in the line where the selector starts
         selectorEndLine:          line where the selector ends
         selectorEndChar:          column where the selector ends
         selectorGroupStartLine:   line where the comma-separated selector group (e.g. .foo, .bar, .baz)
                                   starts that this selector (e.g. .baz) is part of. Particularly relevant for
                                   groups that are on multiple lines.
         selectorGroupStartChar:   column in line where the selector group starts.
         selectorGroup:            the entire selector group containing this selector, or undefined if there
                                   is only one selector in the rule.
         declListStartLine:        line where the declaration list for the rule starts
         declListStartChar:        column in line where the declaration list for the rule starts
         declListEndLine:          line where the declaration list for the rule ends
         declListEndChar:          column in the line where the declaration list for the rule ends
         level:                    the level of the current selector including any containing @media block in the
                                   nesting level count. Use this property with caution since it is primarily for internal
                                   parsing use. For example, two sibling selectors may have different levels if one
                                   of them is nested inside an @media block and it should not be used for sibling info.
         parentSelectors:          all ancestor selectors separated with '/' if the current selector is a nested one
@param {!string} text CSS text to extract from
@param {?string} documentMode language mode of the document that text belongs to, default to css if undefined.
@return {Array.<SelectorInfo>} Array with objects specifying selectors.