Indentation issues in C# editor with code snippets

Dear AlterNET team,

we are using your C# WinForms editor, and are pretty happy with it, we just have been unable to get a consistent indentation behavior to work when editing code snippets (method bodies in our case, indentation level 3). We have based our integration on your “SnippetsParsers” example, and I think I can show the problems with some minimal steps in the example itself:

ISSUE ONE

  1. Start the SnippetsParsers.exe
  2. Select “C#”, “Partial”, “Method”.
  3. Delete all code from the editor.
  4. Write “if (true)” + hit ENTER
  5. Notice: Cursor is not indented, when it should be indented by 1.
    (Note: For comparison, it works in mode: “C#”, “Class-less”, “Method”.)

ISSUE TWO

  1. Start the SnippetsParsers.exe
  2. Select “C#”, “Partial”, “Method”.
  3. Delete all code from the editor.
  4. Hit ENTER and then type “{”
  5. Notice: Braces are indented by 1, when it shouldn’t.
    (Note: For comparison, it works when leaving out the ENTER in step 4.)

There are other such issues, but I guess they all come from a slight bug in the two-way interplay of SmartIndent/SmartFormat with the modified indentation level.
Without turning these features off, I see no override that we can use to fix or correct these issues consistently. If there is a simple fix for these, could you point us in the right direction?

Cheers, Paul

@Paul Thanks for bringing this up - I observed this for a long time in our VB.NET version, but did not report it.
I worked around it by adding a context menu item that calls “Selection.SmartFormatDocument”.

Hopefully, we will get a simple fix for this ;-).

Best regards

Wolfgang

Hi Paul and Wolfgang,

Thank you for reporting this problem. Here’s a quick fix to the CSSnippetParser.cs (should work for VBSnippetParser.cs as well) - both are part of the same demo projects.

We will test it more carefully, but let us know if you see anything wrong with this change.

public class CSSnippetRepository : CsRepository
...
  protected override bool UseIndentService
  {
      get
      {
          return true;// false;
      }
  }

  protected virtual int IndentLevel
  {
      get
      {
          return 1;
      }
  }

  public override string GetSmartIndent(int index, bool autoIndent, bool useSpaces, int spacesInTab)
  {
      var indent = base.GetSmartIndent(index + StartOffset, autoIndent, useSpaces, spacesInTab);
      var s = useSpaces ? new string(' ', IndentLevel * spacesInTab) : new string('\t', IndentLevel);
      if (indent.StartsWith(s))
          indent = indent.Remove(0, s.Length);
      return indent;
  }
...
public class CSMethodRepository : CSSnippetRepository
...
   protected override int IndentLevel
   {
       get
       {
           return 2;
       }
   }
...

Kind regards,
Dmitry

Hi Dmitry,

thank you very much for the fast response, this looks very, very promising.
In fact, as far as I can currently see, there is now only a single oddity left.

Let me reproduce it in the snippet parser example (with its code already adapted like you described).

  1. Select “C#”, “Partial”, “Method”.
  2. Clear out everything.
  3. Write or paste:
Action x = () =>
{
	{
	}
};
  1. Now, try to write into the innermost block by hitting ENTER after the opening bracket. The cursor will be placed at the next line with no indentation whatsoever. It should have been indented twice.

Observations:
If you now just continue to open more and more inner blocks, these are all placed correctly:

Action x = () =>
{
	{
{ 
	{
		{
			{
				{ 
				}
			}
		}
	}
}
	}
};

It seems like lambda functions bodies are not indented correctly.
(Local functions - strangely enough - do not have this problem and are working fine.)

Debugging a bit, it seems like Roslyn’s CSharpIndentationService.GetIndentation() that you are using internally already incorrectly reports no initial indentation (when it should be 2), and then the GetSmartIndent() override only kicks in when the IndentLevel is reached.

Could you have a look?

Kind regards, Paul

I think the suggested resolution does not work in the VBSnippet sample.

Enter this code snippet somewhere in the existing code:

If (5 = 5) Then
	If (6 = 6) Then
	End If
End If

If you press ENTER after the first THEN, “base.GetSmartIndent” returns two tabs. Here, it works.
But if you press ENTER after the second then, “base.GetSmartIndent” returns an empty string and the cursor is placed at the beginning of the line.

Hope I did not miss something when applying the workaround suggestion.

Best regards

Wolfgang

Hi,

It seems that IndentationService may return an indentation based on the position of the previous tokens, such as “{” in lambda or object creation expression, so the approach of getting that indentation and removing one or two levels will not work for these scenarios.

In Visual Basic, IndentationService was not returning anything meaningful, so it always used our fall-back approach - this will be fixed shortly.

We will need to investigate it further, either fix our own indentation logic to accommodate the above scenarios or find a way to use Roslyn’s indentation service.

We will look at it further and update you once we find a solution.
Kind regards,
Dmitry

1 Like

Hi Paul and Wolfgang,

We looked further at it, and it seems that fixing our own indentation service for Snippet Parsers would work better than trying to work around Roslyn’s indentation service.

We’ve pushed an update to NuGet (version 9.1.2), and also new demo projects to our GitHub, as there were some small changes in Snippet parsers.

Also, Visual Basic now uses Roslyn’s indentation service in the case of Visual Basic Parser (but not Snippet Parser).

Please let us know if you find any issues with the new version.

Kind regards,
Dmitry

Thanks!

For updating to 9.1.2, the only necessary change is an additional argument in “IsBlockNode”?

I fear it has not changed in our VB.NET usage. Seems to be related to the fact that we use a property, which means one more indentation level. I sent you a modified sample by mail where I replaced “MethodSnippet” by “PropertySnippet”. “VBMethodRepository.CreateRewriter” has to use a count of “3” instead of “2”. Hope I did not miss anything else.

In the sample, switch to “Visual Basic”. If you press enter at the end of the first line, the caret is indented:


It you enter some valid code and press enter again, the current line will be indented properly, but the caret in the next line is still wrong.

The behavior is different depending on whether the class body itself is indented or not. It seems to work best if it is not indented at all (see screenshot). It it is indented by tab or space, the indentation is “wronger”.

Best regards

Wolfgang

Hi Wolfgang,

Since you have a property definition in your snippet, you would need to change VBMethodRepository.IsBlockNode like this:

        protected override bool IsBlockNode(Microsoft.CodeAnalysis.SyntaxNode node, int pos)
        {
            return base.IsBlockNode(node, pos) && !node.IsKind(SyntaxKind.ClassBlock) && !node.IsKind(SyntaxKind.NamespaceBlock) && !node.IsKind(SyntaxKind.PropertyBlock) && !node.IsKind(SyntaxKind.GetAccessorBlock) && !node.IsKind(SyntaxKind.SetAccessorBlock);
        }

Let me know if it works for you.

Dmitry

Thanks, this works. I could have asked this question years ago ;-). Could you add this to the sample, just in case other users used it this way?

Sorry for hijacking your thread, Paul - my issue sounded the same like yours, but was not related.

@dmitry.medvedev I think there is still a small indentation issue: Enter the “Do” keyword and press enter => in my sample, no indentation happens. It works if you enter “Do While”, but “Do” alone seems to be valid VB.NET code, too.

Best regards

Wolfgang

Hi Wolfgang,

We will update our Snippet parsers so it works correctly in case a snippet contains a property declaration.

We’ve also fixed issue with an indentation for the Do statement. Here’s a workaround for the snippet parser that you can use meanwhile:

     public class VBSnippetRepository
...
        protected override bool IsBlockNode(Microsoft.CodeAnalysis.SyntaxNode node, int pos)
        {
            switch (node.Kind())
            {
                case SyntaxKind.SimpleDoLoopBlock:
                case SyntaxKind.DoWhileLoopBlock:
                case SyntaxKind.DoUntilLoopBlock:
                    return true;
            }

            return base.IsBlockNode(node, pos) && !node.IsKind(SyntaxKind.ClassBlock) && !node.IsKind(SyntaxKind.NamespaceBlock);
        }

Regards,
Dmitry

Many thanks! No need for a workaround, I just noticed it while playing with indentation.

Best regards

Wolfgang

1 Like