CodeMirror.defineMode("tsql", function (config, parserConfig) {
    var indentUnit = 2,
      pkeywords = parserConfig.pkeywords || {},
      skeywords = parserConfig.skeywords || {},
      sobjs = parserConfig.sobjs || {},
      blockKeywords = parserConfig.blockKeywords || {},
      bendKeywords = parserConfig.bendKeywords || {},
      atoms = parserConfig.atoms || {},
      hooks = parserConfig.hooks || {},
      multiLineStrings = parserConfig.multiLineStrings;
    var isOperatorChar = /[+\-*&%=<>!?|\/]/;

    var curPunc;

    function tokenBase(stream, state) {
        var ch = stream.next();
        if (hooks[ch]) {
            var result = hooks[ch](stream, state);
            if (result !== false) return result;
        }
        if (ch == '"' || ch == "'") {
            state.tokenize = tokenString(ch);
            return state.tokenize(stream, state);
        }
        if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
            curPunc = ch;
            return null
        }
        if (/\d/.test(ch)) {
            stream.eatWhile(/[\w\.]/);
            return "number";
        }
        if (ch == "/") {
            if (stream.eat("*")) {
                state.tokenize = tokenComment;
                return tokenComment(stream, state);
            }
        }
        if (ch == "-") {
            if (stream.eat("-")) {
                stream.skipToEnd();
                return "comment";
            }
        }
        if (isOperatorChar.test(ch)) {
            stream.eatWhile(isOperatorChar);
            return "operator";
        }
        stream.eatWhile(/[\w\$_]/);
        var cur = stream.current().toUpperCase();
        if (cur == "GO") {
            curPunc = "GO";
            return "skeyword"
        }
        if (bendKeywords.propertyIsEnumerable(cur)) {
            curPunc = cur;
            return "pkeyword";
        }
        if (pkeywords.propertyIsEnumerable(cur)) {
            if (blockKeywords.propertyIsEnumerable(cur)) {
                curPunc = "block";
            } else { curPunc = null; }
            return "pkeyword";
        }
        if (skeywords.propertyIsEnumerable(cur)) {
            return "skeyword";
        }
        if (sobjs.propertyIsEnumerable(cur)) {
            return "sobj";
        }
        if (atoms.propertyIsEnumerable(cur)) return "atom";
        return "word";
    }

    function tokenString(quote) {
        return function (stream, state) {
            var escaped = false, next, end = false;
            while ((next = stream.next()) != null) {
                if (next == quote && !escaped) { end = true; break; }
                escaped = !escaped && next == "\\";
            }
            if (end || !(escaped || multiLineStrings))
                state.tokenize = tokenBase;
            return "string";
        };
    }

    function tokenComment(stream, state) {
        var maybeEnd = false, ch;
        while (ch = stream.next()) {
            if (ch == "/" && maybeEnd) {
                state.tokenize = tokenBase;
                break;
            }
            maybeEnd = (ch == "*");
        }
        return "comment";
    }

    function Context(indented, column, type, align, prev) {
        this.indented = indented;
        this.column = column;
        this.type = type;
        this.align = align;
        this.prev = prev;
    }
    function pushContext(state, col, type) {
        return state.context = new Context(state.indented, col, type, null, state.context);
    }
    function popContext(state) {
        var t = state.context.type;
        if (t == ")" || t == "b") {
            state.indented = state.context.indented;
        }
        return state.context = state.context.prev;
    }
    // Interface
    return {
        startState: function (basecolumn) {
            return {
                tokenize: null,
                context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
                indented: 0,
                startOfLine: true
            };
        },

        token: function (stream, state) {
            function popUntil(str) {
                while (state.context.type != str && (state.context.prev != null))
                    popContext(state);
                if (state.context.prev)
                    popContext(state);
            }
            var ctx = state.context;
            if (stream.sol()) {
                if (ctx.align == null) ctx.align = false;
                state.indented = stream.indentation();
                state.startOfLine = true;
            }
            if (stream.eatSpace()) return null;
            curPunc = null;
            var style = (state.tokenize || tokenBase)(stream, state);
            if (style == "comment" || style == "meta") return style;
            if (ctx.align == null) ctx.align = true;
            if (curPunc == "GO") {
                popUntil("top");
            } if (curPunc == "BEGIN") {
                pushContext(state, stream.column(), "END");
            } else if (curPunc == "END") {
                popUntil("END");
            } else if (curPunc == "(") {
                pushContext(state, stream.column(), ")");
            } else if (curPunc == ")") {
                popUntil(")");
            } else if (curPunc == "GO") {
                popUntil("top");
            } else if (curPunc == ctx.type) {
                popContext(state);
                if (curPunc == "block") {
                    pushContext(state, stream.column(), "block");
                }
            } else if (curPunc == "block") {
                pushContext(state, stream.column(), "block");
            } else { }
            state.startOfLine = true;
            return style;
        },
        indent: function (state, textAfter) {
            function backUntilWord(str) {
                var pctx = ctx.prev;
                while (pctx) {
                    if (pctx.type == word) {
                        closing = true;
                        ctx = pctx;
                        break;
                    }
                    if (pctx == ctx.prev) break;
                    pctx = ctx.prev;
                }
            }
            if (state.tokenize != tokenBase && state.tokenize != null) return 0;
            var firstChar = textAfter && textAfter.charAt(0), ctx = state.context, closing = firstChar == ctx.type;
            var isBKW = false, isEND = false, word;
            if (textAfter && textAfter[0] != ')') {
                word = textAfter.match(/\w+/);
                if (word) {
                    word = word[0].toUpperCase();
                    isBKW = blockKeywords.propertyIsEnumerable(word);
                    isEND = word == "END";
                    if (!closing) closing = isEND && ctx.type == "END";
                }
            }

            if (word == null) word = firstChar;
            if (ctx.type == "block" && isBKW) closing = true;
            if (word == "GO") {
                closing = true;
                backUntilWord('top');
            }
            if (closing == false && word == ")" || word == "END") {
                backUntilWord(word);                
            }
            //alert("'" + textAfter + "'\n" + closing + ', ' + isEND + ', ' + isBKW + ', ' + word + ', ' + ctx.type);
            //alert(word + ', ' + isBKW + ',' + isEND + ',' + ctx.type + ',' + ctx.indented + ',' + closing);
            return ctx.indented + (closing ? 0 : indentUnit);
        },
        electricChars: "()"
    };
});

