Skip to content
This repository has been archived by the owner on Sep 3, 2024. It is now read-only.

Commit

Permalink
Import Roslyn support code for Highlight References
Browse files Browse the repository at this point in the history
  • Loading branch information
mhutch committed Jul 4, 2019
1 parent 42b4783 commit bdc1c27
Show file tree
Hide file tree
Showing 24 changed files with 3,886 additions and 0 deletions.
141 changes: 141 additions & 0 deletions Editor/Commands/NavigateToHighlightReferenceCommandHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

//ROSLYN IMPORT: Microsoft.CodeAnalysis.Editor.ReferenceHighlighting.NavigateToHighlightReferenceCommandHandler

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
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 MonoDevelop.Xml.Editor.Tags;
using VSCommanding = Microsoft.VisualStudio.Commanding;

namespace MonoDevelop.Xml.Editor.Commands
{
[Export (typeof (VSCommanding.ICommandHandler))]
[ContentType (XmlContentTypeNames.XmlCore)]
[Name ("Xml Navigate to Highlighted Reference Command Handler")]
internal partial class NavigateToHighlightReferenceCommandHandler :
VSCommanding.ICommandHandler<NavigateToNextHighlightedReferenceCommandArgs>,
VSCommanding.ICommandHandler<NavigateToPreviousHighlightedReferenceCommandArgs>
{
private readonly IOutliningManagerService _outliningManagerService;
private readonly IViewTagAggregatorFactoryService _tagAggregatorFactory;

public string DisplayName => "Navigate To Highlighted Reference";

[ImportingConstructor]
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)
{
return GetCommandStateImpl (args);
}

public CommandState GetCommandState (NavigateToPreviousHighlightedReferenceCommandArgs args)
{
return 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)
{
return ExecuteCommandImpl (args, navigateToNext: true, context);
}

public bool ExecuteCommand (NavigateToPreviousHighlightedReferenceCommandArgs args, CommandExecutionContext context)
{
return ExecuteCommandImpl (args, navigateToNext: false, context);
}

private bool ExecuteCommandImpl (EditorCommandArgs args, bool navigateToNext, CommandExecutionContext context)
{
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 ();

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 ());

destIndex += navigateToNext ? 1 : -1;
if (destIndex < 0) {
destIndex = orderedTagSpans.Count - 1;
} else if (destIndex == orderedTagSpans.Count) {
destIndex = 0;
}

return orderedTagSpans[destIndex];
}

private 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 ()
: (SnapshotSpan?)null;
}

private class StartComparer : IComparer<SnapshotSpan>
{
public int Compare (SnapshotSpan x, SnapshotSpan y)
{
return x.Start.CompareTo (y.Start);
}
}
}
}
104 changes: 104 additions & 0 deletions Editor/Roslyn/Extensions/IBufferGraphExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Linq;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Projection;

