private void TryStartRenameSession(Workspace workspace, Solution oldSolution, Solution newSolution, CancellationToken cancellationToken)
        {
            var changedDocuments = newSolution.GetChangedDocuments(oldSolution);

            foreach (var documentId in changedDocuments)
            {
                var document = newSolution.GetDocument(documentId);
                var root     = document.GetSyntaxRootAsync(cancellationToken).WaitAndGetResult(cancellationToken);

                var renameTokenOpt = root.GetAnnotatedNodesAndTokens(RenameAnnotation.Kind)
                                     .Where(s => s.IsToken)
                                     .Select(s => s.AsToken())
                                     .FirstOrNullable();

                if (renameTokenOpt.HasValue)
                {
                    // It's possible that the workspace's current solution is not the same as
                    // newSolution. This can happen if the workspace host performs other edits
                    // during ApplyChanges, such as in the Venus scenario where indentation and
                    // formatting can happen. To work around this, we create a SyntaxPath to the
                    // rename token in the newSolution and resolve it to the current solution.

                    var pathToRenameToken = new SyntaxPath(renameTokenOpt.Value);
                    var latestDocument    = workspace.CurrentSolution.GetDocument(documentId);
                    var latestRoot        = latestDocument.GetSyntaxRootAsync(cancellationToken).WaitAndGetResult(cancellationToken);

                    SyntaxNodeOrToken resolvedRenameToken;
                    if (pathToRenameToken.TryResolve(latestRoot, out resolvedRenameToken) &&
                        resolvedRenameToken.IsToken)
                    {
                        var editorWorkspace   = workspace;
                        var navigationService = editorWorkspace.Services.GetService <IDocumentNavigationService>();
                        if (navigationService.TryNavigateToSpan(editorWorkspace, documentId, resolvedRenameToken.Span))
                        {
                            var openDocument = workspace.CurrentSolution.GetDocument(documentId);
                            var openRoot     = openDocument.GetSyntaxRootAsync(cancellationToken).WaitAndGetResult(cancellationToken);

                            // NOTE: We need to resolve the syntax path again in case VB line commit kicked in
                            // due to the navigation.

                            // TODO(DustinCa): We still have a potential problem here with VB line commit,
                            // because it can insert tokens and all sorts of other business, which could
                            // wind up with us not being able to resolve the token.
                            if (pathToRenameToken.TryResolve(openRoot, out resolvedRenameToken) &&
                                resolvedRenameToken.IsToken)
                            {
                                var snapshot = openDocument.GetTextAsync(cancellationToken).WaitAndGetResult(cancellationToken).FindCorrespondingEditorTextSnapshot();
                                if (snapshot != null)
                                {
                                    _renameService.StartInlineSession(openDocument, resolvedRenameToken.AsToken().Span, cancellationToken);
                                }
                            }
                        }
                    }

                    return;
                }
            }
        }
        private async Task ExecuteWorkerAsync(
            ITextView view,
            ITextBuffer textBuffer,
            TextSpan span,
            IBackgroundWorkIndicatorContext waitContext)
        {
            _threadingContext.ThrowIfNotOnUIThread();

            var cancellationToken = waitContext.UserCancellationToken;

            var document = await textBuffer.CurrentSnapshot.GetFullyLoadedOpenDocumentInCurrentContextWithChangesAsync(waitContext).ConfigureAwait(false);

            if (document is null)
            {
                return;
            }

            var options = await document.GetExtractMethodGenerationOptionsAsync(_globalOptions, cancellationToken).ConfigureAwait(false);

            var result = await ExtractMethodService.ExtractMethodAsync(
                document, span, localFunction : false, options, cancellationToken).ConfigureAwait(false);

            Contract.ThrowIfNull(result);

            if (!result.Succeeded && !result.SucceededWithSuggestion)
            {
                // if it failed due to out/ref parameter in async method, try it with different option
                var newResult = await TryWithoutMakingValueTypesRefAsync(
                    document, span, result, options, cancellationToken).ConfigureAwait(false);

                if (newResult != null)
                {
                    var notificationService = document.Project.Solution.Workspace.Services.GetService <INotificationService>();
                    if (notificationService != null)
                    {
                        // We are about to show a modal UI dialog so we should take over the command execution
                        // wait context. That means the command system won't attempt to show its own wait dialog
                        // and also will take it into consideration when measuring command handling duration.
                        await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

                        if (!notificationService.ConfirmMessageBox(
                                EditorFeaturesResources.Extract_method_encountered_the_following_issues + Environment.NewLine + Environment.NewLine +
                                string.Join(Environment.NewLine, result.Reasons) + Environment.NewLine + Environment.NewLine +
                                EditorFeaturesResources.We_can_fix_the_error_by_not_making_struct_out_ref_parameter_s_Do_you_want_to_proceed,
                                title: EditorFeaturesResources.Extract_Method,
                                severity: NotificationSeverity.Error))
                        {
                            // We handled the command, displayed a notification and did not produce code.
                            return;
                        }

                        await TaskScheduler.Default;
                    }

                    // reset result
                    result = newResult;
                }
                else if (await TryNotifyFailureToUserAsync(document, result, cancellationToken).ConfigureAwait(false))
                {
                    // We handled the command, displayed a notification and did not produce code.
                    return;
                }
            }

            var cleanupOptions = await document.GetCodeCleanupOptionsAsync(_globalOptions, cancellationToken).ConfigureAwait(false);

            var(formattedDocument, methodNameAtInvocation) = await result.GetFormattedDocumentAsync(cleanupOptions, cancellationToken).ConfigureAwait(false);

            await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            ApplyChange_OnUIThread(formattedDocument, textBuffer, waitContext);

            // start inline rename to allow the user to change the name if they want.
            var textSnapshot = textBuffer.CurrentSnapshot;

            document = textSnapshot.GetOpenDocumentInCurrentContextWithChanges();
            if (document != null)
            {
                _renameService.StartInlineSession(document, methodNameAtInvocation.Span, cancellationToken);
            }

            // select invocation span
            view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(textSnapshot, methodNameAtInvocation.Span.End));
            view.SetSelection(methodNameAtInvocation.Span.ToSnapshotSpan(textSnapshot));
        }
        private bool Execute(
            ITextBuffer textBuffer,
            ITextView view,
            CancellationToken cancellationToken)
        {
            var spans = view.Selection.GetSnapshotSpansOnBuffer(textBuffer);

            if (spans.Count(s => s.Length > 0) != 1)
            {
                return(false);
            }

            var document = textBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();

            if (document == null)
            {
                return(false);
            }

            var result = ExtractMethodService.ExtractMethodAsync(
                document, spans.Single().Span.ToTextSpan(), cancellationToken: cancellationToken).WaitAndGetResult(cancellationToken);

            Contract.ThrowIfNull(result);

            if (!result.Succeeded && !result.SucceededWithSuggestion)
            {
                // if it failed due to out/ref parameter in async method, try it with different option
                var newResult = TryWithoutMakingValueTypesRef(document, spans, result, cancellationToken);
                if (newResult != null)
                {
                    var notificationService = document.Project.Solution.Workspace.Services.GetService <INotificationService>();
                    if (notificationService != null)
                    {
                        if (!notificationService.ConfirmMessageBox(
                                EditorFeaturesResources.Extract_method_failed_with_following_reasons_colon + Environment.NewLine + Environment.NewLine +
                                string.Join(Environment.NewLine, result.Reasons) + Environment.NewLine + Environment.NewLine +
                                EditorFeaturesResources.We_can_fix_the_error_by_not_making_struct_out_ref_parameter_s_Do_you_want_to_proceed,
                                title: EditorFeaturesResources.Extract_Method,
                                severity: NotificationSeverity.Error))
                        {
                            // We handled the command, displayed a notification and did not produce code.
                            return(true);
                        }
                    }

                    // reset result
                    result = newResult;
                }
                else if (TryNotifyFailureToUser(document, result))
                {
                    // We handled the command, displayed a notification and did not produce code.
                    return(true);
                }
            }

            // apply the change to buffer
            // get method name token
            ApplyChangesToBuffer(result, textBuffer, cancellationToken);

            // start inline rename
            var methodNameAtInvocation  = result.InvocationNameToken;
            var snapshotAfterFormatting = textBuffer.CurrentSnapshot;
            var documentAfterFormatting = snapshotAfterFormatting.GetOpenDocumentInCurrentContextWithChanges();

            _renameService.StartInlineSession(documentAfterFormatting, methodNameAtInvocation.Span, cancellationToken);

            // select invocation span
            view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(snapshotAfterFormatting, methodNameAtInvocation.Span.End));
            view.SetSelection(
                methodNameAtInvocation.Span.ToSnapshotSpan(snapshotAfterFormatting));

            return(true);
        }
