/* * 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; import com.intellij.formatting.engine.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; import static com.intellij.formatting.InitialInfoBuilder.prepareToBuildBlocksSequentially; public class FormatProcessor { private static final Logger LOG = Logger.getInstance("#com.intellij.formatting.FormatProcessor"); private final WrapBlocksState myWrapState; private boolean myReformatContext; private final Document myDocument; @NotNull private final FormattingProgressCallback myProgressCallback; @NotNull private StateProcessor myStateProcessor; public FormatProcessor(final FormattingDocumentModel docModel, Block rootBlock, CodeStyleSettings settings, CommonCodeStyleSettings.IndentOptions indentOptions, @Nullable FormatTextRanges affectedRanges, @NotNull FormattingProgressCallback progressCallback) { this(docModel, rootBlock, new FormatOptions(settings, indentOptions, affectedRanges, false), progressCallback); } public FormatProcessor(final FormattingDocumentModel model, Block block, FormatOptions options, @NotNull FormattingProgressCallback callback) { myProgressCallback = callback; CommonCodeStyleSettings.IndentOptions defaultIndentOption = options.myIndentOptions; CodeStyleSettings settings = options.mySettings; BlockIndentOptions blockIndentOptions = new BlockIndentOptions(settings, defaultIndentOption, block); myDocument = model.getDocument(); myReformatContext = options.myReformatContext; final InitialInfoBuilder builder = prepareToBuildBlocksSequentially(block, model, options, settings, defaultIndentOption, myProgressCallback); myWrapState = new WrapBlocksState(builder, blockIndentOptions); FormatTextRanges ranges = options.myAffectedRanges; if (ranges != null && myReformatContext) { AdjustFormatRangesState adjustRangesState = new AdjustFormatRangesState(block, ranges); myStateProcessor = new StateProcessor(adjustRangesState); myStateProcessor.setNextState(myWrapState); } else { myStateProcessor = new StateProcessor(myWrapState); } } public BlockRangesMap getBlockRangesMap() { return myWrapState.getBlockRangesMap(); } public void format(FormattingModel model) { format(model, false); } public void format(FormattingModel model, boolean sequentially) { if (sequentially) { myStateProcessor.setNextState(new AdjustWhiteSpacesState(myWrapState, myProgressCallback, myReformatContext)); myStateProcessor.setNextState(new ExpandChildrenIndentState(myDocument, myWrapState)); myStateProcessor.setNextState(new ApplyChangesState(model, myWrapState, myProgressCallback)); } else { formatWithoutRealModifications(false); performModifications(model, false); } } public boolean iteration() { if (myStateProcessor.isDone()) { return true; } myStateProcessor.iteration(); return myStateProcessor.isDone(); } /** * Asks current processor to stop any active sequential processing if any. */ public void stopSequentialProcessing() { myStateProcessor.stop(); } public void formatWithoutRealModifications() { formatWithoutRealModifications(false); } public void formatWithoutRealModifications(boolean sequentially) { myStateProcessor.setNextState(new AdjustWhiteSpacesState(myWrapState, myProgressCallback, myReformatContext)); myStateProcessor.setNextState(new ExpandChildrenIndentState(myDocument, myWrapState)); if (sequentially) { return; } doIterationsSynchronously(); } public void performModifications(FormattingModel model) { performModifications(model, false); } public void performModifications(FormattingModel model, boolean sequentially) { myStateProcessor.setNextState(new ApplyChangesState(model, myWrapState, myProgressCallback)); if (sequentially) { return; } doIterationsSynchronously(); } private void doIterationsSynchronously() { while (!myStateProcessor.isDone()) { myStateProcessor.iteration(); } } public void setAllWhiteSpacesAreReadOnly() { LeafBlockWrapper current = myWrapState.getFirstBlock(); while (current != null) { current.getWhiteSpace().setReadOnly(true); current = current.getNextBlock(); } } public static class ChildAttributesInfo { public final AbstractBlockWrapper parent; public final ChildAttributes attributes; public final int index; public ChildAttributesInfo(final AbstractBlockWrapper parent, final ChildAttributes attributes, final int index) { this.parent = parent; this.attributes = attributes; this.index = index; } } public IndentInfo getIndentAt(final int offset) { LeafBlockWrapper current = processBlocksBefore(offset); AbstractBlockWrapper parent = getParentFor(offset, current); if (parent == null) { final LeafBlockWrapper previousBlock = current.getPreviousBlock(); if (previousBlock != null) parent = getParentFor(offset, previousBlock); if (parent == null) return new IndentInfo(0, 0, 0); } int index = getNewChildPosition(parent, offset); final Block block = myWrapState.getBlockToInfoMap().get(parent); if (block == null) { return new IndentInfo(0, 0, 0); } ChildAttributesInfo info = getChildAttributesInfo(block, index, parent); if (info == null) { return new IndentInfo(0, 0, 0); } IndentAdjuster adjuster = myWrapState.getIndentAdjuster(); return adjuster.adjustLineIndent(current, info); } @Nullable private static ChildAttributesInfo getChildAttributesInfo(@NotNull final Block block, final int index, @Nullable AbstractBlockWrapper parent) { if (parent == null) { return null; } ChildAttributes childAttributes = block.getChildAttributes(index); if (childAttributes == ChildAttributes.DELEGATE_TO_PREV_CHILD) { final Block newBlock = block.getSubBlocks().get(index - 1); AbstractBlockWrapper prevWrappedBlock; if (parent instanceof CompositeBlockWrapper) { prevWrappedBlock = ((CompositeBlockWrapper)parent).getChildren().get(index - 1); } else { prevWrappedBlock = parent.getPreviousBlock(); } return getChildAttributesInfo(newBlock, newBlock.getSubBlocks().size(), prevWrappedBlock); } else if (childAttributes == ChildAttributes.DELEGATE_TO_NEXT_CHILD) { AbstractBlockWrapper nextWrappedBlock; if (parent instanceof CompositeBlockWrapper) { List<AbstractBlockWrapper> children = ((CompositeBlockWrapper)parent).getChildren(); if (children != null && index < children.size()) { nextWrappedBlock = children.get(index); } else { return null; } } else { nextWrappedBlock = ((LeafBlockWrapper)parent).getNextBlock(); } return getChildAttributesInfo(block.getSubBlocks().get(index), 0, nextWrappedBlock); } else { return new ChildAttributesInfo(parent, childAttributes, index); } } private static int getNewChildPosition(final AbstractBlockWrapper parent, final int offset) { AbstractBlockWrapper parentBlockToUse = getLastNestedCompositeBlockForSameRange(parent); if (!(parentBlockToUse instanceof CompositeBlockWrapper)) return 0; final List<AbstractBlockWrapper> subBlocks = ((CompositeBlockWrapper)parentBlockToUse).getChildren(); //noinspection ConstantConditions if (subBlocks != null) { for (int i = 0; i < subBlocks.size(); i++) { AbstractBlockWrapper block = subBlocks.get(i); if (block.getStartOffset() >= offset) return i; } return subBlocks.size(); } else { return 0; } } @Nullable private static AbstractBlockWrapper getParentFor(final int offset, AbstractBlockWrapper block) { AbstractBlockWrapper current = block; while (current != null) { if (current.getStartOffset() < offset && current.getEndOffset() >= offset) { return current; } current = current.getParent(); } return null; } @Nullable private AbstractBlockWrapper getParentFor(final int offset, LeafBlockWrapper block) { AbstractBlockWrapper previous = getPreviousIncompleteBlock(block, offset); if (previous != null) { return getLastNestedCompositeBlockForSameRange(previous); } else { return getParentFor(offset, (AbstractBlockWrapper)block); } } @Nullable private AbstractBlockWrapper getPreviousIncompleteBlock(final LeafBlockWrapper block, final int offset) { if (block == null) { LeafBlockWrapper lastTokenBlock = myWrapState.getLastBlock(); if (lastTokenBlock.isIncomplete()) { return lastTokenBlock; } else { return null; } } AbstractBlockWrapper current = block; while (current.getParent() != null && current.getParent().getStartOffset() > offset) { current = current.getParent(); } if (current.getParent() == null) return null; if (current.getEndOffset() <= offset) { while (!current.isIncomplete() && current.getParent() != null && current.getParent().getEndOffset() <= offset) { current = current.getParent(); } if (current.isIncomplete()) return current; } if (current.getParent() == null) return null; final List<AbstractBlockWrapper> subBlocks = current.getParent().getChildren(); final int index = subBlocks.indexOf(current); if (index < 0) { LOG.assertTrue(false); } if (index == 0) return null; AbstractBlockWrapper currentResult = subBlocks.get(index - 1); if (!currentResult.isIncomplete()) return null; AbstractBlockWrapper lastChild = getLastChildOf(currentResult); while (lastChild != null && lastChild.isIncomplete()) { currentResult = lastChild; lastChild = getLastChildOf(currentResult); } return currentResult; } @Nullable private static AbstractBlockWrapper getLastChildOf(final AbstractBlockWrapper currentResult) { AbstractBlockWrapper parentBlockToUse = getLastNestedCompositeBlockForSameRange(currentResult); if (!(parentBlockToUse instanceof CompositeBlockWrapper)) return null; final List<AbstractBlockWrapper> subBlocks = ((CompositeBlockWrapper)parentBlockToUse).getChildren(); if (subBlocks.isEmpty()) return null; return subBlocks.get(subBlocks.size() - 1); } /** * There is a possible case that particular block is a composite block that contains number of nested composite blocks * that all target the same text range. This method allows to derive the most nested block that shares the same range (if any). * * @param block block to check * @return the most nested block of the given one that shares the same text range if any; given block otherwise */ @NotNull private static AbstractBlockWrapper getLastNestedCompositeBlockForSameRange(@NotNull final AbstractBlockWrapper block) { if (!(block instanceof CompositeBlockWrapper)) { return block; } AbstractBlockWrapper result = block; AbstractBlockWrapper candidate = block; while (true) { List<AbstractBlockWrapper> subBlocks = ((CompositeBlockWrapper)candidate).getChildren(); if (subBlocks == null || subBlocks.size() != 1) { break; } candidate = subBlocks.get(0); if (candidate.getStartOffset() == block.getStartOffset() && candidate.getEndOffset() == block.getEndOffset() && candidate instanceof CompositeBlockWrapper) { result = candidate; } else { break; } } return result; } private LeafBlockWrapper processBlocksBefore(final int offset) { AdjustWhiteSpacesState state = new AdjustWhiteSpacesState(myWrapState, myProgressCallback, myReformatContext); state.prepare(); LeafBlockWrapper last = null; while (!state.isDone() && state.getCurrentBlock().getStartOffset() < offset) { last = state.getCurrentBlock(); state.doIteration(); } return state.getCurrentBlock() != null ? state.getCurrentBlock() : last; } public LeafBlockWrapper getFirstTokenBlock() { return myWrapState.getFirstBlock(); } public WhiteSpace getLastWhiteSpace() { return myWrapState.getLastWhiteSpace(); } public static class FormatOptions { public CodeStyleSettings mySettings; public CommonCodeStyleSettings.IndentOptions myIndentOptions; public FormatTextRanges myAffectedRanges; public boolean myReformatContext; public int myInterestingOffset; public FormatOptions(CodeStyleSettings settings, CommonCodeStyleSettings.IndentOptions options, FormatTextRanges ranges, boolean reformatContext) { this(settings, options, ranges, reformatContext, -1); } public FormatOptions(CodeStyleSettings settings, CommonCodeStyleSettings.IndentOptions options, FormatTextRanges ranges, boolean reformatContext, int interestingOffset) { mySettings = settings; myIndentOptions = options; myAffectedRanges = ranges; myReformatContext = reformatContext; myInterestingOffset = interestingOffset; } } }