2013-06-18 13 views
11

Pracuję nad rozszerzeniem Visual Studio, które udostępnia niektóre funkcje dla niestandardowego języka. Zrobiłem proste podświetlanie składni i zamierzam przejść do takich kwestii jak podświetlanie składni, dopasowywanie klamr, tworzenie konturów i podobne. Podstawowym problemem, który teraz szukam, jest to, że wszystkie te wymagają różnych typów tagów, które (o ile widzę) wymagałyby różnych taggerów. Nie widzę jednak intuicyjnego sposobu dzielenia się informacjami między taggerami, ponieważ wszystkie te trzy rzeczy można wykonać w jednej analizie zawartości. Chodzi mi o to, że mógłbym przetworzyć go trzy razy, ale to nie brzmi jak dobre rozwiązanie.Tworzenie taggera, który ma więcej niż jeden typ tagu dla rozszerzenia VS

Jak mogę zwrócić więcej niż jeden typ znacznika z taggera (może użyć ITag?) Lub udostępnić informacje między więcej niż jednym taggerem?

Moja obecna struktura jest jako takie:

internal class HighlightWordTagger : ITagger<ClassificationTag> 
    { 
     ITextBuffer TextBuffer; 
     IClassificationType Keyword; 
     IClassificationType Comment; 
     IClassificationType Literal; 

     // Probably a giant memory leak 
     Dictionary<ITextSnapshot, List<TagSpan<ClassificationTag>>> SnapshotResults = new Dictionary<ITextSnapshot, List<TagSpan<ClassificationTag>>>(); 

     public HighlightWordTagger(ITextBuffer sourceBuffer, IClassificationTypeRegistryService typeService) 
     { 
      TextBuffer = sourceBuffer; 

      TextBuffer.Changed += (sender, args) => 
      { 
       LexSnapshot(args.After); 

       TagsChanged(this, new SnapshotSpanEventArgs(new SnapshotSpan(args.After, new Span(0, args.After.Length)))); 
      }; 
      Keyword = typeService.GetClassificationType("WideKeyword"); 
      Comment = typeService.GetClassificationType("WideComment"); 
      Literal = typeService.GetClassificationType("WideLiteral"); 
     } 

     public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans) 
     { 
      LexSnapshot(spans[0].Snapshot); 
      foreach (var snapshotspan in SnapshotResults[spans[0].Snapshot]) 
      { 
       foreach (var span in spans) 
       { 
        if (snapshotspan.Span.IntersectsWith(span)) 
        { 
         yield return snapshotspan; 
        } 
       } 
      } 
     } 

     Span SpanFromLexer(Lexer.Range range) 
     { 
      return new Span((int)range.begin.offset, (int)(range.end.offset - range.begin.offset)); 
     } 

     void LexSnapshot(ITextSnapshot shot) 
     { 
      if (SnapshotResults.ContainsKey(shot)) 
       return; 

      var lexer = new Lexer(); 
      var list = new List<TagSpan<ClassificationTag>>(); 
      SnapshotResults[shot] = list; 
      lexer.Read(
       shot.GetText(), 
       (where, what) => 
       { 
        if (what == Lexer.Failure.UnlexableCharacter) 
         return false; 
        var loc = new Span(
         (int)where.offset, 
         (int)shot.Length - (int)where.offset 
        ); 
        if (what == Lexer.Failure.UnterminatedComment) 
         list.Add(new TagSpan<ClassificationTag>(new SnapshotSpan(shot, loc), new ClassificationTag(Comment))); 
        if (what == Lexer.Failure.UnterminatedStringLiteral) 
         list.Add(new TagSpan<ClassificationTag>(new SnapshotSpan(shot, loc), new ClassificationTag(Literal))); 
        return false; 
       }, 
       where => 
       { 
        // Clamp this so it doesn't go over the end when we add \n in the lexer. 
        where.end.offset = where.end.offset > shot.Length ? (uint)(shot.Length) : where.end.offset; 
        var loc = SpanFromLexer(where); 
        list.Add(new TagSpan<ClassificationTag>(new SnapshotSpan(shot, loc), new ClassificationTag(Comment))); 
       }, 
       token => { 
        var location = SpanFromLexer(token.location); 
        if (token.type == Lexer.TokenType.String || token.type == Lexer.TokenType.Integer) 
        { 
         list.Add(new TagSpan<ClassificationTag>(new SnapshotSpan(shot, location), new ClassificationTag(Literal))); 
        } 
        if (lexer.IsKeyword(token.type)) 
        { 
         list.Add(new TagSpan<ClassificationTag>(new SnapshotSpan(shot, location), new ClassificationTag(Keyword))); 
        } 
        return false; 
       } 
      ); 
     } 

     public event EventHandler<SnapshotSpanEventArgs> TagsChanged = delegate { }; 
    } 

