diff --git a/platform/lang-impl/src/com/intellij/lang/ReadOnlyASTNode.java b/platform/lang-impl/src/com/intellij/lang/ReadOnlyASTNode.java new file mode 100644 index 000000000000..0915413d17a3 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/lang/ReadOnlyASTNode.java @@ -0,0 +1,215 @@ +/* + * Copyright 2000-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.lang; + +import com.intellij.openapi.util.UserDataHolderBase; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.SyntaxTraverser; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.TokenSet; +import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.TreeTraversal; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author gregsh + */ +public abstract class ReadOnlyASTNode extends UserDataHolderBase implements ASTNode { + + private final ReadOnlyASTNode myParent; + private final int myIndex; + + public ReadOnlyASTNode(@Nullable ReadOnlyASTNode parent, int index) { + myParent = parent; + myIndex = index; + } + + public abstract List getChildList(); + + @Override + public ReadOnlyASTNode getTreeParent() { + return myParent; + } + + @Override + public T getPsi(@NotNull Class clazz) { + return ObjectUtils.tryCast(getPsi(), clazz); + } + + @Override + public ASTNode getFirstChildNode() { + return ContainerUtil.getFirstItem(getChildList()); + } + + @Override + public ASTNode getLastChildNode() { + return ContainerUtil.getLastItem(getChildList()); + } + + @Override + public ASTNode getTreeNext() { + List list = getTreeParent().getChildList(); + return list.size() > myIndex + 1? list.get(myIndex + 1) : null; + } + + @Override + public ASTNode getTreePrev() { + return myIndex > 0 ? getTreeParent().getChildList().get(myIndex - 1) : null; + } + + @NotNull + @Override + public ASTNode[] getChildren(@Nullable final TokenSet filter) { + List list = getChildList(); + if (list.isEmpty()) return EMPTY_ARRAY; + if (filter != null) { + int count = 0; + for (ASTNode node : list) { + if (filter.contains(node.getElementType())) count ++; + } + ASTNode[] result = new ASTNode[count]; + count = 0; + for (ASTNode node : list) { + if (filter.contains(node.getElementType())) result[count ++] = node; + } + return result; + } + else { + return list.toArray(new ASTNode[list.size()]); + } + } + + @NotNull + @Override + public String getText() { + return getChars().toString(); + } + + @Override + public boolean textContains(char c) { + return StringUtil.indexOf(getChars(), c) >= 0; + } + + @Override + public int getStartOffset() { + return getTextRange().getStartOffset(); + } + + @Override + public int getTextLength() { + return getTextRange().getLength(); + } + + @NotNull + @Override + public ReadOnlyASTNode clone() { + return this; + } + + @Override + public void addChild(@NotNull ASTNode child) { + throw new UnsupportedOperationException(); + } + + @Override + public void addChild(@NotNull ASTNode child, @Nullable ASTNode anchorBefore) { + throw new UnsupportedOperationException(); + } + + @Override + public void addLeaf(@NotNull IElementType leafType, CharSequence leafText, @Nullable ASTNode anchorBefore) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeChild(@NotNull ASTNode child) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeRange(@NotNull ASTNode firstNodeToRemove, ASTNode firstNodeToKeep) { + throw new UnsupportedOperationException(); + } + + @Override + public void replaceChild(@NotNull ASTNode oldChild, @NotNull ASTNode newChild) { + throw new UnsupportedOperationException(); + } + + @Override + public void replaceAllChildrenToChildrenOf(ASTNode anotherParent) { + throw new UnsupportedOperationException(); + } + + @Override + public void addChildren(ASTNode firstChild, ASTNode firstChildToNotAdd, ASTNode anchorBefore) { + throw new UnsupportedOperationException(); + } + + @Override + public ASTNode copyElement() { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public ASTNode findLeafElementAt(int offset) { + if (!getTextRange().contains(offset)) return null; + return SyntaxTraverser.astTraverser(this) + .expandAndFilter(n -> n.getTextRange().contains(offset)) + .traverse(TreeTraversal.LEAVES_DFS) + .first(); + } + + @Nullable + @Override + public ASTNode findChildByType(IElementType type) { + return findChildByType(type, null); + } + + @Nullable + @Override + public ASTNode findChildByType(IElementType type, @Nullable ASTNode anchor) { + boolean flag = anchor == null; + for (ASTNode node : getChildren(null)) { + if (!flag && node.equals(anchor)) flag = true; + if (flag && node.getElementType() == type) return node; + } + return null; + } + + @Nullable + @Override + public ASTNode findChildByType(@NotNull TokenSet typesSet) { + return findChildByType(typesSet, null); + } + + @Nullable + @Override + public ASTNode findChildByType(@NotNull TokenSet typesSet, @Nullable ASTNode anchor) { + boolean flag = anchor == null; + for (ASTNode node : getChildren(null)) { + if (!flag && node.equals(anchor)) flag = true; + if (flag && typesSet.contains(node.getElementType())) return node; + } + return null; + } +} diff --git a/platform/lang-impl/src/com/intellij/lang/TraverserBasedASTNode.java b/platform/lang-impl/src/com/intellij/lang/TraverserBasedASTNode.java new file mode 100644 index 000000000000..099111178a78 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/lang/TraverserBasedASTNode.java @@ -0,0 +1,285 @@ +/* + * Copyright 2000-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.lang; + +import com.intellij.extapi.psi.ASTWrapperPsiElement; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.IdentityCharTable; +import com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.ILazyParseableElementType; +import com.intellij.testFramework.LightVirtualFile; +import com.intellij.util.CharTable; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author gregsh + */ +public class TraverserBasedASTNode extends ReadOnlyASTNode { + + @NotNull + public static FileASTNode createFileNode(@NotNull SyntaxTraverser traverser, @NotNull N node, @NotNull PsiFile psiFile) { + return new FileNode<>(traverser, node, psiFile); + } + + @NotNull + public static FileViewProvider createViewProvider(@NotNull String fileName, @NotNull Language language, @NotNull PsiManager psiManager) { + return new ReadOnlyViewProvider(fileName, language, psiManager); + } + + + protected final N myNode; + protected final SyntaxTraverser myTraverser; + + private final AtomicReference myPsi = new AtomicReference<>(); + private final AtomicReference> myKids = new AtomicReference<>(); + + public TraverserBasedASTNode(@NotNull N node, int index, + @Nullable TraverserBasedASTNode parent, + @NotNull SyntaxTraverser traverser) { + super(parent, index); + myTraverser = traverser; + myNode = node; + } + + @NotNull + @Override + public IElementType getElementType() { + return myTraverser.api.typeOf(myNode); + } + + @NotNull + @Override + public CharSequence getChars() { + return myTraverser.api.textOf(myNode); + } + + @Override + public TextRange getTextRange() { + return myTraverser.api.rangeOf(myNode); + } + + @Override + public List getChildList() { + List kids = myKids.get(); + if (kids != null) return kids; + myKids.compareAndSet(null, childrenImpl()); + return myKids.get(); + } + + private List childrenImpl() { + List children = myTraverser.children(myNode).transform(CHILD_TRANSFORM(myTraverser, 0)).toList(); + if (!children.isEmpty() || getTreeParent() == null) return ContainerUtil.immutableList(children); + + // expand (parse) non-file lazy-parseable nodes + IElementType type = myTraverser.api.typeOf(myNode); + if (!(type instanceof ILazyParseableElementType)) return ContainerUtil.immutableList(children); + PsiBuilder builder = ((ILazyParseableElementType)type).parseLight(this); + SyntaxTraverser s = SyntaxTraverser.lightTraverser(builder); + // avoid ShiftedNode double-shifting + int shift = myTraverser.api.rangeOf(myNode).getStartOffset(); + List childrenLazy = s.api.children(s.getRoot()).transform(CHILD_TRANSFORM(s, shift)).toList(); + return ContainerUtil.immutableList(childrenLazy); + } + + @NotNull + protected TraverserBasedASTNode createChildNode(int index, NN input, SyntaxTraverser s, int shift) { + return shift == 0 ? new TraverserBasedASTNode<>(input, index, this, s) : + new ShiftedNode<>(this, index, shift, s, input); + } + + @Override + public PsiElement getPsi() { + PsiElement psi = myPsi.get(); + if (psi != null) return psi; + myPsi.compareAndSet(null, getPsiImpl()); + return myPsi.get(); + } + + public PsiElement getPsiImpl() { + IElementType type = getElementType(); + ParserDefinition pd = LanguageParserDefinitions.INSTANCE.forLanguage(type.getLanguage()); + if (pd != null && pd.getWhitespaceTokens().contains(type) || + pd == null && type == TokenType.WHITE_SPACE) { + return new PsiWhiteSpaceImpl(getChars()); + } + else if (pd != null && pd.getCommentTokens().contains(type)) { + return new ASTWrapperPsiComment(this); + } + else if (pd == null || myNode instanceof LighterASTTokenNode) { + return new ASTWrapperPsiElement(this); + } + else { + return pd.createElement(this); + } + } + + @NotNull + private Function CHILD_TRANSFORM(final SyntaxTraverser s, final int shift) { + return new Function() { + int index = 0; + + @Override + public ASTNode fun(@Nullable NN input) { + return createChildNode(index++, input, s, shift); + } + }; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TraverserBasedASTNode)) return false; + return Comparing.equal(myNode, ((TraverserBasedASTNode)obj).myNode); + } + + @Override + public int hashCode() { + return myNode.hashCode(); + } + + @NotNull + public static PsiElement[] getChildrenAsPsiArray(@NotNull ASTNode node) { + List list = ((ReadOnlyASTNode)node).getChildList(); + int idx = 0; + for (ASTNode o : list) { + if (o.getPsi() != null) idx++; + } + PsiElement[] result = new PsiElement[idx]; + idx = 0; + for (ASTNode o : list) { + PsiElement psi = o.getPsi(); + if (psi != null) { + result[idx++] = psi; + } + } + return result; + } + + @Nullable + public static PsiElement getFirstPsiChild(@NotNull ASTNode node) { + List list = ((ReadOnlyASTNode)node).getChildList(); + for (ASTNode o : list) { + PsiElement psi = o.getPsi(); + if (psi != null) return psi; + } + return null; + } + + @Nullable + public static PsiElement getLastPsiChild(@NotNull ASTNode node) { + List list = ((ReadOnlyASTNode)node).getChildList(); + for (int i = list.size() - 1; i >= 0; i--) { + ASTNode o = list.get(i); + PsiElement psi = o.getPsi(); + if (psi != null) return psi; + } + return null; + } + + private static class FileNode extends TraverserBasedASTNode implements FileASTNode { + private final PsiFile myPsiFile; + + FileNode(@NotNull SyntaxTraverser traverser, @NotNull N node, @NotNull PsiFile psiFile) { + super(node, -1, null, traverser); + myPsiFile = psiFile; + ((ReadOnlyViewProvider)psiFile.getViewProvider()).forceCachedPsi(psiFile); + } + + @Override + public PsiElement getPsi() { + return myPsiFile; + } + + @NotNull + @Override + public CharTable getCharTable() { + return IdentityCharTable.INSTANCE; + } + + @Override + public boolean isParsed() { + return true; + } + + @NotNull + @Override + public LighterAST getLighterAST() { + return new TreeBackedLighterAST(this); + } + } + + private static class ShiftedNode extends TraverserBasedASTNode { + + private final int myShift; + + ShiftedNode(TraverserBasedASTNode parent, int index, int shift, @NotNull SyntaxTraverser traverser, @NotNull N node) { + super(node, index, parent, traverser); + myShift = shift; + } + + @Override + public TextRange getTextRange() { + return super.getTextRange().shiftRight(myShift); + } + + @NotNull + @Override + protected TraverserBasedASTNode createChildNode(int index, NN input, SyntaxTraverser s, int shift) { + return new ShiftedNode<>(this, index, myShift + shift, s, input); + } + } + + private static class ASTWrapperPsiComment extends ASTWrapperPsiElement implements PsiComment { + public ASTWrapperPsiComment(@NotNull ASTNode node) { + super(node); + } + + @Override + public IElementType getTokenType() { + return getNode().getElementType(); + } + } + + private static class ReadOnlyViewProvider extends SingleRootFileViewProvider { + + ReadOnlyViewProvider(@NotNull String name, @NotNull Language language, @NotNull PsiManager psiManager) { + super(psiManager, new LightVirtualFile(name, language, ""), false); + } + + @Nullable + @Override + protected PsiFile createFile(@NotNull Project project, @NotNull VirtualFile file, @NotNull FileType fileType) { + throw new UnsupportedOperationException("Should not try to create mutable PSI"); + } + + @Nullable + @Override + protected PsiFile createFile(@NotNull Language lang) { + throw new UnsupportedOperationException("Should not try to create mutable PSI"); + } + } +}