mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
move TraverserBasedASTNode to lang-impl
This commit is contained in:
@@ -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<ASTNode> getChildList();
|
||||
|
||||
@Override
|
||||
public ReadOnlyASTNode getTreeParent() {
|
||||
return myParent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends PsiElement> T getPsi(@NotNull Class<T> 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<ASTNode> 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<ASTNode> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<N> extends ReadOnlyASTNode {
|
||||
|
||||
@NotNull
|
||||
public static <N> FileASTNode createFileNode(@NotNull SyntaxTraverser<N> 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<N> myTraverser;
|
||||
|
||||
private final AtomicReference<PsiElement> myPsi = new AtomicReference<>();
|
||||
private final AtomicReference<List<ASTNode>> myKids = new AtomicReference<>();
|
||||
|
||||
public TraverserBasedASTNode(@NotNull N node, int index,
|
||||
@Nullable TraverserBasedASTNode<?> parent,
|
||||
@NotNull SyntaxTraverser<N> 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<ASTNode> getChildList() {
|
||||
List<ASTNode> kids = myKids.get();
|
||||
if (kids != null) return kids;
|
||||
myKids.compareAndSet(null, childrenImpl());
|
||||
return myKids.get();
|
||||
}
|
||||
|
||||
private List<ASTNode> childrenImpl() {
|
||||
List<ASTNode> 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<LighterASTNode> s = SyntaxTraverser.lightTraverser(builder);
|
||||
// avoid ShiftedNode double-shifting
|
||||
int shift = myTraverser.api.rangeOf(myNode).getStartOffset();
|
||||
List<ASTNode> childrenLazy = s.api.children(s.getRoot()).transform(CHILD_TRANSFORM(s, shift)).toList();
|
||||
return ContainerUtil.immutableList(childrenLazy);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected <NN> TraverserBasedASTNode<NN> createChildNode(int index, NN input, SyntaxTraverser<NN> 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 <NN> Function<NN, ASTNode> CHILD_TRANSFORM(final SyntaxTraverser<NN> s, final int shift) {
|
||||
return new Function<NN, ASTNode>() {
|
||||
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<ASTNode> 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<ASTNode> 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<ASTNode> 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<N> extends TraverserBasedASTNode<N> implements FileASTNode {
|
||||
private final PsiFile myPsiFile;
|
||||
|
||||
FileNode(@NotNull SyntaxTraverser<N> 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<N> extends TraverserBasedASTNode<N> {
|
||||
|
||||
private final int myShift;
|
||||
|
||||
ShiftedNode(TraverserBasedASTNode<?> parent, int index, int shift, @NotNull SyntaxTraverser<N> traverser, @NotNull N node) {
|
||||
super(node, index, parent, traverser);
|
||||
myShift = shift;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextRange getTextRange() {
|
||||
return super.getTextRange().shiftRight(myShift);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected <NN> TraverserBasedASTNode<NN> createChildNode(int index, NN input, SyntaxTraverser<NN> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user