New CodeMirror mode; SmartIndent doesn't work when cursor is on blank line in middle of file

Hi everyone,

I’m new to CodeMirror, and am trying to create a new mode for a SQL dialect that does indenting as per internal project style. The algorithm I have (below) works well in most cases; however there is a wierd bug when you reposition the cursor to the beginning of an empty line that isn’t at the end of the file:

^^^^^^^^^^^^^^^^
SET @a = b;

<-- cursor repositioned here, then I press ‘enter’

SELECT
foo,
bar, <— end of file; next line would be indented by 2 from left margin
^^^^^^^^^^^^^^^^

If I press “enter” in the example above, the cursor indents by 2, because the state it “sees” is the state of the very end of the text (not the state at the position of the cursor). Specifically, stream.col() is NOT 0, etc.

This bug doesn’t occur if there is anything whatsoever on the line with the cursor.

Would anyone in the community here be able to give me a pointer in the right direction? I’ve tried reverse engineering and console logging, but since the context & stream are identical (as if CodeMirror can’t tell whether the cursor is where is it in the example, or at the very end of the file), it seems that there is no logic I can add in the “indent” function to differentiate between the two cases.

My mode definition (token, indent function) are as copied below.

Thanks so much if anyone can help!

Emily

        return {
          startState: function() {
            return {tokenize: tokenBase, context: new Context()};
          },

          token: function(stream, state) {
            if (!state.context) {
              state.context = new Context();
            }
            if (stream.eatSpace()) {
              state.context.numTokensSinceSpace = 0;
              if (state.tokenize == tokenBase) return null;
            }
            const style = state.tokenize(stream, state);

            // Extract various values from the stream.
            const token = stream.current().toUpperCase();
            const tokenPos = stream.column();
            const lineIndent = stream.indentation();

            // If we're processing the first token on the line, reinitialize
            // line-specific state variables.  Then, update them based on the
            // token value.
            if (lineIndent === tokenPos) {
              state.context.lineFirstToken = token;
              state.context.parenBalance = 0;
              state.context.thenOnLine = false;
            }
            if (token == '(') {
              state.context.parenBalance++;
            } else if (token == ')') {
              state.context.parenBalance--;
            } else if (token == 'THEN') {
              state.context.thenOnLine = true;
            }

            // Set other non-line-specific context parameters.
            state.context.token = token;
            state.context.tokenStyle = style;
            state.context.numTokensSinceSpace++;
            state.context.lineIndent = lineIndent;
            state.context.tokenPos = tokenPos;
            state.context.cursorPos = tokenPos + token.length;
            state.context.stream = stream;

            return style;
          },

          indent: function(state, textAfter) {
            const ctx = state.context;
            state.context = new Context();
            if (!ctx) {
              return CodeMirror.Pass;
            }

            // Semicolons always cause indent to return to 0.  Also, always
            // indent to 0 if the current cursor position is 0.
            if (ctx.token === ';' || !ctx.cursorPos) {
              return 0;
            }

            // For comments and built-ins (e.g. types), do not change indent.
            if (['comment', 'builtin'].includes(ctx.tokenStyle)) {
              return ctx.lineIndent;
            }

            // Commas on an 'AS' line, or commas on a line with negative
            // parenthesis balance, cause a negative indent.
            if (ctx.token === ',' &&
                (ctx.lineFirstToken === 'AS' || ctx.parenBalance < 0)) {
              return Math.max(0, ctx.lineIndent - 2);
            }

            // Function calls increase indent by 4.
            if (ctx.token === '(' &&
                ctx.numTokensSinceSpace > 1 &&
                !['CREATE', 'DEFINE'].includes(ctx.lineFirstToken)) {
              return ctx.lineIndent + 4;
            }

            // Otherwise, new bracket blocks and continuations increase the
            // indent by 2.
            if (['(', '<', '[', '{'].includes(ctx.token)) {
              return ctx.lineIndent + 2;
            }

            // Close parentheses should decrease the indent only if there are
            // more close parentheses than open parentheses on the line where
            // the user hit 'ENTER'.
            if (ctx.token === ')' && ctx.parenBalance < 0) {
              return Math.max(0, ctx.lineIndent - 2);
            }

            // An equals sign introduces a +2 continuation.
            if (ctx.token === '=') {
              return ctx.lineIndent + 2;
            }

            return ctx.lineIndent;
          },

          blockCommentStart: '/*',
          blockCommentEnd: '*/',
          lineComment: '#'
        };
      });