Exemple #1
0
            // protected override ICSharpStatement DecorateStatement(CSharpElementFactory factory,
            //     ICSharpStatement statement)
            // {
            //     var info = (ValidatePostfixTemplateInfo) Info;
            //     var type = TypeFactory.CreateType(info.ValidatorTypes.First());
            //     var newStatement = factory.CreateStatement("var validator = new $0();", type);
            //     var statementsOwner = StatementsOwnerNavigator.GetByStatement(statement);
            //     return statementsOwner.AddStatementBefore(newStatement, statement);
            // }

            protected override void AfterComplete(ITextControl textControl, ICSharpStatement statement, Suffix suffix)
            {
                var expressionMarker = statement.GetDocumentRange().CreateRangeMarker();
                var solution         = statement.GetSolution();

                ExecuteRefactoring(textControl, statement.Descendants <IObjectCreationExpression>().First(), () =>
                {
                    var documentRange = expressionMarker.DocumentRange;
                    if (!documentRange.IsValid())
                    {
                        return;
                    }
                    solution.GetPsiServices().Files.CommitAllDocuments();

                    var element = TextControlToPsi.GetElement <ICSharpStatement>(solution, documentRange.EndOffset);
                    if (element == null)
                    {
                        return;
                    }

                    textControl.Caret.MoveTo(element.GetDocumentRange().TextRange.EndOffset,
                                             CaretVisualPlacement.DontScrollIfVisible);
                });
                // base.AfterComplete(textControl, statement, suffix);
            }
        public override ISpecificCodeCompletionContext GetCompletionContext(CodeCompletionContext context)
        {
            var relatedText = string.Empty;
            var nodeUnderCursor = TextControlToPsi.GetElement<ITreeNode>(context.Solution, context.TextControl);

            var interestingNode = GetInterestingNode(nodeUnderCursor);
            if (interestingNode == null)
                return null;

            var ranges = GetTextLookupRanges(context, nodeUnderCursor.GetDocumentRange());

            if (interestingNode is GherkinStep step)
            {
                var stepTextRange = step.GetStepTextRange();
                if (IsCursorBeforeNode(context, stepTextRange))
                    return null;

                relatedText = step.GetStepTextBeforeCaret(context.CaretDocumentOffset);
                if (IsCursorAfterNode(context, stepTextRange))
                {
                    stepTextRange = stepTextRange.ExtendRight(context.CaretDocumentOffset.Offset - stepTextRange.EndOffset.Offset);
                    relatedText += " ";
                }

                var replaceRange = stepTextRange;
                var insertRange = stepTextRange.SetEndTo(context.SelectedRange.EndOffset);

                ranges = new TextLookupRanges(insertRange, replaceRange);
            }
            return new GherkinSpecificCodeCompletionContext(context, ranges, interestingNode, relatedText);
        }
Exemple #3
0
        private ITreeNode FindCurrentTreeNode(IDocument document)
        {
            var textControl =
                _textControlManager.TextControls.FirstOrDefault(
                    tc => tc.Document.Moniker.Equals(document.Moniker));

            return(textControl == null ? null : TextControlToPsi.GetElement <ITreeNode>(_solution, textControl));
        }
