private static ILookupItem CreateMethodItem(CSharpCodeCompletionContext context, UnityEventFunction eventFunction,
                                                    IClassLikeDeclaration declaration)
        {
            if (CSharpLanguage.Instance == null)
            {
                return(null);
            }

            var method = eventFunction.CreateDeclaration(CSharpElementFactory.GetInstance(declaration), declaration);

            if (method.DeclaredElement == null)
            {
                return(null);
            }

            var instance = new DeclaredElementInstance(method.DeclaredElement);

            var declaredElementInfo = new DeclaredElementInfo(method.DeclaredName, instance, CSharpLanguage.Instance,
                                                              context.BasicContext.LookupItemsOwner, context)
            {
                Ranges = context.CompletionRanges
            };

            var withMatcher = LookupItemFactory.CreateLookupItem(declaredElementInfo).
                              WithPresentation(_ => new GenerateMemberPresentation(declaredElementInfo, PresenterStyles.DefaultPresenterStyle)).
                              WithBehavior(_ => new UnityEventFunctionBehavior(declaredElementInfo, eventFunction)).
                              WithMatcher(_ => new DeclaredElementMatcher(declaredElementInfo, context.BasicContext.IdentifierMatchingStyle));

            return(withMatcher);
        }
Пример #2
0
        private ILookupItem CreateMethodItem(CSharpCodeCompletionContext context,
                                             UnityEventFunction eventFunction, IClassLikeDeclaration declaration,
                                             bool hasReturnType, AccessRights accessRights,
                                             MemberGenerationContext generationContext)
        {
            if (CSharpLanguage.Instance == null)
            {
                return(null);
            }

            // Only show the modifier in the list text if it's not already specified and there isn't a return type, in
            // which case we default to `private`. E.g. if someone types `OnAnim`, then show `private void OnAnimate...`
            // but if they type `void OnAnim`, they don't want a modifier, and if they type `public void OnAnim` then
            // they want to use `public`
            var showModifier = false;

            if (!hasReturnType && accessRights == AccessRights.NONE)
            {
                showModifier = true;
                accessRights = AccessRights.PRIVATE;
            }

            // Note that we can't keep this declaration - it will become invalid as the user types to narrow down the
            // search and modifies the PSI file. This only affects ReSharper, Rider has different code completion
            // mechanism
            var factory           = CSharpElementFactory.GetInstance(declaration, false);
            var methodDeclaration = eventFunction.CreateDeclaration(factory, declaration, accessRights);

            if (methodDeclaration.DeclaredElement == null)
            {
                return(null);
            }

            // This is effectively the same as GenerateMemberPresentation, but without the overhead that comes
            // with the flexibility of formatting any time of declared element. We just hard code the format
            var predefinedType = context.PsiModule.GetPredefinedType();
            var parameters     = string.Empty;

            if (eventFunction.Parameters.Length > 0)
            {
                var sb = new StringBuilder();
                for (var i = 0; i < eventFunction.Parameters.Length; i++)
                {
                    if (i > 0)
                    {
                        sb.Append(", ");
                    }

                    var parameter = eventFunction.Parameters[i];
                    var type      = predefinedType.TryGetType(parameter.ClrTypeName, NullableAnnotation.Unknown);
                    var typeName  = type?.GetPresentableName(CSharpLanguage.Instance) ??
                                    parameter.ClrTypeName.ShortName;
                    sb.AppendFormat("{0}{1}{2}", parameter.IsByRef ? "out" : string.Empty,
                                    typeName, parameter.IsArray ? "[]" : string.Empty);
                }
                parameters = sb.ToString();
            }
            var text            = $"{eventFunction.Name}({parameters})";
            var parameterOffset = eventFunction.Name.Length;

            var modifier = showModifier
                ? CSharpDeclaredElementPresenter.Instance.Format(accessRights) + " "
                : string.Empty;

            var psiIconManager = context.BasicContext.LookupItemsOwner.Services.PsiIconManager;

            // Note that because this is a text based lookup item, then it won't be included if the normal C# method
            // filter is applied. We can't make it a method based lookup item because the DeclaredElement isn't valid in
            // this situation - it's not a real method, and a DeclaredElementInfo would try to store a pointer to it,
            // and be unable to recreate it when it's needed.
            var textualInfo = new UnityEventFunctionTextualInfo(generationContext.MemberReplaceRanges, text, text)
            {
                Ranges = context.CompletionRanges
            };

            var lookupItem = LookupItemFactory.CreateLookupItem(textualInfo)
                             .WithPresentation(item =>
            {
                var displayName = new RichText($"{modifier}{text} {{ ... }}");

                // GenerateMemberPresentation marks everything as bold, and the parameters + block syntax as not important
                var parameterStartOffset = modifier.Length + parameterOffset;
                LookupUtil.MarkAsNotImportant(displayName,
                                              TextRange.FromLength(parameterStartOffset, displayName.Length - parameterStartOffset));
                LookupUtil.AddEmphasize(displayName, new TextRange(modifier.Length, displayName.Length));

                var image = psiIconManager.GetImage(CLRDeclaredElementType.METHOD);
                psiIconManager.AttachExtensions(image, GetAccessExtensions(accessRights));
                var marker = item.Info.Ranges.CreateVisualReplaceRangeMarker();
                return(new SimplePresentation(displayName, image, marker));
            })
                             .WithBehavior(_ => new UnityEventFunctionBehavior(textualInfo, eventFunction, accessRights))
                             .WithMatcher(_ =>
                                          new ShiftedDeclaredElementMatcher(eventFunction.Name, modifier.Length, textualInfo));

            var description = GetDescription(context, methodDeclaration);

            return(new WrappedLookupItem(lookupItem, description));
        }
        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));
            }
        }
        private static ILookupItem CreateMethodItem(CSharpCodeCompletionContext context,
                                                    UnityEventFunction eventFunction, IClassLikeDeclaration declaration, bool addModifier)
        {
            if (CSharpLanguage.Instance == null)
            {
                return(null);
            }

            var method = eventFunction.CreateDeclaration(CSharpElementFactory.GetInstance(declaration, false), declaration);

            if (method.DeclaredElement == null)
            {
                return(null);
            }

            var instance = new DeclaredElementInstance(method.DeclaredElement);

            var declaredElementInfo = new DeclaredElementInfoWithoutParameterInfo(method.DeclaredName, instance, CSharpLanguage.Instance,
                                                                                  context.BasicContext.LookupItemsOwner, context)
            {
                Ranges = context.CompletionRanges
            };

            // This is effectively the same as GenerateMemberPresentation, but without the overhead that comes
            // with the flexibility of formatting any time of declared element. We just hard code the format
            var predefinedType = context.PsiModule.GetPredefinedType();
            var parameters     = string.Empty;

            if (eventFunction.Parameters.Length > 0)
            {
                var sb = new StringBuilder();
                for (var i = 0; i < eventFunction.Parameters.Length; i++)
                {
                    if (i > 0)
                    {
                        sb.Append(", ");
                    }

                    var parameter = eventFunction.Parameters[i];
                    var type      = predefinedType.TryGetType(parameter.ClrTypeName);
                    var typeName  = type?.GetPresentableName(CSharpLanguage.Instance) ??
                                    parameter.ClrTypeName.ShortName;
                    sb.AppendFormat("{0}{1}{2}", parameter.IsByRef ? "out" : string.Empty,
                                    typeName, parameter.IsArray ? "[]" : string.Empty);
                }
                parameters = sb.ToString();
            }
            var text            = $"{eventFunction.Name}({parameters})";
            var parameterOffset = eventFunction.Name.Length;
            var modifier        = addModifier ? "private " : string.Empty;

            var psiIconManager = context.BasicContext.LookupItemsOwner.Services.PsiIconManager;

            return(LookupItemFactory.CreateLookupItem(declaredElementInfo)
                   .WithPresentation(item =>
            {
                var displayName = new RichText($"{modifier}{text} {{ ... }}");

                // GenerateMemberPresentation marks everything as bold, and the parameters + block syntax as not important
                var parameterStartOffset = modifier.Length + parameterOffset;
                LookupUtil.MarkAsNotImportant(displayName,
                                              TextRange.FromLength(parameterStartOffset, displayName.Length - parameterStartOffset));
                LookupUtil.AddEmphasize(displayName, new TextRange(modifier.Length, displayName.Length));

                var image = psiIconManager.GetImage(CLRDeclaredElementType.METHOD, PsiIconExtension.Private);
                var marker = item.Info.Ranges.CreateVisualReplaceRangeMarker();
                return new SimplePresentation(displayName, image, marker);
            })
                   .WithBehavior(_ => new UnityEventFunctionBehavior(declaredElementInfo, eventFunction))
                   .WithMatcher(_ =>
                                new ShiftedDeclaredElementMatcher(text, modifier.Length, declaredElementInfo,
                                                                  context.BasicContext.IdentifierMatchingStyle)));
        }
