GoToAdjacentMemberCommandHandler.cs 5.2 KB
Newer Older
1 2
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

K
Kevin Pilch-Bisson 已提交
3 4
using System;
using System.ComponentModel.Composition;
5
using System.Linq;
K
Kevin Pilch-Bisson 已提交
6
using System.Threading;
C
Cyrus Najmabadi 已提交
7
using System.Threading.Tasks;
8
using Microsoft.CodeAnalysis.Editor.Commands;
K
Kevin Pilch-Bisson 已提交
9
using Microsoft.CodeAnalysis.Editor.Host;
10
using Microsoft.CodeAnalysis.Editor.Shared;
11 12 13 14 15
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
16
using Microsoft.VisualStudio.Text.Outlining;
K
Kevin Pilch-Bisson 已提交
17
using Roslyn.Utilities;
18

K
Kevin Pilch-Bisson 已提交
19
namespace Microsoft.CodeAnalysis.Editor.CommandHandlers
20
{
21 22
    [ExportCommandHandler(PredefinedCommandHandlerNames.GoToAdjacentMember, ContentTypeNames.RoslynContentType)]
    internal class GoToAdjacentMemberCommandHandler : ICommandHandler<GoToAdjacentMemberCommandArgs>
23 24
    {
        private readonly IWaitIndicator _waitIndicator;
25
        private readonly IOutliningManagerService _outliningManagerService;
26 27

        [ImportingConstructor]
28
        public GoToAdjacentMemberCommandHandler(IWaitIndicator waitIndicator, IOutliningManagerService outliningManagerService)
29 30
        {
            _waitIndicator = waitIndicator;
31
            _outliningManagerService = outliningManagerService;
32 33
        }

34
        public CommandState GetCommandState(GoToAdjacentMemberCommandArgs args, Func<CommandState> nextHandler)
35
        {
36 37
            var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
            var caretPoint = args.TextView.GetCaretPoint(args.SubjectBuffer);
K
Kevin Pilch-Bisson 已提交
38 39 40 41 42
            return IsAvailable(document, caretPoint) ? CommandState.Available : nextHandler();
        }

        private static bool IsAvailable(Document document, SnapshotPoint? caretPoint)
        {
43
            if (document?.SupportsSyntaxTree != true)
44
            {
K
Kevin Pilch-Bisson 已提交
45
                return false;
46 47 48
            }

            if (!caretPoint.HasValue)
49
            {
K
Kevin Pilch-Bisson 已提交
50
                return false;
51 52
            }

53 54
            var documentSupportsFeatureService = document.Project.Solution.Workspace.Services.GetService<IDocumentSupportsFeatureService>();
            return documentSupportsFeatureService?.SupportsNavigationToAnyPosition(document) == true;
55 56
        }

57
        public void ExecuteCommand(GoToAdjacentMemberCommandArgs args, Action nextHandler)
58
        {
59 60
            var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
            var caretPoint = args.TextView.GetCaretPoint(args.SubjectBuffer);
K
Kevin Pilch-Bisson 已提交
61
            if (!IsAvailable(document, caretPoint))
62 63 64 65 66 67
            {
                nextHandler();
                return;
            }

            int? targetPosition = null;
68
            var waitResult = _waitIndicator.Wait(EditorFeaturesResources.Navigating, allowCancel: true, action: waitContext =>
69
            {
C
Cyrus Najmabadi 已提交
70 71
                var task = GetTargetPositionAsync(document, caretPoint.Value.Position, args.Direction == NavigateDirection.Down, waitContext.CancellationToken);
                targetPosition = task.WaitAndGetResult(waitContext.CancellationToken);
72 73 74 75 76 77 78
            });

            if (waitResult == WaitIndicatorResult.Canceled || targetPosition == null)
            {
                return;
            }

79
            args.TextView.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(args.SubjectBuffer.CurrentSnapshot, targetPosition.Value), _outliningManagerService);
80 81
        }

K
Kevin Pilch-Bisson 已提交
82 83 84
        /// <summary>
        /// Internal for testing purposes.
        /// </summary>
C
Cyrus Najmabadi 已提交
85
        internal static async Task<int?> GetTargetPositionAsync(Document document, int caretPosition, bool next, CancellationToken cancellationToken)
86 87 88 89 90 91 92
        {
            var syntaxFactsService = document.GetLanguageService<ISyntaxFactsService>();
            if (syntaxFactsService == null)
            {
                return null;
            }

C
Cyrus Najmabadi 已提交
93
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(true);
94 95
            var members = syntaxFactsService.GetMethodLevelMembers(root);
            if (members.Count == 0)
96 97 98 99
            {
                return null;
            }

100 101
            var starts = members.Select(m => MemberStart(m)).ToArray();
            var index = Array.BinarySearch(starts, caretPosition);
102
            if (index >= 0)
103
            {
104 105
                // We're actually contained in a member, go to the next or previous.
                index = next ? index + 1 : index - 1;
106 107 108
            }
            else
            {
109 110 111
                // We're in between to members, ~index gives us the member we're before, so we'll just
                // advance to the start of it
                index = next ? ~index : ~index - 1;
112 113
            }

114 115
            // Wrap if necessary
            if (index >= members.Count)
116
            {
117
                index = 0;
118
            }
119
            else if (index < 0)
120
            {
121
                index = members.Count - 1;
122 123
            }

124
            return MemberStart(members[index]);
125 126
        }

127
        private static int MemberStart(SyntaxNode node)
128
        {
129 130
            // TODO: Better position within the node (e.g. attributes?)
            return node.SpanStart;
131 132 133
        }
    }
}