How can I highlight a text range in a SyntaxEditior

SyntaxEditor for WPF Forum

Posted 6 years ago by James Kester - Metafile Information Systems, Inc.
Version: 13.2.0591
Avatar

I have a syntax editor and a grammer defined for it, but im also doing some other validation by going out to a database.  I want to be able to highlight text in the syntax editor where the error is, even though it is syntaxically correct. (for example, table doesnt exist.)

Im having trouble though selecting/highlighting the text range i would like.  

I just want to select/highlight a specific text range in the syntax editor.

Thanks.

Comments (15)

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi James,

Do you want to highlight with background colors or do squiggle lines?

Also, is this validation process something that would be done as part of your IParser processing in a worker thread?  It can be tacked on to the normal parsing and then have the resulting IParseData include error information for the parsing and validation.  We do that in our advanced XML language.


Actipro Software Support

Posted 6 years ago by James Kester - Metafile Information Systems, Inc.
Avatar

I want to highlight the background color I suppose.  What i had prior to the Actipro SyntaxEditor was just a regular text box, and i just selected the test range.  When i do my secondary validaion, i get back the string position of the error and the length of the token.  So really all i want to do is something like:

TextRange tr = new TextRange(errorstart, errorend);
mySyntaxEditor.SelectedText(tr);

 something very simple like that. I just haven't seen and properties or functions on the SyntaxEditor to Select text.

 

Alternatively, it would be nice to highlight every instance of a string in the Syntax editor.  For example if the table [Some_Table] doesnt exist id like to highlight every instance of the table.  I want to do highlighting/selecting, becase the code likely will be syntaxically correct, so it should not be a squiggle.  I have a secondary control that is reporting the errors, and i want to be able to click the error, and highlight in the editor where the error is.

 

Thanks.

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi James,

You would use a classification tagger to do background highlighting.  Basically that will let you assign an IClassificationTag over a text range (or ranges).  Then as long as you have a highlighting style with background associated with the IClassificationType used by that tag, it will highlight the background.

Check out the documentation on taggers for info on how to use them.  You'd want to use a CollectionTagger<IClassificationTag> tagger.  Also numerous QuickStart samples show it, such as some in the Adornments section.


Actipro Software Support

Posted 6 years ago by James Kester - Metafile Information Systems, Inc.
Avatar

Great, i did see that section, i will have a closer look.  Thanks.

Posted 6 years ago by James Kester - Metafile Information Systems, Inc.
Avatar

Hmm i seem to be having trouble with this still.  So I have taken the WordHighlightTagger example since it is exactly what im trying to do.  But rather than clicking on the word in the syntaxEditor, im trying to click on a word in a listbox below my syntaxEditor, and then highlight that word in my syntaxeditor.

so i have registered the WordHighLightTagger with the language of my syntaxEditor:

 // Register a tagger provider on the language as a service that can create CustomTag objects
 language.RegisterService(new TextViewTaggerProvider<WordHighlightTagger>(typeof(WordHighlightTagger)));

 I have then overridden the constructor of WordHighlightTagger  to take an extra error string:

 public WordHighlightTagger(IEditorView view, string error)
            : base("Custom",
                new Ordering[] { new Ordering(TaggerKeys.Token, OrderPlacement.Before) }, view.SyntaxEditor.Document)
        {

            // Initialize
            this.view = view;
            this.currentWord = error;

            // Update current word
            this.UpdateCurrentWord();
        }

 I have then modified the Regex to match my tokens correctly.  

so when the user clicks the item in a list box a command is fired that creates a new wordhighlighttagger, passing the view, and error string. however im still not getting anything highlighted.

It looks to me like the GetTags method is the one that does the tagging, but im not sure how to call it or what to pass it.

 

thanks again.

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi James,

If you want to use that sample as a base, a better way would be to not continuously register new taggers.  Instead, you should keep a single tagger instance around and just update its "error" property value.  When the propery value changes, raise a TagsChanged event for the view.CurrentSnapshot length.  That will force the view to refresh.