namespace Microsoft.CodeAnalysis.Editor.Shared.Extensions
{
internal enum BufferMapDirection
{
Identity,
Down,
Up,
Unrelated
}

internal static class IBufferGraphExtensions
{
public static SnapshotSpan? MapUpOrDownToFirstMatch (this IBufferGraph bufferGraph, SnapshotSpan span, Predicate<ITextSnapshot> match)
{
var spans = bufferGraph.MapDownToFirstMatch (span, SpanTrackingMode.EdgeExclusive, match);
if (!spans.Any ()) {
spans = bufferGraph.MapUpToFirstMatch (span, SpanTrackingMode.EdgeExclusive, match);
}

return spans.Select (s => (SnapshotSpan?)s).FirstOrDefault ();
}

public static SnapshotSpan? MapUpOrDownToBuffer (this IBufferGraph bufferGraph, SnapshotSpan span, ITextBuffer targetBuffer)
{
var direction = ClassifyBufferMapDirection (span.Snapshot.TextBuffer, targetBuffer);
switch (direction) {
case BufferMapDirection.Identity:
return span;

case BufferMapDirection.Down: {
var spans = bufferGraph.MapDownToBuffer (span, SpanTrackingMode.EdgeExclusive, targetBuffer);
return spans.Select (s => (SnapshotSpan?)s).FirstOrDefault ();
}

case BufferMapDirection.Up: {
var spans = bufferGraph.MapUpToBuffer (span, SpanTrackingMode.EdgeExclusive, targetBuffer);
return spans.Select (s => (SnapshotSpan?)s).FirstOrDefault ();
}

default:
return null;
}
}

public static SnapshotPoint? MapUpOrDownToBuffer (this IBufferGraph bufferGraph, SnapshotPoint point, ITextBuffer targetBuffer)
{
var direction = ClassifyBufferMapDirection (point.Snapshot.TextBuffer, targetBuffer);
switch (direction) {
case BufferMapDirection.Identity:
return point;

case BufferMapDirection.Down: {
// TODO (https://github.com/dotnet/roslyn/issues/5281): Remove try-catch.
try {
return bufferGraph.MapDownToInsertionPoint (point, PointTrackingMode.Positive, s => s == targetBuffer.CurrentSnapshot);
} catch (ArgumentOutOfRangeException) when (bufferGraph.TopBuffer.ContentType.TypeName == "Interactive Content") {
// Suppress this to work around DevDiv #144964.
// Note: Other callers might be affected, but this is the narrowest workaround for the observed problems.
// A fix is already being reviewed, so a broader change is not required.
return null;
}
}

case BufferMapDirection.Up: {
return bufferGraph.MapUpToBuffer (point, PointTrackingMode.Positive, PositionAffinity.Predecessor, targetBuffer);
}

default:
return null;
}
}

public static BufferMapDirection ClassifyBufferMapDirection (ITextBuffer startBuffer, ITextBuffer destinationBuffer)
{
if (startBuffer == destinationBuffer) {
return BufferMapDirection.Identity;
}

// Are we trying to map down or up?
if (startBuffer is IProjectionBufferBase startProjBuffer && IsSourceBuffer (startProjBuffer, destinationBuffer)) {
return BufferMapDirection.Down;
}

if (destinationBuffer is IProjectionBufferBase destProjBuffer && IsSourceBuffer (destProjBuffer, startBuffer)) {
return BufferMapDirection.Up;
}

return BufferMapDirection.Unrelated;
}

private static bool IsSourceBuffer (IProjectionBufferBase top, ITextBuffer bottom)
{
return top.SourceBuffers.Contains (bottom) ||
top.SourceBuffers.OfType<IProjectionBufferBase> ().Any (b => IsSourceBuffer (b, bottom));
}
}
}
145 changes: 145 additions & 0 deletions Editor/Roslyn/Extensions/ITextSnapshotExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Text.Shared.Extensions
{
internal static partial class ITextSnapshotExtensions
{
public static SnapshotPoint GetPoint (this ITextSnapshot snapshot, int position)
=> new SnapshotPoint (snapshot, position);

public static SnapshotPoint? TryGetPoint (this ITextSnapshot snapshot, int lineNumber, int columnIndex)
{
var position = snapshot.TryGetPosition (lineNumber, columnIndex);
if (position.HasValue) {
return new SnapshotPoint (snapshot, position.Value);
} else {
return null;
}
}

/*
/// <summary>
/// Convert a <see cref="LinePositionSpan"/> to <see cref="TextSpan"/>.
/// </summary>
public static TextSpan GetTextSpan (this ITextSnapshot snapshot, LinePositionSpan span)
{
return TextSpan.FromBounds (
GetPosition (snapshot, span.Start.Line, span.Start.Character),
GetPosition (snapshot, span.End.Line, span.End.Character));
}
*/

public static int GetPosition (this ITextSnapshot snapshot, int lineNumber, int columnIndex)
=> TryGetPosition (snapshot, lineNumber, columnIndex).Value;

public static int? TryGetPosition (this ITextSnapshot snapshot, int lineNumber, int columnIndex)
{
if (lineNumber < 0 || lineNumber >= snapshot.LineCount) {
return null;
}

var end = snapshot.GetLineFromLineNumber (lineNumber).Start.Position + columnIndex;
if (end < 0 || end > snapshot.Length) {
return null;
}

return end;
}

public static bool TryGetPosition (this ITextSnapshot snapshot, int lineNumber, int columnIndex, out SnapshotPoint position)
{
var result = 0;
position = new SnapshotPoint ();

if (lineNumber < 0 || lineNumber >= snapshot.LineCount) {
return false;
}

var line = snapshot.GetLineFromLineNumber (lineNumber);
if (columnIndex < 0 || columnIndex >= line.Length) {
return false;
}

result = line.Start.Position + columnIndex;
position = new SnapshotPoint (snapshot, result);
return true;
}

public static SnapshotSpan GetSpan (this ITextSnapshot snapshot, int start, int length)
=> new SnapshotSpan (snapshot, new Span (start, length));

public static SnapshotSpan GetSpanFromBounds (this ITextSnapshot snapshot, int start, int end)
=> new SnapshotSpan (snapshot, Span.FromBounds (start, end));

public static SnapshotSpan GetSpan (this ITextSnapshot snapshot, Span span)
=> new SnapshotSpan (snapshot, span);

public static ITagSpan<TTag> GetTagSpan<TTag> (this ITextSnapshot snapshot, Span span, TTag tag)
where TTag : ITag
{
return new TagSpan<TTag> (new SnapshotSpan (snapshot, span), tag);
}

public static SnapshotSpan GetSpan (this ITextSnapshot snapshot, int startLine, int startIndex, int endLine, int endIndex)
{
return TryGetSpan (snapshot, startLine, startIndex, endLine, endIndex).Value;
}

public static SnapshotSpan? TryGetSpan (this ITextSnapshot snapshot, int startLine, int startIndex, int endLine, int endIndex)
{
var startPosition = snapshot.TryGetPosition (startLine, startIndex);
var endPosition = snapshot.TryGetPosition (endLine, endIndex);
if (startPosition == null || endPosition == null) {
return null;
}

return new SnapshotSpan (snapshot, Span.FromBounds (startPosition.Value, endPosition.Value));
}

public static SnapshotSpan GetFullSpan (this ITextSnapshot snapshot)
{
Contract.ThrowIfNull (snapshot);

return new SnapshotSpan (snapshot, new Span (0, snapshot.Length));
}

public static NormalizedSnapshotSpanCollection GetSnapshotSpanCollection (this ITextSnapshot snapshot)
{
Contract.ThrowIfNull (snapshot);

return new NormalizedSnapshotSpanCollection (snapshot.GetFullSpan ());
}

public static void GetLineAndCharacter (this ITextSnapshot snapshot, int position, out int lineNumber, out int characterIndex)
{
var line = snapshot.GetLineFromPosition (position);

lineNumber = line.LineNumber;
characterIndex = position - line.Start.Position;
}

/// <summary>
/// Returns the leading whitespace of the line located at the specified position in the given snapshot.
/// </summary>
public static string GetLeadingWhitespaceOfLineAtPosition (this ITextSnapshot snapshot, int position)
{
Contract.ThrowIfNull (snapshot);

var line = snapshot.GetLineFromPosition (position);
var linePosition = line.GetFirstNonWhitespacePosition ();
if (!linePosition.HasValue) {
return line.GetText ();
}

var lineText = line.GetText ();
return lineText.Substring (0, linePosition.Value - line.Start);
}

public static bool AreOnSameLine (this ITextSnapshot snapshot, int x1, int x2)
=> snapshot.GetLineNumberFromPosition (x1) == snapshot.GetLineNumberFromPosition (x2);
}
}
Loading

0 comments on commit bdc1c27

Please sign in to comment.