prawdopodobnie mógłbym zrobić lepszą pracę wolno ponownie Lexing tyle, ale to na innym pytaniem.

+1

@Witaj: Używam tylko leksykonu z jednym znacznikiem. Wzywam do niezarządzanego kodu do lex i parsowania, i to działa jednocześnie. – Puppy

+1

@ Yvette: Tak ... mój komentarz nie był bardzo pomocny. Pozwól, że opublikuję swoją strukturę. – Puppy

+0

To uderzyło mnie, że możesz być w stanie uzyskać ponowne użycie, jeśli zaimplementujesz 'ITagger ' wiele razy w obrębie tej samej klasy (dla różnych 'T's). W ten sposób istnieje przynajmniej oczywisty sposób "dzielenia się informacjami" pomiędzy taggerami - ponieważ jest to ta sama klasa. –

Odpowiedz

5

Skończyło się na konieczności oddzielenia tych obaw. Możesz użyć ITextBuffer.Properties.GetOrCreateSingletonProperty do kojarzenia dowolnych obiektów do wyboru z buforem tekstowym. W efekcie stworzyłem oddzielną klasę leksykalną, kojarząc ją z buforem tekstowym, a następnie wykonując prawie całą logikę, z wyjątkiem tagowania. Następnie w implementacji każdego taggera po prostu odpytuję lexera pod kątem wyników, a następnie otaguję go. Pozwala to wielu znacznikom uzależnić się od tej samej instancji leksykalnej.

Biorąc pod uwagę, że większość lexerów i analizatorów składających się z kilku różnych typów stworzyła więcej niż jeden rodzaj tagów, jestem zaskoczony, że VS sprawia, że ​​hakowanie tak źle powoduje powstanie tego wyniku.

+1

+1 za ranting o rozszerzalności VS. Wymaga * sposobu * za dużo bojownika, ciężko jest debugować (np. Nie można ustawić punktów przerwania na atrybutach MEF), jest * nieodwracalny *, i rzeczy, które powinny być wbudowane (np. Dopasowanie klamrowe) muszą być dodane ręcznie (lub jeśli * są * wbudowane, jak możemy je znaleźć bez dostępu do kodu źródłowego VS?) – Qwertie

3

Jak powiedział szczeniak, możesz używać wielu oddzielnych taggerów i mogą się one nawzajem kontaktować przez GetOrCreateSingletonProperty, ale zakładam, że nie ma gwarancji co do kolejności, w jakiej zostaną utworzone znaczniki, więc proces inicjalizacji może być niewygodny.

Tymczasem odniosłem sukces w łączeniu wielu taggerów w jednej klasie, a nawet stworzyłem kompletny "przykładowy język", aby zademonstrować tę technikę. Oto ona:

