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