/* * Copyright 2000-2015 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.formatting.engine; import com.intellij.formatting.*; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; public class WrapProcessor { private LeafBlockWrapper myFirstWrappedBlockOnLine = null; private BlockRangesMap myBlockRangesMap; private LeafBlockWrapper myWrapCandidate = null; private IndentAdjuster myIndentAdjuster; private int myRightMargin; public WrapProcessor(BlockRangesMap blockHelper, IndentAdjuster indentAdjuster, int rightMargin) { myIndentAdjuster = indentAdjuster; myBlockRangesMap = blockHelper; myRightMargin = rightMargin; } private boolean isSuitableInTheCurrentPosition(final WrapImpl wrap, LeafBlockWrapper currentBlock) { if (wrap.getWrapOffset() < currentBlock.getStartOffset()) { return true; } if (wrap.isWrapFirstElement()) { return true; } if (wrap.getType() == WrapImpl.Type.WRAP_AS_NEEDED) { return positionAfterWrappingIsSuitable(currentBlock); } return wrap.getType() == WrapImpl.Type.CHOP_IF_NEEDED && lineOver(currentBlock) && positionAfterWrappingIsSuitable(currentBlock); } private boolean lineOver(LeafBlockWrapper currentBlock) { return !currentBlock.containsLineFeeds() && CoreFormatterUtil.getStartColumn(currentBlock) + currentBlock.getLength() > myRightMargin; } /** * Ensures that offset of the currentBlock is not increased if we make a wrap on it. */ private boolean positionAfterWrappingIsSuitable(LeafBlockWrapper currentBlock) { final WhiteSpace whiteSpace = currentBlock.getWhiteSpace(); if (whiteSpace.containsLineFeeds()) return true; final int spaces = whiteSpace.getSpaces(); int indentSpaces = whiteSpace.getIndentSpaces(); try { final int startColumnNow = CoreFormatterUtil.getStartColumn(currentBlock); whiteSpace.ensureLineFeed(); myIndentAdjuster.adjustLineIndent(currentBlock); final int startColumnAfterWrap = CoreFormatterUtil.getStartColumn(currentBlock); return startColumnNow > startColumnAfterWrap; } finally { whiteSpace.removeLineFeeds(currentBlock.getSpaceProperty(), myBlockRangesMap); whiteSpace.setSpaces(spaces, indentSpaces); } } @Nullable private WrapImpl getWrapToBeUsed(final ArrayList<WrapImpl> wraps, LeafBlockWrapper currentBlock) { if (wraps.isEmpty()) { return null; } if (myWrapCandidate == currentBlock) return wraps.get(0); for (final WrapImpl wrap : wraps) { if (!isSuitableInTheCurrentPosition(wrap, currentBlock)) continue; if (wrap.isActive()) return wrap; final WrapImpl.Type type = wrap.getType(); if (type == WrapImpl.Type.WRAP_ALWAYS) return wrap; if (type == WrapImpl.Type.WRAP_AS_NEEDED || type == WrapImpl.Type.CHOP_IF_NEEDED) { if (lineOver(currentBlock)) { return wrap; } } } return null; } private boolean isCandidateToBeWrapped(final WrapImpl wrap, LeafBlockWrapper currentBlock) { return isSuitableInTheCurrentPosition(wrap, currentBlock) && (wrap.getType() == WrapImpl.Type.WRAP_AS_NEEDED || wrap.getType() == WrapImpl.Type.CHOP_IF_NEEDED) && !currentBlock.getWhiteSpace().isReadOnly(); } /** * Allows to answer if wrap of the {@link #myWrapCandidate} object (if any) may be replaced by the given wrap. * * @param wrap wrap candidate to check * @return {@code true} if wrap of the {@link #myWrapCandidate} object (if any) may be replaced by the given wrap; * {@code false} otherwise */ private boolean canReplaceWrapCandidate(WrapImpl wrap, LeafBlockWrapper currentBlock) { if (myWrapCandidate == null) return true; WrapImpl.Type type = wrap.getType(); if (wrap.isActive() && (type == WrapImpl.Type.CHOP_IF_NEEDED || type == WrapImpl.Type.WRAP_ALWAYS)) return true; final WrapImpl currentWrap = myWrapCandidate.getWrap(); return wrap == currentWrap || !wrap.isChildOf(currentWrap, currentBlock); } public LeafBlockWrapper processWrap(LeafBlockWrapper currentBlock) { final SpacingImpl spacing = currentBlock.getSpaceProperty(); final WhiteSpace whiteSpace = currentBlock.getWhiteSpace(); final boolean wrapWasPresent = whiteSpace.containsLineFeeds(); if (wrapWasPresent) { myFirstWrappedBlockOnLine = null; if (!whiteSpace.containsLineFeedsInitially()) { whiteSpace.removeLineFeeds(spacing, myBlockRangesMap); } } final boolean wrapIsPresent = whiteSpace.containsLineFeeds(); final ArrayList<WrapImpl> wraps = currentBlock.getWraps(); for (WrapImpl wrap : wraps) { wrap.setWrapOffset(currentBlock.getStartOffset()); } final WrapImpl wrap = getWrapToBeUsed(wraps, currentBlock); if (wrap != null || wrapIsPresent) { if (!wrapIsPresent && !canReplaceWrapCandidate(wrap, currentBlock)) { return myWrapCandidate; } if (wrap != null && wrap.getChopStartBlock() != null) { // getWrapToBeUsed() returns the block only if it actually exceeds the right margin. In this case, we need to go back to the // first block that has the CHOP_IF_NEEDED wrap type and start wrapping from there. LeafBlockWrapper newCurrentBlock = wrap.getChopStartBlock(); wrap.setActive(); return newCurrentBlock; } if (wrap != null && isChopNeeded(wrap, currentBlock)) { wrap.setActive(); } if (!wrapIsPresent) { whiteSpace.ensureLineFeed(); if (!wrapWasPresent) { if (myFirstWrappedBlockOnLine != null && wrap.isChildOf(myFirstWrappedBlockOnLine.getWrap(), currentBlock)) { wrap.ignoreParentWrap(myFirstWrappedBlockOnLine.getWrap(), currentBlock); return myFirstWrappedBlockOnLine; } else { myFirstWrappedBlockOnLine = currentBlock; } } } myWrapCandidate = null; } else { for (final WrapImpl wrap1 : wraps) { if (isCandidateToBeWrapped(wrap1, currentBlock) && canReplaceWrapCandidate(wrap1, currentBlock)) { myWrapCandidate = currentBlock; } if (isChopNeeded(wrap1, currentBlock)) { wrap1.saveChopBlock(currentBlock); } } } if (!whiteSpace.containsLineFeeds() && myWrapCandidate != null && !whiteSpace.isReadOnly() && lineOver(currentBlock)) { return myWrapCandidate; } return null; } private boolean isChopNeeded(final WrapImpl wrap, LeafBlockWrapper currentBlock) { return wrap != null && wrap.getType() == WrapImpl.Type.CHOP_IF_NEEDED && isSuitableInTheCurrentPosition(wrap, currentBlock); } public void reset() { myWrapCandidate = null; } public void onCurrentLineChanged() { myWrapCandidate = null; } }