private static async Task<bool> TryClassifyContainingMemberSpan(TaggerContext<IClassificationTag> context, DocumentSnapshotSpan spanToTag, IEditorClassificationService classificationService, ClassificationTypeMap typeMap) { var range = context.TextChangeRange; if (range == null) { // There was no text change range, we can't just reclassify a member body. return false; } // there was top level edit, check whether that edit updated top level element var document = spanToTag.Document; var cancellationToken = context.CancellationToken; var lastSemanticVersion = (VersionStamp?)context.State; if (lastSemanticVersion != null) { var currentSemanticVersion = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); if (lastSemanticVersion.Value != currentSemanticVersion) { // A top level change was made. We can't perform this optimization. return false; } } var service = document.Project.LanguageServices.GetService<ISyntaxFactsService>(); // perf optimization. Check whether all edits since the last update has happened within // a member. If it did, it will find the member that contains the changes and only refresh // that member. If possible, try to get a speculative binder to make things even cheaper. var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var changedSpan = new TextSpan(range.Value.Span.Start, range.Value.NewLength); var member = service.GetContainingMemberDeclaration(root, changedSpan.Start); if (member == null || !member.FullSpan.Contains(changedSpan)) { // The edit was not fully contained in a member. Reclassify everything. return false; } var subTextSpan = service.GetMemberBodySpanForSpeculativeBinding(member); if (subTextSpan.IsEmpty) { // Wasn't a member we could reclassify independently. return false; } var subSpan = subTextSpan.Contains(changedSpan) ? subTextSpan.ToSpan() : member.FullSpan.ToSpan(); var subSpanToTag = new DocumentSnapshotSpan(spanToTag.Document, new SnapshotSpan(spanToTag.SnapshotSpan.Snapshot, subSpan)); // re-classify only the member we're inside. await ClassifySpansAsync(context, subSpanToTag, classificationService, typeMap).ConfigureAwait(false); return true; }
public static async Task ProduceTagsAsync(TaggerContext<IClassificationTag> context, DocumentSnapshotSpan spanToTag, IEditorClassificationService classificationService, ClassificationTypeMap typeMap) { var document = spanToTag.Document; if (document == null) { return; } if (await TryClassifyContainingMemberSpan(context, spanToTag, classificationService, typeMap).ConfigureAwait(false)) { return; } // We weren't able to use our specialized codepaths for semantic classifying. // Fall back to classifying the full span that was asked for. await ClassifySpansAsync(context, spanToTag, classificationService, typeMap).ConfigureAwait(false); }
private void AddClassifiedSpansForCurrentTree( IEditorClassificationService classificationService, SnapshotSpan span, Document document, List <ClassifiedSpan> classifiedSpans) { List <ClassifiedSpan> tempList; if (!_lastLineCache.TryUseCache(span, out tempList)) { tempList = ClassificationUtilities.GetOrCreateClassifiedSpanList(); classificationService.AddSyntacticClassificationsAsync( document, span.Span.ToTextSpan(), tempList, CancellationToken.None).Wait(CancellationToken.None); _lastLineCache.Update(span, tempList); } // simple case. They're asking for the classifications for a tree that we already have. // Just get the results from the tree and return them. classifiedSpans.AddRange(tempList); }
public static async Task ProduceTagsAsync( TaggerContext <IClassificationTag> context, DocumentSnapshotSpan spanToTag, IEditorClassificationService classificationService, ClassificationTypeMap typeMap) { var document = spanToTag.Document; if (document == null) { return; } if (await TryClassifyContainingMemberSpan(context, spanToTag, classificationService, typeMap).ConfigureAwait(false)) { return; } // We weren't able to use our specialized codepaths for semantic classifying. // Fall back to classifying the full span that was asked for. await ClassifySpansAsync(context, spanToTag, classificationService, typeMap).ConfigureAwait(false); }
protected override Task ProduceTagsAsync(TaggerContext <IClassificationTag> context) { Debug.Assert(context.SpansToTag.IsSingle()); var spanToTag = context.SpansToTag.Single(); // Attempt to get a classification service which will actually produce the results. // If we can't (because we have no Document, or because the language doesn't support // this service), then bail out immediately. if (_classificationService == null) { var document = spanToTag.Document; _classificationService = document?.Project.LanguageServices.GetService <IEditorClassificationService>(); } if (_classificationService == null) { return(SpecializedTasks.EmptyTask); } return(SemanticClassificationUtilities.ProduceTagsAsync(context, spanToTag, _classificationService, _typeMap)); }
public override async Task <IEnumerable <ITagSpan <IClassificationTag> > > ProduceTagsAsync( Document document, SnapshotSpan snapshotSpan, int?caretPosition, CancellationToken cancellationToken) { var snapshot = snapshotSpan.Snapshot; if (document == null) { return(SpecializedCollections.EmptyEnumerable <ITagSpan <IClassificationTag> >()); } if (_classificationService == null) { _classificationService = document.Project.LanguageServices.GetService <IEditorClassificationService>(); } if (_classificationService == null) { return(SpecializedCollections.EmptyEnumerable <ITagSpan <IClassificationTag> >()); } // we don't directly reference the semantic model here, we just keep it alive so // the classification service does not need to block to produce it. using (Logger.LogBlock(FunctionId.Tagger_SemanticClassification_TagProducer_ProduceTags, cancellationToken)) { var textSpan = snapshotSpan.Span.ToTextSpan(); var extensionManager = document.Project.Solution.Workspace.Services.GetService <IExtensionManager>(); var classifiedSpans = ClassificationUtilities.GetOrCreateClassifiedSpanList(); await _classificationService.AddSemanticClassificationsAsync( document, textSpan, classifiedSpans, cancellationToken : cancellationToken).ConfigureAwait(false); return(ClassificationUtilities.ConvertAndReturnList(_typeMap, snapshotSpan.Snapshot, classifiedSpans)); } }
private void AddClassifiedSpansForTokens(IEditorClassificationService classificationService, SnapshotSpan span, List<ClassifiedSpan> classifiedSpans) { classificationService.AddLexicalClassifications( span.Snapshot.AsText(), span.Span.ToTextSpan(), classifiedSpans, CancellationToken.None); }
private void AddClassifiedSpansForPreviousTree( IEditorClassificationService classificationService, SnapshotSpan span, ITextSnapshot lastSnapshot, Document lastDocument, List<ClassifiedSpan> classifiedSpans) { // Slightly more complicated case. They're asking for the classifications for a // different snapshot than what we have a parse tree for. So we first translate the span // that they're asking for so that is maps onto the tree that we have spans for. We then // get the classifications from that tree. We then take the results and translate them // back to the snapshot they want. Finally, as some of the classifications may have // changed, we check for some common cases and touch them up manually so that things // look right for the user. // Note the handling of SpanTrackingModes here: We do EdgeExclusive while mapping back , // and EdgeInclusive mapping forward. What I've convinced myself is that EdgeExclusive // is best when mapping back over a deletion, so that we don't end up classifying all of // the deleted code. In most addition/modification cases, there will be overlap with // existing spans, and so we'll end up classifying well. In the worst case, there is a // large addition that doesn't exist when we map back, and so we don't have any // classifications for it. That's probably okay, because: // 1. If it's that large, it's likely that in reality there are multiple classification // spans within it. // 2.We'll eventually call ClassificationsChanged and re-classify that region anyway. // When mapping back forward, we use EdgeInclusive so that in the common typing cases we // don't end up with half a token colored differently than the other half. // See bugs like http://vstfdevdiv:8080/web/wi.aspx?id=6176 for an example of what can // happen when this goes wrong. // 1) translate the requested span onto the right span for the snapshot that corresponds // to the syntax tree. var translatedSpan = span.TranslateTo(lastSnapshot, SpanTrackingMode.EdgeExclusive); if (translatedSpan.IsEmpty) { // well, there is no information we can get from previous tree, use lexer to // classify given span. soon we will re-classify the region. AddClassifiedSpansForTokens(classificationService, span, classifiedSpans); return; } var tempList = ClassificationUtilities.GetOrCreateClassifiedSpanList(); AddClassifiedSpansForCurrentTree(classificationService, translatedSpan, lastDocument, tempList); var currentSnapshot = span.Snapshot; var currentText = currentSnapshot.AsText(); foreach (var lastClassifiedSpan in tempList) { // 2) Translate those classifications forward so that they correspond to the true // requested snapshot. var lastSnapshotSpan = lastClassifiedSpan.TextSpan.ToSnapshotSpan(lastSnapshot); var currentSnapshotSpan = lastSnapshotSpan.TranslateTo(currentSnapshot, SpanTrackingMode.EdgeInclusive); var currentClassifiedSpan = new ClassifiedSpan(lastClassifiedSpan.ClassificationType, currentSnapshotSpan.Span.ToTextSpan()); // 3) The classifications may be incorrect due to changes in the text. For example, // if "clss" becomes "class", then we want to changes the classification from // 'identifier' to 'keyword'. currentClassifiedSpan = classificationService.AdjustStaleClassification(currentText, currentClassifiedSpan); classifiedSpans.Add(currentClassifiedSpan); } ClassificationUtilities.ReturnClassifiedSpanList(tempList); }
private void AddClassifiedSpansForCurrentTree( IEditorClassificationService classificationService, SnapshotSpan span, Document document, List<ClassifiedSpan> classifiedSpans) { List<ClassifiedSpan> tempList; if (!_lastLineCache.TryUseCache(span, out tempList)) { tempList = ClassificationUtilities.GetOrCreateClassifiedSpanList(); classificationService.AddSyntacticClassificationsAsync( document, span.Span.ToTextSpan(), tempList, CancellationToken.None).Wait(CancellationToken.None); _lastLineCache.Update(span, tempList); } // simple case. They're asking for the classifications for a tree that we already have. // Just get the results from the tree and return them. classifiedSpans.AddRange(tempList); }
private void AddClassifiedSpans(IEditorClassificationService classificationService, SnapshotSpan span, List<ClassifiedSpan> classifiedSpans) { // First, get the tree and snapshot that we'll be operating over. // From this point on we'll do all operations over these values. ITextSnapshot lastSnapshot; Document lastDocument; lock (_gate) { lastSnapshot = _lastParsedSnapshot; lastDocument = _lastParsedDocument; } if (lastDocument == null) { // We don't have a syntax tree yet. Just do a lexical classification of the document. AddClassifiedSpansForTokens(classificationService, span, classifiedSpans); return; } // We have a tree. However, the tree may be for an older version of the snapshot. // If it is for an older version, then classify that older version and translate // the classifications forward. Otherwise, just classify normally. if (lastSnapshot.Version.ReiteratedVersionNumber == span.Snapshot.Version.ReiteratedVersionNumber) { AddClassifiedSpansForCurrentTree(classificationService, span, lastDocument, classifiedSpans); } else { // Slightly more complicated. We have a parse tree, it's just not for the snapshot // we're being asked for. AddClassifiedSpansForPreviousTree(classificationService, span, lastSnapshot, lastDocument, classifiedSpans); } }
private static async Task ClassifySpansAsync(TaggerContext<IClassificationTag> context, DocumentSnapshotSpan spanToTag, IEditorClassificationService classificationService, ClassificationTypeMap typeMap) { try { var document = spanToTag.Document; var snapshotSpan = spanToTag.SnapshotSpan; var snapshot = snapshotSpan.Snapshot; var cancellationToken = context.CancellationToken; using (Logger.LogBlock(FunctionId.Tagger_SemanticClassification_TagProducer_ProduceTags, cancellationToken)) { var classifiedSpans = ClassificationUtilities.GetOrCreateClassifiedSpanList(); await classificationService.AddSemanticClassificationsAsync( document, snapshotSpan.Span.ToTextSpan(), classifiedSpans, cancellationToken: cancellationToken).ConfigureAwait(false); ClassificationUtilities.Convert(typeMap, snapshotSpan.Snapshot, classifiedSpans, context.AddTag); ClassificationUtilities.ReturnClassifiedSpanList(classifiedSpans); var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); // Let the context know that this was the span we actually tried to tag. context.SetSpansTagged(SpecializedCollections.SingletonEnumerable(spanToTag)); context.State = version; } } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
private void AddClassifiedSpansForTokens(IEditorClassificationService classificationService, SnapshotSpan span, List <ClassifiedSpan> classifiedSpans) { classificationService.AddLexicalClassifications( span.Snapshot.AsText(), span.Span.ToTextSpan(), classifiedSpans, CancellationToken.None); }
private void AddClassifiedSpansForPreviousTree( IEditorClassificationService classificationService, SnapshotSpan span, ITextSnapshot lastSnapshot, Document lastDocument, List <ClassifiedSpan> classifiedSpans) { // Slightly more complicated case. They're asking for the classifications for a // different snapshot than what we have a parse tree for. So we first translate the span // that they're asking for so that is maps onto the tree that we have spans for. We then // get the classifications from that tree. We then take the results and translate them // back to the snapshot they want. Finally, as some of the classifications may have // changed, we check for some common cases and touch them up manually so that things // look right for the user. // Note the handling of SpanTrackingModes here: We do EdgeExclusive while mapping back , // and EdgeInclusive mapping forward. What I've convinced myself is that EdgeExclusive // is best when mapping back over a deletion, so that we don't end up classifying all of // the deleted code. In most addition/modification cases, there will be overlap with // existing spans, and so we'll end up classifying well. In the worst case, there is a // large addition that doesn't exist when we map back, and so we don't have any // classifications for it. That's probably okay, because: // 1. If it's that large, it's likely that in reality there are multiple classification // spans within it. // 2.We'll eventually call ClassificationsChanged and re-classify that region anyway. // When mapping back forward, we use EdgeInclusive so that in the common typing cases we // don't end up with half a token colored differently than the other half. // See bugs like http://vstfdevdiv:8080/web/wi.aspx?id=6176 for an example of what can // happen when this goes wrong. // 1) translate the requested span onto the right span for the snapshot that corresponds // to the syntax tree. var translatedSpan = span.TranslateTo(lastSnapshot, SpanTrackingMode.EdgeExclusive); if (translatedSpan.IsEmpty) { // well, there is no information we can get from previous tree, use lexer to // classify given span. soon we will re-classify the region. AddClassifiedSpansForTokens(classificationService, span, classifiedSpans); return; } var tempList = ClassificationUtilities.GetOrCreateClassifiedSpanList(); AddClassifiedSpansForCurrentTree(classificationService, translatedSpan, lastDocument, tempList); var currentSnapshot = span.Snapshot; var currentText = currentSnapshot.AsText(); foreach (var lastClassifiedSpan in tempList) { // 2) Translate those classifications forward so that they correspond to the true // requested snapshot. var lastSnapshotSpan = lastClassifiedSpan.TextSpan.ToSnapshotSpan(lastSnapshot); var currentSnapshotSpan = lastSnapshotSpan.TranslateTo(currentSnapshot, SpanTrackingMode.EdgeInclusive); var currentClassifiedSpan = new ClassifiedSpan(lastClassifiedSpan.ClassificationType, currentSnapshotSpan.Span.ToTextSpan()); // 3) The classifications may be incorrect due to changes in the text. For example, // if "clss" becomes "class", then we want to changes the classification from // 'identifier' to 'keyword'. currentClassifiedSpan = classificationService.AdjustStaleClassification(currentText, currentClassifiedSpan); classifiedSpans.Add(currentClassifiedSpan); } ClassificationUtilities.ReturnClassifiedSpanList(tempList); }
private static async Task <bool> TryClassifyContainingMemberSpan( TaggerContext <IClassificationTag> context, DocumentSnapshotSpan spanToTag, IEditorClassificationService classificationService, ClassificationTypeMap typeMap) { var range = context.TextChangeRange; if (range == null) { // There was no text change range, we can't just reclassify a member body. return(false); } // there was top level edit, check whether that edit updated top level element var document = spanToTag.Document; if (!document.SupportsSyntaxTree) { return(false); } var cancellationToken = context.CancellationToken; var lastSemanticVersion = (VersionStamp?)context.State; if (lastSemanticVersion != null) { var currentSemanticVersion = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); if (lastSemanticVersion.Value != currentSemanticVersion) { // A top level change was made. We can't perform this optimization. return(false); } } var service = document.Project.LanguageServices.GetService <ISyntaxFactsService>(); // perf optimization. Check whether all edits since the last update has happened within // a member. If it did, it will find the member that contains the changes and only refresh // that member. If possible, try to get a speculative binder to make things even cheaper. var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var changedSpan = new TextSpan(range.Value.Span.Start, range.Value.NewLength); var member = service.GetContainingMemberDeclaration(root, changedSpan.Start); if (member == null || !member.FullSpan.Contains(changedSpan)) { // The edit was not fully contained in a member. Reclassify everything. return(false); } var subTextSpan = service.GetMemberBodySpanForSpeculativeBinding(member); if (subTextSpan.IsEmpty) { // Wasn't a member we could reclassify independently. return(false); } var subSpan = subTextSpan.Contains(changedSpan) ? subTextSpan.ToSpan() : member.FullSpan.ToSpan(); var subSpanToTag = new DocumentSnapshotSpan(spanToTag.Document, new SnapshotSpan(spanToTag.SnapshotSpan.Snapshot, subSpan)); // re-classify only the member we're inside. await ClassifySpansAsync(context, subSpanToTag, classificationService, typeMap).ConfigureAwait(false); return(true); }
public TagComputer( ITextBuffer subjectBuffer, IForegroundNotificationService notificationService, IAsynchronousOperationListener asyncListener, ClassificationTypeMap typeMap, SyntacticClassificationTaggerProvider taggerProvider, IViewSupportsClassificationService viewSupportsClassificationServiceOpt, ITextBufferAssociatedViewService associatedViewService, IEditorClassificationService editorClassificationService, string languageName) { _subjectBuffer = subjectBuffer; _notificationService = notificationService; _listener = asyncListener; _typeMap = typeMap; _taggerProvider = taggerProvider; _viewSupportsClassificationServiceOpt = viewSupportsClassificationServiceOpt; _associatedViewService = associatedViewService; _editorClassificationService = editorClassificationService; _languageName = languageName; _workQueue = new AsynchronousSerialWorkQueue(asyncListener); _reportChangeCancellationSource = new CancellationTokenSource(); _lastLineCache = new LastLineCache(); _workspaceRegistration = Workspace.GetWorkspaceRegistration(subjectBuffer.AsTextContainer()); _workspaceRegistration.WorkspaceChanged += OnWorkspaceRegistrationChanged; ConnectToWorkspace(_workspaceRegistration.Workspace); }