Exemple #4
0
        private ITreeNode GetTreeNode([NotNull] ITextControl textControl)
        {
            ITreeNode treeNode = null;

            if (ReentrancyGuard.Current.CanExecuteNow)
            {
                ReadLockCookie.GuardedExecute(
                    () => { treeNode = TextControlToPsi.GetElement <ITreeNode>(_solution, textControl); });
            }

            return(treeNode);
        }
        public override CSharpPostfixExpressionContext FixExpression(CSharpPostfixExpressionContext context)
        {
            var psiServices     = Reference.GetPsiServices();
            var expressionRange = ExecutionContext.GetDocumentRange(context.Expression);
            var referenceRange  = ExecutionContext.GetDocumentRange(Reference);

            var textWithReference = expressionRange.SetEndTo(referenceRange.TextRange.EndOffset).GetText();

            var indexOfReferenceDot = textWithReference.LastIndexOf('.');

            if (indexOfReferenceDot <= 0)
            {
                return(context);
            }

            var realReferenceRange = referenceRange.SetStartTo(expressionRange.TextRange.StartOffset + indexOfReferenceDot);

            var transactionManager = psiServices.Transactions.DocumentTransactionManager;
            var document           = expressionRange.Document;

            // todo: make sure this is not in undo stack!
            using (transactionManager.CreateTransactionCookie(DefaultAction.Commit, FixCommandName))
            {
                document.ReplaceText(realReferenceRange.TextRange, ")");
                document.InsertText(expressionRange.TextRange.StartOffset, "unchecked(");
            }

            //using (psiServices.Solution.CreateTransactionCookie(DefaultAction.Commit, FixCommandName, NullProgressIndicator.Instance))
            //{
            //
            //}

            psiServices.Files.CommitAllDocuments();

            var uncheckedExpression = TextControlToPsi.GetElement <IUncheckedExpression>(psiServices.Solution, document, expressionRange.TextRange.StartOffset + 1);

            if (uncheckedExpression == null)
            {
                return(context);
            }

            var operand = uncheckedExpression.Operand;

            psiServices.Transactions.Execute(FixCommandName, () =>
            {
                LowLevelModificationUtil.DeleteChild(operand);
                LowLevelModificationUtil.ReplaceChildRange(uncheckedExpression, uncheckedExpression, operand);
            });

            Assertion.Assert(operand.IsPhysical(), "operand.IsPhysical()");

            return(new CSharpPostfixExpressionContext(this, operand));
        }
        private ITreeNode GetTreeNode([NotNull] ITextControl textControl)
        {
            var treeNode = TextControlToPsi.GetElement <ITreeNode>(_solution, textControl);

            if (treeNode.IsWhitespaceToken() && treeNode.NextSibling != null)
            {
                treeNode = treeNode.NextSibling;
            }
            if (treeNode != null && CSharpTokenType.DOT == treeNode.GetTokenType())
            {
                treeNode = treeNode.NextSibling;
            }
            return(treeNode);
        }
