private bool UpdateExistingMethod([CanBeNull] IMethodDeclaration methodDeclaration, IPsiServices psiServices)
        {
            if (methodDeclaration?.Body == null)
            {
                return(false);
            }

            var classLikeDeclaration = methodDeclaration.GetContainingTypeDeclaration() as IClassLikeDeclaration;

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

            using (var cookie = new PsiTransactionCookie(psiServices, DefaultAction.Rollback, "UpdateExistingMethod"))
                using (WriteLockCookie.Create())
                {
                    methodDeclaration.SetName(myEventFunction.Name);

                    // TODO: We should also update return type and parameters
                    // This doesn't work - it doesn't shorten the references and we end up "global::System.Void". Don't know
                    // why and don't have time to look into right now.
                    // At least the method signature inspections will help fix up if necessary
                    // When this comes back, remember to try to match the existing parameters - they might be correct but
                    // renamed. We don't want to set the names back and break code
//                methodDeclaration.SetTypeUsage(newDeclaration.TypeUsage);
//                methodDeclaration.SetParams(newDeclaration.Params);

                    cookie.Commit();
                }

            return(true);
        }
        protected override Action <ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress)
        {
            return(textControl =>
            {
                var psiFiles = solution.GetComponent <IPsiFiles>();
                var psiServices = solution.GetComponent <IPsiServices>();

                var buildPsiSourceFile = textControl.Document.GetPsiSourceFile(solution).NotNull();
                var buildCSharpFile = psiFiles.GetPsiFiles <CSharpLanguage>(buildPsiSourceFile).OfType <ICSharpFile>().Single();

                var target = new DeclaredElementInstance <ITypeElement>(_taskClass);
                var usingDirective = CSharpElementFactory.GetInstance(buildCSharpFile).CreateUsingStaticDirective(target);

                if (UsingUtil.GetImportConflicts(buildCSharpFile, target).Any())
                {
                    MessageBox.ShowError("Unable to import static class (import introduces conflicts in file)");
                    return;
                }

                using (var cookie = new PsiTransactionCookie(psiServices, DefaultAction.Rollback, typeof(MissingTaskUsageFix).Name))
                {
                    UsingUtil.AddImportTo(buildCSharpFile, usingDirective);
                    cookie.Commit();
                }

                var method = _taskClass.Methods.First(x => x.ShortName.Equals(_error.Reference.GetName(), StringComparison.OrdinalIgnoreCase));
                textControl.Document.ReplaceText(_error.CalculateRange().TextRange, method.ShortName);
            });
        }
        private void UpdateExistingMethod([CanBeNull] IDeclaration methodDeclaration, IPsiServices psiServices)
        {
            if (methodDeclaration == null)
            {
                return;
            }

            using (var cookie = new PsiTransactionCookie(psiServices, DefaultAction.Rollback, "UpdateExistingMethod"))
                using (WriteLockCookie.Create())
                {
                    methodDeclaration.SetName(myEventFunction.Name);
                    cookie.Commit();
                }
        }
Example #4
0
        protected override void OnAfterComplete(ITextControl textControl, ref TextRange nameRange, ref TextRange decorationRange, TailType tailType,
                                                ref Suffix suffix, ref IRangeMarker caretPositionRangeMarker)
        {
            Solution.GetPsiServices().Files.CommitAllDocuments();
            var psiServices = Solution.GetPsiServices();

            using (var transactionCookie = new PsiTransactionCookie(psiServices, DefaultAction.Rollback, "Lookup item"))
            {
                ImportHelper.AddMissingNamespaceImport(
                    (ICSharpTypeAndNamespaceHolderDeclaration)Context.BasicContext.File,
                    CSharpElementFactory.GetInstance(Context.PsiModule), "System.Reflection");
                psiServices.Caches.Update();
                transactionCookie.Commit();
            }
        }
Example #5
0
        private void UpdateExistingMethod([CanBeNull] IDeclaration methodDeclaration, DocumentRange nameRange,
                                          ITextControl textControl, IPsiServices psiServices)
        {
            if (methodDeclaration == null)
            {
                return;
            }

            // Replacing the entire name of a method first deletes the current name identifier, and leaves us with two
            // broken method declarations - one trying to parse up to the name, and one trying to parse after it. The
            // first doesn't have a name or a body, and the second confuses the parameter list as a tuple return type,
            // and also doesn't have a name. It does have a body. The current caret position gives us the second broken
            // method declaration.
            // We can't just update the PSI, because it's too broken. If we try to set the name of either declaration,
            // there's nothing to replace, so the PSI will just add a child to the end of the list of valid children.
            // For the first declaration, that doesn't include the whitespace after the return type, so we end up with
            // something like: private voidUpdate () { }. For the second declaration, it adds the name identifier after
            // the body.
            // We already know the insert location, so we just add as text. This doesn't affect changing part of an
            // existing name, because that part name means we have one valid method declaration, which we can easily
            // update
            if (methodDeclaration.GetPreviousMeaningfulSiblingThroughWhitespaceAndComments() is
                IMethodDeclaration prevMethod && prevMethod.Body == null &&
                methodDeclaration.GetNextMeaningfulChild(null) is ITupleTypeUsage)
            {
                using (WriteLockCookie.Create())
                    textControl.Document.InsertText(nameRange.StartOffset, myEventFunction.Name);
                return;
            }

            using (var cookie = new PsiTransactionCookie(psiServices, DefaultAction.Rollback, "UpdateExistingMethod"))
                using (WriteLockCookie.Create())
                {
                    methodDeclaration.SetName(myEventFunction.Name);
                    cookie.Commit();
                }
        }
        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);
            }
        }
Example #7
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();
                    }
                }
            }
        }
Example #8
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();
                        }
                });
            });
        }