(function () {
    function words(str) {
        var obj = {}, words = str.split(" ");
        for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
        return obj;
    }
    // C#-style strings where "" escapes a quote.
    function tokenAtString(stream, state) {
        var next;
        while ((next = stream.next()) != null) {
            if (next == '"' && !stream.eat('"')) {
                state.tokenize = null;
                break;
            }
        }
        return "string";
    }
    CodeMirror.defineMIME("text/x-tsql", {
        name: "tsql",
        pkeywords: words("RETURNS ADD EXCEPT PERCENT ALL EXEC PLAN ALTER EXECUTE PRECISION AND EXISTS PRIMARY ANY EXIT PRINT AS FETCH PROC ASC FILE PROCEDURE AUTHORIZATION FILLFACTOR PUBLIC BACKUP FOR RAISERROR FOREIGN READ BETWEEN FREETEXT READTEXT BREAK FREETEXTTABLE RECONFIGURE BROWSE FROM REFERENCES BULK FULL REPLICATION BY FUNCTION RESTORE CASCADE GOTO RESTRICT CASE GRANT RETURN CHECK GROUP REVOKE CHECKPOINT HAVING RIGHT CLOSE HOLDLOCK ROLLBACK CLUSTERED IDENTITY ROWCOUNT COALESCE IDENTITY_INSERT ROWGUIDCOL COLLATE IDENTITYCOL RULE COLUMN IF SAVE COMMIT IN SCHEMA COMPUTE INDEX SELECT CONSTRAINT INNER SESSION_USER CONTAINS INSERT SET CONTAINSTABLE INTERSECT SETUSER CONTINUE SHUTDOWN CONVERT IS SOME CREATE JOIN STATISTICS CROSS KEY SYSTEM_USER CURRENT KILL TABLE CURRENT_DATE LEFT TEXTSIZE CURRENT_TIME LIKE THEN CURRENT_TIMESTAMP LINENO TO CURRENT_USER LOAD TOP CURSOR NATIONAL TRAN DATABASE NOCHECK TRANSACTION DBCC NONCLUSTERED TRIGGER DEALLOCATE NOT TRUNCATE DECLARE NULL TSEQUAL DEFAULT NULLIF UNION DELETE OF UNIQUE DENY OFF UPDATE DESC OFFSETS UPDATETEXT DISK ON USE DISTINCT OPEN USER DISTRIBUTED OPENDATASOURCE VALUES DOUBLE OPENQUERY VARYING DROP OPENROWSET VIEW DUMMY OPENXML WAITFOR DUMP OPTION WHEN ELSE OR WHERE ORDER WHILE ERRLVL OUTER WITH ESCAPE OVER WRITETEXT INTO"                  
        ),
        skeywords: words("ABS DEGREES RAND ACOS EXP ROUND ASIN FLOOR SIGN ATAN LOG SIN ATN2 LOG10 SQRT CEILING PI SQUARE COS POWER TAN COT RADIANS AVG MIN CHECKSUM_AGG OVER Clause COUNT ROWCOUNT_BIG COUNT_BIG STDEV GROUPING STDEVP GROUPING_ID SUM MAX VAR VARP"),
        sobjs: words("SYSCOLUMNS SYSINDEXKEYS SYSCOMMENTS SYSMEMBERS SYSCONSTRAINTS SYSOBJECTS SYSDEPENDS SYSPERMISSIONS SYSFILEGROUPS SYSPROTECTS SYSFILES SYSREFERENCES SYSFOREIGNKEYS SYSTYPES SYSFULLTEXTCATALOGS SYSUSERS SYSINDEXES"),

        blockKeywords: words("SELECT FROM WHERE GROUP ORDER DECLARE SET RETURN INSERT IF THEN ELSE INTO UNION INTERSECT EXCEPT"),
        bendKeywords: words("BEGIN END"),
        atoms: words("true false null"),
        hooks: {
            "@": function (stream, state) {
                if (stream.eat('"')) {
                    state.tokenize = tokenAtString;
                    return tokenAtString(stream, state);
                }
                stream.eatWhile(/[\w\$_]/);
                return "meta";
            }
        }
    });
} ());