I would use a CodeDocumentTaggerProvider instead of TextViewTaggerProvider for this though, since you want to highlight the error in all views for the document.  You can get the existing tagger instance (once it's been created) then by looking in the document's Properties collection for an item with a key that is your tagger type.  Then per above, set its "error" property that you make.


Actipro Software Support

Posted 6 years ago by James Kester - Metafile Information Systems, Inc.
Avatar

Turns out it was incredibly easy, just didnt know how to access the property.

 

ActiproSoftware.Text.TextRange tr = new ActiproSoftware.Text.TextRange(Error.StrPos, Error.StrPos + Error.Id.Length);
                syntaxEditor.ActiveView.Selection.SelectRange(tr);
Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Oh I thought you meant you wanted to highlight instances of your error.  But if you simply want to move the selection to cover the error range, then yes, that will do it.


Actipro Software Support

Posted 6 years ago by James Kester - Metafile Information Systems, Inc.
Avatar

yes, i may try to implement that, but this approcah will work for this application. thanks.

Posted 6 years ago by James Kester - Metafile Information Systems, Inc.
Avatar

So I have now gone back and attempted to implement your suggestion, and I think I have done it correctly, however the GetTags code is still never executed, I must still be missing something.

I have created a CustomTagger class here:  (note the customTag is just an empty class as in the sample)

public class CustomTagger : TaggerBase<CustomTag> 
    {

        private ICodeDocument document;

        /////////////////////////////////////////////////////////////////////////////////////////////////////
        // OBJECT
        /////////////////////////////////////////////////////////////////////////////////////////////////////


        /// <summary>
        /// Initializes a new instance of the <c>CustomTagger</c> class.
        /// </summary>
        /// <param name="document">The document to which this manager is attached.</param>
        public CustomTagger(ICodeDocument document) :
            base("CustomTagger", null, document, true)
        {
            this.document = document;
            this.Pattern = Error;
        }

        private string _Error;
        public string Error
        {
            get
            {
                return _Error;
            }
            set
            {
                if (_Error != value)
                {
                    _Error = value;
                    this.OnTagsChanged(new TagsChangedEventArgs(new TextSnapshotRange(document.CurrentSnapshot, document.CurrentSnapshot.TextRange)));
                }
            }
        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////
        // PUBLIC PROCEDURES
        /////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Returns the tag ranges that intersect with the specified normalized snapshot ranges.
        /// </summary>
        /// <param name="snapshotRanges">The collection of normalized snapshot ranges.</param>
        /// <param name="parameter">An optional parameter that provides contextual information about the tag request.</param>
        /// <returns>The tag ranges that intersect with the specified normalized snapshot ranges.</returns>
        public override IEnumerable<TagSnapshotRange<CustomTag>> GetTags(NormalizedTextSnapshotRangeCollection snapshotRanges, object parameter)
        {
            if (snapshotRanges != null)
            {
                // Loop through the snapshot ranges
                foreach (TextSnapshotRange snapshotRange in snapshotRanges)
                {
                    // Get the text of the snapshot range
                    string text = snapshotRange.Text;

                    // Look for a regex pattern match
                    MatchCollection matches = Regex.Matches(text, pattern, RegexOptions.IgnoreCase);
                    if (matches.Count > 0)
                    {
                        // Loop through the matches
                        foreach (Match match in matches)
                        {
                            // Create a tag
                            CustomTag tag = new CustomTag();

                            // Yield the tag
                            yield return new TagSnapshotRange<CustomTag>(
                                TextSnapshotRange.FromSpan(snapshotRange.Snapshot, snapshotRange.StartOffset + match.Index, match.Length), tag);
                        }
                    }
                }
            }
        }
    }

  

then i have registered a CodeDocumentTaggerProvider with the language:

// Register a tagger provider on the language as a service that can create CustomTag objects
language.RegisterService(new CodeDocumentTaggerProvider<CustomTagger>(typeof(CustomTagger)));

 Then I have created one instance of a CustomTagger in the constructor of my ViewModel:

this.tag = new CustomTagger(_syntaxEditor.Document);

 Then I have a command in my ViewModel that fires when a "Secondary Error" is selected in a listBox outside of the syntaxEditor:

tag.Error = SelectedError;

 

When i walk through the code, the Error is changed correctly and the OnTagsChanged event is raised, however nothing happens.  If i put a breakpoint on the GetTags function, it is never executed.  I seem to be missing something.  Im hoping you can spot it.

Thanks again for all your help.

[Modified 6 years ago]

Posted 6 years ago by James Kester - Metafile Information Systems, Inc.
Avatar

I was wrong, the code does execute when the text in the syntaxEditor is changed.  And when that happens, pattern is always null in this case. When I click on a secondary error, the error is set properly, but getTags is not executed, because the text in the syntaxEditor has not changed.

Then if i type in the syntaxEditor getTags is executed but pattern has gone back to null.  Is there another event i can raise to refresh the text, if the text has not changed in the syntaxEditor?

 

Thanks.

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi James,

Nothing jumps out to me as wrong in terms of the tagger registration.  However the last two sample lines you have there are wrong because the provider will create the tagger instance for each document behind the scenes and put it in the document.Properties collection.  So by manually creating a tagger in code, it's not doing you any good.  You are effectively making a second tagger instance that isn't used.

You need to get the instance from document.Properties and update that instead, if it's available.  That will probably solve your problems.  The ClassificationLayered QuickStart shows an example of that.


Actipro Software Support

Posted 6 years ago by James Kester - Metafile Information Systems, Inc.
Avatar

OK great thanks.  I guess Im just failing to see how to get my "error" into the class without creating an instance of it.  I looked at the ClassificationLayered Quickstart and am now trying to use that, but i seem to have the same problem of getting the "error"  or accessing an error property in the class without creating an instance of it from outside the class. I can see how the tagging works very easily when "actipro" is hard coded as the item to tag.  Is there any way you could provide a simple example of how to tag something dynamicly? something thats coming from out side of the class thats doing the tagging?  I guess thats where im failing to make the connection.  

My "error" is just a string im trying to replace "Actipro" with.

I also didnt see any use of document.Properties in the ClassificationLayered Quickstart

I have got all the quickstarts to work fine when they are just tagging "actipro" but i havent been able to tag my own tokens. Unless i hard code them into the class.

Thanks again.

Answer - Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi James,

Look in the MainControls.xaml.cs of that ClassificationLayered QuickStart and search for "Document.Properties".  It's at the bottom of the class.  That's showing how it is getting the auto-created instance of the tagger that was made for the document.  And it's updating a property on it.  You'd do the same but update your error string instead.


Actipro Software Support

Posted 6 years ago by James Kester - Metafile Information Systems, Inc.
Avatar

Ahh , thanks, i don't know why i didnt look there in the first place! thanks that looks like exactly what i need.

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.