
Thanks - would be great if you could add this functionality in future. Anyone else whos interested, this is what i've come up with - should be pretty easy to extend to handle For/Next Do/While blocks etc. Just register a new instance of the class with VBSyntaxLanguage using RegisterService.
namespace MyApp
{
using System.Windows.Input;
using ActiproSoftware.Text;
using ActiproSoftware.Text.Languages.VB.Implementation;
using ActiproSoftware.Text.Utility;
using ActiproSoftware.Windows.Controls.SyntaxEditor;
internal sealed class VBCloseBlockProvider : ActiproSoftware.Text.Utility.IKeyedObject, IEditorViewKeyInputEventSink
{
string IKeyedObject.Key
{
get { return "VBCloseBlockProvider"; }
}
private bool GoToNextTokenChain(ITextSnapshotReader reader, params int[] tokens)
{
if (tokens == null || tokens.Length < 1) return false;
if (!reader.GoToNextTokenWithId(tokens[0])) return false;
int firstOffset = reader.Offset;
reader.GoToCurrentWordEnd();
for (int i = 1; i < tokens.Length; i++)
{
var spaceTok = reader.ReadToken();
if (spaceTok == null || spaceTok.Id != VBTokenId.Whitespace) return false;
var tok = reader.ReadToken();
if (tok == null || tok.Id != tokens[i]) return false;
}
reader.Offset = firstOffset;
return true;
}
private bool GoToPreviousTokenOnCurrentLine(ITextSnapshotReader reader, params int[] token)
{
int currentLine = reader.Position.Line;
return reader.GoToPreviousTokenWithId(token) && reader.Position.Line == currentLine;
}
void IEditorViewKeyInputEventSink.NotifyKeyDown(IEditorView view, KeyEventArgs e)
{
// NOTE: process when user hits Enter/Return key
if (e.Key != Key.Return && e.Key != Key.Enter) return;
var reader = view.GetReader();
int offset = reader.Offset;
// NOTE: find previous sub/function keyword on the current line
if (!this.GoToPreviousTokenOnCurrentLine(reader, VBTokenId.KeywordSub, VBTokenId.KeywordFunction)) return;
// NOTE: note the type of token potentially marking the block start (sub or function), so corresponding close block can be entered later
var blockStartToken = reader.PeekToken();
// NOTE: ensure matched sub/func is not part of existing close block
if (this.GoToPreviousTokenOnCurrentLine(reader, VBTokenId.KeywordEnd)) return;
// NOTE: from original offset, find next sub or function statement
reader.Offset = offset;
int nextSubFuncOffset = reader.GoToNextTokenWithId(VBTokenId.KeywordSub, VBTokenId.KeywordFunction) ? reader.Offset : -1;
// NOTE: from original offset, find next 'end sub|func' statement
reader.Offset = offset;
int endSubFuncOffset = this.GoToNextTokenChain(reader, VBTokenId.KeywordEnd, blockStartToken.Id) ? reader.Offset : -1;
// NOTE: enter 'end sub|func' if none exists for the current block
if (endSubFuncOffset < 0 || nextSubFuncOffset < 0 || nextSubFuncOffset < endSubFuncOffset)
{
switch(blockStartToken.Id)
{
case VBTokenId.KeywordSub:
view.InsertSurroundingText("\n\t", "\nEnd Sub");
break;
case VBTokenId.KeywordFunction:
view.InsertSurroundingText("\n\t", "\nEnd Function");
break;
}
e.Handled = true;
}
}
void IEditorViewKeyInputEventSink.NotifyKeyUp(IEditorView view, KeyEventArgs e) { }
}
}