PageRenderTime 56ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/platform/lang-impl/src/com/intellij/internal/psiView/formattingblocks/BlockViewerPsiBasedTree.java

http://github.com/JetBrains/intellij-community
Java | 330 lines | 281 code | 46 blank | 3 comment | 62 complexity | 3fec54884ad1e0a5a85ba1ce4ca4c61a MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, MPL-2.0-no-copyleft-exception, MIT, EPL-1.0, AGPL-1.0
  1. // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
  2. package com.intellij.internal.psiView.formattingblocks;
  3. import com.intellij.application.options.CodeStyle;
  4. import com.intellij.diagnostic.AttachmentFactory;
  5. import com.intellij.formatting.ASTBlock;
  6. import com.intellij.formatting.Block;
  7. import com.intellij.formatting.FormattingModel;
  8. import com.intellij.formatting.FormattingModelBuilder;
  9. import com.intellij.internal.psiView.PsiViewerDialog;
  10. import com.intellij.internal.psiView.ViewerPsiBasedTree;
  11. import com.intellij.lang.ASTNode;
  12. import com.intellij.lang.LanguageFormatting;
  13. import com.intellij.lang.injection.InjectedLanguageManager;
  14. import com.intellij.openapi.Disposable;
  15. import com.intellij.openapi.project.Project;
  16. import com.intellij.openapi.util.Disposer;
  17. import com.intellij.openapi.util.TextRange;
  18. import com.intellij.openapi.wm.IdeFocusManager;
  19. import com.intellij.psi.PsiElement;
  20. import com.intellij.psi.PsiFile;
  21. import com.intellij.psi.codeStyle.CodeStyleSettings;
  22. import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
  23. import com.intellij.ui.IdeBorderFactory;
  24. import com.intellij.ui.ScrollPaneFactory;
  25. import com.intellij.ui.tree.AsyncTreeModel;
  26. import com.intellij.ui.tree.StructureTreeModel;
  27. import com.intellij.ui.tree.TreeVisitor;
  28. import com.intellij.ui.treeStructure.SimpleNode;
  29. import com.intellij.ui.treeStructure.Tree;
  30. import com.intellij.util.Function;
  31. import com.intellij.util.containers.JBTreeTraverser;
  32. import com.intellij.util.ui.tree.TreeUtil;
  33. import org.jetbrains.annotations.NotNull;
  34. import org.jetbrains.annotations.Nullable;
  35. import javax.swing.*;
  36. import javax.swing.event.TreeSelectionEvent;
  37. import javax.swing.event.TreeSelectionListener;
  38. import javax.swing.tree.DefaultMutableTreeNode;
  39. import javax.swing.tree.DefaultTreeModel;
  40. import javax.swing.tree.TreePath;
  41. import java.awt.*;
  42. import java.util.HashMap;
  43. import java.util.HashSet;
  44. import java.util.Set;
  45. import static com.intellij.internal.psiView.PsiViewerDialog.initTree;
  46. public class BlockViewerPsiBasedTree implements ViewerPsiBasedTree {
  47. @NotNull
  48. private final JPanel myBlockStructurePanel;
  49. @NotNull
  50. private final Tree myBlockTree;
  51. @NotNull
  52. private final Project myProject;
  53. @NotNull
  54. private final PsiTreeUpdater myUpdater;
  55. @Nullable
  56. private volatile HashMap<PsiElement, BlockTreeNode> myPsiToBlockMap;
  57. private AsyncTreeModel myTreeModel;
  58. private Disposable myTreeModelDisposable = Disposer.newDisposable();
  59. public BlockViewerPsiBasedTree(@NotNull Project project, @NotNull PsiTreeUpdater updater) {
  60. myProject = project;
  61. myUpdater = updater;
  62. myBlockTree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode()));
  63. myBlockStructurePanel = new JPanel(new BorderLayout());
  64. myBlockStructurePanel.add(ScrollPaneFactory.createScrollPane(myBlockTree));
  65. myBlockStructurePanel.setBorder(IdeBorderFactory.createBorder());
  66. initTree(myBlockTree);
  67. }
  68. @Override
  69. public void reloadTree(@Nullable PsiElement rootRootElement, @NotNull String text) {
  70. resetBlockTree();
  71. buildBlockTree(rootRootElement);
  72. }
  73. @Override
  74. public void selectNodeFromPsi(@Nullable PsiElement element) {
  75. if (myTreeModel != null && element != null) {
  76. BlockTreeNode currentBlockNode = findBlockNode(element);
  77. if (currentBlockNode != null) {
  78. selectBlockNode(currentBlockNode);
  79. }
  80. }
  81. }
  82. @NotNull
  83. @Override
  84. public JComponent getComponent() {
  85. return myBlockStructurePanel;
  86. }
  87. @Override
  88. public boolean isFocusOwner() {
  89. return myBlockTree.isFocusOwner();
  90. }
  91. @Override
  92. public void focusTree() {
  93. IdeFocusManager.getInstance(myProject).requestFocus(myBlockTree, true);
  94. }
  95. @Override
  96. public void dispose() {
  97. resetBlockTree();
  98. }
  99. private void resetBlockTree() {
  100. myBlockTree.removeAll();
  101. if (myTreeModel != null) {
  102. Disposer.dispose(myTreeModelDisposable);
  103. myTreeModel = null;
  104. myTreeModelDisposable = Disposer.newDisposable();
  105. }
  106. myPsiToBlockMap = null;
  107. ViewerPsiBasedTree.removeListenerOfClass(myBlockTree, BlockTreeSelectionListener.class);
  108. }
  109. private void buildBlockTree(@Nullable PsiElement rootElement) {
  110. Block rootBlock = rootElement == null ? null : buildBlocks(rootElement);
  111. if (rootBlock == null) {
  112. myTreeModel = null;
  113. myBlockTree.setRootVisible(false);
  114. myBlockTree.setVisible(false);
  115. return;
  116. }
  117. myBlockTree.setVisible(true);
  118. BlockTreeStructure blockTreeStructure = new BlockTreeStructure();
  119. BlockTreeNode rootNode = new BlockTreeNode(rootBlock, null);
  120. StructureTreeModel treeModel = new StructureTreeModel<>(blockTreeStructure, myTreeModelDisposable);
  121. initMap(rootNode, rootElement);
  122. assert myPsiToBlockMap != null;
  123. PsiElement rootPsi = rootNode.getBlock() instanceof ASTBlock ?
  124. ((ASTBlock)rootNode.getBlock()).getNode().getPsi() : rootElement;
  125. BlockTreeNode blockNode = myPsiToBlockMap.get(rootPsi);
  126. if (blockNode == null) {
  127. PsiViewerDialog.LOG.error("PsiViewer: rootNode not found\nCurrent language: " + rootElement.getContainingFile().getLanguage(),
  128. (Throwable)null,
  129. AttachmentFactory.createAttachment(rootElement.getContainingFile().getOriginalFile().getVirtualFile()));
  130. blockNode = findBlockNode(rootPsi);
  131. }
  132. blockTreeStructure.setRoot(blockNode);
  133. myTreeModel = new AsyncTreeModel(treeModel, myTreeModelDisposable);
  134. myBlockTree.setModel(myTreeModel);
  135. myBlockTree.addTreeSelectionListener(new BlockTreeSelectionListener(rootElement));
  136. myBlockTree.setRootVisible(true);
  137. myBlockTree.expandRow(0);
  138. treeModel.invalidate();
  139. }
  140. @Nullable
  141. private BlockTreeNode findBlockNode(PsiElement element) {
  142. HashMap<PsiElement, BlockTreeNode> psiToBlockMap = myPsiToBlockMap;
  143. BlockTreeNode result = psiToBlockMap == null ? null : psiToBlockMap.get(element);
  144. if (result == null) {
  145. TextRange rangeInHostFile = InjectedLanguageManager.getInstance(myProject).injectedToHost(element, element.getTextRange());
  146. result = findBlockNode(rangeInHostFile);
  147. }
  148. return result;
  149. }
  150. @NotNull
  151. private TreeVisitor createVisitor(@NotNull BlockTreeNode currentBlockNode) {
  152. Function<Object, BlockTreeNode> converter = el -> el instanceof DefaultMutableTreeNode ?
  153. (BlockTreeNode)((DefaultMutableTreeNode)el).getUserObject() :
  154. null;
  155. Set<SimpleNode> parents = new HashSet<>();
  156. SimpleNode parent = currentBlockNode.getParent();
  157. while (parent != null) {
  158. parents.add(parent);
  159. parent = parent.getParent();
  160. }
  161. DefaultMutableTreeNode root = getRoot();
  162. if (root != null) {
  163. parents.add((SimpleNode)root.getUserObject());
  164. }
  165. return new TreeVisitor.ByComponent<BlockTreeNode, BlockTreeNode>(currentBlockNode, converter) {
  166. @Override
  167. protected boolean contains(@NotNull BlockTreeNode pathComponent, @NotNull BlockTreeNode thisComponent) {
  168. return parents.contains(pathComponent);
  169. }
  170. };
  171. }
  172. private void selectBlockNode(@Nullable BlockTreeNode currentBlockNode) {
  173. if (myTreeModel == null) return;
  174. if (currentBlockNode != null) {
  175. TreeUtil.promiseSelect(myBlockTree, createVisitor(currentBlockNode));
  176. }
  177. else {
  178. myBlockTree.getSelectionModel().clearSelection();
  179. }
  180. }
  181. public class BlockTreeSelectionListener implements TreeSelectionListener {
  182. @NotNull
  183. private final PsiElement myRootElement;
  184. public BlockTreeSelectionListener(@NotNull PsiElement rootElement) {
  185. myRootElement = rootElement;
  186. }
  187. @Override
  188. public void valueChanged(@NotNull TreeSelectionEvent e) {
  189. if (myTreeModel == null) {
  190. return;
  191. }
  192. TreePath path = myBlockTree.getSelectionModel().getSelectionPath();
  193. if (path == null) return;
  194. DefaultMutableTreeNode component = (DefaultMutableTreeNode)path.getLastPathComponent();
  195. if (component == null) return;
  196. Object item = component.getUserObject();
  197. if (!(item instanceof BlockTreeNode)) return;
  198. BlockTreeNode descriptor = (BlockTreeNode)item;
  199. int blockStart = descriptor.getBlock().getTextRange().getStartOffset();
  200. PsiFile file = myRootElement.getContainingFile();
  201. PsiElement currentPsiEl = InjectedLanguageUtil.findElementAtNoCommit(file, blockStart);
  202. if (currentPsiEl == null) currentPsiEl = file;
  203. int blockLength = descriptor.getBlock().getTextRange().getLength();
  204. while (currentPsiEl.getParent() != null &&
  205. currentPsiEl.getTextRange().getStartOffset() == blockStart &&
  206. currentPsiEl.getTextLength() != blockLength) {
  207. currentPsiEl = currentPsiEl.getParent();
  208. }
  209. BlockTreeNode rootBlockNode = (BlockTreeNode)getRoot().getUserObject();
  210. int baseOffset = 0;
  211. if (rootBlockNode != null) {
  212. baseOffset = rootBlockNode.getBlock().getTextRange().getStartOffset();
  213. }
  214. TextRange range = descriptor.getBlock().getTextRange();
  215. range = range.shiftRight(-baseOffset);
  216. myUpdater.updatePsiTree(currentPsiEl, myBlockTree.hasFocus() ? range : null);
  217. }
  218. }
  219. @Nullable
  220. private BlockTreeNode findBlockNode(TextRange range) {
  221. if (myTreeModel == null || !myBlockStructurePanel.isVisible()) {
  222. return null;
  223. }
  224. DefaultMutableTreeNode root = getRoot();
  225. if (root == null) return null;
  226. BlockTreeNode node = (BlockTreeNode)root.getUserObject();
  227. main_loop:
  228. while (true) {
  229. if (node.getBlock().getTextRange().equals(range)) {
  230. return node;
  231. }
  232. for (BlockTreeNode child : node.getChildren()) {
  233. if (child.getBlock().getTextRange().contains(range)) {
  234. node = child;
  235. continue main_loop;
  236. }
  237. }
  238. return node;
  239. }
  240. }
  241. @Nullable
  242. private static Block buildBlocks(@NotNull PsiElement rootElement) {
  243. FormattingModelBuilder formattingModelBuilder = LanguageFormatting.INSTANCE.forContext(rootElement);
  244. CodeStyleSettings settings = CodeStyle.getSettings(rootElement.getContainingFile());
  245. if (formattingModelBuilder != null) {
  246. FormattingModel formattingModel = formattingModelBuilder.createModel(rootElement, settings);
  247. return formattingModel.getRootBlock();
  248. }
  249. else {
  250. return null;
  251. }
  252. }
  253. private void initMap(BlockTreeNode rootBlockNode, PsiElement psiEl) {
  254. myPsiToBlockMap = new HashMap<>();
  255. JBTreeTraverser<BlockTreeNode> traverser = JBTreeTraverser.of(BlockTreeNode::getChildren);
  256. for (BlockTreeNode block : traverser.withRoot(rootBlockNode)) {
  257. PsiElement currentElem = null;
  258. if (block.getBlock() instanceof ASTBlock) {
  259. ASTNode node = ((ASTBlock)block.getBlock()).getNode();
  260. if (node != null) {
  261. currentElem = node.getPsi();
  262. }
  263. }
  264. if (currentElem == null) {
  265. currentElem =
  266. InjectedLanguageUtil
  267. .findElementAtNoCommit(psiEl.getContainingFile(), block.getBlock().getTextRange().getStartOffset());
  268. }
  269. myPsiToBlockMap.put(currentElem, block);
  270. //nested PSI elements with same ranges will be mapped to one blockNode
  271. // assert currentElem != null; //for Scala-language plugin etc it can be null, because formatterBlocks is not instance of ASTBlock
  272. TextRange curTextRange = currentElem.getTextRange();
  273. PsiElement parentElem = currentElem.getParent();
  274. while (parentElem != null && parentElem.getTextRange().equals(curTextRange)) {
  275. myPsiToBlockMap.put(parentElem, block);
  276. parentElem = parentElem.getParent();
  277. }
  278. }
  279. }
  280. @Nullable
  281. private DefaultMutableTreeNode getRoot() {
  282. return (DefaultMutableTreeNode)myBlockTree.getModel().getRoot();
  283. }
  284. }