internal static SnapshotLineRange GetLineRange(this ITextView textView, int startLine, int endLine = -1) { endLine = endLine >= 0 ? endLine : startLine; return(SnapshotLineRange.CreateForLineNumberRange(textView.TextSnapshot, startLine, endLine).Value); }
private static void GetTagsInBackgroundCore( IAsyncTaggerSource <TData, TTag> asyncTaggerSource, TData data, int chunkCount, Channel channel, NormalizedLineRangeCollection visited, CancellationToken cancellationToken, Action <CompleteReason> onComplete, Action <SnapshotLineRange, ReadOnlyCollection <ITagSpan <TTag> > > onProgress) { CompleteReason completeReason; try { // Keep track of the LineRange values which we've already provided tags for. Don't // duplicate the work var toProcess = new Queue <SnapshotLineRange>(); // *** This value can be wrong *** // This is the version number we expect the Channel to have. It's used // as a hueristic to determine if we should prioritize a value off of the stack or our // local stack. If it's wrong it means we prioritize the wrong value. Not a bug it // just changes the order in which values will appear var versionNumber = channel.CurrentVersion; // Take one value off of the threadedLineRangeStack value. If the value is bigger than // our chunking increment then we will add the value in chunks to the toProcess queue Action popOne = () => { var value = channel.Read(); if (!value.HasValue) { return; } var lineRange = value.Value; if (lineRange.Count <= chunkCount) { toProcess.Enqueue(lineRange); return; } var snapshot = lineRange.Snapshot; var startLineNumber = lineRange.StartLineNumber; while (startLineNumber <= lineRange.LastLineNumber) { var startLine = snapshot.GetLineFromLineNumber(startLineNumber); var localRange = SnapshotLineRange.CreateForLineAndMaxCount(startLine, chunkCount); toProcess.Enqueue(localRange); startLineNumber += chunkCount; } }; // Get the tags for the specified SnapshotLineRange and return the results. No chunking is done here, // the data is just directly processed Action <SnapshotLineRange> getTags = tagLineRange => { var unvisited = visited.GetUnvisited(tagLineRange.LineRange); if (unvisited.HasValue) { var tagList = EmptyTagList; try { tagLineRange = SnapshotLineRange.CreateForLineNumberRange(tagLineRange.Snapshot, unvisited.Value.StartLineNumber, unvisited.Value.LastLineNumber).Value; tagList = asyncTaggerSource.GetTagsInBackground(data, tagLineRange.ExtentIncludingLineBreak, cancellationToken); } catch (Exception e) { // Ignore exceptions that are thrown by IAsyncTaggerSource. If the tagger threw then we consider // the tags to be nothing for this span. // // It's important that we register some value here. If we register nothing then the foreground will // never see this slot as fulfilled and later requests for this span will eventually queue up another // background request EditorUtilsTrace.TraceInfo("AsyncTagger source exception in background processing {0}", e); } visited.Add(tagLineRange.LineRange); onProgress(tagLineRange, tagList); } }; do { versionNumber = channel.CurrentVersion; popOne(); // We've drained both of the sources of input hence we are done if (0 == toProcess.Count) { break; } while (0 != toProcess.Count) { // If at any point the threadLineRangeStack value changes we consider the new values to have // priority over the old ones if (versionNumber != channel.CurrentVersion) { break; } cancellationToken.ThrowIfCancellationRequested(); var lineRange = toProcess.Dequeue(); getTags(lineRange); } } while (!cancellationToken.IsCancellationRequested); completeReason = cancellationToken.IsCancellationRequested ? CompleteReason.Cancelled : CompleteReason.Finished; } catch (OperationCanceledException) { // Don't report cancellation exceptions. These are thrown during cancellation for fast // break from the operation. It's really a control flow mechanism completeReason = CompleteReason.Cancelled; } catch (Exception e) { // Handle cancellation exceptions and everything else. Don't want an errant // exception thrown by the IAsyncTaggerSource to crash the process EditorUtilsTrace.TraceInfo("AsyncTagger Exception in background processing {0}", e); completeReason = CompleteReason.Error; } onComplete(completeReason); }