visual appeal of dataflow grouped values

This commit is contained in:
Alexey Kudravtsev
2012-10-18 19:04:06 +04:00
parent 96e707004b
commit 8cdc222c5e
8 changed files with 97 additions and 49 deletions
@@ -22,11 +22,11 @@ import com.intellij.util.PlatformIcons;
/**
* @author cdr
*/
public class AnalyzeLeavesAction extends AnAction {
class GroupByLeavesAction extends AnAction {
private final SliceTreeBuilder myTreeBuilder;
private static final String TEXT = "Group by leaf expression";
public AnalyzeLeavesAction(SliceTreeBuilder treeBuilder) {
public GroupByLeavesAction(SliceTreeBuilder treeBuilder) {
super(TEXT, "Show original expression values that might appear in this place", PlatformIcons.XML_TAG_ICON);
myTreeBuilder = treeBuilder;
}
@@ -38,13 +38,11 @@ public class AnalyzeLeavesAction extends AnAction {
}
private boolean isAvailabale() {
if (myTreeBuilder.analysisInProgress) return false;
return !myTreeBuilder.splitByLeafExpressions;
return !myTreeBuilder.analysisInProgress && !myTreeBuilder.splitByLeafExpressions;
}
@Override
public void actionPerformed(AnActionEvent e) {
myTreeBuilder.switchToSplittedNodes(myTreeBuilder.getTreeStructure());
myTreeBuilder.switchToGroupedByLeavesNodes();
}
}
@@ -97,15 +97,19 @@ public class SliceLeafAnalyzer {
return filtered;
}
private static void groupByValues(Collection<PsiElement> leaves, SliceRootNode oldRoot, final Map<SliceNode, Collection<PsiElement>> map) {
private static void groupByValues(@NotNull Collection<PsiElement> leaves,
@NotNull SliceRootNode oldRoot,
@NotNull Map<SliceNode, Collection<PsiElement>> map) {
assert oldRoot.myCachedChildren.size() == 1;
SliceRootNode root = createTreeGroupedByValues(leaves, oldRoot, map);
SliceNode oldRootStart = oldRoot.myCachedChildren.get(0);
SliceUsage rootUsage = oldRootStart.getValue();
SliceManager.getInstance(root.getProject()).createToolWindow(true, root, true, SliceManager.getElementDescription(null, rootUsage.getElement(), " Grouped by Value") );
String description = SliceManager.getElementDescription(null, rootUsage.getElement(), " (grouped by value)");
SliceManager.getInstance(root.getProject()).createToolWindow(true, root, true, description);
}
@NotNull
public static SliceRootNode createTreeGroupedByValues(Collection<PsiElement> leaves, SliceRootNode oldRoot, final Map<SliceNode, Collection<PsiElement>> map) {
SliceNode oldRootStart = oldRoot.myCachedChildren.get(0);
SliceRootNode root = oldRoot.copy();
@@ -139,7 +143,7 @@ public class SliceLeafAnalyzer {
return root;
}
public static void startAnalyzeValues(final AbstractTreeStructure treeStructure, final Runnable finish) {
public static void startAnalyzeValues(@NotNull final AbstractTreeStructure treeStructure, @NotNull final Runnable finish) {
final SliceRootNode root = (SliceRootNode)treeStructure.getRootElement();
final Ref<Collection<PsiElement>> leafExpressions = Ref.create(null);
@@ -175,7 +179,6 @@ public class SliceLeafAnalyzer {
}
}
});
}
public static Map<SliceNode, Collection<PsiElement>> createMap() {
@@ -233,8 +236,9 @@ public class SliceLeafAnalyzer {
}
@NotNull
public static Collection<PsiElement> calcLeafExpressions(@NotNull final SliceNode root, AbstractTreeStructure treeStructure,
final Map<SliceNode, Collection<PsiElement>> map) {
public static Collection<PsiElement> calcLeafExpressions(@NotNull final SliceNode root,
@NotNull AbstractTreeStructure treeStructure,
@NotNull final Map<SliceNode, Collection<PsiElement>> map) {
final SliceNodeGuide guide = new SliceNodeGuide(treeStructure);
WalkingState<SliceNode> walkingState = new WalkingState<SliceNode>(guide) {
@Override
@@ -18,13 +18,17 @@ package com.intellij.slicer;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usages.ChunkExtractor;
import com.intellij.usages.TextChunk;
import com.intellij.usages.Usage;
import com.intellij.usages.UsageInfo2UsageAdapter;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -82,11 +86,23 @@ public class SliceLeafValueRootNode extends SliceNode implements MyColoredTreeCe
renderer.append(UsageViewBundle.message("node.invalid") + " ", SliceUsageCellRenderer.ourInvalidAttributes);
}
else {
renderer.append(element.getText(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
appendElementText((UsageInfo2UsageAdapter)usage, element, renderer);
}
}
else {
renderer.append("Other", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
}
}
private static void appendElementText(@NotNull UsageInfo2UsageAdapter usage,
@NotNull final PsiElement element,
@NotNull final SliceUsageCellRenderer renderer) {
PsiFile file = element.getContainingFile();
List<TextChunk> result = new ArrayList<TextChunk>();
ChunkExtractor.getExtractor(element.getContainingFile()).createTextChunks(usage, file.getText(), element.getTextRange().getStartOffset(), element.getTextRange().getEndOffset(), result);
for (TextChunk chunk : result) {
renderer.append(chunk.getText(), SimpleTextAttributes.fromTextAttributes(chunk.getAttributes()));
}
}
}
@@ -43,6 +43,7 @@ import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
@@ -84,7 +85,11 @@ public abstract class SlicePanel extends JPanel implements TypeSafeDataProvider,
private boolean isDisposed;
private final ToolWindow myToolWindow;
public SlicePanel(final Project project, boolean dataFlowToThis, SliceNode rootNode, boolean splitByLeafExpressions, final ToolWindow toolWindow) {
public SlicePanel(@NotNull final Project project,
boolean dataFlowToThis,
@NotNull SliceNode rootNode,
boolean splitByLeafExpressions,
@NotNull final ToolWindow toolWindow) {
super(new BorderLayout());
myToolWindow = toolWindow;
final ToolWindowManagerListener listener = new ToolWindowManagerListener() {
@@ -181,6 +186,7 @@ public abstract class SlicePanel extends JPanel implements TypeSafeDataProvider,
ToolTipManager.sharedInstance().unregisterComponent(myTree);
}
@NotNull
private JTree createTree() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode();
final Tree tree = new Tree(new DefaultTreeModel(root)){
@@ -282,6 +288,7 @@ public abstract class SlicePanel extends JPanel implements TypeSafeDataProvider,
return null;
}
@Nullable
private List<UsageInfo> getSelectedUsageInfos() {
TreePath[] paths = myTree.getSelectionPaths();
if (paths == null) return null;
@@ -327,26 +334,31 @@ public abstract class SlicePanel extends JPanel implements TypeSafeDataProvider,
return navigatables;
}
@NotNull
private ActionToolbar createToolbar() {
final DefaultActionGroup actionGroup = new DefaultActionGroup();
actionGroup.add(new MyRefreshAction(myTree));
actionGroup.add(myAutoScrollToSourceHandler.createToggleAction());
if (isToShowAutoScrollButton()) {
actionGroup.add(myAutoScrollToSourceHandler.createToggleAction());
}
actionGroup.add(new CloseAction());
actionGroup.add(new ToggleAction(UsageViewBundle.message("preview.usages.action.text"), "preview", AllIcons.Actions.Preview) {
@Override
public boolean isSelected(AnActionEvent e) {
return isPreview();
}
if (isToShowPreviewButton()) {
actionGroup.add(new ToggleAction(UsageViewBundle.message("preview.usages.action.text"), "preview", AllIcons.Actions.Preview) {
@Override
public boolean isSelected(AnActionEvent e) {
return isPreview();
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
setPreview(state);
layoutPanel();
}
});
@Override
public void setSelected(AnActionEvent e, boolean state) {
setPreview(state);
layoutPanel();
}
});
}
if (myBuilder.dataFlowToThis) {
actionGroup.add(new AnalyzeLeavesAction(myBuilder));
actionGroup.add(new GroupByLeavesAction(myBuilder));
actionGroup.add(new CanItBeNullAction(myBuilder));
}
@@ -355,10 +367,12 @@ public abstract class SlicePanel extends JPanel implements TypeSafeDataProvider,
return ActionManager.getInstance().createActionToolbar(ActionPlaces.TYPE_HIERARCHY_VIEW_TOOLBAR, actionGroup, false);
}
public boolean isToShowAutoScrollButton() {return true;}
public abstract boolean isAutoScroll();
public abstract void setAutoScroll(boolean autoScroll);
public boolean isToShowPreviewButton() {return true;}
public abstract boolean isPreview();
public abstract void setPreview(boolean preview);
@@ -16,7 +16,6 @@
package com.intellij.slicer;
import com.intellij.ide.util.treeView.AbstractTreeBuilder;
import com.intellij.ide.util.treeView.AbstractTreeStructure;
import com.intellij.ide.util.treeView.AlphaComparator;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.openapi.project.Project;
@@ -85,7 +84,7 @@ public class SliceTreeBuilder extends AbstractTreeBuilder {
return false;
}
public void switchToSplittedNodes(final AbstractTreeStructure treeStructure) {
public void switchToGroupedByLeavesNodes() {
analysisInProgress = true;
SliceLeafAnalyzer.startAnalyzeValues(getTreeStructure(), new Runnable(){
@Override
@@ -19,6 +19,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.*;
import org.intellij.lang.annotations.JdkConstants;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
@@ -161,10 +162,12 @@ public class TextAttributes implements JDOMExternalizable, Cloneable {
return getForegroundColor() == null && getBackgroundColor() == null && getEffectColor() == null && getFontType() == Font.PLAIN;
}
@NotNull
public AttributesFlyweight getFlyweight() {
return myAttrs;
}
@NotNull
public static TextAttributes fromFlyweight(AttributesFlyweight flyweight) {
TextAttributes f = new TextAttributes();
f.myAttrs = flyweight;
@@ -89,8 +89,8 @@ public class ChunkExtractor {
protected Map<PsiFile, ChunkExtractor> create() {
return new FactoryMap<PsiFile, ChunkExtractor>() {
@Override
protected ChunkExtractor create(PsiFile key) {
return new ChunkExtractor(key);
protected ChunkExtractor create(PsiFile psiFile) {
return new ChunkExtractor(psiFile);
}
};
}
@@ -98,10 +98,15 @@ public class ChunkExtractor {
}
};
public static TextChunk[] extractChunks(@NotNull PsiFile file, UsageInfo2UsageAdapter usageAdapter) {
return ourExtractors.get().getValue().get(file).extractChunks(usageAdapter, file);
@NotNull
public static TextChunk[] extractChunks(@NotNull PsiFile file, @NotNull UsageInfo2UsageAdapter usageAdapter) {
return getExtractor(file).extractChunks(usageAdapter, file);
}
@NotNull
public static ChunkExtractor getExtractor(@NotNull PsiFile file) {
return ourExtractors.get().getValue().get(file);
}
private ChunkExtractor(@NotNull PsiFile file) {
myColorsScheme = UsageTreeColorsScheme.getInstance().getScheme();
@@ -127,6 +132,7 @@ public class ChunkExtractor {
return minStart == Integer.MAX_VALUE ? -1 : minStart;
}
@NotNull
private TextChunk[] extractChunks(@NotNull UsageInfo2UsageAdapter usageInfo2UsageAdapter, @NotNull PsiFile file) {
int absoluteStartOffset = usageInfo2UsageAdapter.getNavigationOffset();
if (absoluteStartOffset == -1) return TextChunk.EMPTY_ARRAY;
@@ -135,7 +141,6 @@ public class ChunkExtractor {
int visibleStartOffset = myDocument instanceof DocumentWindow ? ((DocumentWindow)myDocument).injectedToHost(absoluteStartOffset) : absoluteStartOffset;
int lineNumber = myDocument.getLineNumber(absoluteStartOffset);
//int columnNumber = absoluteStartOffset - myDocument.getLineStartOffset(lineNumber);
int visibleLineNumber = visibleDocument.getLineNumber(visibleStartOffset);
int visibleColumnNumber = visibleStartOffset - visibleDocument.getLineStartOffset(visibleLineNumber);
final List<TextChunk> result = new ArrayList<TextChunk>();
@@ -146,9 +151,6 @@ public class ChunkExtractor {
if (lineStartOffset > lineEndOffset) return TextChunk.EMPTY_ARRAY;
final CharSequence chars = myDocument.getCharsSequence();
if (myLexer.getTokenStart() > absoluteStartOffset) {
myLexer.start(chars);
}
if (lineEndOffset - lineStartOffset > MAX_LINE_TO_SHOW) {
lineStartOffset = Math.max(lineStartOffset, absoluteStartOffset - OFFSET_BEFORE_TO_SHOW_WHEN_LONG_LINE);
lineEndOffset = Math.min(lineEndOffset, absoluteStartOffset + OFFSET_AFTER_TO_SHOW_WHEN_LONG_LINE);
@@ -164,11 +166,12 @@ public class ChunkExtractor {
return createTextChunks(usageInfo2UsageAdapter, chars, lineStartOffset, lineEndOffset, result);
}
private TextChunk[] createTextChunks(final UsageInfo2UsageAdapter usageInfo2UsageAdapter,
final CharSequence chars,
int start,
int end,
final List<TextChunk> result) {
@NotNull
public TextChunk[] createTextChunks(@NotNull UsageInfo2UsageAdapter usageInfo2UsageAdapter,
@NotNull CharSequence chars,
int start,
int end,
@NotNull List<TextChunk> result) {
final Lexer lexer = myLexer;
final SyntaxHighlighter highlighter = myHighlighter;
@@ -177,6 +180,9 @@ public class ChunkExtractor {
int i = StringUtil.indexOf(chars, '\n', start, end);
if (i != -1) end = i;
if (lexer.getTokenStart() > start) {
lexer.start(chars);
}
boolean isBeginning = true;
while (lexer.getTokenType() != null) {
@@ -191,7 +197,7 @@ public class ChunkExtractor {
if (hiStart >= hiEnd) { continue; }
String text = chars.subSequence(hiStart, hiEnd).toString();
if (isBeginning && text.trim().length() == 0) continue;
if (isBeginning && text.trim().isEmpty()) continue;
isBeginning = false;
IElementType tokenType = lexer.getTokenType();
TextAttributesKey[] tokenHighlights = highlighter.getTokenHighlights(tokenType);
@@ -206,12 +212,12 @@ public class ChunkExtractor {
return result.toArray(new TextChunk[result.size()]);
}
private void processIntersectingRange(UsageInfo2UsageAdapter usageInfo2UsageAdapter,
final CharSequence chars,
private void processIntersectingRange(@NotNull UsageInfo2UsageAdapter usageInfo2UsageAdapter,
@NotNull final CharSequence chars,
int hiStart,
final int hiEnd,
TextAttributesKey[] tokenHighlights,
final List<TextChunk> result) {
@NotNull TextAttributesKey[] tokenHighlights,
@NotNull final List<TextChunk> result) {
final TextAttributes originalAttrs = convertAttributes(tokenHighlights);
final int[] lastOffset = {hiStart};
usageInfo2UsageAdapter.processRangeMarkers(new Processor<Segment>() {
@@ -235,7 +241,12 @@ public class ChunkExtractor {
}
}
private static void addChunk(CharSequence chars, int start, int end, TextAttributes originalAttrs, boolean bold, List<TextChunk> result) {
private static void addChunk(@NotNull CharSequence chars,
int start,
int end,
@NotNull TextAttributes originalAttrs,
boolean bold,
@NotNull List<TextChunk> result) {
if (start >= end) return;
TextAttributes attrs = bold
@@ -250,7 +261,8 @@ public class ChunkExtractor {
|| s1 == s2 && e1 == e2;
}
private TextAttributes convertAttributes(TextAttributesKey[] keys) {
@NotNull
private TextAttributes convertAttributes(@NotNull TextAttributesKey[] keys) {
TextAttributes attrs = myColorsScheme.getAttributes(HighlighterColors.TEXT);
for (TextAttributesKey key : keys) {
@@ -30,10 +30,12 @@ public class TextChunk {
myText = text;
}
@NotNull
public TextAttributes getAttributes() {
return TextAttributes.fromFlyweight(myAttributes);
}
@NotNull
public String getText() {
return myText;
}