From d3e1df23e507b7c604df64e6b8b651cccc5ffcd8 Mon Sep 17 00:00:00 2001 From: m0rkeulv Date: Sun, 26 Nov 2023 20:19:41 +0100 Subject: [PATCH] Adding Quickfix for typeTags and Extract Method. --- CHANGELOG.md | 4 + .../intention/AddReturnTypeTagIntention.java | 2 +- .../RemoveReturnTypeTagIntention.java | 6 +- .../HaxeRefactoringSupportProvider.java | 3 +- .../extractMethod/ExtractMethodBuilder.java | 326 ++++++++++++++++++ .../HaxeExtractMethodHandler.java | 235 +++++++++++++ .../UnableToExtractMethodException.java | 4 + .../introduce/HaxeIntroduceHandler.java | 4 +- .../model/type/HaxeExpressionEvaluator.java | 7 + .../haxe/util/HaxeElementGenerator.java | 15 + 10 files changed, 600 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/intellij/plugins/haxe/ide/refactoring/extractMethod/ExtractMethodBuilder.java create mode 100644 src/main/java/com/intellij/plugins/haxe/ide/refactoring/extractMethod/HaxeExtractMethodHandler.java create mode 100644 src/main/java/com/intellij/plugins/haxe/ide/refactoring/extractMethod/UnableToExtractMethodException.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 18fbf80f8..732838507 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.4.19 +* Quickfixes for adding or removing type tags (return types & variable types) +* Very basic Extract Method support + ## 1.4.18 * Support for Intellij 2023.3 * Improvement: Better support for local functions diff --git a/src/main/java/com/intellij/plugins/haxe/ide/intention/AddReturnTypeTagIntention.java b/src/main/java/com/intellij/plugins/haxe/ide/intention/AddReturnTypeTagIntention.java index 35b777960..8aa9b4143 100644 --- a/src/main/java/com/intellij/plugins/haxe/ide/intention/AddReturnTypeTagIntention.java +++ b/src/main/java/com/intellij/plugins/haxe/ide/intention/AddReturnTypeTagIntention.java @@ -77,7 +77,7 @@ private void attemptToFindMethod(Editor editor, PsiFile file) { PsiElement place = file.findElementAt(editor.getCaretModel().getOffset()); if (place instanceof HaxeMethod method) { myMethod = method; - }else { + }else if (place != null){ myMethod = findParentOfTypeButStopIfTypeIs(place, HaxeMethod.class, HaxeBlockStatement.class); } } diff --git a/src/main/java/com/intellij/plugins/haxe/ide/intention/RemoveReturnTypeTagIntention.java b/src/main/java/com/intellij/plugins/haxe/ide/intention/RemoveReturnTypeTagIntention.java index 282f5afb2..fc984200e 100644 --- a/src/main/java/com/intellij/plugins/haxe/ide/intention/RemoveReturnTypeTagIntention.java +++ b/src/main/java/com/intellij/plugins/haxe/ide/intention/RemoveReturnTypeTagIntention.java @@ -8,6 +8,7 @@ import com.intellij.plugins.haxe.lang.psi.HaxeBlockStatement; import com.intellij.plugins.haxe.lang.psi.HaxeMethod; import com.intellij.plugins.haxe.lang.psi.HaxeMethodDeclaration; +import com.intellij.plugins.haxe.lang.psi.HaxeTypeTag; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.util.IncorrectOperationException; @@ -50,7 +51,8 @@ public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file public void invoke(@NotNull final Project project, Editor editor, PsiFile file) throws IncorrectOperationException { HaxeMethodDeclaration declaration = (HaxeMethodDeclaration)myMethod; - declaration.getTypeTag().delete(); + HaxeTypeTag tag = declaration.getTypeTag(); + if (tag != null) tag.delete(); } @@ -59,7 +61,7 @@ private void attemptToFindMethod(Editor editor, PsiFile file) { if (place instanceof HaxeMethod method) { myMethod = method; } - else { + else if (place != null) { myMethod = findParentOfTypeButStopIfTypeIs(place, HaxeMethod.class, HaxeBlockStatement.class); } } diff --git a/src/main/java/com/intellij/plugins/haxe/ide/refactoring/HaxeRefactoringSupportProvider.java b/src/main/java/com/intellij/plugins/haxe/ide/refactoring/HaxeRefactoringSupportProvider.java index f18987ad9..8daf8c8a3 100644 --- a/src/main/java/com/intellij/plugins/haxe/ide/refactoring/HaxeRefactoringSupportProvider.java +++ b/src/main/java/com/intellij/plugins/haxe/ide/refactoring/HaxeRefactoringSupportProvider.java @@ -19,6 +19,7 @@ import com.intellij.lang.refactoring.RefactoringSupportProvider; import com.intellij.plugins.haxe.ide.refactoring.extractInterface.ExtractInterfaceHandler; +import com.intellij.plugins.haxe.ide.refactoring.extractMethod.HaxeExtractMethodHandler; import com.intellij.plugins.haxe.ide.refactoring.extractSuperclass.ExtractSuperclassHandler; import com.intellij.plugins.haxe.ide.refactoring.introduce.HaxeIntroduceVariableHandler; import com.intellij.plugins.haxe.ide.refactoring.introduceField.HaxeIntroduceConstantHandler; @@ -64,7 +65,7 @@ public RefactoringActionHandler getExtractClassHandler() { @Nullable @Override public RefactoringActionHandler getExtractMethodHandler() { - return super.getExtractMethodHandler(); + return new HaxeExtractMethodHandler(); } @Nullable diff --git a/src/main/java/com/intellij/plugins/haxe/ide/refactoring/extractMethod/ExtractMethodBuilder.java b/src/main/java/com/intellij/plugins/haxe/ide/refactoring/extractMethod/ExtractMethodBuilder.java new file mode 100644 index 000000000..93bf5011f --- /dev/null +++ b/src/main/java/com/intellij/plugins/haxe/ide/refactoring/extractMethod/ExtractMethodBuilder.java @@ -0,0 +1,326 @@ +package com.intellij.plugins.haxe.ide.refactoring.extractMethod; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.NlsSafe; +import com.intellij.openapi.util.TextRange; +import com.intellij.plugins.haxe.ide.refactoring.HaxeRefactoringUtil; +import com.intellij.plugins.haxe.lang.psi.*; +import com.intellij.plugins.haxe.model.type.HaxeExpressionEvaluator; +import com.intellij.plugins.haxe.model.type.ResultHolder; +import com.intellij.plugins.haxe.util.HaxeElementGenerator; +import com.intellij.plugins.haxe.util.HaxeNameSuggesterUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiParserFacade; +import com.intellij.psi.PsiReference; +import com.intellij.psi.search.PsiSearchHelper; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class ExtractMethodBuilder { + + private int startOffset; + private int endOffset; + + private String selectedText; + private List expressions; + private List assignExpressions; + private List assignExpressionsOutsideSelection; + private List referencesUsed; + private List referencesToParameters; + private List localVariables; + private Map localUsedOutside; + + private Map parametersMap; + public ExtractMethodBuilder expressions(List expressions) { + this.expressions = expressions; + return this; + } + public ExtractMethodBuilder startOffset(int offset) { + startOffset = offset; + return this; + } + public ExtractMethodBuilder endOffset(int offset) { + endOffset = offset; + return this; + } + + public ExtractMethodBuilder selectedText(@Nullable @NlsSafe String text) { + selectedText = text; + return this; + } + + + public void validateAndProcessExpressions() { + + assignExpressions = findAssignExpressions(); + assignExpressionsOutsideSelection = findAssignsOutsideSelection(assignExpressions); + + referencesUsed = findReferencesUsed(expressions, assignExpressions); + referencesToParameters = findOutsideReferencesForParameterList(referencesUsed); + + localVariables = findLocalVariables(); + localUsedOutside = findLocalVarsUsedOutside(localVariables); + + parametersMap = buildParameterList(referencesToParameters); + + // check if we can extract method + if (moreThanOneLocalVarReferencedOutsideSelection()) { + throw new UnableToExtractMethodException(); + } else if (localVarDeclarationPartiallyOutsideOfSelection()) { + throw new UnableToExtractMethodException(); + } else if (moreThanOneLocalVariableUsedOutsideSelection()) { + throw new UnableToExtractMethodException(); + } else if (requireMoreThanOneReturnValue()) { + throw new UnableToExtractMethodException(); + }else if (selectionContainsMethod()) { + throw new UnableToExtractMethodException(); + } + } + + private boolean localVarDeclarationPartiallyOutsideOfSelection() { + TextRange range = new TextRange(startOffset, endOffset); + for (HaxeLocalVarDeclaration variable : localVariables) { + if (variable.getParent() instanceof HaxeLocalVarDeclarationList list) { + if (!range.contains(list.getTextRange())) return true; + } + } + return false; + } + + private boolean requireMoreThanOneReturnValue() { + return assignExpressionsOutsideSelection.size() + localUsedOutside.size() > 1; + } + + private boolean moreThanOneLocalVariableUsedOutsideSelection() { + return localUsedOutside.size() > 1; + } + + private boolean moreThanOneLocalVarReferencedOutsideSelection() { + return assignExpressionsOutsideSelection.size() > 1; + } + + private List findAssignExpressions() { + List assignExpressions = new ArrayList<>(); + for (HaxePsiCompositeElement expression : expressions) { + if (expression instanceof HaxeAssignExpression assignExpression) assignExpressions.add(assignExpression); + assignExpressions.addAll(PsiTreeUtil.findChildrenOfType(expression, HaxeAssignExpression.class)); + } + return assignExpressions; + } + + private List findAssignsOutsideSelection(List assignExpressions) { + List outside = new ArrayList<>(); + for (HaxeAssignExpression expression : assignExpressions) { + HaxeExpression leftExpression = expression.getLeftExpression(); + if (leftExpression instanceof HaxeReferenceExpression referenceExpression) { + PsiElement resolve = referenceExpression.resolve(); + if (resolve == null) { + // todo error + break; + } + if (resolve instanceof HaxeLocalVarDeclaration) { + if (!resolve.getTextRange().intersects(startOffset, endOffset)) { + outside.add(referenceExpression); + } + } + } + } + return outside; + } + + + private List findReferencesUsed(List expressions, + List ignoreList) { + List referenceExpressions = new ArrayList<>(); + for (HaxePsiCompositeElement expression : expressions) { + //todo filter out members, we only need local variables + if (expression instanceof HaxeReferenceExpression haxeReferenceExpression) referenceExpressions.add(haxeReferenceExpression); + referenceExpressions.addAll(PsiTreeUtil.findChildrenOfType(expression, HaxeReferenceExpression.class)); + } + for (HaxeAssignExpression expression : ignoreList) { + referenceExpressions.remove(expression.getLeftExpression()); + } + return referenceExpressions; + } + + private List findOutsideReferencesForParameterList(List referencesUsed) { + List outside = new ArrayList<>(); + for (HaxeReferenceExpression referenceExpression : referencesUsed) { + PsiElement resolve = referenceExpression.resolve(); + if (resolve instanceof HaxeLocalVarDeclaration) { + if (!resolve.getTextRange().intersects(startOffset, endOffset)) { + outside.add(referenceExpression); + } + } + } + return outside; + } + + private List findLocalVariables() { + List localVarList = new ArrayList<>(); + for (HaxePsiCompositeElement expression : expressions) { + if (expression instanceof HaxeLocalVarDeclarationList varDeclarationList) { + localVarList.addAll(varDeclarationList.getLocalVarDeclarationList()); + } + else if (expression instanceof HaxeLocalVarDeclaration varDeclaration) { + localVarList.add(varDeclaration); + } + } + return localVarList; + } + + private Map findLocalVarsUsedOutside(List localVariables) { + Map outside = new HashMap<>(); + TextRange markedText = new TextRange(startOffset, endOffset); + for (HaxeLocalVarDeclaration varDeclaration : localVariables) { + HaxeComponentName componentName = varDeclaration.getComponentName(); + SearchScope useScope = PsiSearchHelper.getInstance(componentName.getProject()).getUseScope(componentName); + + + List references = new ArrayList<>(ReferencesSearch.search(componentName, useScope).findAll()); + for (PsiReference reference : references) { + TextRange range = reference.getElement().getTextRange(); + if (!markedText.contains(range)) { + outside.put(reference, varDeclaration); + } + } + } + return outside; + } + + private boolean selectionContainsMethod() { + for (HaxePsiCompositeElement expression : expressions) { + if (expression instanceof HaxeMethodDeclaration) { + return true; + } + } + return false; + } + + protected List getSuggestedNames(HaxePsiCompositeElement compositeElement) { + Set usedNames = HaxeRefactoringUtil.collectUsedNames(compositeElement); + List candidates = new ArrayList<>(); + if (compositeElement instanceof HaxeExpression expression) { + candidates.addAll(HaxeNameSuggesterUtil.getSuggestedNames(expression, false)); + }else { + candidates.add("extracted"); + } + + List result = new ArrayList<>(); + for (String candidate : candidates) { + int index = 0; + String suffix = ""; + while (usedNames.contains(candidate + suffix)) { + suffix = Integer.toString(++index); + } + result.add(candidate + suffix); + } + + return result; + } + + public HaxeMethodDeclaration buildExtractedMethod(@Nullable Project project) { + + + + HaxePsiCompositeElement fistElement = expressions.get(0); + String suggestedName = getSuggestedNames(fistElement).get(0); + + boolean firstStatementReturn = PsiTreeUtil.getParentOfType(fistElement, HaxeBinaryExpression.class) != null; + + String block = buildMethodContent(selectedText, firstStatementReturn); + String method = buildMethod(suggestedName, block, parametersMap); + + return HaxeElementGenerator.createMethodDeclaration(project, method); + } + + private String buildMethod(String suggestedName, String block, Map parameters) { + StringBuilder builder = new StringBuilder(); + builder.append("\nfunction "+suggestedName+" ("); + parameters.forEach((paramName, paramType) -> { + builder.append(paramName); + if (paramType != null && !paramType.isBlank()) { + builder.append(":").append(paramType); + } + }); + builder.append(")"); + builder.append(block); + builder.append("\n\n"); + return builder.toString(); + } + + private String buildMethodContent(String selectedText, boolean firstStatementReturn) { + if (firstStatementReturn) { + if (selectedText.trim().endsWith(";")) { + return "{\n return " + selectedText + "\n}\n"; + }else { + return "{\n return " + selectedText + ";\n}\n"; + } + }else { + return "{\n" + selectedText + "\n}\n"; + } + } + + private Map buildParameterList(List RefrencesForparameters) { + Map parameterNameTypeMap = new HashMap<>(); + for (HaxeReferenceExpression ref : RefrencesForparameters) { + ResultHolder type = HaxeExpressionEvaluator.evaluate(ref, null).result; + if (type.isUnknown()) { + parameterNameTypeMap.put(ref.getReferenceName(), null); + } + else { + parameterNameTypeMap.put(ref.getReferenceName(), type.toPresentationString()); + } + } + return parameterNameTypeMap; + } + + + + + + public PsiElement buildReplacementExpression(@Nullable Project project, HaxeMethodDeclaration methodDeclaration) { + + boolean containsReturnStatements = PsiTreeUtil.getChildOfType(methodDeclaration, HaxeReturnStatement.class) != null; + + String methodName = methodDeclaration.getComponentName().getText(); + String methodCall = methodName + "(" + String.join(", ", parametersMap.keySet()) + ")"; + + if (!assignExpressionsOutsideSelection.isEmpty()) { + // TODO find type of assign // TODO this is wrong check local vars used outside for return/assign + methodCall = assignExpressionsOutsideSelection.get(0).getText() + " = " + methodCall; + } + else if (!localUsedOutside.isEmpty() && !containsReturnStatements) { + Set references = localUsedOutside.keySet(); + String varName = localUsedOutside.get(references.iterator().next()).getText(); + methodCall = "var " + varName + " = " + methodCall; + + HaxeBlockStatement blockStatement = methodDeclaration.getBlockStatement(); + PsiElement newLine = PsiParserFacade.getInstance(project).createWhiteSpaceFromText("\n\n").copy(); + PsiElement returnStatement = HaxeElementGenerator.createStatementFromText(project, "return " + varName + ";").copy(); + List expressionList = blockStatement.getExpressionList(); + if (expressionList.isEmpty()) { + throw new UnableToExtractMethodException(); + } + PsiElement lastExpression = expressionList.get(expressionList.size() - 1); + if (lastExpression.getNextSibling().textMatches(";")) lastExpression = lastExpression.getNextSibling(); + blockStatement.addAfter(returnStatement, lastExpression); + blockStatement.addAfter(newLine, lastExpression); + //CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(blockStatement); + // TODO add return value + } + else if (!localUsedOutside.isEmpty()) { + // can not return value and assign as the method already got return statments + throw new UnableToExtractMethodException(); + } + + return HaxeElementGenerator.createStatementFromText(project, methodCall); + + } + + +} diff --git a/src/main/java/com/intellij/plugins/haxe/ide/refactoring/extractMethod/HaxeExtractMethodHandler.java b/src/main/java/com/intellij/plugins/haxe/ide/refactoring/extractMethod/HaxeExtractMethodHandler.java new file mode 100644 index 000000000..521518022 --- /dev/null +++ b/src/main/java/com/intellij/plugins/haxe/ide/refactoring/extractMethod/HaxeExtractMethodHandler.java @@ -0,0 +1,235 @@ +package com.intellij.plugins.haxe.ide.refactoring.extractMethod; + +import com.intellij.codeInsight.CodeInsightUtilCore; +import com.intellij.codeInsight.template.impl.TemplateManagerImpl; +import com.intellij.codeInsight.template.impl.TemplateState; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.plugins.haxe.ide.refactoring.introduce.HaxeIntroduceHandler; +import com.intellij.plugins.haxe.ide.refactoring.introduce.HaxeIntroduceOperation; +import com.intellij.plugins.haxe.lang.psi.*; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer; +import com.intellij.refactoring.util.CommonRefactoringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + + +public class HaxeExtractMethodHandler extends HaxeIntroduceHandler { + public HaxeExtractMethodHandler() { + super("Extract Method"); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) { + performAction(new HaxeIntroduceOperation(project, editor, file, null)); + } + + @Override + protected void performAction(HaxeIntroduceOperation operation) { + final PsiFile file = operation.getFile(); + if (!CommonRefactoringUtil.checkReadOnlyStatus(file)) { + return; + } + final Editor editor = operation.getEditor(); + final Project project = editor.getProject(); + if (editor.getSettings().isVariableInplaceRenameEnabled()) { + final TemplateState templateState = TemplateManagerImpl.getTemplateState(operation.getEditor()); + if (templateState != null && !templateState.isFinished()) { + return; + } + } + + + PsiElement startElement = null; + PsiElement stopElement = null; + + final SelectionModel selectionModel = editor.getSelectionModel(); + + final int selectionStart = selectionModel.getSelectionStart(); + final int selectionEnd = selectionModel.getSelectionEnd(); + + if (selectionModel.hasSelection()) { + startElement = getOuterMostParentWithSameTextRangeStart(file.findElementAt(selectionStart)); + stopElement = file.findElementAt(selectionEnd); + } + + startElement = findStartElement(startElement); + stopElement = findEndElement(stopElement, selectionStart); + + + if (startElement == null || stopElement == null) { + showCannotPerformError(project, editor); + return; + } + + int startOffset = startElement.getTextRange().getStartOffset(); + int endOffset = stopElement.getTextRange().getEndOffset(); + + List expressions = collectExpressions((HaxePsiCompositeElement)startElement, (HaxePsiCompositeElement)stopElement); + + ExtractMethodBuilder methodBuilder = new ExtractMethodBuilder() + .startOffset(startOffset) + .endOffset(endOffset) + .selectedText(selectionModel.getSelectedText()) + .expressions(expressions); + + try { + methodBuilder.validateAndProcessExpressions(); + + + HaxeMethodDeclaration methodDeclaration = methodBuilder.buildExtractedMethod(project); + PsiElement replacementExpression = methodBuilder.buildReplacementExpression(project, methodDeclaration); + + + Document document = editor.getDocument(); + HaxeMethodDeclaration parentMethod = PsiTreeUtil.getParentOfType(startElement, HaxeMethodDeclaration.class); + if (parentMethod != null) { + + WriteCommandAction.writeCommandAction(project, parentMethod.getContainingFile()) + .withGroupId("Extract method") + .compute(() -> + { + + parentMethod.getParent().addAfter(methodDeclaration, parentMethod); + + CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(parentMethod); + + PsiDocumentManager instance = PsiDocumentManager.getInstance(project); + instance.doPostponedOperationsAndUnblockDocument(document); + instance.commitDocument(document); + + int number = document.getLineNumber(selectionEnd); + String documentText = document.getText(new TextRange(selectionEnd, document.getLineEndOffset(number))); + + if (documentText.trim().startsWith(";")) { + document.replaceString(selectionStart, selectionEnd, replacementExpression.getText()); + } else { + document.replaceString(selectionStart, selectionEnd, replacementExpression.getText() + ";"); + } + + + TextRange textRange = new TextRange(selectionStart, selectionStart + replacementExpression.getTextLength()); + editor.getSelectionModel().setSelection(selectionStart, selectionStart + replacementExpression.getTextLength()); + + CodeStyleManager.getInstance(project).reformatText(file, List.of(textRange)); + instance.commitDocument(document); + return null; + }); + + startRenameMethod(operation, editor, file); + } + }catch (UnableToExtractMethodException extractMethodException) { + // TODO better Error messages + showCannotPerformError(project, editor); + } + + } + + private static void startRenameMethod(HaxeIntroduceOperation operation, Editor editor, PsiFile file) { + int newSelectionEnd = editor.getSelectionModel().getSelectionEnd(); + + PsiElement element = file.findElementAt(newSelectionEnd); + while (element != null && !(element instanceof HaxePsiCompositeElement)) element = element.getPrevSibling(); + HaxeCallExpression newCallExpression = element instanceof HaxeCallExpression haxeCallExpression + ? haxeCallExpression + : PsiTreeUtil.getParentOfType(element, HaxeCallExpression.class); + if (newCallExpression == null) newCallExpression = PsiTreeUtil.findChildOfType(element, HaxeCallExpression.class); + if (newCallExpression != null && newCallExpression.getExpression() instanceof HaxeReferenceExpression referenceExpression) { + operation.getEditor().getCaretModel().moveToOffset(newCallExpression.getExpression().getTextRange().getEndOffset()); + PsiElement resolve = referenceExpression.resolve(); + if (resolve instanceof HaxeMethodDeclaration method) { + + final InplaceVariableIntroducer introducer = + new HaxeInplaceVariableIntroducer(method.getComponentName(), operation, + List.of(method.getComponentName())); + + introducer.performInplaceRefactoring(new LinkedHashSet<>(Optional.ofNullable(operation.getSuggestedNames()).orElse(List.of()))); + } + } + } + + @Nullable + private static PsiElement findEndElement(PsiElement stopElement, int selectionStart) { + while (stopElement != null && !(stopElement instanceof HaxePsiCompositeElement)) { + PsiElement sibling = stopElement.getPrevSibling(); + if (sibling == null) break; + // too wide, find child + if (sibling.getTextRange().getStartOffset() < selectionStart) { + sibling = sibling.getLastChild(); + } + stopElement = sibling; + } + return stopElement; + } + + @Nullable + private static PsiElement findStartElement(PsiElement startElement) { + while (startElement != null + && !( startElement instanceof HaxeLocalVarDeclarationList) + && !(startElement instanceof HaxeExpression) + && !( startElement instanceof HaxePsiField)) { + PsiElement sibling = startElement.getNextSibling(); + if (sibling == null) break; + startElement = sibling; + } + return startElement; + } + + private PsiElement getOuterMostParentWithSameTextRangeStart(PsiElement element) { + if (element == null) return null; + TextRange range = element.getTextRange(); + int startOffset = range.getStartOffset(); + //int endOffset = range.getEndOffset(); + PsiElement outerMost = element; + while (outerMost != null) { + PsiElement parent = outerMost.getParent(); + if (parent != null + && parent.getTextRange().getStartOffset() == startOffset + //&& parent.getTextRange().getEndOffset() == endOffset + ) { + outerMost = parent; + }else { + break; + } + } + return outerMost; + } + + + + + private List collectExpressions(HaxePsiCompositeElement start, HaxePsiCompositeElement stop) { + TextRange stopRange = stop.getTextRange(); + int endOffset = stopRange.getEndOffset(); + List expressions = new ArrayList<>(); + + expressions.add(start); + HaxeExpression next = PsiTreeUtil.getNextSiblingOfType(start, HaxeExpression.class); + while (next != null) { + if (next == stop) break; + if (next.getTextRange().getEndOffset() > endOffset) break;// TODO error + expressions.add(next); + next = PsiTreeUtil.getNextSiblingOfType(next, HaxeExpression.class); + } + expressions.add(stop); + + return expressions; + } + + @Override + protected @Nullable PsiElement addDeclaration(@NotNull PsiElement expression, + @NotNull PsiElement declaration, + @NotNull HaxeIntroduceOperation operation) { + return null; + } +} diff --git a/src/main/java/com/intellij/plugins/haxe/ide/refactoring/extractMethod/UnableToExtractMethodException.java b/src/main/java/com/intellij/plugins/haxe/ide/refactoring/extractMethod/UnableToExtractMethodException.java new file mode 100644 index 000000000..fd082ca8e --- /dev/null +++ b/src/main/java/com/intellij/plugins/haxe/ide/refactoring/extractMethod/UnableToExtractMethodException.java @@ -0,0 +1,4 @@ +package com.intellij.plugins.haxe.ide.refactoring.extractMethod; + +public class UnableToExtractMethodException extends RuntimeException{ +} diff --git a/src/main/java/com/intellij/plugins/haxe/ide/refactoring/introduce/HaxeIntroduceHandler.java b/src/main/java/com/intellij/plugins/haxe/ide/refactoring/introduce/HaxeIntroduceHandler.java index 2772d3d32..be8ca5d3c 100644 --- a/src/main/java/com/intellij/plugins/haxe/ide/refactoring/introduce/HaxeIntroduceHandler.java +++ b/src/main/java/com/intellij/plugins/haxe/ide/refactoring/introduce/HaxeIntroduceHandler.java @@ -194,7 +194,7 @@ protected boolean checkIntroduceContext(PsiFile file, Editor editor, PsiElement return true; } - private void showCannotPerformError(Project project, Editor editor) { + protected void showCannotPerformError(Project project, Editor editor) { CommonRefactoringUtil.showErrorHint( project, editor, @@ -592,7 +592,7 @@ protected abstract PsiElement addDeclaration(@NotNull final PsiElement expressio @NotNull HaxeIntroduceOperation operation); - private static class HaxeInplaceVariableIntroducer extends InplaceVariableIntroducer { + protected static class HaxeInplaceVariableIntroducer extends InplaceVariableIntroducer { private final HaxeComponentName myTarget; public HaxeInplaceVariableIntroducer(HaxeComponentName target, diff --git a/src/main/java/com/intellij/plugins/haxe/model/type/HaxeExpressionEvaluator.java b/src/main/java/com/intellij/plugins/haxe/model/type/HaxeExpressionEvaluator.java index c231bacd8..f31073c16 100644 --- a/src/main/java/com/intellij/plugins/haxe/model/type/HaxeExpressionEvaluator.java +++ b/src/main/java/com/intellij/plugins/haxe/model/type/HaxeExpressionEvaluator.java @@ -59,6 +59,13 @@ public class HaxeExpressionEvaluator { static { log.setLevel(LogLevel.INFO); } + @NotNull + static public HaxeExpressionEvaluatorContext evaluate(PsiElement element, HaxeGenericResolver resolver) { + ProgressIndicatorProvider.checkCanceled(); + HaxeExpressionEvaluatorContext context = new HaxeExpressionEvaluatorContext(element); + context.result = handle(element, context, resolver); + return context; + } @NotNull static public HaxeExpressionEvaluatorContext evaluate(PsiElement element, HaxeExpressionEvaluatorContext context, HaxeGenericResolver resolver) { diff --git a/src/main/java/com/intellij/plugins/haxe/util/HaxeElementGenerator.java b/src/main/java/com/intellij/plugins/haxe/util/HaxeElementGenerator.java index 47368dd08..ebf5b46a3 100644 --- a/src/main/java/com/intellij/plugins/haxe/util/HaxeElementGenerator.java +++ b/src/main/java/com/intellij/plugins/haxe/util/HaxeElementGenerator.java @@ -19,7 +19,9 @@ */ package com.intellij.plugins.haxe.util; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; import com.intellij.plugins.haxe.HaxeFileType; import com.intellij.plugins.haxe.HaxeLanguage; import com.intellij.plugins.haxe.lang.psi.*; @@ -27,6 +29,8 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileFactory; +import com.intellij.psi.PsiMember; +import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.impl.PsiFileFactoryImpl; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.testFramework.LightVirtualFile; @@ -170,6 +174,17 @@ public static HaxeMethodDeclaration createMethodDeclaration(Project myProject, final HaxeModule haxeModule = PsiTreeUtil.getChildOfType(dummyFile, HaxeModule.class); final HaxeClass haxeClass = PsiTreeUtil.getChildOfType(haxeModule, HaxeClass.class); assert haxeClass != null; + reformat(haxeClass); return (HaxeMethodDeclaration)haxeClass.getHaxeMethodsSelf(null).iterator().next(); } + + private static void reformat(final PsiMember movedElement) { + ApplicationManager.getApplication().runWriteAction(() -> { + final TextRange range = movedElement.getTextRange(); + final PsiFile file = movedElement.getContainingFile(); + final PsiFile baseFile = file.getViewProvider().getPsi(file.getViewProvider().getBaseLanguage()); + CodeStyleManager.getInstance(movedElement.getProject()).reformatText(baseFile, range.getStartOffset(), range.getEndOffset()); + }); + } + }