Пример #1
0
 public static void HandleFailureToReference(string packageLocation, ITextControlManager textControlManager, IShellLocks shellLocks)
 {
     // TODO: Wish we didn't have to do this, either
     // If we failed to install the package, it's because something has gone wrong,
     // and we don't want the rest of the process to continue. Unfortunately, ReSharper
     // doesn't display any error messages, so we'll very hackily try and find the
     // current text editor, and display a tooltip with an error message.
     // (This replicates the experience when something else goes wrong in the context
     // actions that use ModuleReferencerService.) It's not a nice thing to do, but
     // it's safe with how ReSharper uses IModuleReferencer out of the box. If anyone
     // else uses it, and we go wrong, well, fingers crossed. We should either get
     // the tooltip as expected, multiple error messages, or no error messages. Hopefully,
     // we shouldn't see this very often, although I've probably just jinxed it now...
     //
     // Ideally, ReSharper should provide a better error mechanism as part of IModuleReferencer
     if (string.IsNullOrEmpty(packageLocation))
     {
         var textControl = textControlManager.FocusedTextControl.Value;
         if (textControl != null)
         {
             var shell = Shell.Instance;
             shellLocks.Queue("Failed to import type",
                              () => shell.Components.Tooltips().ShowAtCaret(EternalLifetime.Instance,
                                                                            "Failed to add NuGet package.",
                                                                            textControl, shellLocks,
                                                                            shell.GetComponent <IActionManager>()));
         }
     }
 }
Пример #2
0
        private void Accept(ITextControl textControl, DocumentRange nameRange, ISolution solution)
        {
            var psiServices = solution.GetPsiServices();

            // Get the node at the caret. This will be the identifier
            var identifierNode = TextControlToPsi.GetElement <ITreeNode>(solution, textControl) as IIdentifier;

            if (identifierNode == null)
            {
                return;
            }

            var methodDeclaration = TextControlToPsi.GetElement <IMethodDeclaration>(solution, textControl);

            if (UpdateExistingMethod(methodDeclaration, psiServices))
            {
                return;
            }

            // Delete the half completed identifier node. Also delete any explicitly entered return type, as our
            // declared element will create one anyway
            if (!(identifierNode.GetPreviousMeaningfulSibling() is ITypeUsage typeUsage))
            {
                // E.g. `void OnAnim{caret} [SerializeField]...` This is parsed as a field with an array specifier
                var fieldDeclaration = identifierNode.GetContainingNode <IFieldDeclaration>();
                typeUsage = fieldDeclaration?.GetPreviousMeaningfulSibling() as ITypeUsage;
            }

            var parameterListStart = methodDeclaration?.LPar;
            var parameterListEnd   = methodDeclaration?.RPar;

            using (var cookie = new PsiTransactionCookie(psiServices, DefaultAction.Rollback, "RemoveIdentifier"))
                using (new DisableCodeFormatter())
                {
                    using (WriteLockCookie.Create())
                    {
                        ModificationUtil.DeleteChild(identifierNode);
                        if (typeUsage != null)
                        {
                            nameRange = nameRange.Shift(-typeUsage.GetTextLength());
                            ModificationUtil.DeleteChild(typeUsage);

                            // Also delete the parameter list, if there is one. If there was an existing method declaration,
                            // with parameter list and body, we would have fixed it by simply replacing the name. Deleting
                            // an existing parameter list allows rewriting the return type, method name, parameter list and
                            // body
                            if (parameterListStart != null && parameterListEnd != null)
                            {
                                ModificationUtil.DeleteChildRange(parameterListStart, parameterListEnd);
                            }
                            else if (parameterListStart != null)
                            {
                                ModificationUtil.DeleteChild(parameterListStart);
                            }
                            else if (parameterListEnd != null)
                            {
                                ModificationUtil.DeleteChild(parameterListEnd);
                            }
                        }
                    }

                    cookie.Commit();
                }

            // Insert a dummy method declaration, as text, which means the PSI is reparsed. This will remove empty type
            // usages and merge leading attributes into a method declaration, such that we can copy them and replace
            // them once the declared element has expanded. This also fixes up the case where the type usage picks up
            // the attribute of the next code construct as an array specifier. E.g. `OnAni{caret} [SerializeField]`
            using (WriteLockCookie.Create())
                textControl.Document.InsertText(nameRange.StartOffset, "void Foo(){}");

            psiServices.Files.CommitAllDocuments();

            methodDeclaration = TextControlToPsi.GetElement <IMethodDeclaration>(solution, textControl);
            if (methodDeclaration == null)
            {
                return;
            }

            var attributeList = methodDeclaration.FirstChild as IAttributeSectionList;

            using (var cookie = new PsiTransactionCookie(psiServices, DefaultAction.Rollback, "RemoveInsertedDeclaration"))
                using (new DisableCodeFormatter())
                {
                    using (WriteLockCookie.Create())
                        ModificationUtil.DeleteChild(methodDeclaration);
                    cookie.Commit();
                }

            var classDeclaration = TextControlToPsi.GetElement <IClassLikeDeclaration>(solution, textControl);

            Assertion.AssertNotNull(classDeclaration, "classDeclaration != null");

            var factory = CSharpElementFactory.GetInstance(classDeclaration);

            // Get the UnityEventFunction generator to actually insert the methods
            GenerateCodeWorkflowBase.ExecuteNonInteractive(
                GeneratorUnityKinds.UnityEventFunctions, solution, textControl, identifierNode.Language,
                configureContext: context =>
            {
                // Note that the generated code will use the access rights, if specified. However, if they haven't
                // been specified (NONE) or they are the default for methods (PRIVATE), the generated code will be
                // whatever the current code style setting is - implicit or explicit
                var declaredElement = myEventFunction.CreateDeclaration(factory, classDeclaration, myAccessRights)
                                      .DeclaredElement.NotNull("declaredElement != null");
                context.InputElements.Clear();
                context.InputElements.Add(new GeneratorDeclaredElement(declaredElement));
            },
                onCompleted: context =>
            {
                if (attributeList == null)
                {
                    return;
                }

                methodDeclaration = TextControlToPsi.GetElement <IMethodDeclaration>(solution, textControl);
                if (methodDeclaration == null)
                {
                    return;
                }

                // The Generate workflow adds a helper function to the queue to select the contents of the method.
                // Unfortunately, the offsets are calculated before adding the callback to the queue. If we modify
                // the PSI directly here, the offsets are incorrect and the selection is wrong. Doing it this way
                // loses the selection, but at least everything works.
                // Technically, we should probably add the attributes during the generation method, but then we'd
                // lose how multiple attributes are split into sections, etc.
                myShellLocks.Queue(Lifetime.Eternal, "FinishDeclaration", () =>
                {
                    using (ReadLockCookie.Create())
                        using (var transactionCookie =
                                   new PsiTransactionCookie(psiServices, DefaultAction.Rollback, "FinishDeclaration"))
                        {
                            methodDeclaration.SetAttributeSectionList(attributeList);
                            transactionCookie.Commit();
                        }
                });
            });
        }