package com.intellij.lang.javascript.changesignature; import com.intellij.lang.ASTNode; import com.intellij.lang.javascript.JSTokenTypes; import com.intellij.lang.javascript.JavaScriptFileType; import com.intellij.lang.javascript.psi.JSFunction; import com.intellij.lang.javascript.refactoring.changeSignature.JSChangeSignatureDialog; import com.intellij.lang.javascript.refactoring.changeSignature.JSMethodDescriptor; import com.intellij.lang.javascript.refactoring.changeSignature.JSParameterInfo; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.BaseRefactoringProcessor; import com.intellij.refactoring.changeSignature.inplace.LanguageChangeSignatureDetector; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.List; public class JSChangeSignatureDetector implements LanguageChangeSignatureDetector<JSChangeInfo> { @NotNull @Override public JSChangeInfo createInitialChangeInfo(@NotNull PsiElement element) { JSFunction method = PsiTreeUtil.getParentOfType(element, JSFunction.class, false); if (method == null || !isInsideMethodSignature(element, method)) { return null; } if (PsiTreeUtil.hasErrorElements(method.getParameterList())) { return null; } return new JSChangeInfo(method); } private static boolean isInsideMethodSignature(PsiElement element, @NotNull JSFunction method) { TextRange r = getRange(method); if (r != null && r.contains(element.getTextOffset())) { return true; } if (element instanceof PsiErrorElement && element.getPrevSibling().getNode().getElementType() == JSTokenTypes.COLON) { // function foo():<caret> {} return true; } if (element instanceof PsiWhiteSpace && element.getPrevSibling() instanceof PsiErrorElement && element.getPrevSibling().getPrevSibling().getNode().getElementType() == JSTokenTypes.COLON) { // function foo():<caret> {} return true; } return false; } @Override public void performChange(JSChangeInfo changeInfo, Editor editor, @NotNull final String oldText) { final JSChangeInfo jsChangeInfo = changeInfo; JSMethodDescriptor descriptor = new JSMethodDescriptor(jsChangeInfo.getMethod(), false) { @Override public String getName() { return jsChangeInfo.getNewName(); } @Override public List<JSParameterInfo> getParameters() { return Arrays.asList(jsChangeInfo.getNewParameters()); } @Override public int getParametersCount() { return getParameters().size(); } @Override public String getVisibility() { return jsChangeInfo.getNewVisibility().name(); } @Override public JSFunction getMethod() { return jsChangeInfo.getMethod(); } }; JSChangeSignatureDialog d = new JSChangeSignatureDialog(descriptor, changeInfo.getMethod()) { @Override protected void invokeRefactoring(BaseRefactoringProcessor processor) { revertChanges(jsChangeInfo.getMethod(), oldText); super.invokeRefactoring(processor); } }; d.showAndGet(); } // TODO generalize private static void revertChanges(final PsiElement method, final String oldText) { //UndoManager.getInstance(method.getProject()).undoableActionPerformed(new ); ApplicationManager.getApplication().runWriteAction(() -> { final PsiFile file = method.getContainingFile(); final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(method.getProject()); final Document document = documentManager.getDocument(file); if (document != null) { final TextRange textRange = method.getTextRange(); document.replaceString(textRange.getStartOffset(), textRange.getEndOffset(), oldText); documentManager.commitDocument(document); } }); } @Override public boolean isChangeSignatureAvailableOnElement(PsiElement element, JSChangeInfo currentInfo) { return element.getNode().getElementType() == JSTokenTypes.IDENTIFIER && Comparing.equal(currentInfo.getMethod(), element.getParent().getParent()); } @Nullable private static TextRange getRange(PsiElement element) { JSFunction f = PsiTreeUtil.getParentOfType(element, JSFunction.class, false); if (f == null) return null; ASTNode identifier = f.findNameIdentifier(); if (identifier == null) { return null; } PsiElement e = f.getReturnTypeElement(); if (e == null) { ASTNode colon = f.getNode().findChildByType(JSTokenTypes.COLON); if (colon != null) { e = colon.getPsi(); } } if (e == null) { e = f.getParameterList(); } return new TextRange(identifier.getTextRange().getStartOffset(), e.getTextRange().getEndOffset()); } @Override public boolean ignoreChanges(PsiElement element) { return false; } @Override public TextRange getHighlightingRange(@NotNull JSChangeInfo changeInfo) { return getRange(changeInfo.getMethod()); } @Override public FileType getFileType() { return JavaScriptFileType.INSTANCE; } @Override public JSChangeInfo createNextChangeInfo(String signature, @NotNull JSChangeInfo currentInfo, boolean delegate) { return null; } }