/// <summary>Boilerplate factory class that associates <see cref="SampleLanguageForVS"/>, 
/// and file extension .samplelang, with content type "Sample Language".</summary> 
[Export(typeof(IClassifierProvider))] 
[Export(typeof(ITaggerProvider))] 
[TagType(typeof(ClassificationTag))] 
[TagType(typeof(ErrorTag))] 
[ContentType("Sample Language")] 
internal class SampleLanguageForVSProvider : IClassifierProvider, ITaggerProvider 
{ 
    [Export] 
    [Name("Sample Language")] // Must match the [ContentType] attributes 
    [BaseDefinition("code")] 
    internal static ContentTypeDefinition _ = null; 
    [Export] 
    [FileExtension(".samplelang")] 
    [ContentType("Sample Language")] 
    internal static FileExtensionToContentTypeDefinition _1 = null; 

    [Import] IClassificationTypeRegistryService _registry = null; // Set via MEF 

    public static SampleLanguageForVS Get(IClassificationTypeRegistryService registry, ITextBuffer buffer) 
    { 
     return buffer.Properties.GetOrCreateSingletonProperty<SampleLanguageForVS>(
      delegate { return new SampleLanguageForVS(registry, buffer); }); 
    } 
    public IClassifier GetClassifier(ITextBuffer buffer) 
    { 
     return Get(_registry, buffer); 
    } 
    public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag 
    { 
     return Get(_registry, buffer) as ITagger<T>; 
    } 
} 

