コード例 #1
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();
                        }
                });
            });
        }
コード例 #2
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();
                    }
                }
            }
        }
コード例 #3
0
        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;

                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 UnityEventFunction generator to actually insert the methods
            GenerateCodeWorkflowBase.ExecuteNonInteractive(
                GeneratorUnityKinds.UnityEventFunctions, solution, textControl, methodDeclaration.Language,
                configureContext: context =>
            {
                var inputElements = from e in context.ProvidedElements.Cast <GeneratorDeclaredElement <IMethod> >()
                                    where myEventFunction.Match(e.DeclaredElement) != EventFunctionMatch.NoMatch
                                    select e;

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

            if (!string.IsNullOrEmpty(attributeText))
            {
                using (WriteLockCookie.Create())
                    textControl.Document.InsertText(insertionIndex, attributeText);
            }
        }
コード例 #4
0
        private void DoFormatStatementOnColon(ITextControl textControl)
        {
            IFile file = CommitPsi(textControl);

            if (file == null)
            {
                return;
            }
            int charPos = TextControlToLexer(textControl, textControl.Caret.Offset());

            if (charPos < 0)
            {
                return;
            }

            var tokenNode = file.FindTokenAt(textControl.Document, charPos - 1) as ITokenNode;

            if (tokenNode == null || tokenNode.GetTokenType() != PsiTokenType.COLON)
            {
                return;
            }

            var node = tokenNode.GetContainingNode <IRuleDeclaration>();

            // do format if semicolon finished the statement
            if (node == null)
            {
                return;
            }

            // Select the correct start node for formatting
            ITreeNode startNode = node.FindFormattingRangeToLeft();

            if (startNode == null)
            {
                startNode = node.FirstChild;
            }

            var codeFormatter = GetCodeFormatter(tokenNode);

            using (PsiTransactionCookie.CreateAutoCommitCookieWithCachesUpdate(PsiServices, "Format code"))
            {
                using (WriteLockCookie.Create())
                {
                    codeFormatter.Format(startNode, tokenNode, CodeFormatProfile.DEFAULT);
                }
            }

            DocumentRange newPosition = tokenNode.GetDocumentRange();

            if (newPosition.IsValid())
            {
                textControl.Caret.MoveTo(newPosition.TextRange.EndOffset, CaretVisualPlacement.DontScrollIfVisible);
            }

            var nextSibling = tokenNode.NextSibling;

            while (nextSibling != null)
            {
                if (nextSibling is IRuleBody)
                {
                    break;
                }
                nextSibling = nextSibling.NextSibling;
            }
            if (nextSibling is IRuleBody)
            {
                newPosition = nextSibling.GetDocumentRange();
                if (newPosition.IsValid())
                {
                    textControl.Caret.MoveTo(newPosition.TextRange.EndOffset, CaretVisualPlacement.DontScrollIfVisible);
                }
            }
        }
コード例 #5
0
        private bool ReformatForSmartEnter(string dummyText, ITextControl textControl, IFile file, TreeTextRange reparseTreeOffset, TreeOffset lBraceTreePos, TreeOffset rBraceTreePos, bool insertEnterAfter = false)
        {
            // insert dummy text and reformat
            TreeOffset newCaretPos;
            var        codeFormatter = GetCodeFormatter(file);

            using (PsiTransactionCookie.CreateAutoCommitCookieWithCachesUpdate(PsiServices, "Typing assist"))
            {
                string newLine      = Environment.NewLine;
                string textToInsert = newLine + dummyText;
                if (insertEnterAfter)
                {
                    textToInsert = textToInsert + newLine;
                }
                file = file.ReParse(reparseTreeOffset, textToInsert);
                if (file == null)
                {
                    return(false);
                }

                ITreeNode lBraceNode = file.FindTokenAt(lBraceTreePos);
                if (lBraceNode == null)
                {
                    return(false);
                }

                var dummyNode = file.FindTokenAt(reparseTreeOffset.StartOffset + newLine.Length) as ITokenNode;

                var languageService = file.Language.LanguageService();
                if (languageService == null)
                {
                    return(false);
                }

                while (dummyNode != null && languageService.IsFilteredNode(dummyNode))
                {
                    dummyNode = dummyNode.GetNextToken();
                }

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

                var rBraceNode = file.FindTokenAt(rBraceTreePos + newLine.Length + dummyText.Length + (insertEnterAfter ? newLine.Length : 0));

                var boundSettingsStore = SettingsStore.BindToContextTransient(textControl.ToContextRange());

                codeFormatter.Format(lBraceNode, CodeFormatProfile.DEFAULT, null, boundSettingsStore);
                codeFormatter.Format(
                    rBraceNode.FindFormattingRangeToLeft(),
                    rBraceNode,
                    CodeFormatProfile.DEFAULT,
                    null,
                    boundSettingsStore);
                codeFormatter.Format(lBraceNode.Parent, CodeFormatProfile.INDENT, null);

                newCaretPos = dummyNode.GetTreeStartOffset();
                file        = file.ReParse(new TreeTextRange(newCaretPos, newCaretPos + dummyText.Length), "");
                Assertion.Assert(file != null, "fileFullName != null");
            }

            // dposition cursor
            DocumentRange newCaretPosition = file.GetDocumentRange(newCaretPos);

            if (newCaretPosition.IsValid())
            {
                textControl.Caret.MoveTo(newCaretPosition.TextRange.StartOffset, CaretVisualPlacement.DontScrollIfVisible);
            }

            return(true);
        }
コード例 #6
0
        private void DoSmartIndentOnEnter(ITextControl textControl)
        {
            var originalOffset = textControl.Caret.Offset();

            var offset     = TextControlToLexer(textControl, originalOffset);
            var mixedLexer = GetCachingLexer(textControl);

            // if there is something on that line, then use existing text
            if (offset <= 0 || !mixedLexer.FindTokenAt(offset - 1))
            {
                return;
            }

            if (mixedLexer.TokenType == PsiTokenType.C_STYLE_COMMENT || mixedLexer.TokenType == PsiTokenType.STRING_LITERAL)
            {
                return;
            }

            if (offset <= 0 || !mixedLexer.FindTokenAt(offset))
            {
                return;
            }

            while (mixedLexer.TokenType == PsiTokenType.WHITE_SPACE)
            {
                mixedLexer.Advance();
            }

            offset = mixedLexer.TokenType == null ? offset : mixedLexer.TokenStart;
            var extraText = (mixedLexer.TokenType == PsiTokenType.NEW_LINE || mixedLexer.TokenType == null) ? "foo " : String.Empty;

            var projectItem = textControl.Document.GetPsiSourceFile(Solution);

            if (projectItem == null || !projectItem.IsValid())
            {
                return;
            }
            var services = Solution.GetPsiServices();

            using (services.Transactions.DocumentTransactionManager.CreateTransactionCookie(DefaultAction.Commit, "Typing assist"))
            {
                // If the new line is empty, the do default indentation
                int lexerOffset = offset;
                if (extraText.Length > 0)
                {
                    textControl.Document.InsertText(lexerOffset, extraText);
                }

                services.Files.CommitAllDocuments();
                var file = projectItem.GetPsiFile <PsiLanguage>(new DocumentRange(textControl.Document, offset));

                if (file == null)
                {
                    return;
                }

                var rangeInJsTree = file.Translate(new DocumentOffset(textControl.Document, offset));

                if (!rangeInJsTree.IsValid())
                {
                    if (extraText.Length > 0)
                    {
                        textControl.Document.DeleteText(new TextRange(lexerOffset, lexerOffset + extraText.Length));
                    }
                    return;
                }

                var tokenNode = file.FindTokenAt(rangeInJsTree) as ITokenNode;
                if (tokenNode == null)
                {
                    if (extraText.Length > 0)
                    {
                        textControl.Document.DeleteText(new TextRange(lexerOffset, lexerOffset + extraText.Length));
                    }
                    return;
                }
                var codeFormatter = GetCodeFormatter(file);
                int offsetInToken = rangeInJsTree.Offset - tokenNode.GetTreeStartOffset().Offset;

                using (PsiTransactionCookie.CreateAutoCommitCookieWithCachesUpdate(PsiServices, "Typing assist"))
                {
                    Lifetimes.Using(
                        lifetime =>
                    {
                        var boundSettingsStore = SettingsStore.CreateNestedTransaction(lifetime, "PsiTypingAssist").BindToContextTransient(textControl.ToContextRange());
                        var prevToken          = tokenNode.GetPrevToken();
                        if (prevToken == null)
                        {
                            return;
                        }

                        /*if (tokenNode.Parent is IParenExpression || prevToken.Parent is IParenExpression)
                         * {
                         * var node = tokenNode.Parent;
                         * if (prevToken.Parent is IParenExpression)
                         * {
                         *  node = prevToken.Parent;
                         * }
                         * codeFormatter.Format(node.FirstChild, node.LastChild,
                         *  CodeFormatProfile.DEFAULT, NullProgressIndicator.Instance, boundSettingsStore);
                         * }
                         * else
                         * {*/
                        codeFormatter.Format(prevToken, tokenNode,
                                             CodeFormatProfile.INDENT, NullProgressIndicator.Instance, boundSettingsStore);
                        //}
                    });
                    offset = file.GetDocumentRange(tokenNode.GetTreeStartOffset()).TextRange.StartOffset +
                             offsetInToken;
                }

                if (extraText.Length > 0)
                {
                    lexerOffset = offset;
                    textControl.Document.DeleteText(new TextRange(lexerOffset, lexerOffset + extraText.Length));
                }
            }

            textControl.Caret.MoveTo(offset, CaretVisualPlacement.DontScrollIfVisible);
        }
コード例 #7
0
        public async void Execute(ISolution solution, ITextControl textControl)
        {
            if (this.ExistingProjectFile != null)
            {
                await ShowProjectFile(solution, this.ExistingProjectFile, null);

                return;
            }

            using (ReadLockCookie.Create())
            {
                using (var cookie = solution.CreateTransactionCookie(DefaultAction.Rollback, this.Text, NullProgressIndicator.Create()))
                {
                    var declaration = this._provider.GetSelectedElement <ICSharpTypeDeclaration>();

                    var declaredType = declaration?.DeclaredElement;
                    if (declaredType == null)
                    {
                        return;
                    }

                    var settingsStore  = declaration.GetSettingsStore();
                    var helperSettings = ReSharperHelperSettings.GetSettings(settingsStore);

                    var testProject = this.CachedTestProject ?? this.ResolveTargetTestProject(declaration, solution, helperSettings);
                    if (testProject == null)
                    {
                        return;
                    }

                    var classNamespaceParts = TrimDefaultProjectNamespace(declaration.GetProject().NotNull(), declaredType.GetContainingNamespace().QualifiedName);
                    var testFolderLocation  = classNamespaceParts.Aggregate(testProject.Location, (current, part) => current.Combine(part));

                    var testNamespace = StringUtil.MakeFQName(testProject.GetDefaultNamespace(), StringUtil.MakeFQName(classNamespaceParts));

                    var testFolder = testProject.GetOrCreateProjectFolder(testFolderLocation, cookie);
                    if (testFolder == null)
                    {
                        return;
                    }

                    var testClassName = declaredType.ShortName + helperSettings.TestClassNameSuffix;
                    var testFileName  = testClassName + ".cs";

                    var testFileTemplate = StoredTemplatesProvider.Instance.EnumerateTemplates(settingsStore, TemplateApplicability.File).FirstOrDefault(t => t.Description == TemplateDescription);
                    if (testFileTemplate != null)
                    {
                        await FileTemplatesManager.Instance.CreateFileFromTemplateAsync(testFileName, new ProjectFolderWithLocation(testFolder), testFileTemplate);

                        return;
                    }

                    var newFile = AddNewItemHelper.AddFile(testFolder, testFileName).ToSourceFile();
                    if (newFile == null)
                    {
                        return;
                    }

                    int?caretPosition;
                    using (PsiTransactionCookie.CreateAutoCommitCookieWithCachesUpdate(newFile.GetPsiServices(), "CreateTestClass"))
                    {
                        var csharpFile = newFile.GetDominantPsiFile <CSharpLanguage>() as ICSharpFile;
                        if (csharpFile == null)
                        {
                            return;
                        }

                        var elementFactory = CSharpElementFactory.GetInstance(csharpFile);

                        var namespaceDeclaration = elementFactory.CreateNamespaceDeclaration(testNamespace);
                        var addedNs = csharpFile.AddNamespaceDeclarationAfter(namespaceDeclaration, null);

                        var classLikeDeclaration = (IClassLikeDeclaration)elementFactory.CreateTypeMemberDeclaration("public class $0 {}", testClassName);
                        var addedTypeDeclaration = addedNs.AddTypeDeclarationAfter(classLikeDeclaration, null) as IClassDeclaration;

                        caretPosition = addedTypeDeclaration?.Body?.GetDocumentRange().TextRange.StartOffset + 1;
                    }

                    cookie.Commit(NullProgressIndicator.Create());

                    await ShowProjectFile(solution, newFile.ToProjectFile().NotNull(), caretPosition);
                }
            }
        }