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