public Task <bool> HasSuggestedActionsAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { return(Task.Factory.StartNew(() => { DevSkimError error = GetErrorUnderCaret(range); return (error != null); })); }
private void OnBufferChange(object sender, TextContentChangedEventArgs e) { _currentSnapshot = e.After; // Translate all of the old dirty spans to the new snapshot. NormalizedSnapshotSpanCollection newDirtySpans = _dirtySpans.CloneAndTrackTo(e.After, SpanTrackingMode.EdgeInclusive); // Dirty all the spans that changed. foreach (var change in e.Changes) { newDirtySpans = NormalizedSnapshotSpanCollection.Union(newDirtySpans, new NormalizedSnapshotSpanCollection(e.After, change.NewSpan)); } // Translate all the security errors to the new snapshot (and remove anything that is a dirty region since we will need to check that again). var oldSecurityErrors = this.Factory.CurrentSnapshot; var newSecurityErrors = new DevSkimErrorsSnapshot(this.FilePath, oldSecurityErrors.VersionNumber + 1); // Copy all of the old errors to the new errors unless the error was affected by the text change foreach (var error in oldSecurityErrors.Errors) { Debug.Assert(error.NextIndex == -1); var newError = DevSkimError.CloneAndTranslateTo(error, e.After); if (newError != null) { Debug.Assert(newError.Span.Length == error.Span.Length); error.NextIndex = newSecurityErrors.Errors.Count; newSecurityErrors.Errors.Add(newError); } } this.UpdateSecurityErrors(newSecurityErrors); _dirtySpans = newDirtySpans; // Start processing the dirty spans (which no-ops if we're already doing it). if (_dirtySpans.Count != 0) { this.KickUpdate(); } }
public IEnumerable <SuggestedActionSet> GetSuggestedActions(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { DevSkimError error = GetErrorUnderCaret(range); if (error != null) { List <ISuggestedAction> fixActions = new List <ISuggestedAction>(); ITrackingSpan trackingSpan = range.Snapshot.CreateTrackingSpan(error.Span, SpanTrackingMode.EdgeInclusive); // Create list of fixes if the rule has them.. if (error.Rule.Fixes != null) { foreach (FixRecord fix in error.Rule.Fixes) { fixActions.Add(new FixSuggestedAction(trackingSpan, fix)); } } int suppressDays = Settings.GetSettings().SuppressDays; List <ISuggestedAction> suppActions = new List <ISuggestedAction>(); var line = range.Snapshot.GetLineFromPosition(range.Start); trackingSpan = line.Snapshot.CreateTrackingSpan(line.Extent, SpanTrackingMode.EdgeInclusive); suppActions.Add(new SuppressSuggestedAction(trackingSpan, error.Rule)); suppActions.Add(new SuppressSuggestedAction(trackingSpan, error.Rule, suppressDays)); suppActions.Add(new SuppressSuggestedAction(trackingSpan, null)); suppActions.Add(new SuppressSuggestedAction(trackingSpan, null, suppressDays)); // We don't want empty group and spacer in the pop-up menu if (fixActions.Count > 0) { return new SuggestedActionSet[] { new SuggestedActionSet(fixActions), new SuggestedActionSet(suppActions) } } ; else { return new SuggestedActionSet[] { new SuggestedActionSet(suppActions) } }; } return(Enumerable.Empty <SuggestedActionSet>()); }
private void DoUpdate() { // It would be good to do all of this work on a background thread but we can't: // _classifier.GetClassificationSpans() should only be called on the UI thread because some classifiers assume they are called from the UI thread. // Raising the TagsChanged event from the taggers needs to happen on the UI thread (because some consumers might assume it is being raised on the UI thread). // // Updating the snapshot for the factory and calling the sink can happen on any thread but those operations are so fast that there is no point. if ((!_isDisposed) && (_dirtySpans.Count > 0)) { var line = _dirtySpans[0].Start.GetContainingLine(); if (line.Length > 0) { var oldSecurityErrors = this.Factory.CurrentSnapshot; var newSecurityErrors = new DevSkimErrorsSnapshot(this.FilePath, oldSecurityErrors.VersionNumber + 1); // Go through the existing errors. If they are on the line we are currently parsing then // copy them to oldLineErrors, otherwise they go to the new errors. var oldLineErrors = new List <DevSkimError>(); foreach (var error in oldSecurityErrors.Errors) { Debug.Assert(error.NextIndex == -1); if (line.Extent.Contains(error.Span)) { error.NextIndex = -1; oldLineErrors.Add(error); // Do not clone old error here ... we'll do that later there is no change. } else { error.NextIndex = newSecurityErrors.Errors.Count; newSecurityErrors.Errors.Add(DevSkimError.Clone(error)); // We must clone the old error here. } } int expectedErrorCount = newSecurityErrors.Errors.Count + oldLineErrors.Count; bool anyNewErrors = false; // provide whole line to processor so it has complete overview of the scanned code string text = line.GetText(); //var classifications = _classifier.GetClassificationSpans(line.Extent); //foreach (var classification in classifications) //{ //if (classification.ClassificationType.IsOfType(PredefinedClassificationTypeNames.Comment) || classification.ClassificationType.IsOfType("xml doc comment - text")) // classification.Span.GetText(); int index = 0; while (index < text.Length) { MatchResult result = RuleProcessor.IsMatch(text, index, line.Snapshot.ContentType.TypeName); int errorStart = result.Location; if (errorStart < 0) { break; } int errorLength = result.Length; if (errorLength > 0) // Ignore any single character error. { //var newSpan = new SnapshotSpan(classification.Span.Start + errorStart, errorLength); var newSpan = new SnapshotSpan(line.Start + errorStart, errorLength); var oldError = oldLineErrors.Find((e) => e.Span == newSpan); if (oldError != null) { // There was a security error at the same span as the old one so we should be able to just reuse it. oldError.NextIndex = newSecurityErrors.Errors.Count; newSecurityErrors.Errors.Add(DevSkimError.Clone(oldError)); // Don't clone the old error yet } else { newSecurityErrors.Errors.Add(new DevSkimError(newSpan, result.Rule)); anyNewErrors = true; } index = errorStart + errorLength; } else { // How can you have a error with a length of 0? Handle it gracefully in any case. index = errorStart + 1; } } //endforeach} // This does a deep comparison so we will only do the update if the a different set of errors was discovered compared to what we had previously. // If there were any new errors or if we didn't see all the expected errors then there is a change and we need to update the security errors. if (anyNewErrors || (newSecurityErrors.Errors.Count != expectedErrorCount)) { this.UpdateSecurityErrors(newSecurityErrors); } else { // There were no changes so we don't need to update our snapshot. // We have, however, dirtied the old errors by setting their NextIndex property on the assumption that we would be updating the errors. // Revert all those changes. foreach (var error in oldSecurityErrors.Errors) { error.NextIndex = -1; } } } // NormalizedSnapshotSpanCollection.Difference doesn't quite do what we need here. If I have {[5,5), [10,20)} and subtract {[5, 15)} and do a ...Difference, I // end up with {[5,5), [15,20)} (the zero length span at the beginning isn't getting removed). A zero-length span at the end wouldn't be removed but, in this case, // that is the desired behavior (the zero length span at the end could be a change at the beginning of the next line, which we'd want to keep). var newDirtySpans = new List <Span>(_dirtySpans.Count + 1); var extent = line.ExtentIncludingLineBreak; for (int i = 0; (i < _dirtySpans.Count); ++i) { Span s = _dirtySpans[i]; if ((s.End < extent.Start) || (s.Start >= extent.End)) // Intentionally asymmetric { newDirtySpans.Add(s); } else { if (s.Start < extent.Start) { newDirtySpans.Add(Span.FromBounds(s.Start, extent.Start)); } if ((s.End >= extent.End) && (extent.End < line.Snapshot.Length)) { newDirtySpans.Add(Span.FromBounds(extent.End, s.End)); //This could add a zero length span (which is by design since we want to ensure we security check the next line). } } } _dirtySpans = new NormalizedSnapshotSpanCollection(line.Snapshot, newDirtySpans); if (_dirtySpans.Count == 0) { // We've cleaned up all the dirty spans. _isUpdating = false; } else { // Still more work to do. _uiThreadDispatcher.BeginInvoke(new Action(() => this.DoUpdate()), DispatcherPriority.Background); } } }