Indentation for a language without curly braces

SyntaxEditor for Windows Forms Forum

The latest build of this product (v22.1.3) was released 1 month ago, which was before this thread was created.
Posted 8 days ago by user123456
Version: 22.1.3
Avatar

Currently I am working on an implementation of a custom Syntax language. The SampleBrowser provides great examples, but there is one thing that I cannot get clear yet.

The SimpleIndentProvider method GetIndentAmount is called when enter is pressed or a curly brace is entered.

My custom language does not use curly braces, but an 'if' and 'for' loop are closed with an 'end' statement. The method looks for a TokenID that I defined for an 'end' statement as it does for a closing curly brace in the SimpleIndentProvider example, but de-indentation occurs after the 'end'.

I think it has something to do with the method being triggered on newline only, whereas in the SimpleIndentProvider it gets triggered on pressing } too.

How can I make sure that in order to de-indent the if/for block, the GetIndentAmount method is triggered when 'end' is typed also?

Comments (3)

Posted 8 days ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hello,

That is a tricky scenario since for languages like the Simple example, we can watch for the single '}' character being typed.  Whereas when you have a longer keyword like 'End', there is no single typed character to watch for. 

If your language ends these loops with 'End If' or 'End For' for instance, you could watch for a Space character being typed and then if the previous word was 'End', engage auto-indent. 

But if the 'End' word by itself is what ends all loops, then that's harder.  You would have to possibly watch for certain characters like 'd' to be typed and then see if the 'End' keyword token was created right before the current offset.  Then kick off auto-indent in response.

Of course in any case, moving the caret to a new line should trigger the auto-indent too as a fallback.  I don't think Visual Studio even auto-indents the End line for Visual Basic until the caret is moved off the line.


Actipro Software Support

Posted 7 days ago by user123456
Avatar

Thank you for your reply.

It is not a problem that 'end' de-indents only after pressing 'enter' but I can't get that to work.

When the IndentProvider finds the EndLoop token, everything I try results in de-indenting the lines after 'end', not the 'end' line itself. 

One example of what I tried in public override int GetIndentAmount(TextSnapshotOffset snapshotOffset, int defaultAmount)

 case ProceduresTokenId.EndLoop:
     return reader.Snapshot.Lines[indentationBaseLineIndex].IndentAmount - tabSize;
     break;

the indentation or lines after starting a 'for' or 'if' work correctly, it's just the de-indentation of the 'end' line I can't get to work.

I like this suggestion: 

But if the 'End' word by itself is what ends all loops, then that's harder.  You would have to possibly watch for certain characters like 'd' to be typed and then see if the 'End' keyword token was created right before the current offset.  Then kick off auto-indent in response.

However, I should get de-indentation working at all first. Then, how could I trigger auto-indent when 'd' is typed? I think it won't be difficult to check the last typed character, but I haven't found yet how to trigger auto-indent, otherwise than it does by default (on entering newline)

Posted 7 days ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hello,

1) Regarding why de-indenting End isn't working, I would first suggest you put a breakpoint on that line to see if it's even called when you expect, such as when pressing Enter.  If it is, then see what the indent amount result you are returning is, and if it's what you expect.  If it is a value one too many (which keeps the End line indented from the base line index) then you would need to step through your GetIndentAmount logic and debug why it's not de-indenting a level.

2) For the second question, our VBIndentProvider does this kind of thing to watch for space being typed:

protected virtual void OnDocumentTextChanged(SyntaxEditor editor, EditorSnapshotChangedEventArgs e) {
	if ((editor != null) && (!editor.Document.IsReadOnly) && (e != null) && (e.TextChange != null) && (e.TextChange.Source == editor.ActiveView) && (e.TextChange.Operations?.Count == 1)) {
		var typedText = e.TypedText;
		switch (typedText) {
			case " ":
				this.AutoIndent(editor, new TextSnapshotOffset(e.NewSnapshot, e.TextChange.Operations[0].InsertionEndOffset), s => {
					// Ensure the comparison is case-insensitive
					if (s != null)
						s = s.ToUpperInvariant();

					switch (s) {
						case "CASE ":
						case "CATCH ":
						case "ELSE ":
						case "ELSEIF ":
						case "END ":  // Shouldn't have a line terminator after though
						case "FINALLY ":
						case "LOOP ":
						case "NEXT ":
							return true;
						default:
							return false;
					}
				});
				break;
		}
	}
}

Where it's calling an AutoIndent method that looks at the characters before the space and makes sure they match one of those case statements.  That way, we aren't indenting all the time at spaces, only when appropriate, after one of those keywords.

And our AutoIndent method is:

private void AutoIndent(SyntaxEditor editor, TextSnapshotOffset snapshotOffset, Func<string, bool> predicate) {
	if (editor == null)
		throw new ArgumentNullException("editor");

	if ((!snapshotOffset.IsDeleted) && (snapshotOffset.Offset > 0)) {
		// Get the snapshot line
		var snapshotLine = snapshotOffset.Snapshot.Lines[snapshotOffset.Position.Line];

		// If there is a predicate, use it to validate whether the auto-indent should occur
		var firstNonWhitespaceCharacterOffset = snapshotLine.FirstNonWhitespaceCharacterOffset;
		if (predicate != null) {
			var lineStartText = snapshotOffset.Snapshot.GetSubstring(new TextRange(snapshotLine.StartOffset, snapshotOffset.Offset)).TrimStart();
			if (!predicate(lineStartText))
				return;
		}

		// Get the current and desired indent amounts
		var indentAmount = snapshotLine.IndentAmount;
		var desiredIndentAmount = this.GetIndentAmount(new TextSnapshotOffset(snapshotOffset.Snapshot, firstNonWhitespaceCharacterOffset), indentAmount);

		if (indentAmount != desiredIndentAmount) {
			// Generate the auto-indent text
			string indentText = StringHelper.GetIndentText(editor.Document.AutoConvertTabsToSpaces, editor.Document.TabSize, desiredIndentAmount);

			// Indent
			var textChange = snapshotOffset.Snapshot.CreateTextChange(TextChangeTypes.AutoIndent, new TextChangeOptions() { RetainSelection = true });
			textChange.ReplaceText(new TextRange(snapshotLine.StartOffset, firstNonWhitespaceCharacterOffset), indentText);
			textChange.Apply();
		}
	}
}


Actipro Software Support

Add Comment

Please log in to a validated account to post comments.