internal class SampleLanguageForVS : IClassifier, 
    ITagger<ClassificationTag>, 
    ITagger<ErrorTag>, 
    IBackgroundAnalyzerImpl<object, IList<ITagSpan<ITag>>> 
{ 
    protected IClassificationTypeRegistryService _registry; 
    protected ITextBuffer _buffer; 
    protected IClassificationType _commentType; 
    protected ClassificationTag _outerParenTag; 
    protected IList<ITagSpan<ITag>> _resultTags; 
    protected BackgroundAnalyzerForVS<object, IList<ITagSpan<ITag>>> _parseHelper; 

    public SampleLanguageForVS(IClassificationTypeRegistryService registry,ITextBuffer buffer) 
    { 
     _registry = registry; 
     _buffer = buffer; 
     _commentType = registry.GetClassificationType(PredefinedClassificationTypeNames.Comment); 
     _outerParenTag = MakeTag(PredefinedClassificationTypeNames.Keyword); 
     _parseHelper = new BackgroundAnalyzerForVS<object, IList<ITagSpan<ITag>>>(buffer, this, true); 
    } 
    ClassificationTag MakeTag(string name) 
    { 
     return new ClassificationTag(_registry.GetClassificationType(name)); 
    } 

    #region Classifier (lexical analysis) 

    public event EventHandler<ClassificationChangedEventArgs> ClassificationChanged; 

    public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span) 
    { 
     List<ClassificationSpan> spans = new List<ClassificationSpan>(); 
     var line = span.Snapshot.GetLineFromPosition(span.Start); 
     do { 
      var cspan = GetLineClassification(line); 
      if (cspan != null) 
       spans.Add(cspan); 

      if (line.EndIncludingLineBreak.Position >= span.Snapshot.Length) break; 
      line = span.Snapshot.GetLineFromPosition(line.EndIncludingLineBreak.Position); 
     } while (line.EndIncludingLineBreak < span.End.Position); 
     return spans; 
    } 

    public ClassificationSpan GetLineClassification(ITextSnapshotLine line) 
    { 
     var span = new Span(line.Start.Position, line.Length); 
     var sspan = new SnapshotSpan(line.Snapshot, span); 
     int i; 
     for (i = span.Start; i < line.Snapshot.Length && char.IsWhiteSpace(line.Snapshot[i]); i++) { } 
     if (i < line.Snapshot.Length && 
      (line.Snapshot[i] == '#' || 
      line.Snapshot[i] == '/' && i + 1 < line.Snapshot.Length && line.Snapshot[i+1] == '/')) 
      return new ClassificationSpan(sspan, _commentType); 
     return null; 
    } 

    #endregion 

    #region Background analysis (the two taggers) 

    public object GetInputSnapshot() 
    { 
     return null; // this example has no state to pass to the analysis thread. 
    } 
    public IList<ITagSpan<ITag>> RunAnalysis(ITextSnapshot snapshot, object input, System.Threading.CancellationToken cancelToken) 
    { 
     List<ITagSpan<ITag>> results = new List<ITagSpan<ITag>>(); 
     // On analysis thread: produce classification tags for nested [(parens)] 
     // and warning tags for backslashes. 
     int parenLevel = 0; 
     for (int i = 0; i < snapshot.Length; i++) 
     { 
      char c = snapshot[i]; 
      if (c == '\\') 
       results.Add(new TagSpan<ErrorTag>(
        new SnapshotSpan(snapshot, new Span(i, 1)), 
        new ErrorTag("compiler warning", "Caution: that's not really a slash, it's a backslash!!"))); 
      bool open = (c == '[' || c == '('); 
      bool close = (c == ']' || c == ')'); 
      if (close) { 
       if (parenLevel > 0) 
        parenLevel--; 
       else { 
        results.Add(new TagSpan<ErrorTag>(
         new SnapshotSpan(snapshot, new Span(i, Math.Min(2, snapshot.Length-i))), 
         new ErrorTag("syntax error", "Caution: closing parenthesis without matching opener"))); 
       } 
      } 
      if ((open || close) && parenLevel == 0) 
       results.Add(new TagSpan<ClassificationTag>(
        new SnapshotSpan(snapshot, new Span(i, 1)), 
        _outerParenTag)); 
      if (open) 
       parenLevel++; 
     } 
     return results; 
    } 
    public void OnRunSucceeded(IList<ITagSpan<ITag>> results) 
    { 
     _resultTags = results; 
     // We don't know which tags changed unless we do some fancy diff, so 
     // act as if everything changed. 
     if (TagsChanged != null) // should always be true 
      TagsChanged(this, new SnapshotSpanEventArgs(new SnapshotSpan(_buffer.CurrentSnapshot, new Span(0, _buffer.CurrentSnapshot.Length)))); 
    } 

    #endregion 

    #region ITagger<ClassificationTag> and ITagger<ErrorTag> Members 

    IEnumerable<ITagSpan<ErrorTag>> ITagger<ErrorTag>.GetTags(NormalizedSnapshotSpanCollection spans) 
    { 
     return GetTags<ErrorTag>(spans); 
    } 
    IEnumerable<ITagSpan<ClassificationTag>> ITagger<ClassificationTag>.GetTags(NormalizedSnapshotSpanCollection spans) 
    { 
     return GetTags<ClassificationTag>(spans); 
    } 
    public IEnumerable<ITagSpan<TTag>> GetTags<TTag>(NormalizedSnapshotSpanCollection spans) where TTag : ITag 
    { 
     if (_resultTags == null) 
      return null; 

     // TODO: make more efficient for large files with e.g. binary search 
     int start = spans[0].Start.Position, end = spans[spans.Count-1].End.Position; 
     return _resultTags.Where(ts => ts.Span.End >= start && ts.Span.Start <= end).OfType<ITagSpan<TTag>>(); 
    } 

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged; 

    #endregion 
} 

All że brakuje powyżej using oświadczenia (patrz complete source file) i klasa BackgroundAnalyzerForVS. Jeśli podłączysz ten kod do projektu vsix, otrzymasz "lexing", opóźnione "parsowanie", ostrzeżenie i tagi błędów. Plik demonstracyjny:

Open this in Visual Studio to see "sample" syntax highlighting. 
    // Backslashes are underlined. 
    \\ <-- Such as those ones. 
When you start a parenthetical (like this) the parens are highlighted, 
but ([nested parens (like this)]) are not highlighted. 
# Do not write a closing ")" without an opening "(". 
Powiązane problemy