public static void HandleFailureToReference(string packageLocation, ITextControlManager textControlManager, IShellLocks shellLocks) { // TODO: Wish we didn't have to do this, either // If we failed to install the package, it's because something has gone wrong, // and we don't want the rest of the process to continue. Unfortunately, ReSharper // doesn't display any error messages, so we'll very hackily try and find the // current text editor, and display a tooltip with an error message. // (This replicates the experience when something else goes wrong in the context // actions that use ModuleReferencerService.) It's not a nice thing to do, but // it's safe with how ReSharper uses IModuleReferencer out of the box. If anyone // else uses it, and we go wrong, well, fingers crossed. We should either get // the tooltip as expected, multiple error messages, or no error messages. Hopefully, // we shouldn't see this very often, although I've probably just jinxed it now... // // Ideally, ReSharper should provide a better error mechanism as part of IModuleReferencer if (string.IsNullOrEmpty(packageLocation)) { var textControl = textControlManager.FocusedTextControl.Value; if (textControl != null) { var shell = Shell.Instance; shellLocks.Queue("Failed to import type", () => shell.Components.Tooltips().ShowAtCaret(EternalLifetime.Instance, "Failed to add NuGet package.", textControl, shellLocks, shell.GetComponent <IActionManager>())); } } }
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(); } }); }); }