Exemple #7
0
            protected override void AfterComplete(ITextControl textControl, ICSharpExpression expression)
            {
                var expressionRange  = expression.GetDocumentRange();
                var expressionMarker = expressionRange.CreateRangeMarker();
                var solution         = expression.GetSolution();

                ExecuteRefactoring(textControl, expression, () =>
                {
                    var referenceRange = expressionMarker.Range;
                    if (!referenceRange.IsValid)
                    {
                        return;
                    }

                    var reference = TextControlToPsi.GetElement <IReferenceExpression>(solution, textControl.Document, referenceRange.EndOffset);

                    if (reference != null && reference.QualifierExpression == null)
                    {
                        var endOffset = reference.GetDocumentRange().TextRange.EndOffset;
                        textControl.Caret.MoveTo(endOffset, CaretVisualPlacement.DontScrollIfVisible);
                    }
                });
            }
        public override void Accept(
            ITextControl textControl, DocumentRange nameRange,
            LookupItemInsertType insertType, Suffix suffix, ISolution solution, bool keepCaretStill)
        {
            var psiServices             = solution.GetPsiServices();
            var updateMethodDeclaration = TextControlToPsi.GetElement <IMethodDeclaration>(solution, textControl);

            if (!Info.ShouldGenerateMethod)
            {
                UpdateExistingMethod(updateMethodDeclaration, psiServices);
                return;
            }

            var isCoroutine = updateMethodDeclaration?.TypeUsage is IUserTypeUsage userTypeUsage &&
                              userTypeUsage.ScalarTypeName?.ShortName == "IEnumerator";

            var fixedNameRange = nameRange.SetStartTo(Info.MemberReplaceRanges.InsertRange.StartOffset);
            var memberRange    = Info.MemberReplaceRanges.GetAcceptRange(fixedNameRange, insertType);

            // 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.ReplaceText(memberRange, "void Foo(){}");
            }

            psiServices.Files.CommitAllDocuments();

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

            if (methodDeclaration == null)
            {
                return;
            }

            var methodDeclarationCopy = methodDeclaration.Copy();
            var nodesBeforeCopyRange  = NodesBeforeMethodHeader(methodDeclarationCopy);

            using (new PsiTransactionCookie(psiServices, DefaultAction.Commit, "RemoveInsertedDeclaration"))
                using (WriteLockCookie.Create())
                {
                    LowLevelModificationUtil.DeleteChild(methodDeclaration);
                }

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

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

            var factory = CSharpElementFactory.GetInstance(classDeclaration);

            GenerateCodeWorkflowBase.ExecuteNonInteractive(
                GeneratorUnityKinds.UnityEventFunctions, solution, textControl, methodDeclaration.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 knownTypesCache = solution.GetComponent <KnownTypesCache>();
                var declaredElement = myEventFunction.CreateDeclaration(factory, knownTypesCache, classDeclaration,
                                                                        myAccessRights, makeCoroutine: isCoroutine)
                                      .DeclaredElement.NotNull("declaredElement != null");
                context.InputElements.Clear();
                context.InputElements.Add(new GeneratorDeclaredElement(declaredElement));
            },
                onCompleted: context =>
            {
                if (nodesBeforeCopyRange.IsEmpty)
                {
                    return;
                }

                foreach (var outputElement in context.OutputElements)
                {
                    if (outputElement is GeneratorDeclarationElement declarationElement)
                    {
                        using (new PsiTransactionCookie(psiServices, DefaultAction.Commit, "BringBackAttributes"))
                            using (WriteLockCookie.Create())
                            {
                                var newDeclaration = declarationElement.Declaration;
                                ModificationUtil.AddChildRangeAfter(newDeclaration, anchor: null, nodesBeforeCopyRange);
                            }

                        return;
                    }
                }
            });

            ITreeRange NodesBeforeMethodHeader(IMethodDeclaration declaration)
            {
                var firstNode = declaration.ModifiersList ?? declaration.TypeUsage as ITreeNode;

                var smthBeforeTypeUsage = firstNode?.PrevSibling;

                if (smthBeforeTypeUsage == null)
                {
                    return(TreeRange.Empty);
                }

                return(new TreeRange(declaration.FirstChild, smthBeforeTypeUsage));
            }
        }
        public override void Accept(ITextControl textControl, TextRange nameRange, LookupItemInsertType lookupItemInsertType, Suffix suffix,
                                    ISolution solution, bool keepCaretStill)
        {
            var rangeMarker = nameRange.CreateRangeMarkerWithMappingToDocument(textControl.Document);

            var identifierNode = TextControlToPsi.GetElement <ITreeNode>(solution, textControl);
            var psiServices    = solution.GetPsiServices();

            if (identifierNode != null)
            {
                IErrorElement errorElement = null;

                ITreeNode usage = identifierNode.GetContainingNode <IUserTypeUsage>();
                if (usage != null)
                {
                    errorElement = usage.NextSibling as IErrorElement;
                }
                else
                {
                    usage = identifierNode.PrevSibling;
                    while (usage != null && !(usage is ITypeUsage))
                    {
                        usage = usage.PrevSibling;
                    }
                    errorElement = identifierNode.NextSibling as IErrorElement;
                }

                using (var cookie = new PsiTransactionCookie(psiServices, DefaultAction.Rollback, "RemoveIdentifier"))
                    using (new DisableCodeFormatter())
                    {
                        using (WriteLockCookie.Create())
                        {
                            ModificationUtil.DeleteChild(identifierNode);
                            if (usage != null)
                            {
                                ModificationUtil.DeleteChild(usage);
                            }
                            if (errorElement != null)
                            {
                                ModificationUtil.DeleteChild(errorElement);
                            }
                        }

                        cookie.Commit();
                    }
            }

            using (WriteLockCookie.Create())
            {
                textControl.Document.InsertText(rangeMarker.Range.StartOffset, "void Foo(){}");
            }

            psiServices.Files.CommitAllDocuments();

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

            if (methodDeclaration == null)
            {
                return;
            }

            var insertionIndex = methodDeclaration.GetTreeStartOffset().Offset;

            string attributeText = null;

            var attributeList = methodDeclaration.FirstChild as IAttributeSectionList;

            if (attributeList != null)
            {
                attributeText = attributeList.GetText();
                var treeNode = attributeList.NextSibling;
                while (treeNode is IWhitespaceNode)
                {
                    attributeText += treeNode.GetText();
                    treeNode       = treeNode.NextSibling;
                }
            }

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

                    cookie.Commit();
                }

            // Get the UniteMessages generator to actually insert the methods
            GenerateCodeWorkflowBase.ExecuteNonInteractive(
                GeneratorUnityKinds.UnityMessages, solution, textControl, methodDeclaration.Language,
                configureContext: context =>
            {
                var inputElements = from e in context.ProvidedElements.Cast <GeneratorDeclaredElement <IMethod> >()
                                    where myMessage.Match(e.DeclaredElement)
                                    select e;

                context.InputElements.Clear();
                context.InputElements.AddRange(inputElements);
            });

            if (!string.IsNullOrEmpty(attributeText))
            {
                using (WriteLockCookie.Create())
                    textControl.Document.InsertText(insertionIndex, attributeText);
            }
        }