Beispiel #4
0
        private bool Execute(
            ITextBuffer textBuffer,
            ITextView view,
            IUIThreadOperationContext waitContext)
        {
            var cancellationToken = waitContext.UserCancellationToken;

            var spans = view.Selection.GetSnapshotSpansOnBuffer(textBuffer);

            if (spans.Count(s => s.Length > 0) != 1)
            {
                return(false);
            }

            var document = textBuffer.CurrentSnapshot.GetFullyLoadedOpenDocumentInCurrentContextWithChanges(
                waitContext, _threadingContext);

            if (document == null)
            {
                return(false);
            }

            var options = ExtractMethodOptions.From(document.Project);
            var result  = ExtractMethodService.ExtractMethodAsync(
                document, spans.Single().Span.ToTextSpan(), localFunction: false, options, cancellationToken).WaitAndGetResult(cancellationToken);

            Contract.ThrowIfNull(result);

            if (!result.Succeeded && !result.SucceededWithSuggestion)
            {
                // if it failed due to out/ref parameter in async method, try it with different option
                var newResult = TryWithoutMakingValueTypesRef(document, spans, result, options, cancellationToken);
                if (newResult != null)
                {
                    var notificationService = document.Project.Solution.Workspace.Services.GetService <INotificationService>();
                    if (notificationService != null)
                    {
                        // We are about to show a modal UI dialog so we should take over the command execution
                        // wait context. That means the command system won't attempt to show its own wait dialog
                        // and also will take it into consideration when measuring command handling duration.
                        waitContext.TakeOwnership();
                        if (!notificationService.ConfirmMessageBox(
                                EditorFeaturesResources.Extract_method_encountered_the_following_issues + Environment.NewLine + Environment.NewLine +
                                string.Join(Environment.NewLine, result.Reasons) + Environment.NewLine + Environment.NewLine +
                                EditorFeaturesResources.We_can_fix_the_error_by_not_making_struct_out_ref_parameter_s_Do_you_want_to_proceed,
                                title: EditorFeaturesResources.Extract_Method,
                                severity: NotificationSeverity.Error))
                        {
                            // We handled the command, displayed a notification and did not produce code.
                            return(true);
                        }
                    }

                    // reset result
                    result = newResult;
                }
                else if (TryNotifyFailureToUser(document, result, waitContext))
                {
                    // We handled the command, displayed a notification and did not produce code.
                    return(true);
                }
            }

            // apply the change to buffer
            // get method name token
            ApplyChangesToBuffer(result, textBuffer, cancellationToken);

            // start inline rename
            var methodNameAtInvocation  = result.InvocationNameToken;
            var snapshotAfterFormatting = textBuffer.CurrentSnapshot;
            var documentAfterFormatting = snapshotAfterFormatting.GetOpenDocumentInCurrentContextWithChanges();

            _renameService.StartInlineSession(documentAfterFormatting, methodNameAtInvocation.Span, cancellationToken);

            // select invocation span
            view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(snapshotAfterFormatting, methodNameAtInvocation.Span.End));
            view.SetSelection(
                methodNameAtInvocation.Span.ToSnapshotSpan(snapshotAfterFormatting));

            return(true);
        }
        private async Task TryNavigateToLocationOrStartRenameSessionAsync(Workspace workspace, Solution oldSolution, Solution newSolution, CancellationToken cancellationToken)
        {
            var changedDocuments = newSolution.GetChangedDocuments(oldSolution);

            foreach (var documentId in changedDocuments)
            {
                var document = newSolution.GetRequiredDocument(documentId);
                if (!document.SupportsSyntaxTree)
                {
                    continue;
                }

                var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

                var navigationTokenOpt = root.GetAnnotatedTokens(NavigationAnnotation.Kind)
                                         .FirstOrNull();
                if (navigationTokenOpt.HasValue)
                {
                    var navigationService = workspace.Services.GetRequiredService <IDocumentNavigationService>();
                    await navigationService.TryNavigateToPositionAsync(
                        workspace, documentId, navigationTokenOpt.Value.SpanStart, cancellationToken).ConfigureAwait(false);

                    return;
                }

                var renameTokenOpt = root.GetAnnotatedTokens(RenameAnnotation.Kind)
                                     .FirstOrNull();

                if (renameTokenOpt.HasValue)
                {
                    // It's possible that the workspace's current solution is not the same as
                    // newSolution. This can happen if the workspace host performs other edits
                    // during ApplyChanges, such as in the Venus scenario where indentation and
                    // formatting can happen. To work around this, we create a SyntaxPath to the
                    // rename token in the newSolution and resolve it to the current solution.

                    var pathToRenameToken = new SyntaxPath(renameTokenOpt.Value);
                    var latestDocument    = workspace.CurrentSolution.GetDocument(documentId);
                    if (latestDocument != null)
                    {
                        var latestRoot = await latestDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

                        if (pathToRenameToken.TryResolve(latestRoot, out var resolvedRenameToken) &&
                            resolvedRenameToken.IsToken)
                        {
                            var editorWorkspace   = workspace;
                            var navigationService = editorWorkspace.Services.GetRequiredService <IDocumentNavigationService>();
                            if (await navigationService.TryNavigateToSpanAsync(
                                    editorWorkspace, documentId, resolvedRenameToken.Span, cancellationToken).ConfigureAwait(false))
                            {
                                var openDocument = workspace.CurrentSolution.GetRequiredDocument(documentId);
                                var openRoot     = await openDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

                                // NOTE: We need to resolve the syntax path again in case VB line commit kicked in
                                // due to the navigation.

                                // TODO(DustinCa): We still have a potential problem here with VB line commit,
                                // because it can insert tokens and all sorts of other business, which could
                                // wind up with us not being able to resolve the token.
                                if (pathToRenameToken.TryResolve(openRoot, out resolvedRenameToken) &&
                                    resolvedRenameToken.IsToken)
                                {
                                    var text = await openDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);

                                    var snapshot = text.FindCorrespondingEditorTextSnapshot();
                                    if (snapshot != null)
                                    {
                                        await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

                                        _renameService.StartInlineSession(openDocument, resolvedRenameToken.AsToken().Span, cancellationToken);
                                    }
                                }
                            }
                        }
                    }

                    return;
                }
            }
        }