From 20f9c56ae83bdd663909683935c2d51149ab9d8f Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 18 Jul 2011 18:40:03 +0400 Subject: [PATCH] PsiViewer: show formatter block structure --- .../internal/psiView/PsiViewerDialog.form | 71 ++- .../internal/psiView/PsiViewerDialog.java | 468 ++++++++++++++---- .../internal/psiView/PsiViewerSettings.java | 1 + .../internal/psiView/ViewerTreeStructure.java | 2 +- .../formattingblocks/BlockTreeBuilder.java | 16 + .../formattingblocks/BlockTreeNode.java | 62 +++ .../formattingblocks/BlockTreeStructure.java | 15 + 7 files changed, 518 insertions(+), 117 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/internal/psiView/formattingblocks/BlockTreeBuilder.java create mode 100644 platform/lang-impl/src/com/intellij/internal/psiView/formattingblocks/BlockTreeNode.java create mode 100644 platform/lang-impl/src/com/intellij/internal/psiView/formattingblocks/BlockTreeStructure.java diff --git a/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerDialog.form b/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerDialog.form index 2b82b4967968..6ca168b726cf 100644 --- a/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerDialog.form +++ b/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerDialog.form @@ -45,7 +45,7 @@ - + @@ -57,17 +57,74 @@ - + + + + + + + + + + + + + + + - - + - + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerDialog.java b/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerDialog.java index 58132d26ea1d..70d6e93a5a37 100644 --- a/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerDialog.java +++ b/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerDialog.java @@ -17,9 +17,17 @@ package com.intellij.internal.psiView; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import com.intellij.formatting.ASTBlock; +import com.intellij.formatting.Block; +import com.intellij.formatting.FormattingModel; +import com.intellij.formatting.FormattingModelBuilder; import com.intellij.ide.ui.ListCellRendererWrapper; +import com.intellij.internal.psiView.formattingblocks.BlockTreeBuilder; +import com.intellij.internal.psiView.formattingblocks.BlockTreeNode; +import com.intellij.internal.psiView.formattingblocks.BlockTreeStructure; import com.intellij.lang.ASTNode; import com.intellij.lang.Language; +import com.intellij.lang.LanguageFormatting; import com.intellij.lang.LanguageUtil; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.actionSystem.DataProvider; @@ -52,12 +60,16 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileFactory; import com.intellij.psi.PsiReference; +import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.impl.source.resolve.FileContextUtil; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.PsiUtilBase; import com.intellij.testFramework.LightVirtualFile; -import com.intellij.ui.*; +import com.intellij.ui.CollectionComboBoxModel; +import com.intellij.ui.SortedComboBoxModel; +import com.intellij.ui.TitledBorderWithMnemonic; +import com.intellij.ui.TreeSpeedSearch; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBList; import com.intellij.ui.treeStructure.Tree; @@ -67,6 +79,7 @@ import com.intellij.util.ObjectUtils; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -103,10 +116,19 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { private JPanel myReferencesPanel; private JSplitPane myTextSplit; private JSplitPane myTreeSplit; - private Tree myTree; - private ViewerTreeBuilder myTreeBuilder; + private Tree myPsiTree; + private ViewerTreeBuilder myPsiTreeBuilder; private JList myRefs; + private Tree myBlockTree; + private JPanel myBlockStructurePanel; + private JSplitPane myBlockRefSplitPane; + @Nullable + private BlockTreeBuilder myBlockTreeBuilder; + private RangeHighlighter myHighlighter; + private RangeHighlighter myIntersectHighlighter; + private HashMap myPsiToBlockMap; + private final Set mySourceWrappers = Sets.newTreeSet(); private final EditorEx myEditor; private final EditorListener myEditorListener = new EditorListener(); @@ -114,6 +136,14 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { private int myLastParsedTextHashCode = 17; private int myNewDocumentHashCode = 11; + private int myIgnoreBlockTreeSelectionMarker = 0; + + private void createUIComponents() { + myPsiTree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode())); + myBlockTree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode())); + myRefs = new JBList(new DefaultListModel()); + } + private static class ExtensionComparator implements Comparator { private final String myOnTop; @@ -188,13 +218,9 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { protected void init() { initBorders(); - myTree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode())); - UIUtil.setLineStyleAngled(myTree); - myTree.setRootVisible(false); - myTree.setShowsRootHandles(true); - myTree.updateUI(); - final TreeCellRenderer renderer = myTree.getCellRenderer(); - myTree.setCellRenderer(new TreeCellRenderer() { + initTree(myPsiTree); + final TreeCellRenderer renderer = myPsiTree.getCellRenderer(); + myPsiTree.setCellRenderer(new TreeCellRenderer() { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { final Component c = renderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); @@ -212,24 +238,10 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { return c; } }); - ToolTipManager.sharedInstance().registerComponent(myTree); - TreeUtil.installActions(myTree); - new TreeSpeedSearch(myTree); - myTreeBuilder = new ViewerTreeBuilder(myProject, myTree); - myTree.addTreeSelectionListener(new MyTreeSelectionListener()); + myPsiTreeBuilder = new ViewerTreeBuilder(myProject, myPsiTree); + Disposer.register(getDisposable(), myPsiTreeBuilder); + myPsiTree.addTreeSelectionListener(new MyPsiTreeSelectionListener()); - JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree); - JPanel panel = new JPanel(new BorderLayout()); - panel.add(scrollPane, BorderLayout.CENTER); - myStructureTreePanel.setLayout(new BorderLayout()); - myStructureTreePanel.add(panel, BorderLayout.CENTER); - - myRefs = new JBList(new DefaultListModel()); - JScrollPane refScrollPane = ScrollPaneFactory.createScrollPane(myRefs); - JPanel refPanel = new JPanel(new BorderLayout()); - refPanel.add(refScrollPane, BorderLayout.CENTER); - myReferencesPanel.setLayout(new BorderLayout()); - myReferencesPanel.add(refPanel, BorderLayout.CENTER); final GoToListener listener = new GoToListener(); myRefs.addKeyListener(listener); myRefs.addMouseListener(listener); @@ -244,6 +256,8 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { } }); + initTree(myBlockTree); + myEditor.getSettings().setFoldingOutlineShown(false); myEditor.getDocument().addDocumentListener(myEditorListener); myEditor.getSelectionModel().addSelectionListener(myEditorListener); @@ -365,23 +379,23 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { }); myExtensionComboBox.addFocusListener(new AutoExpandFocusListener(myExtensionComboBox)); - final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myTreeBuilder.getTreeStructure(); + final ViewerTreeStructure psiTreeStructure = (ViewerTreeStructure)myPsiTreeBuilder.getTreeStructure(); myShowWhiteSpacesBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - treeStructure.setShowWhiteSpaces(myShowWhiteSpacesBox.isSelected()); - myTreeBuilder.queueUpdate(); + psiTreeStructure.setShowWhiteSpaces(myShowWhiteSpacesBox.isSelected()); + myPsiTreeBuilder.queueUpdate(); } }); myShowTreeNodesCheckBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - treeStructure.setShowTreeNodes(myShowTreeNodesCheckBox.isSelected()); - myTreeBuilder.queueUpdate(); + psiTreeStructure.setShowTreeNodes(myShowTreeNodesCheckBox.isSelected()); + myPsiTreeBuilder.queueUpdate(); } }); myShowWhiteSpacesBox.setSelected(settings.showWhiteSpaces); - treeStructure.setShowWhiteSpaces(settings.showWhiteSpaces); + psiTreeStructure.setShowWhiteSpaces(settings.showWhiteSpaces); myShowTreeNodesCheckBox.setSelected(settings.showTreeNodes); - treeStructure.setShowTreeNodes(settings.showTreeNodes); + psiTreeStructure.setShowTreeNodes(settings.showTreeNodes); myTextPanel.setLayout(new BorderLayout()); myTextPanel.add(myEditor.getComponent(), BorderLayout.CENTER); @@ -406,11 +420,22 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { } myTextSplit.setDividerLocation(settings.textDividerLocation); myTreeSplit.setDividerLocation(settings.treeDividerLocation); + myBlockRefSplitPane.setDividerLocation(settings.blockRefDividerLocation); updateEditor(); super.init(); } + private static void initTree(JTree tree) { + UIUtil.setLineStyleAngled(tree); + tree.setRootVisible(false); + tree.setShowsRootHandles(true); + tree.updateUI(); + ToolTipManager.sharedInstance().registerComponent(tree); + TreeUtil.installActions(tree); + new TreeSpeedSearch(tree); + } + protected String getDimensionServiceKey() { return "#com.intellij.internal.psiView.PsiViewerDialog"; } @@ -439,17 +464,12 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { } }, KeyStroke.getKeyStroke(KeyEvent.VK_S, mask)); - registerKeyboardAction(new ActionListener() { - public void actionPerformed(ActionEvent e) { - focusTree(); - } - }, KeyStroke.getKeyStroke(KeyEvent.VK_S, mask)); registerKeyboardAction(new ActionListener() { public void actionPerformed(ActionEvent e) { - focusTree(); + focusBlockTree(); } - }, KeyStroke.getKeyStroke(KeyEvent.VK_S, mask)); + }, KeyStroke.getKeyStroke(KeyEvent.VK_K, mask)); registerKeyboardAction(new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -460,11 +480,14 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { registerKeyboardAction(new ActionListener() { public void actionPerformed(ActionEvent e) { if (myRefs.isFocusOwner()) { - focusTree(); + focusBlockTree(); } - else if (myTree.isFocusOwner()) { + else if (myPsiTree.isFocusOwner()) { focusRefs(); } + else if (myBlockTree.isFocusOwner()) { + focusTree(); + } } }, KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); } @@ -478,7 +501,7 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { } private void focusTree() { - IdeFocusManager.getInstance(myProject).requestFocus(myTree, true); + IdeFocusManager.getInstance(myProject).requestFocus(myPsiTree, true); } private void focusRefs() { @@ -490,15 +513,40 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { } } + private void focusBlockTree() { + IdeFocusManager.getInstance(myProject).requestFocus(myBlockTree, true); + } + private void initBorders() { myTextPanel.setBorder(new TitledBorderWithMnemonic("&Text")); myStructureTreePanel.setBorder(new TitledBorderWithMnemonic("PSI &Structure")); myReferencesPanel.setBorder(new TitledBorderWithMnemonic("&References")); + myBlockStructurePanel.setBorder(new TitledBorderWithMnemonic("Bloc&k Structure")); + } + + private void updateIntersectHighlighter(int highlightStart, int highlightEnd) { + if (myIntersectHighlighter != null) { + myEditor.getMarkupModel().removeHighlighter(myIntersectHighlighter); + myIntersectHighlighter.dispose(); + } + if (myEditor.getSelectionModel().hasSelection()) { + int selectionStart = myEditor.getSelectionModel().getSelectionStart(); + int selectionEnd = myEditor.getSelectionModel().getSelectionEnd(); + TextRange resRange = new TextRange(highlightStart, highlightEnd).intersection(new TextRange(selectionStart, selectionEnd)); + if (resRange != null) { + TextAttributes attributes = new TextAttributes(); + attributes.setBackgroundColor(Color.LIGHT_GRAY); + attributes.setForegroundColor(Color.white); + myIntersectHighlighter = myEditor.getMarkupModel() + .addRangeHighlighter(resRange.getStartOffset(), resRange.getEndOffset(), HighlighterLayer.LAST + 1, attributes, + HighlighterTargetArea.EXACT_RANGE); + } + } } @Nullable private PsiElement getPsiElement() { - final TreePath path = myTree.getSelectionPath(); + final TreePath path = myPsiTree.getSelectionPath(); return path == null ? null : getPsiElement((DefaultMutableTreeNode)path.getLastPathComponent()); } @@ -596,6 +644,9 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { } protected void doOKAction() { + if (myBlockTreeBuilder != null) { + Disposer.dispose(myBlockTreeBuilder); + } final String text = myEditor.getDocument().getText(); myEditor.getSelectionModel().removeSelection(); @@ -631,20 +682,83 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { rootElement = null; Messages.showMessageDialog(myProject, e.getMessage(), "Error", Messages.getErrorIcon()); } - ViewerTreeStructure structure = (ViewerTreeStructure)myTreeBuilder.getTreeStructure(); + ViewerTreeStructure structure = (ViewerTreeStructure)myPsiTreeBuilder.getTreeStructure(); structure.setRootPsiElement(rootElement); - myTreeBuilder.queueUpdate(); - myTree.setRootVisible(true); - myTree.expandRow(0); - myTree.setRootVisible(false); + myPsiTreeBuilder.queueUpdate(); + myPsiTree.setRootVisible(true); + myPsiTree.expandRow(0); + myPsiTree.setRootVisible(false); + + Block rootBlock = buildBlocks(rootElement); + if(rootBlock == null){ + myBlockTreeBuilder = null; + myBlockTree.setRootVisible(false); + myBlockTree.setVisible(false); + return; + } + + myBlockTree.setVisible(true); + myBlockTreeBuilder = new BlockTreeBuilder(myBlockTree); + BlockTreeStructure blockTreeStructure = (BlockTreeStructure)myBlockTreeBuilder.getTreeStructure(); + BlockTreeNode rootNode = new BlockTreeNode(rootBlock, null); + blockTreeStructure.setRoot(rootNode); + myPsiToBlockMap = new HashMap(); + final PsiElement psiFile = ((ViewerTreeStructure)myPsiTreeBuilder.getTreeStructure()).getRootPsiElement(); + initMap(rootNode, psiFile); + blockTreeStructure.setRoot(myPsiToBlockMap.get(rootElement)); + myBlockTree.addTreeSelectionListener(new MyBlockTreeSelectionListener()); + myBlockTree.setRootVisible(true); + myBlockTree.expandRow(0); + myBlockTreeBuilder.queueUpdate(); + + } + + @Nullable + private static Block buildBlocks(@NotNull PsiElement rootElement) { + FormattingModelBuilder formattingModelBuilder = LanguageFormatting.INSTANCE.forContext(rootElement); + CodeStyleSettings settings = new CodeStyleSettings(); + if (formattingModelBuilder != null) { + FormattingModel formattingModel = formattingModelBuilder.createModel(rootElement, settings); + return formattingModel.getRootBlock(); + } + else { + return null; + } + } + + private void initMap(BlockTreeNode rootBlockNode, PsiElement psiEl) { + PsiElement currentElem = null; + if (rootBlockNode.getBlock() instanceof ASTBlock) { + ASTNode node = ((ASTBlock)rootBlockNode.getBlock()).getNode(); + if (node != null) { + currentElem = node.getPsi(); + } + } + if (currentElem == null) { + currentElem = + InjectedLanguageUtil.findElementAtNoCommit(psiEl.getContainingFile(), rootBlockNode.getBlock().getTextRange().getStartOffset()); + } + myPsiToBlockMap.put(currentElem, rootBlockNode); + +//nested PSI elements with same ranges will be mapped to one blockNode + assert currentElem != null; + TextRange curTextRange = currentElem.getTextRange(); + PsiElement parentElem = currentElem.getParent(); + while (parentElem != null && parentElem.getTextRange().equals(curTextRange)) { + myPsiToBlockMap.put(parentElem, rootBlockNode); + parentElem = parentElem.getParent(); + } + for (BlockTreeNode block : rootBlockNode.getChildren()) { + initMap(block, psiEl); + } } public Object getData(@NonNls String dataId) { if (PlatformDataKeys.NAVIGATABLE.is(dataId)) { String fqn = null; - if (myTree.hasFocus()) { - final TreePath path = myTree.getSelectionPath(); + if (myPsiTree.hasFocus()) { + final TreePath path = myPsiTree.getSelectionPath(); if (path != null) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); if (!(node.getUserObject() instanceof ViewerNodeDescriptor)) return null; @@ -670,19 +784,18 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { return null; } - private class MyTreeSelectionListener implements TreeSelectionListener { + private class MyPsiTreeSelectionListener implements TreeSelectionListener { private final TextAttributes myAttributes; - private RangeHighlighter myHighlighter; - public MyTreeSelectionListener() { + public MyPsiTreeSelectionListener() { myAttributes = new TextAttributes(); myAttributes.setBackgroundColor(SELECTION_BG_COLOR); myAttributes.setForegroundColor(Color.white); } public void valueChanged(TreeSelectionEvent e) { - if (!myEditor.getDocument().getText().equals(myLastParsedText)) return; - TreePath path = myTree.getSelectionPath(); + if (!myEditor.getDocument().getText().equals(myLastParsedText) || myBlockTree.hasFocus()) return; + TreePath path = myPsiTree.getSelectionPath(); clearSelection(); if (path != null) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); @@ -693,57 +806,148 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { ? (PsiElement)elementObject : elementObject instanceof ASTNode ? ((ASTNode)elementObject).getPsi() : null; if (element != null) { - TextRange hostRange = InjectedLanguageManager.getInstance(myProject).injectedToHost(element, element.getTextRange()); - int start = hostRange.getStartOffset(); - int end = hostRange.getEndOffset(); - final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myTreeBuilder.getTreeStructure(); + TextRange rangeInHostFile = InjectedLanguageManager.getInstance(myProject).injectedToHost(element, element.getTextRange()); + int start = rangeInHostFile.getStartOffset(); + int end = rangeInHostFile.getEndOffset(); + final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myPsiTreeBuilder.getTreeStructure(); PsiElement rootPsiElement = treeStructure.getRootPsiElement(); if (rootPsiElement != null) { int baseOffset = rootPsiElement.getTextRange().getStartOffset(); start -= baseOffset; end -= baseOffset; } - final int textLength = myEditor.getDocument().getTextLength(); if (end <= textLength) { myHighlighter = myEditor.getMarkupModel().addRangeHighlighter(start, end, HighlighterLayer.LAST, myAttributes, HighlighterTargetArea.EXACT_RANGE); - if (myTree.hasFocus()) { + updateIntersectHighlighter(start, end); + + if (myPsiTree.hasFocus()) { myEditor.getCaretModel().moveToOffset(start); myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); } - else { - myEditor.getScrollingModel().scrollTo(myEditor.offsetToLogicalPosition(start), ScrollType.MAKE_VISIBLE); - } } - updateReferences(element); + if (myBlockTreeBuilder != null && myPsiTree.hasFocus()) { + BlockTreeNode currentBlockNode = myPsiToBlockMap.get(element); + if (currentBlockNode != null) { + selectBlockNode(currentBlockNode); + } + else { + selectBlockNodeByRange(rangeInHostFile,true); + } + } + updateReferences(element); } } } + } - public void updateReferences(PsiElement element) { - final DefaultListModel model = (DefaultListModel)myRefs.getModel(); - model.clear(); - final Object cache = myRefs.getClientProperty(REFS_CACHE); - if (cache instanceof Map) { - ((Map)cache).clear(); - } else { - myRefs.putClientProperty(REFS_CACHE, new HashMap()); + private void selectBlockNode(@Nullable BlockTreeNode currentBlockNode) { + if (myBlockTreeBuilder == null) return; + if (currentBlockNode != null) { + myIgnoreBlockTreeSelectionMarker++; + myBlockTreeBuilder.select(currentBlockNode, new Runnable() { + @Override + public void run() { + // hope this is always called! + assert myIgnoreBlockTreeSelectionMarker > 0; + myIgnoreBlockTreeSelectionMarker--; + } + }); + } + else { + myIgnoreBlockTreeSelectionMarker++; + try { + myBlockTree.getSelectionModel().clearSelection(); } - if (element != null) { - for (PsiReference reference : element.getReferences()) { - model.addElement(reference.getClass().getName()); + finally { + assert myIgnoreBlockTreeSelectionMarker > 0; + myIgnoreBlockTreeSelectionMarker--; + } + } + } + + private class MyBlockTreeSelectionListener implements TreeSelectionListener { + private final TextAttributes myAttributes; + + public MyBlockTreeSelectionListener() { + myAttributes = new TextAttributes(); + myAttributes.setBackgroundColor(SELECTION_BG_COLOR); + myAttributes.setForegroundColor(Color.white); + } + + public void valueChanged(TreeSelectionEvent e) { + if (myIgnoreBlockTreeSelectionMarker > 0 || myBlockTreeBuilder == null) { + return; + } + + Set blockElementsSet = myBlockTreeBuilder.getSelectedElements(); + if (blockElementsSet.isEmpty()) return; + BlockTreeNode descriptor = (BlockTreeNode)blockElementsSet.iterator().next(); + PsiElement rootPsi = ((ViewerTreeStructure)myPsiTreeBuilder.getTreeStructure()).getRootPsiElement(); + int blockStart = descriptor.getBlock().getTextRange().getStartOffset(); + PsiElement currentPsiEl = InjectedLanguageUtil.findElementAtNoCommit(rootPsi.getContainingFile(), blockStart); + int blockLength = descriptor.getBlock().getTextRange().getLength(); + while (currentPsiEl != null && + currentPsiEl.getTextRange().getStartOffset() == blockStart && + currentPsiEl.getTextLength() != blockLength) { + currentPsiEl = currentPsiEl.getParent(); + } + final BlockTreeStructure treeStructure = (BlockTreeStructure)myBlockTreeBuilder.getTreeStructure(); + BlockTreeNode rootBlockNode = treeStructure.getRootElement(); + int baseOffset = 0; + if (rootBlockNode != null) { + baseOffset = rootBlockNode.getBlock().getTextRange().getStartOffset(); + } + if (currentPsiEl != null) { + TextRange range = descriptor.getBlock().getTextRange(); + range = range.shiftRight(-baseOffset); + int start = range.getStartOffset(); + int end = range.getEndOffset(); + final int textLength = myEditor.getDocument().getTextLength(); + + if (myBlockTree.hasFocus()) { + clearSelection(); + if (end <= textLength) { + myHighlighter = myEditor.getMarkupModel() + .addRangeHighlighter(start, end, HighlighterLayer.LAST, myAttributes, HighlighterTargetArea.EXACT_RANGE); + updateIntersectHighlighter(start, end); + + myEditor.getCaretModel().moveToOffset(start); + myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); + } + } + updateReferences(currentPsiEl); + if (!myPsiTree.hasFocus()) { + myPsiTreeBuilder.select(currentPsiEl); } } } + } - private void clearSelection() { - if (myHighlighter != null) { - myEditor.getMarkupModel().removeHighlighter(myHighlighter); - myHighlighter.dispose(); + + public void updateReferences(PsiElement element) { + final DefaultListModel model = (DefaultListModel)myRefs.getModel(); + model.clear(); + final Object cache = myRefs.getClientProperty(REFS_CACHE); + if (cache instanceof Map) { + ((Map)cache).clear(); + } else { + myRefs.putClientProperty(REFS_CACHE, new HashMap()); + } + if (element != null) { + for (PsiReference reference : element.getReferences()) { + model.addElement(reference.getClass().getName()); } } } + private void clearSelection() { + if (myHighlighter != null) { + myEditor.getMarkupModel().removeHighlighter(myHighlighter); + myHighlighter.dispose(); + } + } + public void doCancelAction() { final PsiViewerSettings settings = PsiViewerSettings.getSettings(); final SourceWrapper wrapper = (SourceWrapper)myFileTypeComboBox.getSelectedItem(); @@ -754,12 +958,16 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { final Object selectedDialect = myDialectComboBox.getSelectedItem(); settings.dialect = myDialectComboBox.isVisible() && selectedDialect != null ? selectedDialect.toString() : ""; settings.textDividerLocation = myTextSplit.getDividerLocation(); - settings.treeDividerLocation = myTreeSplit.getDividerLocation(); + settings.treeDividerLocation = myTreeSplit.getDividerLocation(); + settings.blockRefDividerLocation = myBlockRefSplitPane.getDividerLocation(); super.doCancelAction(); } public void dispose() { - Disposer.dispose(myTreeBuilder); + Disposer.dispose(myPsiTreeBuilder); + if (myBlockTreeBuilder != null) { + Disposer.dispose(myBlockTreeBuilder); + } EditorFactory.getInstance().releaseEditor(myEditor); super.dispose(); @@ -818,12 +1026,11 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { } private class GoToListener implements KeyListener, MouseListener, ListSelectionListener { - private RangeHighlighter myHighlighter; + private RangeHighlighter myListenerHighlighter; private final TextAttributes myAttributes = new TextAttributes(Color.white, SELECTION_BG_COLOR, Color.red, EffectType.BOXED, Font.PLAIN); private void navigate() { - clearSelection(); final Object value = myRefs.getSelectedValue(); if (value instanceof String) { final String fqn = (String)value; @@ -854,10 +1061,10 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { final PsiReference[] references = element.getReferences(); if (ind < references.length) { final TextRange textRange = references[ind].getRangeInElement(); - TextRange range = element.getTextRange(); + TextRange range = InjectedLanguageManager.getInstance(myProject).injectedToHost(element, element.getTextRange()); int start = range.getStartOffset(); int end = range.getEndOffset(); - final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myTreeBuilder.getTreeStructure(); + final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myPsiTreeBuilder.getTreeStructure(); PsiElement rootPsiElement = treeStructure.getRootPsiElement(); if (rootPsiElement != null) { int baseOffset = rootPsiElement.getTextRange().getStartOffset(); @@ -867,16 +1074,17 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { start += textRange.getStartOffset(); end = start + textRange.getLength(); - myHighlighter = myEditor.getMarkupModel() + myListenerHighlighter = myEditor.getMarkupModel() .addRangeHighlighter(start, end, HighlighterLayer.FIRST + 1, myAttributes, HighlighterTargetArea.EXACT_RANGE); } } } public void clearSelection() { - if (myHighlighter != null && ArrayUtil.contains(myHighlighter, (Object[])myEditor.getMarkupModel().getAllHighlighters())) { - myHighlighter.dispose(); - myHighlighter = null; + if (myListenerHighlighter != null && + ArrayUtil.contains(myListenerHighlighter, (Object[])myEditor.getMarkupModel().getAllHighlighters())) { + myListenerHighlighter.dispose(); + myListenerHighlighter = null; } } @@ -912,31 +1120,52 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { private class EditorListener implements CaretListener, SelectionListener, DocumentListener { public void caretPositionChanged(CaretEvent e) { if (!available() || myEditor.getSelectionModel().hasSelection()) return; - final PsiFile psiFile = getPsiFile(); - if (psiFile == null) return; - final int offset = myEditor.getCaretModel().getOffset(); - final PsiElement element = PsiUtilBase.getElementAtOffset(psiFile, offset); - myTreeBuilder.select(element); + final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myPsiTreeBuilder.getTreeStructure(); + final PsiElement rootPsiElement = treeStructure.getRootPsiElement(); + final PsiElement rootElement = ((ViewerTreeStructure)myPsiTreeBuilder.getTreeStructure()).getRootPsiElement(); + int baseOffset = rootPsiElement.getTextRange().getStartOffset(); + final int offset = myEditor.getCaretModel().getOffset() + baseOffset; + final PsiElement element = InjectedLanguageUtil.findElementAtNoCommit(rootElement.getContainingFile(), offset); + if (element != null && myBlockTreeBuilder !=null) { + TextRange rangeInHostFile = InjectedLanguageManager.getInstance(myProject).injectedToHost(element, element.getTextRange()); + selectBlockNodeByRange(rangeInHostFile, true); + } + myPsiTreeBuilder.select(element); } public void selectionChanged(SelectionEvent e) { if (!available() || !myEditor.getSelectionModel().hasSelection()) return; - final PsiFile psiFile = getPsiFile(); - if (psiFile == null) return; + final PsiElement rootElement =((ViewerTreeStructure)myPsiTreeBuilder.getTreeStructure()).getRootPsiElement(); final SelectionModel selection = myEditor.getSelectionModel(); - final int start = selection.getSelectionStart(); - final int end = selection.getSelectionEnd(); - final PsiElement element = findCommonParent(PsiUtilBase.getElementAtOffset(psiFile, start), PsiUtilBase.getElementAtOffset(psiFile, end)); - myTreeBuilder.select(element); + final ViewerTreeStructure treeStructure = (ViewerTreeStructure)myPsiTreeBuilder.getTreeStructure(); + PsiElement rootPsiElement = treeStructure.getRootPsiElement(); + int baseOffset = rootPsiElement.getTextRange().getStartOffset(); + final int start = selection.getSelectionStart()+baseOffset; + final int end = selection.getSelectionEnd()+baseOffset - 1; + final PsiElement element = + findCommonParent(InjectedLanguageUtil.findElementAtNoCommit(rootElement.getContainingFile(), start), + InjectedLanguageUtil.findElementAtNoCommit(rootElement.getContainingFile(), end)); + if (element != null && myBlockTreeBuilder != null) { + if (myEditor.getContentComponent().hasFocus()) { + TextRange rangeInHostFile = InjectedLanguageManager.getInstance(myProject).injectedToHost(element, element.getTextRange()); + selectBlockNodeByRange(rangeInHostFile, true); + updateIntersectHighlighter(myHighlighter.getStartOffset(), myHighlighter.getEndOffset()); + } + } + myPsiTreeBuilder.select(element); } @Nullable private PsiElement findCommonParent(PsiElement start, PsiElement end) { - final TextRange range = end.getTextRange(); - while (start != null && !start.getTextRange().contains(range)) { - start = start.getParent(); + if (end == null || start == end) { + return start; } - return start; + final TextRange endRange = end.getTextRange(); + PsiElement parent = start.getContext(); + while (parent != null && !parent.getTextRange().contains(endRange)) { + parent = parent.getContext(); + } + return parent; } private boolean available() { @@ -945,7 +1174,7 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { @Nullable private PsiFile getPsiFile() { - final PsiElement root = ((ViewerTreeStructure)myTreeBuilder.getTreeStructure()).getRootPsiElement(); + final PsiElement root = ((ViewerTreeStructure)myPsiTreeBuilder.getTreeStructure()).getRootPsiElement(); return root instanceof PsiFile ? (PsiFile)root : null; } @@ -982,4 +1211,25 @@ public class PsiViewerDialog extends DialogWrapper implements DataProvider { return false; } } + + private void selectBlockNodeByRange(TextRange range, boolean selectParentIfNotFound) { + if(myBlockTreeBuilder ==null )return; + BlockTreeNode node = (BlockTreeNode)myBlockTreeBuilder.getTreeStructure().getRootElement(); + main_loop: + while (true) { + if (node.getBlock().getTextRange().equals(range)) { + selectBlockNode(node); + return; + } + + for (BlockTreeNode child : node.getChildren()) { + if (child.getBlock().getTextRange().contains(range)) { + node = child; + continue main_loop; + } + } + selectBlockNode(selectParentIfNotFound ? node : null); + return; + } + } } diff --git a/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerSettings.java b/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerSettings.java index 799cbf21abe7..83282b2563c2 100644 --- a/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerSettings.java +++ b/platform/lang-impl/src/com/intellij/internal/psiView/PsiViewerSettings.java @@ -35,6 +35,7 @@ public class PsiViewerSettings implements PersistentStateComponent() { + @Override + public BlockTreeNode fun(Block block) { + return new BlockTreeNode(block, BlockTreeNode.this); + } + }); + } + + protected void update(PresentationData presentation) { + presentation.addText(new String(myBlock.getClass().getSimpleName()), SimpleTextAttributes.REGULAR_ATTRIBUTES); + + if (myBlock.getIndent() != null) { + presentation.addText(" " + String.valueOf(myBlock.getIndent()).replaceAll("[<>]", " "), SimpleTextAttributes.GRAY_ATTRIBUTES); + } + else { + presentation.addText(" Indent: null", SimpleTextAttributes.GRAY_ATTRIBUTES); + } + if (myBlock.getAlignment() != null) { + presentation + .addText(" " + String.valueOf(myBlock.getAlignment()), new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, Color.darkGray)); + } + if (myBlock.getWrap() != null) { + presentation + .addText(" " + String.valueOf(myBlock.getWrap()), new SimpleTextAttributes(SimpleTextAttributes.STYLE_ITALIC, Color.BLUE)); + } + } + + + @Override + public Object[] getEqualityObjects() { + return new Object[]{myBlock}; + } + + @Override + public boolean isAlwaysLeaf() { + return myBlock.isLeaf(); + } +} diff --git a/platform/lang-impl/src/com/intellij/internal/psiView/formattingblocks/BlockTreeStructure.java b/platform/lang-impl/src/com/intellij/internal/psiView/formattingblocks/BlockTreeStructure.java new file mode 100644 index 000000000000..e4163eb65fec --- /dev/null +++ b/platform/lang-impl/src/com/intellij/internal/psiView/formattingblocks/BlockTreeStructure.java @@ -0,0 +1,15 @@ +package com.intellij.internal.psiView.formattingblocks; + +import com.intellij.ui.treeStructure.SimpleTreeStructure; + +public class BlockTreeStructure extends SimpleTreeStructure { + private BlockTreeNode myRoot; + + public BlockTreeNode getRootElement() { + return myRoot; + } + + public void setRoot(BlockTreeNode root) { + myRoot = root; + } +}