Exemple #10
0
        private ITreeNode FindCurrentTreeNode()
        {
            var textControl = _textControlManager.FocusedTextControl.Value;

            return(textControl == null ? null : TextControlToPsi.GetElement <ITreeNode>(_solution, textControl));
        }
Exemple #11
0
        private void Accept(ITextControl textControl, IRangeMarker rangeMarker, ISolution solution)
        {
            var psiServices = solution.GetPsiServices();

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

            if (identifierNode == null)
            {
                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;
            }

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

                    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(rangeMarker.DocumentRange.StartOffset, "void Foo(){}");

            psiServices.Files.CommitAllDocuments();

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

            // Get the UnityEventFunction generator to actually insert the methods
            GenerateCodeWorkflowBase.ExecuteNonInteractive(
                GeneratorUnityKinds.UnityEventFunctions, solution, textControl, identifierNode.Language,
                configureContext: context =>
            {
                var inputElements = from e in context.ProvidedElements.Cast <GeneratorDeclaredElement <IMethod> >()
                                    where myEventFunction.Match(e.DeclaredElement) != MethodSignatureMatch.NoMatch
                                    select e;

                context.InputElements.Clear();
                context.InputElements.AddRange(inputElements);
            });

            if (attributeList != null)
            {
                methodDeclaration = TextControlToPsi.GetElement <IMethodDeclaration>(solution, textControl);
                if (methodDeclaration != null)
                {
                    using (var transactionCookie =
                               new PsiTransactionCookie(psiServices, DefaultAction.Rollback, "InsertAttributes"))
                    {
                        methodDeclaration.SetAttributeSectionList(attributeList);
                        transactionCookie.Commit();
                    }
                }
            }
        }
