/* * Copyright 2000-2016 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.psi.impl.source.codeStyle; import com.intellij.lang.Language; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Conditions; import com.intellij.psi.tree.IElementType; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * @author Rustam Vishnyakov */ public abstract class SemanticEditorPosition { public interface SyntaxElement {} private final EditorEx myEditor; private final HighlighterIterator myIterator; private final CharSequence myChars; public SemanticEditorPosition(@NotNull EditorEx editor, int offset) { myEditor = editor; myChars = editor.getDocument().getCharsSequence(); myIterator = editor.getHighlighter().createIterator(offset); } public SemanticEditorPosition beforeOptional(@NotNull SyntaxElement syntaxElement) { if (!myIterator.atEnd()) { if (syntaxElement.equals(map(myIterator.getTokenType()))) myIterator.retreat(); } return this; } public SemanticEditorPosition beforeOptionalMix(@NotNull SyntaxElement... elements) { while (isAtAnyOf(elements)) { myIterator.retreat(); } return this; } public SemanticEditorPosition afterOptionalMix(@NotNull SyntaxElement... elements) { while (isAtAnyOf(elements)) { myIterator.advance(); } return this; } public boolean isAtMultiline() { if (!myIterator.atEnd()) { return CharArrayUtil.containLineBreaks(myChars, myIterator.getStart(), myIterator.getEnd()); } return false; } public SemanticEditorPosition before() { if (!myIterator.atEnd()) { myIterator.retreat(); } return this; } public SemanticEditorPosition afterOptional(@NotNull SyntaxElement syntaxElement) { if (!myIterator.atEnd()) { if (syntaxElement.equals(map(myIterator.getTokenType()))) myIterator.advance(); } return this; } public SemanticEditorPosition after() { if (!myIterator.atEnd()) { myIterator.advance(); } return this; } public SemanticEditorPosition beforeParentheses(@NotNull SyntaxElement leftParenthesis, @NotNull SyntaxElement rightParenthesis) { int parenLevel = 0; while (!myIterator.atEnd()) { SyntaxElement currElement = map(myIterator.getTokenType()); myIterator.retreat(); if (rightParenthesis.equals(currElement)) { parenLevel++; } else if (leftParenthesis.equals(currElement)) { parenLevel--; if (parenLevel < 1) { break; } } } return this; } public SemanticEditorPosition findLeftParenthesisBackwardsSkippingNested(@NotNull SyntaxElement leftParenthesis, @NotNull SyntaxElement rightParenthesis) { return findLeftParenthesisBackwardsSkippingNested(leftParenthesis, rightParenthesis, Conditions.alwaysFalse()); } public SemanticEditorPosition findLeftParenthesisBackwardsSkippingNested(@NotNull SyntaxElement leftParenthesis, @NotNull SyntaxElement rightParenthesis, @NotNull Condition<SyntaxElement> terminationCondition) { while (!myIterator.atEnd()) { if (terminationCondition.value(map(myIterator.getTokenType()))) { break; } if (rightParenthesis.equals(map(myIterator.getTokenType()))) { beforeParentheses(leftParenthesis, rightParenthesis); } else if (leftParenthesis.equals(map(myIterator.getTokenType()))) { break; } myIterator.retreat(); } return this; } public boolean isAfterOnSameLine(@NotNull SyntaxElement... syntaxElements) { myIterator.retreat(); while (!myIterator.atEnd() && !isAtMultiline()) { SyntaxElement currElement = map(myIterator.getTokenType()); for (SyntaxElement element : syntaxElements) { if (element.equals(currElement)) return true; } myIterator.retreat(); } return false; } public boolean isAt(@NotNull SyntaxElement syntaxElement) { return !myIterator.atEnd() && syntaxElement.equals(map(myIterator.getTokenType())); } public boolean isAt(@NotNull IElementType elementType) { return !myIterator.atEnd() && myIterator.getTokenType() == elementType; } public boolean isAtEnd() { return myIterator.atEnd(); } public int getStartOffset() { return myIterator.getStart(); } @SuppressWarnings("unused") public boolean isAtAnyOf(@NotNull SyntaxElement... syntaxElements) { if (!myIterator.atEnd()) { SyntaxElement currElement = map(myIterator.getTokenType()); for (SyntaxElement element : syntaxElements) { if (element.equals(currElement)) return true; } } return false; } public CharSequence getChars() { return myChars; } public int findStartOf(@NotNull SyntaxElement element) { while (!myIterator.atEnd()) { if (element.equals(map(myIterator.getTokenType()))) return myIterator.getStart(); myIterator.retreat(); } return -1; } public boolean hasEmptyLineAfter(int offset) { for (int i = offset + 1; i < myIterator.getEnd(); i++) { if (myChars.charAt(i) == '\n') return true; } return false; } public EditorEx getEditor() { return myEditor; } @Nullable public Language getLanguage() { return !myIterator.atEnd() ? myIterator.getTokenType().getLanguage() : null; } public boolean isAtLanguage(@Nullable Language language) { if (language != null && !myIterator.atEnd()) { return language== Language.ANY || myIterator.getTokenType().getLanguage().is(language); } return false; } @Nullable public SyntaxElement getCurrElement() { return !myIterator.atEnd() ? map(myIterator.getTokenType()) : null; } public boolean matchesRule(@NotNull Rule rule) { return rule.check(this); } public interface Rule { boolean check(SemanticEditorPosition position); } public abstract SyntaxElement map(@NotNull IElementType elementType); @Override public String toString() { return myIterator.getTokenType().toString(); } }