GoToAdjacentMemberCommandHandler.cs 6.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.Shared;
9 10 11 12
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
13
using Microsoft.VisualStudio.Commanding;
14
using Microsoft.VisualStudio.Text;
15 16
using Microsoft.VisualStudio.Text.Editor.Commanding;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
17
using Microsoft.VisualStudio.Text.Outlining;
18
using Microsoft.VisualStudio.Utilities;
K
Kevin Pilch-Bisson 已提交
19
using Roslyn.Utilities;
20
using VSCommanding = Microsoft.VisualStudio.Commanding;
21

K
Kevin Pilch-Bisson 已提交
22
namespace Microsoft.CodeAnalysis.Editor.CommandHandlers
23
{
24 25 26 27 28 29
    [Export(typeof(VSCommanding.ICommandHandler))]
    [ContentType(ContentTypeNames.RoslynContentType)]
    [Name(PredefinedCommandHandlerNames.GoToAdjacentMember)]
    internal class GoToAdjacentMemberCommandHandler : 
        VSCommanding.ICommandHandler<GoToNextMemberCommandArgs>, 
        VSCommanding.ICommandHandler<GoToPreviousMemberCommandArgs>
30
    {
31
        private readonly IOutliningManagerService _outliningManagerService;
32

33 34
        public string DisplayName => EditorFeaturesResources.Go_To_Adjacent_Member_Command_Handler;

35
        [ImportingConstructor]
36
        public GoToAdjacentMemberCommandHandler(IOutliningManagerService outliningManagerService)
37
        {
38
            _outliningManagerService = outliningManagerService;
39 40
        }

41 42 43 44 45 46
        public VSCommanding.CommandState GetCommandState(GoToNextMemberCommandArgs args)
        {
            return GetCommandStateImpl(args);
        }

        public bool ExecuteCommand(GoToNextMemberCommandArgs args, CommandExecutionContext context)
47
        {
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
            return ExecuteCommandImpl(args, gotoNextMember: true, context);
        }

        public VSCommanding.CommandState GetCommandState(GoToPreviousMemberCommandArgs args)
        {
            return GetCommandStateImpl(args);
        }

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

        private VSCommanding.CommandState GetCommandStateImpl(EditorCommandArgs args)
        { 
63 64
            var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
            var caretPoint = args.TextView.GetCaretPoint(args.SubjectBuffer);
65
            return IsAvailable(document, caretPoint) ? VSCommanding.CommandState.Available : VSCommanding.CommandState.Unspecified;
K
Kevin Pilch-Bisson 已提交
66 67 68 69
        }

        private static bool IsAvailable(Document document, SnapshotPoint? caretPoint)
        {
70
            if (document?.SupportsSyntaxTree != true)
71
            {
K
Kevin Pilch-Bisson 已提交
72
                return false;
73 74 75
            }

            if (!caretPoint.HasValue)
76
            {
K
Kevin Pilch-Bisson 已提交
77
                return false;
78 79
            }

80 81
            var documentSupportsFeatureService = document.Project.Solution.Workspace.Services.GetService<IDocumentSupportsFeatureService>();
            return documentSupportsFeatureService?.SupportsNavigationToAnyPosition(document) == true;
82 83
        }

84
        private bool ExecuteCommandImpl(EditorCommandArgs args, bool gotoNextMember, CommandExecutionContext context)
85
        {
86 87
            var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
            var caretPoint = args.TextView.GetCaretPoint(args.SubjectBuffer);
K
Kevin Pilch-Bisson 已提交
88
            if (!IsAvailable(document, caretPoint))
89
            {
90
                return false;
91 92 93
            }

            int? targetPosition = null;
94

95
            using (context.OperationContext.AddScope(allowCancellation: true, description: EditorFeaturesResources.Navigating))
96
            {
97 98
                var task = GetTargetPositionAsync(document, caretPoint.Value.Position, gotoNextMember, context.OperationContext.UserCancellationToken);
                targetPosition = task.WaitAndGetResult(context.OperationContext.UserCancellationToken);
99
            }
100

101
            if (targetPosition != null)
102
            {
103
                args.TextView.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(args.SubjectBuffer.CurrentSnapshot, targetPosition.Value), _outliningManagerService);
104 105
            }

106
            return true;
107 108
        }

K
Kevin Pilch-Bisson 已提交
109 110 111
        /// <summary>
        /// Internal for testing purposes.
        /// </summary>
C
Cyrus Najmabadi 已提交
112
        internal static async Task<int?> GetTargetPositionAsync(Document document, int caretPosition, bool next, CancellationToken cancellationToken)
113 114 115 116 117 118 119
        {
            var syntaxFactsService = document.GetLanguageService<ISyntaxFactsService>();
            if (syntaxFactsService == null)
            {
                return null;
            }

C
Cyrus Najmabadi 已提交
120
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(true);
121 122
            var members = syntaxFactsService.GetMethodLevelMembers(root);
            if (members.Count == 0)
123 124 125 126
            {
                return null;
            }

127 128
            var starts = members.Select(m => MemberStart(m)).ToArray();
            var index = Array.BinarySearch(starts, caretPosition);
129
            if (index >= 0)
130
            {
131 132
                // We're actually contained in a member, go to the next or previous.
                index = next ? index + 1 : index - 1;
133 134 135
            }
            else
            {
136 137 138
                // 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;
139 140
            }

141 142
            // Wrap if necessary
            if (index >= members.Count)
143
            {
144
                index = 0;
145
            }
146
            else if (index < 0)
147
            {
148
                index = members.Count - 1;
149 150
            }

151
            return MemberStart(members[index]);
152 153
        }

154
        private static int MemberStart(SyntaxNode node)
155
        {
156 157
            // TODO: Better position within the node (e.g. attributes?)
            return node.SpanStart;
158 159 160
        }
    }
}