Пример #5
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();
                        }
                });
            });
        }
Пример #6
0
        private ILookupItem CreateMethodItem(CSharpCodeCompletionContext context,
                                             UnityEventFunction eventFunction, IClassLikeDeclaration declaration,
                                             bool hasReturnType, AccessRights accessRights)
        {
            if (CSharpLanguage.Instance == null)
            {
                return(null);
            }

            // Only show the modifier in the list text if it's not already specified and there isn't a return type, in
            // which case we default to `private`. E.g. if someone types `OnAnim`, then show `private void OnAnimate...`
            // but if they type `void OnAnim`, they don't want a modifier, and if they type `public void OnAnim` then
            // they want to use `public`
            var showModifier = false;

            if (!hasReturnType && accessRights == AccessRights.NONE)
            {
                showModifier = true;
                accessRights = AccessRights.PRIVATE;
            }

            var factory           = CSharpElementFactory.GetInstance(declaration, false);
            var methodDeclaration = eventFunction.CreateDeclaration(factory, declaration, accessRights);

            if (methodDeclaration.DeclaredElement == null)
            {
                return(null);
            }

            var instance = new DeclaredElementInstance(methodDeclaration.DeclaredElement);

            var declaredElementInfo = new DeclaredElementInfoWithoutParameterInfo(methodDeclaration.DeclaredName,
                                                                                  instance, CSharpLanguage.Instance, context.BasicContext.LookupItemsOwner, context)
            {
                Ranges = context.CompletionRanges
            };

            // This is effectively the same as GenerateMemberPresentation, but without the overhead that comes
            // with the flexibility of formatting any time of declared element. We just hard code the format
            var predefinedType = context.PsiModule.GetPredefinedType();
            var parameters     = string.Empty;

            if (eventFunction.Parameters.Length > 0)
            {
                var sb = new StringBuilder();
                for (var i = 0; i < eventFunction.Parameters.Length; i++)
                {
                    if (i > 0)
                    {
                        sb.Append(", ");
                    }

                    var parameter = eventFunction.Parameters[i];
                    var type      = predefinedType.TryGetType(parameter.ClrTypeName, NullableAnnotation.Unknown);
                    var typeName  = type?.GetPresentableName(CSharpLanguage.Instance) ??
                                    parameter.ClrTypeName.ShortName;
                    sb.AppendFormat("{0}{1}{2}", parameter.IsByRef ? "out" : string.Empty,
                                    typeName, parameter.IsArray ? "[]" : string.Empty);
                }
                parameters = sb.ToString();
            }
            var text            = $"{eventFunction.Name}({parameters})";
            var parameterOffset = eventFunction.Name.Length;

            var modifier = showModifier
                ? CSharpDeclaredElementPresenter.Instance.Format(accessRights) + " "
                : string.Empty;

            var psiIconManager = context.BasicContext.LookupItemsOwner.Services.PsiIconManager;

            return(LookupItemFactory.CreateLookupItem(declaredElementInfo)
                   .WithPresentation(item =>
            {
                var displayName = new RichText($"{modifier}{text} {{ ... }}");

                // GenerateMemberPresentation marks everything as bold, and the parameters + block syntax as not important
                var parameterStartOffset = modifier.Length + parameterOffset;
                LookupUtil.MarkAsNotImportant(displayName,
                                              TextRange.FromLength(parameterStartOffset, displayName.Length - parameterStartOffset));
                LookupUtil.AddEmphasize(displayName, new TextRange(modifier.Length, displayName.Length));

                var image = psiIconManager.GetImage(methodDeclaration.DeclaredElement,
                                                    methodDeclaration.DeclaredElement.PresentationLanguage, true);
                var marker = item.Info.Ranges.CreateVisualReplaceRangeMarker();
                return new SimplePresentation(displayName, image, marker);
            })
                   .WithBehavior(_ => new UnityEventFunctionBehavior(myShellLocks, declaredElementInfo, eventFunction, accessRights))
                   .WithMatcher(_ =>
                                new ShiftedDeclaredElementMatcher(eventFunction.Name, modifier.Length, declaredElementInfo,
                                                                  context.BasicContext.IdentifierMatchingStyle)));
        }