public SarifLocationTextMarkerTagger( ITextView textView, ITextBuffer textBuffer, IPersistentSpanFactory persistentSpanFactory, ITextViewCaretListenerService <ITextMarkerTag> textViewCaretListenerService, ISarifErrorListEventSelectionService sarifErrorListEventSelectionService) { ThreadHelper.ThrowIfNotOnUIThread(); if (!SdkUIUtilities.TryGetFileNameFromTextBuffer(textBuffer, out this.filePath)) { throw new ArgumentException("Always expect to be able to get file name from text buffer.", nameof(textBuffer)); } this.TextBuffer = textBuffer; this.persistentSpanFactory = persistentSpanFactory; this.sarifErrorListEventSelectionService = sarifErrorListEventSelectionService; // Subscribe to the SARIF error item being selected from the error list // so we can properly filter the tags being shown in the editor // to the currently selected item. this.sarifErrorListEventSelectionService.SelectedItemChanged += this.SelectedSarifItemChanged; // Subscribe to the caret position so we can send enter and exit notifications // to the tags so they can decide potentially change their colors. textViewCaretListenerService.CreateListener(textView, this); }
/// <summary> /// Initializes a new instance of the <see cref="FixSuggestedActionsSource"/> class. /// </summary> /// <param name="textView"> /// The <see cref="ITextView"/> for which this source will offer fix suggestions. /// </param> /// <param name="textBuffer"> /// The <see cref="ITextBuffer"/> associated with the <see cref="ITextView"/> for which this /// source will offer fix suggestions. /// </param> /// <param name="persistentSpanFactory"> /// A factory for creating the persistent spans that specify the error locations and the /// replacement locations (which are not necessarily the same). /// </param> /// <param name="previewProvider"> /// Creates the XAML UIControl that displays the preview. /// </param> public FixSuggestedActionsSource( ITextView textView, ITextBuffer textBuffer, IPersistentSpanFactory persistentSpanFactory, IPreviewProvider previewProvider) { ThreadHelper.ThrowIfNotOnUIThread(); this.textView = textView; this.textBuffer = textBuffer; this.persistentSpanFactory = persistentSpanFactory; this.previewProvider = previewProvider; // when text changed and sarif errors item changes, need to refresh errorInFile IErrorList errorList = ServiceProvider.GlobalProvider.GetService(typeof(SVsErrorList)) as IErrorList; this.errorListTableControl = errorList?.TableControl; this.errorListTableControl.EntriesChanged += this.ErrorListTableControl_EntriesChanged; this.RefreshPersistentSpans(); // Keep track of which error is associated with each suggested action, so that when // the action is invoked, the associated error can be marked as fixed. When we mark // an error as fixed, we tell VS to recompute the list of suggested actions, so that // it doesn't suggest actions for errors that are already fixed. this.fixToErrorDictionary = new Dictionary <FixSuggestedAction, SarifErrorListItem>(); }
public SarifLocationErrorTagger(ITextBuffer textBuffer, IPersistentSpanFactory persistentSpanFactory, ISarifErrorListEventSelectionService sarifErrorListEventSelectionService) { ThreadHelper.ThrowIfNotOnUIThread(); if (!SdkUIUtilities.TryGetFileNameFromTextBuffer(textBuffer, out this.filePath)) { throw new ArgumentException("Always expect to be able to get file name from text buffer.", nameof(textBuffer)); } this.TextBuffer = textBuffer; this.persistentSpanFactory = persistentSpanFactory; this.sarifErrorListEventSelectionService = sarifErrorListEventSelectionService; this.sarifErrorListEventSelectionService.SelectedItemChanged += this.SarifErrorListEventSelectionService_SelectedItemChanged; }
private bool TryCreatePersistentSpan(ITextBuffer textBuffer, IPersistentSpanFactory persistentSpanFactory) { ThreadHelper.ThrowIfNotOnUIThread(); if (this.persistentSpan != null) { return(true); } if (!this.TryToFullyPopulateRegionAndFilePath()) { return(false); } return(SpanHelper.TryCreatePersistentSpan(this.fullyPopulatedRegion, textBuffer, persistentSpanFactory, out this.persistentSpan)); }
/// <summary> /// Initializes a new instance of the <see cref="FixSuggestedActionsSource"/> class. /// </summary> /// <param name="textView"> /// The <see cref="ITextView"/> for which this source will offer fix suggestions. /// </param> /// <param name="textBuffer"> /// The <see cref="ITextBuffer"/> associated with the <see cref="ITextView"/> for which this /// source will offer fix suggestions. /// </param> /// <param name="persistentSpanFactory"> /// A factory for creating the persistent spans that specify the error locations and the /// replacement locations (which are not necessarily the same). /// </param> /// <param name="previewProvider"> /// Creates the XAML UIControl that displays the preview. /// </param> public FixSuggestedActionsSource( ITextView textView, ITextBuffer textBuffer, IPersistentSpanFactory persistentSpanFactory, IPreviewProvider previewProvider) { ThreadHelper.ThrowIfNotOnUIThread(); this.textView = textView; this.textBuffer = textBuffer; this.persistentSpanFactory = persistentSpanFactory; this.previewProvider = previewProvider; if (this.previewProvider is EditActionPreviewProvider editActionPreviewProvider) { editActionPreviewProvider.ApplyFixesInDocument += this.PreviewProdiver_ApplyFixesInDocument; } // when text changed and sarif errors item changes, need to refresh errorInFile var errorList = ServiceProvider.GlobalProvider.GetService(typeof(SVsErrorList)) as IErrorList; this.errorListTableControl = errorList?.TableControl; if (this.errorListTableControl != null) { this.errorListTableControl.EntriesChanged += this.ErrorListTableControl_EntriesChanged; } var component = ServiceProvider.GlobalProvider.GetService(typeof(SComponentModel)) as IComponentModel; this.sarifErrorListEventSelectionService = component?.GetService <ISarifErrorListEventSelectionService>(); if (this.sarifErrorListEventSelectionService != null) { this.sarifErrorListEventSelectionService.NavigatedItemChanged += this.SarifListErrorItemNavigated; } this.RefreshPersistentSpans(); // Keep track of which error is associated with each suggested action, so that when // the action is invoked, the associated error can be marked as fixed. When we mark // an error as fixed, we tell VS to recompute the list of suggested actions, so that // it doesn't suggest actions for errors that are already fixed. this.fixToErrorDictionary = new Dictionary <FixSuggestedAction, SarifErrorListItem>(); }
/// <summary> /// Returns the tags represented by this result marker. /// </summary> /// <remarks> /// This is only called by either <see cref="SarifLocationErrorTagger"/> or <see cref="SarifLocationTextMarkerTagger"/>.</remarks> /// <typeparam name="T">Specifies which tag type the tagger is asking for.</typeparam> /// <param name="textBuffer">The text buffer that the tags are being requested.</param> /// <param name="persistentSpanFactory">The persistent span factory that can be used to create the persistent spans for the tags.</param> /// <returns>List of tags returned by result marker.</returns> public IEnumerable <ISarifLocationTag> GetTags <T>(ITextBuffer textBuffer, IPersistentSpanFactory persistentSpanFactory) where T : ITag { ThreadHelper.ThrowIfNotOnUIThread(); var tags = new List <ISarifLocationTag>(); if (!this.TryCreatePersistentSpan(textBuffer, persistentSpanFactory)) { return(tags); } if (typeof(T) == typeof(IErrorTag) && this.ToolTipContent != null && this.ErrorType != null) { tags.Add(new SarifLocationErrorTag( this.persistentSpan, runIndex: this.RunIndex, resultId: this.ResultId, errorType: this.ErrorType, toolTipContent: this.ToolTipContent, toolTipXamlString: this.ToolTipXamlString, context: this.Context)); } if (typeof(T) == typeof(ITextMarkerTag)) { tags.Add(new SarifLocationTextMarkerTag( this.persistentSpan, runIndex: this.RunIndex, resultId: this.ResultId, nonHighlightedTextMarkerTagType: this.NonHighlightedColor, highlightedTextMarkerTagType: this.HighlightedColor, context: this.Context)); } return(tags); }
internal static bool TryCreatePersistentSpan(Region fullyPopulatedRegion, ITextBuffer textBuffer, IPersistentSpanFactory persistentSpanFactory, out IPersistentSpan persistentSpan) { ThreadHelper.ThrowIfNotOnUIThread(); persistentSpan = null; if (!TryCreateTextSpanWithinDocumentFromSourceRegion(fullyPopulatedRegion, textBuffer, out TextSpan documentSpan)) { return(false); } if (!persistentSpanFactory.CanCreate(textBuffer)) { return(false); } persistentSpan = persistentSpanFactory.Create( textBuffer.CurrentSnapshot, startLine: documentSpan.iStartLine, startIndex: documentSpan.iStartIndex, endLine: documentSpan.iEndLine, endIndex: documentSpan.iEndIndex, trackingMode: SpanTrackingMode.EdgeInclusive); return(true); }
/// <summary> /// Attempts to navigate a VS editor to the text marker. /// </summary> /// <param name="usePreviewPane">Indicates whether to use VS's preview pane.</param> /// <param name="moveFocusToCaretLocation">Indicates whether to move focus to the caret location.</param> /// <returns>Returns true if a VS editor was opened.</returns> /// <remarks> /// The <paramref name="usePreviewPane"/> indicates whether Visual Studio opens the document as a preview (tab to the right) /// rather than as an "open code editor" (tab attached to other open documents on the left). /// </remarks> public bool NavigateTo(bool usePreviewPane, bool moveFocusToCaretLocation) { ThreadHelper.ThrowIfNotOnUIThread(); bool documentWasOpened = false; // Make sure to fully populate region. if (!this.TryToFullyPopulateRegionAndFilePath()) { return(false); } // If the tag doesn't have a persistent span, or its associated document isn't open, // then this indicates that we need to attempt to open the document and cause it to // be tagged. if (!this.PersistentSpanValid()) { // Now, we need to make sure the document gets tagged before the next section of code // in this method attempts to navigate to it. // So the flow looks like this. Get Visual Studio to open the document for us. // That will cause Visual Studio to create a text view for it. // Now, just because a text view is created does not mean that // a request for "tags" has occurred. Tagging (and the display of those tags) // is highly asynchronous. Taggers are created on demand and disposed when they are no longer // needed. It is quite common to have multiple taggers active for the same text view and text buffers // at the same time. (An easy example is a split-window scenario). // This class relies on a persistent span (this.persistentSpan) being non-null and valid. // This class "creates" the persistent span when it is asked for its tags in the GetTags // method. // To facilitate that, we will: // 1) Open the document // 2) Get the text view from the document. // 2a) That alone may be enough to make the persistent span valid if it was already created. // 3) If the persistent span still isn't valid (likely because we have crated the persistent span yet), ask ourselves for the tags which will // cause the span to be created. IVsWindowFrame vsWindowFrame = SdkUIUtilities.OpenDocument(ServiceProvider.GlobalProvider, this.resolvedFullFilePath, usePreviewPane); if (vsWindowFrame == null) { return(false); } vsWindowFrame.Show(); documentWasOpened = true; // At this point, the persistent span may have "become valid" due to the document open. // If not, then ask ourselves for the tags which will create the persistent span. if (!this.PersistentSpanValid()) { if (!SdkUIUtilities.TryGetTextViewFromFrame(vsWindowFrame, out ITextView textView)) { return(false); } var componentModel = (IComponentModel)Package.GetGlobalService(typeof(SComponentModel)); if (componentModel == null) { return(false); } IPersistentSpanFactory persistentSpanFactory = componentModel.GetService <IPersistentSpanFactory>(); if (persistentSpanFactory == null) { return(false); } if (!this.TryCreatePersistentSpan(textView.TextBuffer, persistentSpanFactory)) { return(false); } } } if (!this.PersistentSpanValid()) { return(false); } // Now, if the span IS valid it doesn't mean that the editor is visible, so make sure we open the document // for the user if needed. // But before we try to call "open document" let's see if we can find an active view because calling // "open document" is super slow (which causes keyboard navigation from items in the SARIF explorer to be slow // IF "open document" is called every time. if (!documentWasOpened || !SdkUIUtilities.TryGetActiveViewForTextBuffer(this.persistentSpan.Span.TextBuffer, out IWpfTextView wpfTextView)) { IVsWindowFrame vsWindowFrame = SdkUIUtilities.OpenDocument(ServiceProvider.GlobalProvider, this.resolvedFullFilePath, usePreviewPane); if (vsWindowFrame == null) { return(false); } vsWindowFrame.Show(); if (!SdkUIUtilities.TryGetActiveViewForTextBuffer(this.persistentSpan.Span.TextBuffer, out wpfTextView)) { return(false); } } ITextSnapshot currentSnapshot = this.persistentSpan.Span.TextBuffer.CurrentSnapshot; // Note that "GetSpan" is not really a great name. What is actually happening // is the "Span" that "GetSpan" is called on is "mapped" onto the passed in // text snapshot. In essence what this means is take the "persistent span" // that we have and "replay" any edits that have occurred and return a new // span. So, if the span is no longer relevant (lets say the text has been deleted) // then you'll get back an empty span. SnapshotSpan trackingSpanSnapshot = this.persistentSpan.Span.GetSpan(currentSnapshot); // If the caret is already in the text within the marker, don't re-select it // otherwise users cannot move the caret in the region. // If the caret isn't in the marker, move it there. if (!trackingSpanSnapshot.Contains(wpfTextView.Caret.Position.BufferPosition) && !trackingSpanSnapshot.IsEmpty) { wpfTextView.Selection.Select(trackingSpanSnapshot, isReversed: false); wpfTextView.Caret.MoveTo(trackingSpanSnapshot.End); wpfTextView.Caret.EnsureVisible(); if (moveFocusToCaretLocation) { wpfTextView.VisualElement.Focus(); } } return(true); }