Problem with Squiggles and SQL

SyntaxEditor for WPF Forum

Posted 9 years ago by Craig - Varigence, Inc.
Version: 10.2.0533
Avatar
I have an application where we use the SyntaxEditor for T-Sql editing. As you may or may not know, T-Sql can statements can wrap across multiple lines. For example, this is a single statement:

SELECT
ID
, CAST(('Test1_' + CAST(ID AS NVARCHAR)) AS NVARCHAR(255)) AS Column1
, CAST(('Test2_' + CAST(ID AS NVARCHAR)) AS NVARCHAR(255)) AS Column2 
, CAST(('Test3_' + CAST(ID AS NVARCHAR)) AS NVARCHAR(255)) AS Column3
, CAST(('Test4_' + CAST(ID AS NVARCHAR)) AS NVARCHAR(255)) AS Column4
, CAST(('Test5_' + CAST(ID AS NVARCHAR)) AS NVARCHAR(255)) AS Column5
FROM 
dbo.Number 
However, when I try to implement a class that extends TaggerBase to generate SquiggleTags for parser errors, I can't get the Squiggle Tags to fully appear.

My GetTags method is as follows:

public override IEnumerable<TagSnapshotRange<ISquiggleTag>> GetTags(NormalizedTextSnapshotRangeCollection snapshotRanges, object parameter)
{
    if (snapshotRanges != null)
    {
        // Loop through the snapshot ranges
        foreach (var snapshotRange in snapshotRanges)
        {
            var isLastLine = Document.CurrentSnapshot.SnapshotRange.EndPosition.Line == snapshotRange.EndPosition.Line &&
                            Document.CurrentSnapshot.SnapshotRange.EndPosition.Character == snapshotRange.EndPosition.Character;

            if (isLastLine)
            {
                var documentText = Document.CurrentSnapshot.Text.Replace(Environment.NewLine, " ");
                var parseResult = Parser.Parse(documentText);
                foreach (var error in parseResult.Errors)
                {
                    var tag = new SquiggleTag();
                    tag.ClassificationType = ClassificationTypes.SyntaxError;
                    tag.ContentProvider = new PlainTextContentProvider(error.Message);

                    yield return new TagSnapshotRange<ISquiggleTag>(new TextSnapshotRange(Document.CurrentSnapshot, error.Start.ColumnNumber, error.End.ColumnNumber), tag);
                }
            }
        }
    }
}
You'll notice that I'm not actually using each snapshot range. Instead, I determine when I'm getting the 'last' snapshot for the change and then I parse my entire document. The reason is that I'm using a SQL parser from Microsoft (in the Microsoft.SqlServer.Management.SqlParser.Parser namespace) to determine if parser errors are present. The Parse function needs the entire string to produce accurate errors so there's no point to using the snapshot ranges. I didn't write my own parser because the Microsoft one works correctly.

With the correct errors determined, my problem arises. Let's say we're using this SQL statement:

select 
'TestValue'' 
AS Column FROM 
Table
There's an error on the second line due to the additional quote. So, when the last snapshot range is provided in GetTags, I parse the entire statement and learn of the error. Now, I create a snapshot range to match the error's starting and ending offset. It turns out the parser indicates the error is from position 9 to position 46 (since it's seeing the text as one whole line). I translate those positions into a snapshot range that encompasses the second through fourth lines of the document's text and return the TagSnapshotRange. But to my dismay, only the last line has a squiggle.

Generally, it appears that because I'm returning the TagSnapshotRanges when processing the final snapshot range, only squiggle areas that overlap the final snapshot range will be displayed.

So, I'm looking for a way to workaround this problem, or a better approach altogether. A version of GetTags that gave me the entire document's snapshot range would be perfect. Is there such a thing? Do you have recommendations on how I can use GetTags() or some other means to make Squiggles appear with the restriction that I parse the entire text instead of snapshot-by-snapshot?

FWIW, The best idea I came up with was:

public override IEnumerable<TagSnapshotRange<ISquiggleTag>> GetTags(NormalizedTextSnapshotRangeCollection snapshotRanges, object parameter)
{
    _snapshotRanges = new List<TagSnapshotRange<ISquiggleTag>>();

    var documentText = Document.CurrentSnapshot.Text.Replace(Environment.NewLine, " ");
    var parseResult = Parser.Parse(documentText);
    foreach (var error in parseResult.Errors)
    {
        var tag = new SquiggleTag();
        tag.ClassificationType = ClassificationTypes.SyntaxError;
        tag.ContentProvider = new PlainTextContentProvider(error.Message);
        _snapshotRanges.Add(new TagSnapshotRange<ISquiggleTag>(new TextSnapshotRange(Document.CurrentSnapshot, error.Start.ColumnNumber, error.End.ColumnNumber), tag));
    }

    return _snapshotRanges;
}
This isn't quite finished but the notion is to parse the text immediately and then keep returning the same snapshot ranges so they can be applied to each range in the editor. However, this is a very hacky approach so I'm hoping you can suggest something better.

Thanks,

-Craig

Comments (2)

Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Craig,

Yes that is correct, the tagging mechanism is sort of a "pull" mechanism where it only requests ranges it needs for display or other features. That way it's very optimized and fast. Tags specified outside of the requested range are ignored.

A couple suggestions...

1) I see you doing CurrentSnapshot.Text.Replace(Environment.NewLine, " "). Internally we convert all line ends to \n only. The Text property converts those back out to \r\n. If you are looking for a single character line terminator to pass to their parser, just call GetText(LineTerminator.Newline) instead.

2) I don't really like that the SQL parser is being called from within GetTags. What you should be doing instead is executing the SQL parser in an IParser service. This way you could have it execute in a background worker thread too since parsing is an "expensive" operation.

If the parse data object returned by your IParser.Parse method implements IParseErrorProvider, and if you have these two services registered on your language, you'll also get squiggle lines and mouse hover error messages for free:
language.RegisterService(new CodeDocumentTaggerProvider<ParseErrorTagger>(typeof(ParseErrorTagger)));
language.RegisterService(new SquiggleTagQuickInfoProvider());
Hope that helps.


Actipro Software Support

Posted 9 years ago by Craig - Varigence, Inc.
Avatar
Just wanted to say thanks for your suggestions; they were exactly what I needed to get squiggles working.

-Craig
The latest build of this product (v2019.1 build 0683) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.