|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.Commanding;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Text.Outlining;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.ReferenceHighlighting
{
[Export(typeof(ICommandHandler))]
[ContentType(ContentTypeNames.RoslynContentType)]
[Name(PredefinedCommandHandlerNames.NavigateToHighlightedReference)]
internal partial class NavigateToHighlightReferenceCommandHandler :
ICommandHandler<NavigateToNextHighlightedReferenceCommandArgs>,
ICommandHandler<NavigateToPreviousHighlightedReferenceCommandArgs>
{
private readonly IOutliningManagerService _outliningManagerService;
private readonly IViewTagAggregatorFactoryService _tagAggregatorFactory;
public string DisplayName => EditorFeaturesResources.Navigate_To_Highlight_Reference;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public NavigateToHighlightReferenceCommandHandler(
IOutliningManagerService outliningManagerService,
IViewTagAggregatorFactoryService tagAggregatorFactory)
{
_outliningManagerService = outliningManagerService ?? throw new ArgumentNullException(nameof(outliningManagerService));
_tagAggregatorFactory = tagAggregatorFactory ?? throw new ArgumentNullException(nameof(tagAggregatorFactory));
}
public CommandState GetCommandState(NavigateToNextHighlightedReferenceCommandArgs args)
=> GetCommandStateImpl(args);
public CommandState GetCommandState(NavigateToPreviousHighlightedReferenceCommandArgs args)
=> GetCommandStateImpl(args);
private CommandState GetCommandStateImpl(EditorCommandArgs args)
{
using var tagAggregator = _tagAggregatorFactory.CreateTagAggregator<NavigableHighlightTag>(args.TextView);
var tagUnderCursor = FindTagUnderCaret(tagAggregator, args.TextView);
return tagUnderCursor == null ? CommandState.Unavailable : CommandState.Available;
}
public bool ExecuteCommand(NavigateToNextHighlightedReferenceCommandArgs args, CommandExecutionContext context)
=> ExecuteCommandImpl(args, navigateToNext: true);
public bool ExecuteCommand(NavigateToPreviousHighlightedReferenceCommandArgs args, CommandExecutionContext context)
=> ExecuteCommandImpl(args, navigateToNext: false);
private bool ExecuteCommandImpl(EditorCommandArgs args, bool navigateToNext)
{
using (var tagAggregator = _tagAggregatorFactory.CreateTagAggregator<NavigableHighlightTag>(args.TextView))
{
var tagUnderCursor = FindTagUnderCaret(tagAggregator, args.TextView);
if (tagUnderCursor == null)
{
return false;
}
var spans = GetTags(tagAggregator, args.TextView.TextSnapshot.GetFullSpan()).ToList();
Contract.ThrowIfFalse(spans.Any(), "We should have at least found the tag under the cursor!");
var destTag = GetDestinationTag(tagUnderCursor.Value, spans, navigateToNext);
if (args.TextView.TryMoveCaretToAndEnsureVisible(destTag.Start, _outliningManagerService))
{
args.TextView.SetSelection(destTag);
}
}
return true;
}
private static IEnumerable<SnapshotSpan> GetTags(
ITagAggregator<NavigableHighlightTag> tagAggregator,
SnapshotSpan span)
{
return tagAggregator.GetTags(span)
.SelectMany(tag => tag.Span.GetSpans(span.Snapshot.TextBuffer))
.OrderBy(tag => tag.Start);
}
private static SnapshotSpan GetDestinationTag(
SnapshotSpan tagUnderCursor,
List<SnapshotSpan> orderedTagSpans,
bool navigateToNext)
{
var destIndex = orderedTagSpans.BinarySearch(tagUnderCursor, new StartComparer());
Contract.ThrowIfFalse(destIndex >= 0, "Expected to find start tag in the collection");
destIndex += navigateToNext ? 1 : -1;
if (destIndex < 0)
{
destIndex = orderedTagSpans.Count - 1;
}
else if (destIndex == orderedTagSpans.Count)
{
destIndex = 0;
}
return orderedTagSpans[destIndex];
}
private static SnapshotSpan? FindTagUnderCaret(
ITagAggregator<NavigableHighlightTag> tagAggregator,
ITextView textView)
{
// We always want to be working with the surface buffer here, so this line is correct
var caretPosition = textView.Caret.Position.BufferPosition.Position;
var tags = GetTags(tagAggregator, new SnapshotSpan(textView.TextSnapshot, new Span(caretPosition, 0)));
return tags.Any()
? tags.First()
: null;
}
}
}
|