IDEA-19061 Integrate the Rearranger-plugin into core-IDEA

Switching old 'members order' to the arrangement engine
This commit is contained in:
Denis.Zhdanov
2012-09-06 13:21:41 +04:00
parent 1ed38b0a6c
commit c7d351933b
11 changed files with 165 additions and 85 deletions
@@ -48,11 +48,11 @@ public class JavaArrangementVisitor extends JavaElementVisitor {
private final Stack<JavaElementArrangementEntry> myStack = new Stack<JavaElementArrangementEntry>();
@NotNull private final List<JavaElementArrangementEntry> myRootEntries;
@NotNull private Document myDocument;
@NotNull private Collection<TextRange> myRanges;
@Nullable private Document myDocument;
public JavaArrangementVisitor(@NotNull List<JavaElementArrangementEntry> entries,
@NotNull Document document,
@Nullable Document document,
@NotNull Collection<TextRange> ranges)
{
myRootEntries = entries;
@@ -78,7 +78,7 @@ public class JavaArrangementVisitor extends JavaElementVisitor {
JavaElementArrangementEntry entry = createNewEntry(aClass.getTextRange(), ArrangementEntryType.CLASS, aClass.getName(), false);
processEntry(entry, null, aClass);
}
@Override
public void visitJavaFile(PsiJavaFile file) {
for (PsiClass psiClass : file.getClasses()) {
@@ -162,9 +162,9 @@ public class JavaArrangementVisitor extends JavaElementVisitor {
DefaultArrangementEntry current = getCurrent();
JavaElementArrangementEntry entry;
if (canArrange) {
TextRange expandedRange = ArrangementUtil.expandToLine(range, myDocument.getCharsSequence());
TextRange expandedRange = myDocument == null ? null : ArrangementUtil.expandToLine(range, myDocument.getCharsSequence());
TextRange rangeToUse = expandedRange == null ? range : expandedRange;
entry = new JavaElementArrangementEntry(current, rangeToUse, type, name, expandedRange != null);
entry = new JavaElementArrangementEntry(current, rangeToUse, type, name, myDocument == null || expandedRange != null);
}
else {
entry = new JavaElementArrangementEntry(current, range, type, name, false);
@@ -114,11 +114,19 @@ public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>,
return result;
}
@Nullable
@Override
public JavaElementArrangementEntry wrap(@NotNull PsiElement element) {
List<JavaElementArrangementEntry> result = new ArrayList<JavaElementArrangementEntry>();
element.accept(new JavaArrangementVisitor(result, null, Collections.singleton(element.getTextRange())));
return result.size() == 1 ? result.get(0) : null;
}
@NotNull
@Override
public Collection<JavaElementArrangementEntry> parse(@NotNull PsiElement root,
@NotNull Document document,
@NotNull Collection<TextRange> ranges)
public List<JavaElementArrangementEntry> parse(@NotNull PsiElement root,
@Nullable Document document,
@NotNull Collection<TextRange> ranges)
{
// Following entries are subject to arrangement: class, interface, field, method.
List<JavaElementArrangementEntry> result = new ArrayList<JavaElementArrangementEntry>();
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@ import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.JavaTemplateUtil;
import com.intellij.lang.ASTNode;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.module.Module;
@@ -32,6 +34,7 @@ import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.arrangement.MemberOrderService;
import com.intellij.psi.impl.compiled.ClsClassImpl;
import com.intellij.psi.impl.source.codeStyle.ImportHelper;
import com.intellij.psi.search.GlobalSearchScope;
@@ -174,43 +177,37 @@ public class JavaPsiImplementationHelperImpl extends JavaPsiImplementationHelper
return importHelper.getDefaultAnchor(list, statement);
}
@Nullable
@Override
public PsiElement getDefaultMemberAnchor(PsiClass aClass, PsiMember member) {
public PsiElement getDefaultMemberAnchor(@NotNull PsiClass aClass, @NotNull PsiMember member) {
CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(aClass.getProject());
int order = getMemberOrderWeight(member, settings);
if (order < 0) return null;
PsiElement lastMember = null;
for (PsiElement child = aClass.getFirstChild(); child != null; child = child.getNextSibling()) {
int order1 = getMemberOrderWeight(child, settings);
if (order1 < 0) continue;
if (order1 > order) {
if (lastMember != null) {
PsiElement nextSibling = lastMember.getNextSibling();
while (nextSibling instanceof PsiJavaToken && (nextSibling.getText().equals(",") || nextSibling.getText().equals(";"))) {
nextSibling = nextSibling.getNextSibling();
}
return nextSibling == null ? aClass.getLBrace().getNextSibling() : nextSibling;
}
else {
// The main idea is to avoid to anchor to 'white space' element because that causes reformatting algorithm
// to perform incorrectly. The algorithm is encapsulated at PostprocessReformattingAspect.doPostponedFormattingInner().
final PsiElement lBrace = aClass.getLBrace();
if (lBrace != null) {
PsiElement result = lBrace.getNextSibling();
while (result instanceof PsiWhiteSpace) {
result = result.getNextSibling();
}
return result;
}
}
MemberOrderService service = ServiceManager.getService(MemberOrderService.class);
PsiElement anchor = service.getAnchor(member, settings.getCommonSettings(JavaLanguage.INSTANCE), aClass);
if (anchor != null && anchor != aClass) {
while (anchor instanceof PsiJavaToken && (anchor.getText().equals(",") || anchor.getText().equals(";"))) {
anchor = anchor.getNextSibling();
}
if (anchor != null) {
return anchor;
}
lastMember = child;
}
// The main idea is to avoid to anchor to 'white space' element because that causes reformatting algorithm
// to perform incorrectly. The algorithm is encapsulated at PostprocessReformattingAspect.doPostponedFormattingInner().
final PsiElement lBrace = aClass.getLBrace();
if (lBrace != null) {
PsiElement result = lBrace.getNextSibling();
while (result instanceof PsiWhiteSpace) {
result = result.getNextSibling();
}
return result;
}
return aClass.getRBrace();
}
// TODO remove as soon as arrangement sub-system is provided for groovy.
public static int getMemberOrderWeight(PsiElement member, CodeStyleSettings settings) {
if (member instanceof PsiField) {
if (member instanceof PsiEnumConstant) {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.JavaPsiImplementationHelper;
import org.jetbrains.annotations.NotNull;
/**
* @author yole
@@ -46,7 +47,7 @@ public class CoreJavaPsiImplementationHelper extends JavaPsiImplementationHelper
}
@Override
public PsiElement getDefaultMemberAnchor(PsiClass psiClass, PsiMember firstPsi) {
public PsiElement getDefaultMemberAnchor(@NotNull PsiClass psiClass, @NotNull PsiMember firstPsi) {
throw new UnsupportedOperationException("TODO");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -46,7 +47,8 @@ public abstract class JavaPsiImplementationHelper {
public abstract ASTNode getDefaultImportAnchor(PsiImportList list, PsiImportStatementBase statement);
public abstract PsiElement getDefaultMemberAnchor(PsiClass psiClass, PsiMember firstPsi);
@Nullable
public abstract PsiElement getDefaultMemberAnchor(@NotNull PsiClass psiClass, @NotNull PsiMember firstPsi);
public abstract void setupCatchBlock(String exceptionName, PsiElement context, PsiCatchSection element);
}
@@ -19,7 +19,7 @@ import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryMatcher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
/**
* Represents a processing unit during 'rearrangement' operation. I.e. entry's position can be changed during the processing.
@@ -54,7 +54,7 @@ public interface ArrangementEntry {
* @see #getParent()
*/
@NotNull
Collection<? extends ArrangementEntry> getChildren();
List<? extends ArrangementEntry> getChildren();
/**
* @return start offset of the current entry (inclusive) within the target document. Rearranger engine uses this information
@@ -19,7 +19,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
@@ -51,7 +50,7 @@ public class DefaultArrangementEntry implements ArrangementEntry {
@NotNull
@Override
public Collection<? extends ArrangementEntry> getChildren() {
public List<? extends ArrangementEntry> getChildren() {
return myChildren;
}
@@ -24,6 +24,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
/**
* Encapsulates language-specific rearrangement logic.
@@ -38,16 +39,29 @@ public interface Rearranger<E extends ArrangementEntry> {
LanguageExtension<Rearranger<?>> EXTENSION = new LanguageExtension<Rearranger<?>>("com.intellij.lang.rearranger");
/**
* Tries to wrap given element to the corresponding arrangement entry.
* <p/>
* This is useful in a situation when new element is generated and we're deciding where to insert it (e.g. new field is
* generated and we want to insert it according to the arrangement rules like 'fields before methods').
*
* @param element element to wrap into format eligible for further processing by arrangement engine
* @return arrangement entry for the given element if it's possible to perform the mapping;
* <code>null</code> otherwise
*/
@Nullable
E wrap(@NotNull PsiElement element);
/**
* Allows to build rearranger-interested data for the given element.
*
* @param root root element which children should be parsed for the rearrangement
* @param document document which corresponds to the target PSI tree
* @param ranges target offsets ranges to use for filtering given root's children
* @return given root's children which are subject for further rearrangement
* @return given root's children which are subject for further rearrangement
*/
@NotNull
Collection<E> parse(@NotNull PsiElement root, @NotNull Document document, @NotNull Collection<TextRange> ranges);
List<E> parse(@NotNull PsiElement root, @Nullable Document document, @NotNull Collection<TextRange> ranges);
/**
* Allows to answer how many blank lines should be inserted before the target arrangement entry which position is changed.
@@ -93,7 +93,7 @@ class DocumentFoldingInfo implements JDOMExternalizable, CodeFoldingState {
}
}
void setToEditor(@NotNull Editor editor) {
void setToEditor(@NotNull final Editor editor) {
ApplicationManager.getApplication().assertIsDispatchThread();
final PsiManager psiManager = PsiManager.getInstance(myProject);
if (psiManager.isDisposed()) return;
@@ -15,11 +15,19 @@
*/
package com.intellij.psi.codeStyle.arrangement;
import com.intellij.lang.Language;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.codeStyle.arrangement.engine.ArrangementEngine;
import com.intellij.psi.codeStyle.arrangement.settings.ArrangementStandardSettingsAware;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* The whole arrangement idea is to allow to change file entries order according to the user-provided rules.
* <p/>
@@ -33,25 +41,57 @@ import org.jetbrains.annotations.Nullable;
*/
public class MemberOrderService {
public static final int UNDEFINED_WEIGHT = -1;
/**
* Allows to get a given member's weight according to the
* Tries to find an element at the given context which should be the previous sibling for the given 'member'element according to the
* {@link CommonCodeStyleSettings#getArrangementRules() user-defined arrangement rules}.
* <p/>
* That means that we can call this method for different members and derive their relative order by comparing their weights.
* E.g. the IDE might generate given 'member' element and wants to know element after which it should be inserted
*
* @param member target member which weight should be calculated
* @param member target member which anchor should be calculated
* @param settings code style settings to use
* @param context given member's context (if any). Is useful when we have, say, an existing class and want to know where
* to insert a new field. Not only 'by type' filtering might be exploited (like 'fields before methods') but
* 'by name' as well, i.e. we want to insert a new field to the 'fidles' group and define its position
* according to the lexicographical fields order
* @return given member's weight if the one can be computed;
* {@link #UNDEFINED_WEIGHT} otherwise
* @param context given member's context
* @return given member's anchor if the one can be computed;
* given 'context' element if given member should be the first child
* <code>null</code> otherwise
*/
public int getMemberOrderWeight(@NotNull PsiElement member, @NotNull CommonCodeStyleSettings settings, @Nullable PsiElement context) {
// TODO den implement
return UNDEFINED_WEIGHT;
@SuppressWarnings("MethodMayBeStatic")
@Nullable
public PsiElement getAnchor(@NotNull PsiElement member, @NotNull CommonCodeStyleSettings settings, @NotNull PsiElement context) {
Language language = context.getLanguage();
Rearranger<?> rearranger = Rearranger.EXTENSION.forLanguage(language);
if (rearranger == null) {
return null;
}
List<? extends ArrangementRule> rules = settings.getArrangementRules();
if (rules.isEmpty() && rearranger instanceof ArrangementStandardSettingsAware) {
rules = ((ArrangementStandardSettingsAware)rearranger).getDefaultRules();
}
if (rules == null) {
return null;
}
ArrangementEntry memberEntry = rearranger.wrap(member);
if (memberEntry == null) {
return null;
}
List<? extends ArrangementEntry> entries = rearranger.parse(context, null, Collections.singleton(context.getTextRange()));
if (entries.isEmpty()) {
return null;
}
ArrangementEntry parentEntry = entries.get(0);
List<ArrangementEntry> entriesWithNew = new ArrayList<ArrangementEntry>(parentEntry.getChildren());
entriesWithNew.add(memberEntry);
List<ArrangementEntry> arranged = ArrangementEngine.arrange(entriesWithNew, rules);
int i = arranged.indexOf(memberEntry);
if (i <= 0) {
return context;
}
ArrangementEntry anchorEntry = arranged.get(i - 1);
return context.findElementAt(anchorEntry.getEndOffset() - context.getTextRange().getStartOffset());
}
}
@@ -192,29 +192,52 @@ public class ArrangementEngine {
}
}
@SuppressWarnings("unchecked")
private static <E extends ArrangementEntry> void doArrange(@NotNull List<ArrangementEntryWrapper<E>> entries,
@NotNull Context<E> context)
/**
* Arranges (re-orders) given entries according to the given rules.
*
* @param entries entries to arrange
* @param rules rules to use for arrangement
* @param <E> arrangement entry type
* @return arranged list of the given rules
*/
@NotNull
public static <E extends ArrangementEntry> List<E> arrange(@NotNull Collection<E> entries,
@NotNull List<? extends ArrangementRule> rules)
{
List<ArrangementEntryWrapper<E>> arranged = new ArrayList<ArrangementEntryWrapper<E>>();
Set<ArrangementEntryWrapper<E>> unprocessed = new LinkedHashSet<ArrangementEntryWrapper<E>>(entries);
List<E> arranged = new ArrayList<E>();
Set<E> unprocessed = new LinkedHashSet<E>(entries);
for (ArrangementRule rule : context.rules) {
for (ArrangementEntryWrapper<E> wrapper : entries) {
if (wrapper.getEntry().canBeMatched() && unprocessed.contains(wrapper) && rule.getMatcher().isMatched(wrapper.getEntry())) {
arranged.add(wrapper);
unprocessed.remove(wrapper);
for (ArrangementRule rule : rules) {
for (E entry : entries) {
if (entry.canBeMatched() && unprocessed.contains(entry) && rule.getMatcher().isMatched(entry)) {
arranged.add(entry);
unprocessed.remove(entry);
}
}
}
arranged.addAll(unprocessed);
context.prepare(arranged);
return arranged;
}
@SuppressWarnings("unchecked")
private static <E extends ArrangementEntry> void doArrange(@NotNull List<ArrangementEntryWrapper<E>> wrappers,
@NotNull Context<E> context) {
if (wrappers.isEmpty()) {
return;
}
Map<E, ArrangementEntryWrapper<E>> map = new LinkedHashMap<E, ArrangementEntryWrapper<E>>();
for (ArrangementEntryWrapper<E> wrapper : wrappers) {
map.put(wrapper.getEntry(), wrapper);
}
List<E> arranged = arrange(map.keySet(), context.rules);
context.prepare(wrappers.get(0).getParent());
// We apply changes from the last position to the first position in order not to bother with offsets shifts.
for (int i = arranged.size() - 1; i >= 0; i--) {
ArrangementEntryWrapper<E> arrangedWrapper = arranged.get(i);
ArrangementEntryWrapper<E> initialWrapper = entries.get(i);
context.replace(arrangedWrapper, initialWrapper, i > 0 ? arranged.get(i - 1) : null);
ArrangementEntryWrapper<E> arrangedWrapper = map.get(arranged.get(i));
ArrangementEntryWrapper<E> initialWrapper = wrappers.get(i);
context.replace(arrangedWrapper, initialWrapper, i > 0 ? map.get(arranged.get(i - 1)) : null);
}
}
@@ -264,11 +287,7 @@ public class ArrangementEngine {
return new Context<T>(rearranger, wrappers, document, rules, settings);
}
public void prepare(@NotNull List<ArrangementEntryWrapper<E>> arrangedEntries) {
if (arrangedEntries.isEmpty()) {
return;
}
ArrangementEntryWrapper<E> parent = arrangedEntries.get(0).getParent();
public void prepare(@Nullable ArrangementEntryWrapper<E> parent) {
if (parent == null) {
myParentText = document.getText();
myParentShift = 0;