Exemple #12
0
        private static AfterCompletionHandler BakeAfterComplete([NotNull] ILookupItem lookupItem, [NotNull] ISolution solution, int argumentsCount)
        {
            // sorry, ugly as f**k :(
            return((ITextControl textControl, ref TextRange range, ref TextRange decoration,
                    TailType tailType, ref Suffix suffix, ref IRangeMarker caretMarker) =>
            {
                var psiServices = solution.GetPsiServices();
                psiServices.CommitAllDocuments();

                var allMethods = GetAllTargetMethods(lookupItem);
                if (allMethods.Count == 0)
                {
                    return;
                }

                var reference = TextControlToPsi.GetElement <IReferenceExpression>(solution, textControl.Document, range.StartOffset);
                if (reference == null)
                {
                    return;
                }

                var decorationText = textControl.Document.GetText(decoration);
                var decorationRange = new DocumentRange(textControl.Document, decoration);

                var hasMoreParameters = HasMoreParameters(allMethods, argumentsCount);
                if (!hasMoreParameters) // put caret 'foo(arg){here};'
                {
                    caretMarker = decorationRange.EndOffsetRange().CreateRangeMarker();
                }
                else if (argumentsCount > 0)
                {
                    var parenthesisCloseIndex = decorationText.LastIndexOf(')');
                    if (parenthesisCloseIndex >= 0)
                    {
                        var delta = decoration.Length - parenthesisCloseIndex;
                        caretMarker = decorationRange.EndOffsetRange().Shift(-delta).CreateRangeMarker();
                    }
                }

                var qualifierExpression = reference.QualifierExpression.NotNull("qualifierExpression != null");
                var referencePointer = reference.CreateTreeElementPointer();

                var qualifierText = InsertQualifierAsArgument(
                    qualifierExpression, allMethods, argumentsCount, textControl, decoration, decorationText);

                // TODO: mmm?
                if (!hasMoreParameters && !decorationText.EndsWith(")", StringComparison.Ordinal))
                {
                    caretMarker = caretMarker.DocumentRange.Shift(+qualifierText.Length).CreateRangeMarker();
                }

                // replace qualifier with type (predefined/user type)
                var ownerType = allMethods[0].GetContainingType().NotNull("ownerType != null");
                FixQualifierExpression(textControl, qualifierExpression, ownerType);

                psiServices.CommitAllDocuments();

                var newReference = referencePointer.GetTreeNode();
                if (newReference != null)
                {
                    var keyword = CSharpTypeFactory.GetTypeKeyword(ownerType.GetClrName());
                    if (keyword == null) // bind user type
                    {
                        var newQualifier = (IReferenceExpression)newReference.QualifierExpression;
                        if (newQualifier != null)
                        {
                            var elementInstance = lookupItem.GetDeclaredElement().NotNull("elementInstance != null");
                            newQualifier.Reference.BindTo(ownerType, elementInstance.Substitution);
                        }

                        range = newReference.NameIdentifier.GetDocumentRange().TextRange;
                        decoration = TextRange.InvalidRange;
                    }

                    // show parameter info when needed
                    if (hasMoreParameters)
                    {
                        var factory = solution.GetComponent <LookupItemsOwnerFactory>();
                        var lookupItemsOwner = factory.CreateLookupItemsOwner(textControl);

                        LookupUtil.ShowParameterInfo(solution, textControl, lookupItemsOwner);
                    }
                }

                TipsManager.Instance.FeatureIsUsed(
                    "Plugin.ControlFlow.PostfixTemplates.<static>", textControl.Document, solution);
            });
        }
            public void Accept(ITextControl textControl, TextRange nameRange, LookupItemInsertType lookupItemInsertType, Suffix suffix, ISolution solution, bool keepCaretStill)
            {
                /* PLEASE DO NOT OBSERVE THIS */
                string text;

                if (lookupItemInsertType == LookupItemInsertType.Insert)
                {
                    if (Info.InsertText.IndexOf(')') == -1)
                    {
                        Info.InsertText += ")";
                    }

                    text = Info.Text + Info.InsertText;
                    textControl.Document.ReplaceText(Info.Ranges.InsertRange.JoinRight(nameRange), text);
                }
                else
                {
                    if (Info.ReplaceText.IndexOf(')') == -1)
                    {
                        Info.ReplaceText += ")";
                    }

                    text = Info.Text + Info.ReplaceText;
                    textControl.Document.ReplaceText(Info.Ranges.ReplaceRange.JoinRight(nameRange), text);
                }

                var tailType = Info.TailType;

                if (tailType != null)
                {
                    LookupUtil.InsertTailType(textControl, nameRange.StartOffset + text.Length, tailType, solution, emulateTypingOfSpace: false);
                }

                /* PLEASE DO NOT OBSERVE THIS */

                var psiServices = solution.GetPsiServices();

                psiServices.Files.CommitAllDocuments();

                var referenceExpression = TextControlToPsi.GetElement <IReferenceExpression>(solution, textControl.Document, nameRange.StartOffset);

                if (referenceExpression == null || myMethods.Count == 0)
                {
                    return;
                }

                var existingArgumentsCount = GetExistingArgumentsCount(referenceExpression);
                var referencePointer       = referenceExpression.CreateTreeElementPointer();

                InsertQualifierAsArgument(referenceExpression, existingArgumentsCount, textControl);

                psiServices.Files.CommitAllDocuments();

                var reference1 = referencePointer.GetTreeNode();

                if (reference1 != null)
                {
                    var textRange = reference1.NameIdentifier.GetDocumentRange().TextRange;
                    var pointers  = Info.DeclaredElementPointers.ToList();

#if RESHARPER92
                    var caretPositionMarker = RangeMarker.InvalidMarker;
                    LookupUtil.BindRange(solution, textControl, textRange, pointers, reference1.Language, ref caretPositionMarker);
#else
                    LookupUtil.BindRange(solution, textControl, textRange, pointers, reference1.Language);
#endif

                    psiServices.Files.CommitAllDocuments();

                    var reference3 = referencePointer.GetTreeNode();
                    if (reference3 != null)
                    {
                        ApplyQualifierCodeStyle(reference3);

                        psiServices.Files.CommitAllDocuments();

                        var reference2 = referencePointer.GetTreeNode();
                        if (reference2 != null)
                        {
                            PlaceCaretAfterCompletion(textControl, reference2, existingArgumentsCount, lookupItemInsertType);
                        }
                    }
                }
            }
Exemple #14
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();
                        }
                });
            });
        }