();
-
- private LeafBlockWrapper myWrapCandidate = null;
- private LeafBlockWrapper myFirstWrappedBlockOnLine = null;
-
- private LeafBlockWrapper myFirstTokenBlock;
- private LeafBlockWrapper myLastTokenBlock;
-
- /**
- * Formatter provides a notion of {@link DependantSpacingImpl dependent spacing}, i.e. spacing that insist on line feed if target
- * dependent region contains line feed.
- *
- * Example:
- *
- * int[] data = {1, 2, 3};
- *
- * We want to keep that in one line if possible but place curly braces on separate lines if the width is not enough:
- *
- * int[] data = { | < right margin
- * 1, 2, 3 |
- * } |
- *
- * There is a possible case that particular block has dependent spacing property that targets region that lays beyond the
- * current block. E.g. consider example above - '1' block has dependent spacing that targets the whole
- * '{1, 2, 3}' block. So, it's not possible to answer whether line feed should be used during processing block
- * '1'.
- *
- * We store such 'forward dependencies' at the current collection where the key is the range of the target 'dependent forward
- * region' and value is dependent spacing object.
- *
- * Every time we detect that formatter changes 'has line feeds' status of such dependent region, we
- * {@link DependantSpacingImpl#setDependentRegionLinefeedStatusChanged() mark} the dependent spacing as changed and schedule one more
- * formatting iteration.
- */
- private SortedMap myPreviousDependencies =
- new TreeMap(new Comparator() {
- @Override
- public int compare(final TextRange o1, final TextRange o2) {
- int offsetsDelta = o1.getEndOffset() - o2.getEndOffset();
-
- if (offsetsDelta == 0) {
- offsetsDelta = o2.getStartOffset() - o1.getStartOffset(); // starting earlier is greater
- }
- return offsetsDelta;
- }
- });
-
- private final HashSet myAlignAgain = new HashSet();
+ private final Document myDocument;
+
@NotNull
private final FormattingProgressCallback myProgressCallback;
- private WhiteSpace myLastWhiteSpace;
- private boolean myDisposed;
- private CommonCodeStyleSettings.IndentOptions myJavaIndentOptions;
- private final int myRightMargin;
-
@NotNull
- private State myCurrentState;
- private MultiMap myExpandableIndents;
- private int myTotalBlocksWithAlignments;
- private int myBlockRollbacks;
-
- private AlignmentCyclesDetector myCyclesDetector;
+ private StateProcessor myStateProcessor;
public FormatProcessor(final FormattingDocumentModel docModel,
Block rootBlock,
@@ -178,91 +51,39 @@ public class FormatProcessor {
this(docModel, rootBlock, new FormatOptions(settings, indentOptions, affectedRanges, false), progressCallback);
}
- public FormatProcessor(FormattingDocumentModel model,
+ public FormatProcessor(final FormattingDocumentModel model,
Block block,
FormatOptions options,
@NotNull FormattingProgressCallback callback)
{
myProgressCallback = callback;
- myDefaultIndentOption = options.myIndentOptions;
- mySettings = options.mySettings;
+
+ CommonCodeStyleSettings.IndentOptions defaultIndentOption = options.myIndentOptions;
+ CodeStyleSettings settings = options.mySettings;
+ BlockIndentOptions blockIndentOptions = new BlockIndentOptions(settings, defaultIndentOption, block);
+
myDocument = model.getDocument();
myReformatContext = options.myReformatContext;
- myCurrentState = new WrapBlocksState(block, model, options.myAffectedRanges, options.myInterestingOffset);
- myRightMargin = getRightMargin(block);
+
+ final InitialInfoBuilder builder = prepareToBuildBlocksSequentially(block, model, options, settings, defaultIndentOption, myProgressCallback);
+ myWrapState = new WrapBlocksState(builder, blockIndentOptions);
+
+ myStateProcessor = new StateProcessor(myWrapState);
}
-
- private int getRightMargin(Block rootBlock) {
- Language language = null;
- if (rootBlock instanceof ASTBlock) {
- ASTNode node = ((ASTBlock)rootBlock).getNode();
- if (node != null) {
- PsiElement psiElement = node.getPsi();
- if (psiElement.isValid()) {
- PsiFile psiFile = psiElement.getContainingFile();
- if (psiFile != null) {
- language = psiFile.getViewProvider().getBaseLanguage();
- }
- }
- }
- }
- return mySettings.getRightMargin(language);
- }
-
- private LeafBlockWrapper getLastBlock() {
- LeafBlockWrapper result = myFirstTokenBlock;
- while (result.getNextBlock() != null) {
- result = result.getNextBlock();
- }
- return result;
- }
-
- private static TIntObjectHashMap buildTextRangeToInfoMap(final LeafBlockWrapper first) {
- final TIntObjectHashMap result = new TIntObjectHashMap();
- LeafBlockWrapper current = first;
- while (current != null) {
- result.put(current.getStartOffset(), current);
- current = current.getNextBlock();
- }
- return result;
+
+ public BlockRangesMap getBlockRangesMap() {
+ return myWrapState.getBlockRangesMap();
}
public void format(FormattingModel model) {
format(model, false);
}
- /**
- * Asks current processor to perform formatting.
- *
- * There are two processing approaches at the moment:
- *
- *
- * - perform formatting during the current method call;
- * -
- * split the whole formatting process to the set of fine-grained tasks and execute them sequentially during
- * subsequent {@link #iteration()} calls;
- *
- *
- *
- *
- * Here is rationale for the second approach - formatting may introduce changes to the underlying document and IntelliJ IDEA
- * is designed in a way that write access is allowed from EDT only. That means that every time we execute particular action
- * from EDT we have no chance of performing any other actions from EDT simultaneously (e.g. we may want to show progress bar
- * that reflects current formatting state but the progress bar can' bet updated if formatting is performed during a single long
- * method call). So, we can interleave formatting iterations with GUI state updates.
- *
- * @param model target formatting model
- * @param sequentially flag that indicates what kind of processing should be used
- */
public void format(FormattingModel model, boolean sequentially) {
if (sequentially) {
- AdjustWhiteSpacesState adjustState = new AdjustWhiteSpacesState();
- ExpandChildrenIndent expandChildrenIndent = new ExpandChildrenIndent();
- ApplyChangesState applyChangesState = new ApplyChangesState(model);
-
- expandChildrenIndent.setNext(applyChangesState);
- adjustState.setNext(expandChildrenIndent);
- myCurrentState.setNext(adjustState);
+ myStateProcessor.setNextState(new AdjustWhiteSpacesState(myWrapState, myProgressCallback, myReformatContext));
+ myStateProcessor.setNextState(new ExpandChildrenIndentState(myDocument, myWrapState));
+ myStateProcessor.setNextState(new ApplyChangesState(model, myWrapState, myProgressCallback));
}
else {
formatWithoutRealModifications(false);
@@ -270,52 +91,32 @@ public class FormatProcessor {
}
}
- /**
- * Asks current processor to perform processing iteration
- *
- * @return true if the processing is finished; false otherwise
- * @see #format(FormattingModel, boolean)
- */
public boolean iteration() {
- if (myCurrentState.isDone()) {
+ if (myStateProcessor.isDone()) {
return true;
}
- myCurrentState.iteration();
- return myCurrentState.isDone();
+ myStateProcessor.iteration();
+ return myStateProcessor.isDone();
}
/**
* Asks current processor to stop any active sequential processing if any.
*/
public void stopSequentialProcessing() {
- myCurrentState.stop();
+ myStateProcessor.stop();
}
public void formatWithoutRealModifications() {
formatWithoutRealModifications(false);
}
- @SuppressWarnings({"WhileLoopSpinsOnField"})
public void formatWithoutRealModifications(boolean sequentially) {
- AdjustWhiteSpacesState adjustSpace = new AdjustWhiteSpacesState();
- adjustSpace.setNext(new ExpandChildrenIndent());
- myCurrentState.setNext(adjustSpace);
-
+ myStateProcessor.setNextState(new AdjustWhiteSpacesState(myWrapState, myProgressCallback, myReformatContext));
+ myStateProcessor.setNextState(new ExpandChildrenIndentState(myDocument, myWrapState));
if (sequentially) {
return;
}
-
- doIterationsSynchronously(FormattingStateId.PROCESSING_BLOCKS);
- }
-
- private void reset() {
- myBackwardShiftedAlignedBlocks.clear();
- myAlignmentMappings.clear();
- myPreviousDependencies.clear();
- myWrapCandidate = null;
- if (myRootBlockWrapper != null) {
- myRootBlockWrapper.reset();
- }
+ doIterationsSynchronously();
}
public void performModifications(FormattingModel model) {
@@ -323,710 +124,33 @@ public class FormatProcessor {
}
public void performModifications(FormattingModel model, boolean sequentially) {
- assert !myDisposed;
- myCurrentState.setNext(new ApplyChangesState(model));
+ myStateProcessor.setNextState(new ApplyChangesState(model, myWrapState, myProgressCallback));
if (sequentially) {
return;
}
- doIterationsSynchronously(FormattingStateId.APPLYING_CHANGES);
+ doIterationsSynchronously();
}
- /**
- * Perform iterations against the {@link #myCurrentState current state} until it's {@link FormattingStateId type}
- * is {@link FormattingStateId#getPreviousStates() less} or equal to the given state.
- *
- * @param state target state to process
- */
- private void doIterationsSynchronously(@NotNull FormattingStateId state) {
- while ((myCurrentState.getStateId() == state || state.getPreviousStates().contains(myCurrentState.getStateId()))
- && !myCurrentState.isDone())
- {
- myCurrentState.iteration();
- }
- }
-
- public void setJavaIndentOptions(final CommonCodeStyleSettings.IndentOptions javaIndentOptions) {
- myJavaIndentOptions = javaIndentOptions;
- }
-
- /**
- * Performs formatter changes in a series of blocks, for each block a new contents of document is calculated
- * and whole document is replaced in one operation.
- *
- * @param blocksToModify changes introduced by formatter
- * @param model current formatting model
- * @param indentOption indent options to use
- */
- @SuppressWarnings({"deprecation"})
- private void applyChangesAtRewriteMode(@NotNull final List blocksToModify,
- @NotNull final FormattingModel model,
- @NotNull CommonCodeStyleSettings.IndentOptions indentOption)
- {
- FormattingDocumentModel documentModel = model.getDocumentModel();
- Document document = documentModel.getDocument();
- CaretOffsetUpdater caretOffsetUpdater = new CaretOffsetUpdater(document);
-
- if (document instanceof DocumentEx) ((DocumentEx)document).setInBulkUpdate(true);
- try {
- List changes = new ArrayList();
- int shift = 0;
- int currentIterationShift = 0;
- for (LeafBlockWrapper block : blocksToModify) {
- WhiteSpace whiteSpace = block.getWhiteSpace();
- CharSequence newWs = documentModel.adjustWhiteSpaceIfNecessary(
- whiteSpace.generateWhiteSpace(getIndentOptionsToUse(block, indentOption)), whiteSpace.getStartOffset(),
- whiteSpace.getEndOffset(), block.getNode(), false
- );
- if (changes.size() > 10000) {
- caretOffsetUpdater.update(changes);
- CharSequence mergeResult = BulkChangesMerger.INSTANCE.mergeToCharSequence(document.getChars(), document.getTextLength(), changes);
- document.replaceString(0, document.getTextLength(), mergeResult);
- shift += currentIterationShift;
- currentIterationShift = 0;
- changes.clear();
- }
- TextChangeImpl change = new TextChangeImpl(newWs, whiteSpace.getStartOffset() + shift, whiteSpace.getEndOffset() + shift);
- currentIterationShift += change.getDiff();
- changes.add(change);
- }
- caretOffsetUpdater.update(changes);
- CharSequence mergeResult = BulkChangesMerger.INSTANCE.mergeToCharSequence(document.getChars(), document.getTextLength(), changes);
- document.replaceString(0, document.getTextLength(), mergeResult);
- }
- finally {
- if (document instanceof DocumentEx) ((DocumentEx)document).setInBulkUpdate(false);
- }
-
- caretOffsetUpdater.restoreCaretLocations();
- cleanupBlocks(blocksToModify);
- }
-
- private static void cleanupBlocks(List blocks) {
- for (LeafBlockWrapper block : blocks) {
- block.getParent().dispose();
- block.dispose();
- }
- blocks.clear();
- }
-
- @Nullable
- private static DocumentEx getAffectedDocument(final FormattingModel model) {
- final Document document = model.getDocumentModel().getDocument();
- if (document instanceof DocumentEx) {
- return (DocumentEx)document;
- }
- else {
- return null;
- }
- }
-
- private static int replaceWhiteSpace(final FormattingModel model,
- @NotNull final LeafBlockWrapper block,
- int shift,
- final CharSequence _newWhiteSpace,
- final CommonCodeStyleSettings.IndentOptions options
- ) {
- final WhiteSpace whiteSpace = block.getWhiteSpace();
- final TextRange textRange = whiteSpace.getTextRange();
- final TextRange wsRange = textRange.shiftRight(shift);
- final String newWhiteSpace = _newWhiteSpace.toString();
- TextRange newWhiteSpaceRange = model instanceof FormattingModelEx
- ? ((FormattingModelEx) model).replaceWhiteSpace(wsRange, block.getNode(), newWhiteSpace)
- : model.replaceWhiteSpace(wsRange, newWhiteSpace);
-
- shift += newWhiteSpaceRange.getLength() - textRange.getLength();
-
- if (block.isLeaf() && whiteSpace.containsLineFeeds() && block.containsLineFeeds()) {
- final TextRange currentBlockRange = block.getTextRange().shiftRight(shift);
-
- IndentInside oldBlockIndent = whiteSpace.getInitialLastLineIndent();
- IndentInside whiteSpaceIndent = IndentInside.createIndentOn(IndentInside.getLastLine(newWhiteSpace));
- final int shiftInside = calcShift(oldBlockIndent, whiteSpaceIndent, options);
-
- if (shiftInside != 0 || !oldBlockIndent.equals(whiteSpaceIndent)) {
- final TextRange newBlockRange = model.shiftIndentInsideRange(block.getNode(), currentBlockRange, shiftInside);
- shift += newBlockRange.getLength() - block.getLength();
- }
- }
- return shift;
- }
-
- @NotNull
- private List collectBlocksToModify() {
- List blocksToModify = new ArrayList();
-
- for (LeafBlockWrapper block = myFirstTokenBlock; block != null; block = block.getNextBlock()) {
- final WhiteSpace whiteSpace = block.getWhiteSpace();
- if (!whiteSpace.isReadOnly()) {
- final String newWhiteSpace = whiteSpace.generateWhiteSpace(getIndentOptionsToUse(block, myDefaultIndentOption));
- if (!whiteSpace.equalsToString(newWhiteSpace)) {
- blocksToModify.add(block);
- }
- }
- }
- return blocksToModify;
- }
-
- @NotNull
- private CommonCodeStyleSettings.IndentOptions getIndentOptionsToUse(@NotNull AbstractBlockWrapper block,
- @NotNull CommonCodeStyleSettings.IndentOptions fallbackIndentOptions)
- {
- final Language language = block.getLanguage();
- if (language == null) {
- return fallbackIndentOptions;
- }
- final CommonCodeStyleSettings commonSettings = mySettings.getCommonSettings(language);
- if (commonSettings == null) {
- return fallbackIndentOptions;
- }
- final CommonCodeStyleSettings.IndentOptions result = commonSettings.getIndentOptions();
- return result == null ? fallbackIndentOptions : result;
- }
-
- private void processToken() {
- final SpacingImpl spaceProperty = myCurrentBlock.getSpaceProperty();
- final WhiteSpace whiteSpace = myCurrentBlock.getWhiteSpace();
-
- if (isReformatSelectedRangesContext()) {
- if (isCurrentBlockAlignmentUsedInRangesToModify() && whiteSpace.isReadOnly() && spaceProperty != null && !spaceProperty.isReadOnly()) {
- whiteSpace.setReadOnly(false);
- whiteSpace.setLineFeedsAreReadOnly(true);
- }
- }
-
- whiteSpace.arrangeLineFeeds(spaceProperty, this);
-
- if (!whiteSpace.containsLineFeeds()) {
- whiteSpace.arrangeSpaces(spaceProperty);
- }
-
- try {
- if (processWrap()) {
- return;
- }
- }
- finally {
- if (whiteSpace.containsLineFeeds()) {
- onCurrentLineChanged();
- }
- }
-
- if (!adjustIndent()) {
- return;
- }
-
- defineAlignOffset(myCurrentBlock);
-
- if (myCurrentBlock.containsLineFeeds()) {
- onCurrentLineChanged();
- }
-
-
- final List ranges = getDependentRegionRangesAfterCurrentWhiteSpace(spaceProperty, whiteSpace);
- if (!ranges.isEmpty()) {
- registerUnresolvedDependentSpacingRanges(spaceProperty, ranges);
- }
-
- if (!whiteSpace.isIsReadOnly() && shouldReformatPreviouslyLocatedDependentSpacing(whiteSpace)) {
- myAlignAgain.add(whiteSpace);
- }
- else if (!myAlignAgain.isEmpty()) {
- myAlignAgain.remove(whiteSpace);
- }
-
- myCurrentBlock = myCurrentBlock.getNextBlock();
- }
-
- private boolean isReformatSelectedRangesContext() {
- return myReformatContext && !ContainerUtil.isEmpty(myAlignmentsInsideRangesToModify);
- }
-
- private boolean isCurrentBlockAlignmentUsedInRangesToModify() {
- AbstractBlockWrapper block = myCurrentBlock;
- AlignmentImpl alignment = myCurrentBlock.getAlignment();
-
- while (alignment == null) {
- block = block.getParent();
- if (block == null || block.getStartOffset() != myCurrentBlock.getStartOffset()) {
- return false;
- }
- alignment = block.getAlignment();
- }
-
- return myAlignmentsInsideRangesToModify.contains(alignment);
- }
-
- private boolean shouldReformatPreviouslyLocatedDependentSpacing(WhiteSpace space) {
- final TextRange changed = space.getTextRange();
- final SortedMap sortedHeadMap = myPreviousDependencies.tailMap(changed);
-
- for (final Map.Entry entry : sortedHeadMap.entrySet()) {
- final TextRange textRange = entry.getKey();
-
- if (textRange.contains(changed)) {
- final DependantSpacingImpl spacing = entry.getValue();
- if (spacing.isDependentRegionLinefeedStatusChanged()) {
- continue;
- }
-
- final boolean containedLineFeeds = spacing.getMinLineFeeds() > 0;
- final boolean containsLineFeeds = containsLineFeeds(textRange);
-
- if (containedLineFeeds != containsLineFeeds) {
- spacing.setDependentRegionLinefeedStatusChanged();
- return true;
- }
- }
- }
-
- return false;
- }
-
- private void registerUnresolvedDependentSpacingRanges(final SpacingImpl spaceProperty, List unprocessedRanges) {
- final DependantSpacingImpl dependantSpaceProperty = (DependantSpacingImpl)spaceProperty;
- if (dependantSpaceProperty.isDependentRegionLinefeedStatusChanged()) return;
-
- for (TextRange range: unprocessedRanges) {
- myPreviousDependencies.put(range, dependantSpaceProperty);
- }
- }
-
- private static List getDependentRegionRangesAfterCurrentWhiteSpace(final SpacingImpl spaceProperty,
- final WhiteSpace whiteSpace)
- {
- if (!(spaceProperty instanceof DependantSpacingImpl)) return ContainerUtil.emptyList();
-
- if (whiteSpace.isReadOnly() || whiteSpace.isLineFeedsAreReadOnly()) return ContainerUtil.emptyList();
-
- DependantSpacingImpl spacing = (DependantSpacingImpl)spaceProperty;
- return ContainerUtil.filter(spacing.getDependentRegionRanges(), new Condition() {
- @Override
- public boolean value(TextRange dependencyRange) {
- return whiteSpace.getStartOffset() < dependencyRange.getEndOffset();
- }
- });
- }
-
- /**
- * Processes the wrap of the current block.
- *
- * @return true if we have changed myCurrentBlock and need to restart its processing; false if myCurrentBlock is unchanged and we can
- * continue processing
- */
- private boolean processWrap() {
- final SpacingImpl spacing = myCurrentBlock.getSpaceProperty();
- final WhiteSpace whiteSpace = myCurrentBlock.getWhiteSpace();
-
- final boolean wrapWasPresent = whiteSpace.containsLineFeeds();
-
- if (wrapWasPresent) {
- myFirstWrappedBlockOnLine = null;
-
- if (!whiteSpace.containsLineFeedsInitially()) {
- whiteSpace.removeLineFeeds(spacing, this);
- }
- }
-
- final boolean wrapIsPresent = whiteSpace.containsLineFeeds();
-
- final ArrayList wraps = myCurrentBlock.getWraps();
- for (WrapImpl wrap : wraps) {
- wrap.setWrapOffset(myCurrentBlock.getStartOffset());
- }
-
- final WrapImpl wrap = getWrapToBeUsed(wraps);
-
- if (wrap != null || wrapIsPresent) {
- if (!wrapIsPresent && !canReplaceWrapCandidate(wrap)) {
- myCurrentBlock = myWrapCandidate;
- return true;
- }
- 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.
- myCurrentBlock = wrap.getChopStartBlock();
- wrap.setActive();
- return true;
- }
- if (wrap != null && isChopNeeded(wrap)) {
- wrap.setActive();
- }
-
- if (!wrapIsPresent) {
- whiteSpace.ensureLineFeed();
- if (!wrapWasPresent) {
- if (myFirstWrappedBlockOnLine != null && wrap.isChildOf(myFirstWrappedBlockOnLine.getWrap(), myCurrentBlock)) {
- wrap.ignoreParentWrap(myFirstWrappedBlockOnLine.getWrap(), myCurrentBlock);
- myCurrentBlock = myFirstWrappedBlockOnLine;
- return true;
- }
- else {
- myFirstWrappedBlockOnLine = myCurrentBlock;
- }
- }
- }
-
- myWrapCandidate = null;
- }
- else {
- for (final WrapImpl wrap1 : wraps) {
- if (isCandidateToBeWrapped(wrap1) && canReplaceWrapCandidate(wrap1)) {
- myWrapCandidate = myCurrentBlock;
- }
- if (isChopNeeded(wrap1)) {
- wrap1.saveChopBlock(myCurrentBlock);
- }
- }
- }
-
- if (!whiteSpace.containsLineFeeds() && myWrapCandidate != null && !whiteSpace.isReadOnly() && lineOver()) {
- myCurrentBlock = myWrapCandidate;
- return true;
- }
-
- return false;
- }
-
- /**
- * 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 true if wrap of the {@link #myWrapCandidate} object (if any) may be replaced by the given wrap;
- * false otherwise
- */
- private boolean canReplaceWrapCandidate(WrapImpl wrap) {
- 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, myCurrentBlock);
- }
-
- private boolean isCandidateToBeWrapped(final WrapImpl wrap) {
- return isSuitableInTheCurrentPosition(wrap) &&
- (wrap.getType() == WrapImpl.Type.WRAP_AS_NEEDED || wrap.getType() == WrapImpl.Type.CHOP_IF_NEEDED) &&
- !myCurrentBlock.getWhiteSpace().isReadOnly();
- }
-
- private void onCurrentLineChanged() {
- myWrapCandidate = null;
- }
-
- /**
- * Adjusts indent of the current block.
- *
- * @return true if current formatting iteration should be continued;
- * false otherwise (e.g. if previously processed block is shifted inside this method for example
- * because of specified alignment options)
- */
- private boolean adjustIndent() {
- AlignmentImpl alignment = CoreFormatterUtil.getAlignment(myCurrentBlock);
- WhiteSpace whiteSpace = myCurrentBlock.getWhiteSpace();
-
- if (alignment == null || myAlignmentsToSkip.contains(alignment)) {
- if (whiteSpace.containsLineFeeds()) {
- adjustSpacingByIndentOffset();
- }
- else {
- whiteSpace.arrangeSpaces(myCurrentBlock.getSpaceProperty());
- }
- return true;
- }
-
- BlockAlignmentProcessor alignmentProcessor = ALIGNMENT_PROCESSORS.get(alignment.getAnchor());
- if (alignmentProcessor == null) {
- LOG.error(String.format("Can't find alignment processor for alignment anchor %s", alignment.getAnchor()));
- return true;
- }
-
- BlockAlignmentProcessor.Context context = new BlockAlignmentProcessor.Context(
- myDocument, alignment, myCurrentBlock, myAlignmentMappings, myBackwardShiftedAlignedBlocks,
- getIndentOptionsToUse(myCurrentBlock, myDefaultIndentOption));
- final LeafBlockWrapper offsetResponsibleBlock = alignment.getOffsetRespBlockBefore(myCurrentBlock);
- myCyclesDetector.registerOffsetResponsibleBlock(offsetResponsibleBlock);
- BlockAlignmentProcessor.Result result = alignmentProcessor.applyAlignment(context);
- switch (result) {
- case TARGET_BLOCK_PROCESSED_NOT_ALIGNED: return true;
- case TARGET_BLOCK_ALIGNED: storeAlignmentMapping(); return true;
- case BACKWARD_BLOCK_ALIGNED:
- if (offsetResponsibleBlock == null) {
- return true;
- }
- Set blocksCausedRealignment = new HashSet();
- myBackwardShiftedAlignedBlocks.clear();
- myBackwardShiftedAlignedBlocks.put(offsetResponsibleBlock, blocksCausedRealignment);
- blocksCausedRealignment.add(myCurrentBlock);
- storeAlignmentMapping(myCurrentBlock, offsetResponsibleBlock);
-
- if (myCyclesDetector.isCycleDetected()) {
- reportAlignmentProcessingError(context);
- return true;
- }
- else {
- myCyclesDetector.registerBlockRollback(myCurrentBlock);
- myCurrentBlock = offsetResponsibleBlock.getNextBlock();
- }
- onCurrentLineChanged();
- return false;
- case RECURSION_DETECTED:
- myCurrentBlock = offsetResponsibleBlock; // Fall through to the 'register alignment to skip'.
- case UNABLE_TO_ALIGN_BACKWARD_BLOCK:
- myAlignmentsToSkip.add(alignment);
- return false;
- default: return true;
- }
- }
-
- private static void reportAlignmentProcessingError(Context context) {
- ASTNode node = context.targetBlock.getNode();
- Language language = node != null ? node.getPsi().getLanguage() : null;
- LogMessageEx.error(LOG,
- (language != null ? language.getDisplayName() + ": " : "") +
- "Can't align block " + context.targetBlock, context.document.getText());
- }
-
- /**
- * We need to track blocks which white spaces are modified because of alignment rules.
- *
- * This method encapsulates the logic of storing such information.
- */
- private void storeAlignmentMapping() {
- AlignmentImpl alignment = null;
- AbstractBlockWrapper block = myCurrentBlock;
- while (alignment == null && block != null) {
- alignment = block.getAlignment();
- block = block.getParent();
- }
- if (alignment != null) {
- block = alignment.getOffsetRespBlockBefore(myCurrentBlock);
- if (block != null) {
- storeAlignmentMapping(myCurrentBlock, block);
- }
- }
- }
-
- private void storeAlignmentMapping(AbstractBlockWrapper block1, AbstractBlockWrapper block2) {
- doStoreAlignmentMapping(block1, block2);
- doStoreAlignmentMapping(block2, block1);
- }
-
- private void doStoreAlignmentMapping(AbstractBlockWrapper key, AbstractBlockWrapper value) {
- Set wrappers = myAlignmentMappings.get(key);
- if (wrappers == null) {
- myAlignmentMappings.put(key, wrappers = new HashSet());
- }
- wrappers.add(value);
- }
-
- /**
- * Applies indent to the white space of {@link #myCurrentBlock currently processed wrapped block}. Both indentation
- * and alignment options are took into consideration here.
- */
- private void adjustLineIndent() {
- IndentData alignOffset = getAlignOffset();
-
- if (alignOffset == null) {
- adjustSpacingByIndentOffset();
- }
- else {
- myCurrentBlock.getWhiteSpace().setSpaces(alignOffset.getSpaces(), alignOffset.getIndentSpaces());
- }
- }
-
- private void adjustSpacingByIndentOffset() {
- IndentData offset = myCurrentBlock.calculateOffset(getIndentOptionsToUse(myCurrentBlock, myDefaultIndentOption));
- myCurrentBlock.getWhiteSpace().setSpaces(offset.getSpaces(), offset.getIndentSpaces());
- }
-
- private boolean isChopNeeded(final WrapImpl wrap) {
- return wrap != null && wrap.getType() == WrapImpl.Type.CHOP_IF_NEEDED && isSuitableInTheCurrentPosition(wrap);
- }
-
- private boolean isSuitableInTheCurrentPosition(final WrapImpl wrap) {
- if (wrap.getWrapOffset() < myCurrentBlock.getStartOffset()) {
- return true;
- }
-
- if (wrap.isWrapFirstElement()) {
- return true;
- }
-
- if (wrap.getType() == WrapImpl.Type.WRAP_AS_NEEDED) {
- return positionAfterWrappingIsSuitable();
- }
-
- return wrap.getType() == WrapImpl.Type.CHOP_IF_NEEDED && lineOver() && positionAfterWrappingIsSuitable();
- }
-
- /**
- * Ensures that offset of the {@link #myCurrentBlock currently processed block} is not increased if we make a wrap on it.
- *
- * @return true if it's ok to wrap at the currently processed block; false otherwise
- */
- private boolean positionAfterWrappingIsSuitable() {
- final WhiteSpace whiteSpace = myCurrentBlock.getWhiteSpace();
- if (whiteSpace.containsLineFeeds()) return true;
- final int spaces = whiteSpace.getSpaces();
- int indentSpaces = whiteSpace.getIndentSpaces();
- try {
- final int startColumnNow = CoreFormatterUtil.getStartColumn(myCurrentBlock);
- whiteSpace.ensureLineFeed();
- adjustLineIndent();
- final int startColumnAfterWrap = CoreFormatterUtil.getStartColumn(myCurrentBlock);
- return startColumnNow > startColumnAfterWrap;
- }
- finally {
- whiteSpace.removeLineFeeds(myCurrentBlock.getSpaceProperty(), this);
- whiteSpace.setSpaces(spaces, indentSpaces);
- }
- }
-
- @Nullable
- private WrapImpl getWrapToBeUsed(final ArrayList wraps) {
- if (wraps.isEmpty()) {
- return null;
- }
- if (myWrapCandidate == myCurrentBlock) return wraps.get(0);
-
- for (final WrapImpl wrap : wraps) {
- if (!isSuitableInTheCurrentPosition(wrap)) 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()) {
- return wrap;
- }
- }
- }
- return null;
- }
-
- /**
- * @return true if {@link #myCurrentBlock currently processed wrapped block} doesn't contain line feeds and
- * exceeds right margin; false otherwise
- */
- private boolean lineOver() {
- return !myCurrentBlock.containsLineFeeds() &&
- CoreFormatterUtil.getStartColumn(myCurrentBlock) + myCurrentBlock.getLength() > myRightMargin;
- }
-
- private void defineAlignOffset(final LeafBlockWrapper block) {
- AbstractBlockWrapper current = myCurrentBlock;
- while (true) {
- final AlignmentImpl alignment = current.getAlignment();
- if (alignment != null) {
- alignment.setOffsetRespBlock(block);
- }
- current = current.getParent();
- if (current == null) return;
- if (current.getStartOffset() != myCurrentBlock.getStartOffset()) return;
-
- }
- }
-
- /**
- * Tries to get align-implied indent of the current block.
- *
- * @return indent of the current block if any; null otherwise
- */
- @Nullable
- private IndentData getAlignOffset() {
- AbstractBlockWrapper current = myCurrentBlock;
- while (true) {
- final AlignmentImpl alignment = current.getAlignment();
- LeafBlockWrapper offsetResponsibleBlock;
- if (alignment != null && (offsetResponsibleBlock = alignment.getOffsetRespBlockBefore(myCurrentBlock)) != null) {
- final WhiteSpace whiteSpace = offsetResponsibleBlock.getWhiteSpace();
- if (whiteSpace.containsLineFeeds()) {
- return new IndentData(whiteSpace.getIndentSpaces(), whiteSpace.getSpaces());
- }
- else {
- final int offsetBeforeBlock = CoreFormatterUtil.getStartColumn(offsetResponsibleBlock);
- final AbstractBlockWrapper indentedParentBlock = CoreFormatterUtil.getIndentedParentBlock(myCurrentBlock);
- if (indentedParentBlock == null) {
- return new IndentData(0, offsetBeforeBlock);
- }
- else {
- final int parentIndent = indentedParentBlock.getWhiteSpace().getIndentOffset();
- if (parentIndent > offsetBeforeBlock) {
- return new IndentData(0, offsetBeforeBlock);
- }
- else {
- return new IndentData(parentIndent, offsetBeforeBlock - parentIndent);
- }
- }
- }
- }
- else {
- current = current.getParent();
- if (current == null || current.getStartOffset() != myCurrentBlock.getStartOffset()) return null;
- }
- }
- }
-
- public boolean containsLineFeeds(final TextRange dependency) {
- LeafBlockWrapper child = myTextRangeToWrapper.get(dependency.getStartOffset());
- if (child == null) return false;
- if (child.containsLineFeeds()) return true;
- final int endOffset = dependency.getEndOffset();
- while (child.getEndOffset() < endOffset) {
- child = child.getNextBlock();
- if (child == null) return false;
- if (child.getWhiteSpace().containsLineFeeds()) return true;
- if (child.containsLineFeeds()) return true;
- }
- return false;
- }
-
- @Nullable
- public LeafBlockWrapper getBlockAtOrAfter(final int startOffset) {
- int current = startOffset;
- LeafBlockWrapper result = null;
- while (current < myLastWhiteSpace.getStartOffset()) {
- final LeafBlockWrapper currentValue = myTextRangeToWrapper.get(current);
- if (currentValue != null) {
- result = currentValue;
- break;
- }
- current++;
- }
-
- LeafBlockWrapper prevBlock = getPrevBlock(result);
-
- if (prevBlock != null && prevBlock.contains(startOffset)) {
- return prevBlock;
- }
- else {
- return result;
- }
- }
-
- @Nullable
- private LeafBlockWrapper getPrevBlock(@Nullable final LeafBlockWrapper result) {
- if (result != null) {
- return result.getPreviousBlock();
- }
- else {
- return myLastTokenBlock;
+ private void doIterationsSynchronously() {
+ while (!myStateProcessor.isDone()) {
+ myStateProcessor.iteration();
}
}
public void setAllWhiteSpacesAreReadOnly() {
- LeafBlockWrapper current = myFirstTokenBlock;
+ LeafBlockWrapper current = myWrapState.getFirstBlock();
while (current != null) {
current.getWhiteSpace().setReadOnly(true);
current = current.getNextBlock();
}
}
- static class ChildAttributesInfo {
+ public static class ChildAttributesInfo {
public final AbstractBlockWrapper parent;
- final ChildAttributes attributes;
- final int index;
+ public final ChildAttributes attributes;
+ public final int index;
public ChildAttributesInfo(final AbstractBlockWrapper parent, final ChildAttributes attributes, final int index) {
this.parent = parent;
@@ -1036,15 +160,15 @@ public class FormatProcessor {
}
public IndentInfo getIndentAt(final int offset) {
- processBlocksBefore(offset);
- AbstractBlockWrapper parent = getParentFor(offset, myCurrentBlock);
+ LeafBlockWrapper current = processBlocksBefore(offset);
+ AbstractBlockWrapper parent = getParentFor(offset, current);
if (parent == null) {
- final LeafBlockWrapper previousBlock = myCurrentBlock.getPreviousBlock();
+ 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 = myInfos.get(parent);
+ final Block block = myWrapState.getBlockToInfoMap().get(parent);
if (block == null) {
return new IndentInfo(0, 0, 0);
@@ -1055,7 +179,8 @@ public class FormatProcessor {
return new IndentInfo(0, 0, 0);
}
- return adjustLineIndent(info.parent, info.attributes, info.index);
+ IndentAdjuster adjuster = myWrapState.getIndentAdjuster();
+ return adjuster.adjustLineIndent(current, info);
}
@Nullable
@@ -1100,40 +225,7 @@ public class FormatProcessor {
return new ChildAttributesInfo(parent, childAttributes, index);
}
}
-
- private IndentInfo adjustLineIndent(final AbstractBlockWrapper parent, final ChildAttributes childAttributes, final int index) {
- int alignOffset = getAlignOffsetBefore(childAttributes.getAlignment(), null);
- if (alignOffset == -1) {
- return parent.calculateChildOffset(getIndentOptionsToUse(parent, myDefaultIndentOption), childAttributes, index).createIndentInfo();
- }
- else {
- AbstractBlockWrapper indentedParentBlock = CoreFormatterUtil.getIndentedParentBlock(myCurrentBlock);
- if (indentedParentBlock == null) {
- return new IndentInfo(0, 0, alignOffset);
- }
- else {
- int indentOffset = indentedParentBlock.getWhiteSpace().getIndentOffset();
- if (indentOffset > alignOffset) {
- return new IndentInfo(0, 0, alignOffset);
- }
- else {
- return new IndentInfo(0, indentOffset, alignOffset - indentOffset);
- }
- }
- }
- }
-
- private static int getAlignOffsetBefore(@Nullable final Alignment alignment, @Nullable final LeafBlockWrapper blockAfter) {
- if (alignment == null) return -1;
- final LeafBlockWrapper alignRespBlock = ((AlignmentImpl)alignment).getOffsetRespBlockBefore(blockAfter);
- if (alignRespBlock != null) {
- return CoreFormatterUtil.getStartColumn(alignRespBlock);
- }
- else {
- return -1;
- }
- }
-
+
private static int getNewChildPosition(final AbstractBlockWrapper parent, final int offset) {
AbstractBlockWrapper parentBlockToUse = getLastNestedCompositeBlockForSameRange(parent);
if (!(parentBlockToUse instanceof CompositeBlockWrapper)) return 0;
@@ -1177,8 +269,9 @@ public class FormatProcessor {
@Nullable
private AbstractBlockWrapper getPreviousIncompleteBlock(final LeafBlockWrapper block, final int offset) {
if (block == null) {
- if (myLastTokenBlock.isIncomplete()) {
- return myLastTokenBlock;
+ LeafBlockWrapper lastTokenBlock = myWrapState.getLastBlock();
+ if (lastTokenBlock.isIncomplete()) {
+ return lastTokenBlock;
}
else {
return null;
@@ -1263,343 +356,36 @@ public class FormatProcessor {
}
return result;
}
-
- private void processBlocksBefore(final int offset) {
- while (true) {
- myAlignAgain.clear();
- myCurrentBlock = myFirstTokenBlock;
- while (myCurrentBlock != null && myCurrentBlock.getStartOffset() < offset) {
- processToken();
- if (myCurrentBlock == null) {
- myCurrentBlock = myLastTokenBlock;
- if (myCurrentBlock != null) {
- myProgressCallback.afterProcessingBlock(myCurrentBlock);
- }
- break;
- }
- }
- if (myAlignAgain.isEmpty()) return;
- reset();
+
+ 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 myFirstTokenBlock;
+ return myWrapState.getFirstBlock();
}
public WhiteSpace getLastWhiteSpace() {
- return myLastWhiteSpace;
+ return myWrapState.getLastWhiteSpace();
}
-
- /**
- * Calculates difference in visual columns between the given indents.
- *
- * @param oldIndent old indent
- * @param newIndent new indent
- * @param options indent options to use
- * @return difference in visual columns between the given indents
- */
- private static int calcShift(@NotNull final IndentInside oldIndent,
- @NotNull final IndentInside newIndent,
- @NotNull final CommonCodeStyleSettings.IndentOptions options)
- {
- if (oldIndent.equals(newIndent)) return 0;
- return newIndent.getSpacesCount(options) - oldIndent.getSpacesCount(options);
- }
-
- /**
- * Utility method to use during debugging formatter processing.
- *
- * @return text that contains intermediate formatter-introduced changes (even not committed yet)
- */
- @SuppressWarnings("UnusedDeclaration")
- @NotNull
- private String getCurrentText() {
- StringBuilder result = new StringBuilder();
- for (LeafBlockWrapper block = myFirstTokenBlock; block != null; block = block.getNextBlock()) {
- result.append(block.getWhiteSpace().generateWhiteSpace(getIndentOptionsToUse(block, myDefaultIndentOption)));
- result.append(myDocument.getCharsSequence().subSequence(block.getStartOffset(), block.getEndOffset()));
- }
- return result.toString();
- }
-
- private abstract class State {
-
- private final FormattingStateId myStateId;
-
- private State myNextState;
- private boolean myDone;
-
- protected State(FormattingStateId stateId) {
- myStateId = stateId;
- }
-
- public void iteration() {
- if (!isDone()) {
- doIteration();
- }
- shiftStateIfNecessary();
- }
-
- public boolean isDone() {
- return myDone;
- }
-
- protected void setDone(boolean done) {
- myDone = done;
- }
-
- public void setNext(@NotNull State state) {
- if (getStateId() == state.getStateId() || (myNextState != null && myNextState.getStateId() == state.getStateId())) {
- return;
- }
- myNextState = state;
- shiftStateIfNecessary();
- }
-
- public FormattingStateId getStateId() {
- return myStateId;
- }
-
- public void stop() {
- }
-
- protected abstract void doIteration();
- protected abstract void prepare();
-
- private void shiftStateIfNecessary() {
- if (isDone() && myNextState != null) {
- myCurrentState = myNextState;
- myNextState = null;
- myCurrentState.prepare();
- }
- }
- }
-
- private class WrapBlocksState extends State {
-
- private final InitialInfoBuilder myWrapper;
- private final FormattingDocumentModel myModel;
-
- WrapBlocksState(@NotNull Block root,
- @NotNull FormattingDocumentModel model,
- @Nullable final FormatTextRanges affectedRanges,
- int interestingOffset)
- {
- super(FormattingStateId.WRAPPING_BLOCKS);
- myModel = model;
- myWrapper = InitialInfoBuilder.prepareToBuildBlocksSequentially(
- root, model, affectedRanges, mySettings, myDefaultIndentOption, interestingOffset, myProgressCallback
- );
- myWrapper.setCollectAlignmentsInsideFormattingRange(myReformatContext);
-
- myExpandableIndents = myWrapper.getExpandableIndentsBlocks();
- }
-
- @Override
- protected void prepare() {
- }
-
- @Override
- public void doIteration() {
- if (isDone()) {
- return;
- }
-
- setDone(myWrapper.iteration());
- if (!isDone()) {
- return;
- }
-
- myInfos = myWrapper.getBlockToInfoMap();
- myRootBlockWrapper = myWrapper.getRootBlockWrapper();
- myFirstTokenBlock = myWrapper.getFirstTokenBlock();
- myLastTokenBlock = myWrapper.getLastTokenBlock();
- myCurrentBlock = myFirstTokenBlock;
- myTextRangeToWrapper = buildTextRangeToInfoMap(myFirstTokenBlock);
- int lastBlockOffset = getLastBlock().getEndOffset();
- myLastWhiteSpace = new WhiteSpace(lastBlockOffset, false);
- myLastWhiteSpace.append(Math.max(lastBlockOffset, myWrapper.getEndOffset()), myModel, myDefaultIndentOption);
- myAlignmentsInsideRangesToModify = myWrapper.getAlignmentsInsideRangeToModify();
- myTotalBlocksWithAlignments = myWrapper.getBlocksToAlign().values().size();
-
- myCyclesDetector = new AlignmentCyclesDetector(myTotalBlocksWithAlignments);
- }
- }
-
- private class AdjustWhiteSpacesState extends State {
-
- AdjustWhiteSpacesState() {
- super(FormattingStateId.PROCESSING_BLOCKS);
- }
-
- @Override
- protected void prepare() {
- }
-
- @Override
- protected void doIteration() {
- LeafBlockWrapper blockToProcess = myCurrentBlock;
- processToken();
- if (blockToProcess != null) {
- myProgressCallback.afterProcessingBlock(blockToProcess);
- }
-
- if (myCurrentBlock != null) {
- return;
- }
-
- if (myAlignAgain.isEmpty()) {
- setDone(true);
- }
- else {
- myAlignAgain.clear();
- myPreviousDependencies.clear();
- myCurrentBlock = myFirstTokenBlock;
- }
- }
- }
-
- private class ApplyChangesState extends State {
-
- private final FormattingModel myModel;
- private List myBlocksToModify;
- private int myShift;
- private int myIndex;
- private boolean myResetBulkUpdateState;
-
- private ApplyChangesState(FormattingModel model) {
- super(FormattingStateId.APPLYING_CHANGES);
- myModel = model;
- }
-
- @Override
- protected void prepare() {
- myBlocksToModify = collectBlocksToModify();
- // call doModifications static method to ensure no access to state
- // thus we may clear formatting state
- reset();
-
- myInfos = null;
- myRootBlockWrapper = null;
- myTextRangeToWrapper = null;
- myPreviousDependencies = null;
- myLastWhiteSpace = null;
- myFirstTokenBlock = null;
- myLastTokenBlock = null;
- myDisposed = true;
-
- if (myBlocksToModify.isEmpty()) {
- setDone(true);
- return;
- }
-
- //for GeneralCodeFormatterTest
- if (myJavaIndentOptions == null) {
- myJavaIndentOptions = mySettings.getIndentOptions(StdFileTypes.JAVA);
- }
-
- myProgressCallback.beforeApplyingFormatChanges(myBlocksToModify);
-
- final int blocksToModifyCount = myBlocksToModify.size();
- if (blocksToModifyCount > BULK_REPLACE_OPTIMIZATION_CRITERIA) {
- applyChangesAtRewriteMode(myBlocksToModify, myModel, myDefaultIndentOption);
- setDone(true);
- }
- else if (blocksToModifyCount > 50) {
- DocumentEx updatedDocument = getAffectedDocument(myModel);
- if (updatedDocument != null) {
- updatedDocument.setInBulkUpdate(true);
- myResetBulkUpdateState = true;
- }
- }
- }
-
- @Override
- protected void doIteration() {
- LeafBlockWrapper blockWrapper = myBlocksToModify.get(myIndex);
- myShift = replaceWhiteSpace(
- myModel,
- blockWrapper,
- myShift,
- blockWrapper.getWhiteSpace().generateWhiteSpace(getIndentOptionsToUse(blockWrapper, myDefaultIndentOption)),
- myDefaultIndentOption
- );
- myProgressCallback.afterApplyingChange(blockWrapper);
- // block could be gc'd
- blockWrapper.getParent().dispose();
- blockWrapper.dispose();
- myBlocksToModify.set(myIndex, null);
- myIndex++;
-
- if (myIndex >= myBlocksToModify.size()) {
- setDone(true);
- }
- }
-
- @Override
- protected void setDone(boolean done) {
- super.setDone(done);
-
- if (myResetBulkUpdateState) {
- DocumentEx document = getAffectedDocument(myModel);
- if (document != null) {
- document.setInBulkUpdate(false);
- myResetBulkUpdateState = false;
- }
- }
-
- if (done) {
- myModel.commitChanges();
- }
- }
-
- @Override
- public void stop() {
- if (myIndex > 0) {
- UIUtil.invokeAndWaitIfNeeded(new Runnable() {
- @Override
- public void run() {
- myModel.commitChanges();
- }
- });
- }
- }
- }
-
- private static class CaretOffsetUpdater {
- private final Map myCaretOffsets = new HashMap();
-
- private CaretOffsetUpdater(@NotNull Document document) {
- Editor[] editors = EditorFactory.getInstance().getEditors(document);
- for (Editor editor : editors) {
- myCaretOffsets.put(editor, editor.getCaretModel().getOffset());
- }
- }
-
- private void update(@NotNull List extends TextChange> changes) {
- BulkChangesMerger merger = BulkChangesMerger.INSTANCE;
- for (Map.Entry entry : myCaretOffsets.entrySet()) {
- entry.setValue(merger.updateOffset(entry.getValue(), changes));
- }
- }
-
- private void restoreCaretLocations() {
- for (Map.Entry entry : myCaretOffsets.entrySet()) {
- entry.getKey().getCaretModel().moveToOffset(entry.getValue());
- }
- }
- }
-
-
+
public static class FormatOptions {
- private CodeStyleSettings mySettings;
- private CommonCodeStyleSettings.IndentOptions myIndentOptions;
+ public CodeStyleSettings mySettings;
+ public CommonCodeStyleSettings.IndentOptions myIndentOptions;
- private FormatTextRanges myAffectedRanges;
- private boolean myReformatContext;
+ public FormatTextRanges myAffectedRanges;
+ public boolean myReformatContext;
- private int myInterestingOffset;
+ public int myInterestingOffset;
public FormatOptions(CodeStyleSettings settings,
CommonCodeStyleSettings.IndentOptions options,
@@ -1620,192 +406,4 @@ public class FormatProcessor {
myInterestingOffset = interestingOffset;
}
}
-
- private class ExpandChildrenIndent extends State {
- private Iterator myIterator;
- private MultiMap myBlocksToRealign = new MultiMap();
-
- public ExpandChildrenIndent() {
- super(FormattingStateId.EXPANDING_CHILDREN_INDENTS);
- }
-
- @Override
- protected void doIteration() {
- if (myIterator == null) {
- myIterator = myExpandableIndents.keySet().iterator();
- }
- if (!myIterator.hasNext()) {
- setDone(true);
- return;
- }
-
- final ExpandableIndent indent = myIterator.next();
- Collection blocksToExpandIndent = myExpandableIndents.get(indent);
- if (shouldExpand(blocksToExpandIndent)) {
- for (AbstractBlockWrapper block : blocksToExpandIndent) {
- indent.setEnforceIndent(true);
- reindentNewLineChildren(block);
- indent.setEnforceIndent(false);
- }
- }
-
- restoreAlignments(myBlocksToRealign);
- myBlocksToRealign.clear();
- }
-
- private void restoreAlignments(MultiMap blocks) {
- for (Alignment alignment : blocks.keySet()) {
- AlignmentImpl alignmentImpl = (AlignmentImpl)alignment;
- if (!alignmentImpl.isAllowBackwardShift()) continue;
-
- Set toRealign = alignmentImpl.getOffsetResponsibleBlocks();
- arrangeSpaces(toRealign);
-
- LeafBlockWrapper rightMostBlock = getRightMostBlock(toRealign);
- int maxSpacesBeforeBlock = rightMostBlock.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
- int rightMostBlockLine = myDocument.getLineNumber(rightMostBlock.getStartOffset());
-
- for (LeafBlockWrapper block : toRealign) {
- int currentBlockLine = myDocument.getLineNumber(block.getStartOffset());
- if (currentBlockLine == rightMostBlockLine) continue;
-
- int blockIndent = block.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
- int delta = maxSpacesBeforeBlock - blockIndent;
- if (delta > 0) {
- int newSpaces = block.getWhiteSpace().getTotalSpaces() + delta;
- adjustSpacingToKeepAligned(block, newSpaces);
- }
- }
- }
- }
-
- private void adjustSpacingToKeepAligned(LeafBlockWrapper block, int newSpaces) {
- WhiteSpace space = block.getWhiteSpace();
- SpacingImpl property = block.getSpaceProperty();
- if (property == null) return;
- space.arrangeSpaces(new SpacingImpl(newSpaces, newSpaces,
- property.getMinLineFeeds(),
- property.isReadOnly(),
- property.isSafe(),
- property.shouldKeepLineFeeds(),
- property.getKeepBlankLines(),
- property.shouldKeepFirstColumn(),
- property.getPrefLineFeeds()));
- }
-
- private LeafBlockWrapper getRightMostBlock(Collection toRealign) {
- int maxSpacesBeforeBlock = -1;
- LeafBlockWrapper rightMostBlock = null;
-
- for (LeafBlockWrapper block : toRealign) {
- int spaces = block.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
- if (spaces > maxSpacesBeforeBlock) {
- maxSpacesBeforeBlock = spaces;
- rightMostBlock = block;
- }
- }
-
- return rightMostBlock;
- }
-
- private void arrangeSpaces(Collection toRealign) {
- for (LeafBlockWrapper block : toRealign) {
- WhiteSpace whiteSpace = block.getWhiteSpace();
- SpacingImpl spacing = block.getSpaceProperty();
- whiteSpace.arrangeSpaces(spacing);
- }
- }
-
- private boolean shouldExpand(Collection blocksToExpandIndent) {
- AbstractBlockWrapper last = null;
- for (AbstractBlockWrapper block : blocksToExpandIndent) {
- if (block.getWhiteSpace().containsLineFeeds()) {
- return true;
- }
- last = block;
- }
-
- if (last != null) {
- AbstractBlockWrapper next = getNextBlock(last);
- if (next != null && next.getWhiteSpace().containsLineFeeds()) {
- int nextNewLineBlockIndent = next.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
- if (nextNewLineBlockIndent >= finMinNewLineIndent(blocksToExpandIndent)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- private int finMinNewLineIndent(@NotNull Collection wrappers) {
- int totalMinimum = Integer.MAX_VALUE;
- for (AbstractBlockWrapper wrapper : wrappers) {
- int minNewLineIndent = findMinNewLineIndent(wrapper);
- if (minNewLineIndent < totalMinimum) {
- totalMinimum = minNewLineIndent;
- }
- }
- return totalMinimum;
- }
-
- private int findMinNewLineIndent(@NotNull AbstractBlockWrapper block) {
- if (block instanceof LeafBlockWrapper && block.getWhiteSpace().containsLineFeeds()) {
- return block.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
- }
- else if (block instanceof CompositeBlockWrapper) {
- List children = ((CompositeBlockWrapper)block).getChildren();
- int currentMin = Integer.MAX_VALUE;
- for (AbstractBlockWrapper child : children) {
- int childIndent = findMinNewLineIndent(child);
- if (childIndent < currentMin) {
- currentMin = childIndent;
- }
- }
- return currentMin;
- }
- return Integer.MAX_VALUE;
- }
-
- private AbstractBlockWrapper getNextBlock(AbstractBlockWrapper block) {
- List children = block.getParent().getChildren();
- int nextBlockIndex = children.indexOf(block) + 1;
- if (nextBlockIndex < children.size()) {
- return children.get(nextBlockIndex);
- }
- return null;
- }
-
- private void reindentNewLineChildren(final @NotNull AbstractBlockWrapper block) {
- if (block instanceof LeafBlockWrapper) {
- WhiteSpace space = block.getWhiteSpace();
-
- if (space.containsLineFeeds()) {
- myCurrentBlock = (LeafBlockWrapper)block;
- adjustIndent();
- storeAlignmentsAfterCurrentBlock();
- }
- }
- else if (block instanceof CompositeBlockWrapper) {
- List children = ((CompositeBlockWrapper)block).getChildren();
- for (AbstractBlockWrapper childBlock : children) {
- reindentNewLineChildren(childBlock);
- }
- }
- }
-
- private void storeAlignmentsAfterCurrentBlock() {
- LeafBlockWrapper current = myCurrentBlock.getNextBlock();
- while (current != null && !current.getWhiteSpace().containsLineFeeds()) {
- if (current.getAlignment() != null) {
- myBlocksToRealign.putValue(current.getAlignment(), current);
- }
- current = current.getNextBlock();
- }
- }
-
- @Override
- protected void prepare() {
- }
- }
}
diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java b/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java
index f8dbc4ed53f9..2fd4d92f865c 100644
--- a/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java
+++ b/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java
@@ -16,6 +16,7 @@
package com.intellij.formatting;
+import com.intellij.formatting.engine.ExpandableIndent;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
@@ -181,8 +182,6 @@ public class FormatterImpl extends FormatterEx
FormatProcessor processor = new FormatProcessor(
model.getDocumentModel(), model.getRootBlock(), settings, indentOptions, affectedRanges, FormattingProgressCallback.EMPTY
);
- processor.setJavaIndentOptions(javaIndentOptions);
-
processor.format(model);
return processor;
}
@@ -306,21 +305,6 @@ public class FormatterImpl extends FormatterEx
execute(task);
}
- /**
- * Execute given sequential formatting task. Two approaches are possible:
- *
- *
- * -
- * synchronous - the task is completely executed during the current method processing;
- *
- * -
- * asynchronous - the task is executed at background thread under the progress dialog;
- *
- *
- *
- *
- * @param task task to execute
- */
private void execute(@NotNull SequentialTask task) {
disableFormatting();
Application application = ApplicationManager.getApplication();
@@ -363,7 +347,7 @@ public class FormatterImpl extends FormatterEx
final FormatProcessor processor = buildProcessorAndWrapBlocks(
model, block, settings, indentOptions, new FormatTextRanges(affectedRange, true)
);
- final LeafBlockWrapper blockBefore = processor.getBlockAtOrAfter(affectedRange.getStartOffset());
+ final LeafBlockWrapper blockBefore = processor.getBlockRangesMap().getBlockAtOrAfter(affectedRange.getStartOffset());
LOG.assertTrue(blockBefore != null);
WhiteSpace whiteSpace = blockBefore.getWhiteSpace();
LOG.assertTrue(whiteSpace != null);
@@ -473,7 +457,7 @@ public class FormatterImpl extends FormatterEx
documentModel, block, settings, indentOptions, new FormatTextRanges(affectedRange, true), offset
);
- final LeafBlockWrapper blockAfterOffset = processor.getBlockAtOrAfter(offset);
+ final LeafBlockWrapper blockAfterOffset = processor.getBlockRangesMap().getBlockAtOrAfter(offset);
if (blockAfterOffset != null && blockAfterOffset.contains(offset)) {
return offset;
@@ -492,40 +476,14 @@ public class FormatterImpl extends FormatterEx
return offset;
}
- /**
- * Delegates to
- * {@link #buildProcessorAndWrapBlocks(FormattingDocumentModel, Block, CodeStyleSettings, CommonCodeStyleSettings.IndentOptions, FormatTextRanges, int)}
- * with '-1' as an interested offset.
- *
- * @param docModel
- * @param rootBlock
- * @param settings
- * @param indentOptions
- * @param affectedRanges
- * @return
- */
private static FormatProcessor buildProcessorAndWrapBlocks(final FormattingDocumentModel docModel,
Block rootBlock,
CodeStyleSettings settings,
CommonCodeStyleSettings.IndentOptions indentOptions,
- @Nullable FormatTextRanges affectedRanges)
- {
+ @Nullable FormatTextRanges affectedRanges) {
return buildProcessorAndWrapBlocks(docModel, rootBlock, settings, indentOptions, affectedRanges, -1);
}
-
- /**
- * Builds {@link FormatProcessor} instance and asks it to wrap all {@link Block code blocks}
- * {@link FormattingModel#getRootBlock() derived from the given model}.
- *
- * @param docModel target model
- * @param rootBlock root block to process
- * @param settings code style settings to use
- * @param indentOptions indent options to use
- * @param affectedRanges ranges to reformat
- * @param interestingOffset interesting offset; '-1' if no particular offset has a special interest
- * @return format processor instance with wrapped {@link Block code blocks}
- */
- @SuppressWarnings({"StatementWithEmptyBody"})
+
private static FormatProcessor buildProcessorAndWrapBlocks(final FormattingDocumentModel docModel,
Block rootBlock,
CodeStyleSettings settings,
@@ -599,7 +557,7 @@ public class FormatterImpl extends FormatterEx
final FormatProcessor processor = buildProcessorAndWrapBlocks(
documentModel, block, settings, indentOptions, new FormatTextRanges(affectedRange, true), offset
);
- final LeafBlockWrapper blockAfterOffset = processor.getBlockAtOrAfter(offset);
+ final LeafBlockWrapper blockAfterOffset = processor.getBlockRangesMap().getBlockAtOrAfter(offset);
if (blockAfterOffset != null && !blockAfterOffset.contains(offset)) {
final WhiteSpace whiteSpace = blockAfterOffset.getWhiteSpace();
diff --git a/platform/lang-impl/src/com/intellij/formatting/IndentImpl.java b/platform/lang-impl/src/com/intellij/formatting/IndentImpl.java
index 314531d5714b..cbed59277f78 100644
--- a/platform/lang-impl/src/com/intellij/formatting/IndentImpl.java
+++ b/platform/lang-impl/src/com/intellij/formatting/IndentImpl.java
@@ -18,7 +18,7 @@ package com.intellij.formatting;
import org.jetbrains.annotations.NonNls;
-class IndentImpl extends Indent {
+public class IndentImpl extends Indent {
private final boolean myIsAbsolute;
private final boolean myRelativeToDirectParent;
diff --git a/platform/lang-impl/src/com/intellij/formatting/IndentInside.java b/platform/lang-impl/src/com/intellij/formatting/IndentInside.java
index 709a08d7d3ee..b6b126f0812f 100644
--- a/platform/lang-impl/src/com/intellij/formatting/IndentInside.java
+++ b/platform/lang-impl/src/com/intellij/formatting/IndentInside.java
@@ -21,7 +21,7 @@ import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-class IndentInside {
+public class IndentInside {
public int whiteSpaces = 0;
public int tabs = 0;
@@ -66,7 +66,7 @@ class IndentInside {
}
@NotNull
- static IndentInside createIndentOn(@Nullable final CharSequence lastLine) {
+ public static IndentInside createIndentOn(@Nullable final CharSequence lastLine) {
final IndentInside result = new IndentInside();
if (lastLine == null) {
return result;
@@ -79,7 +79,7 @@ class IndentInside {
}
@NotNull
- static CharSequence getLastLine(@NotNull final CharSequence text) {
+ public static CharSequence getLastLine(@NotNull final CharSequence text) {
int i = CharArrayUtil.shiftBackwardUntil(text, text.length() - 1, "\n");
if (i < 0) {
return text;
diff --git a/platform/lang-impl/src/com/intellij/formatting/InitialInfoBuilder.java b/platform/lang-impl/src/com/intellij/formatting/InitialInfoBuilder.java
index b9db5823b859..db44a57a373f 100644
--- a/platform/lang-impl/src/com/intellij/formatting/InitialInfoBuilder.java
+++ b/platform/lang-impl/src/com/intellij/formatting/InitialInfoBuilder.java
@@ -17,6 +17,7 @@
package com.intellij.formatting;
import com.intellij.diagnostic.LogMessageEx;
+import com.intellij.formatting.engine.ExpandableIndent;
import com.intellij.lang.LanguageFormatting;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
@@ -46,7 +47,7 @@ import java.util.Set;
* Allows to build {@link AbstractBlockWrapper formatting block wrappers} for the target {@link Block formatting blocks}.
* The main idea of block wrapping is to associate information about {@link WhiteSpace white space before block} with the block itself.
*/
-class InitialInfoBuilder {
+public class InitialInfoBuilder {
private static final Logger LOG = Logger.getInstance("#com.intellij.formatting.InitialInfoBuilder");
private final Map myResult = new THashMap();
@@ -98,15 +99,16 @@ class InitialInfoBuilder {
myFormatterTagHandler = new FormatterTagHandler(settings);
}
- protected static InitialInfoBuilder prepareToBuildBlocksSequentially(Block root,
- FormattingDocumentModel model,
- @Nullable final FormatTextRanges affectedRanges,
- @NotNull CodeStyleSettings settings,
- final CommonCodeStyleSettings.IndentOptions options,
- int interestingOffset,
- @NotNull FormattingProgressCallback progressCallback)
+ protected static InitialInfoBuilder prepareToBuildBlocksSequentially(
+ Block root,
+ FormattingDocumentModel model,
+ FormatProcessor.FormatOptions formatOptions,
+ CodeStyleSettings settings,
+ CommonCodeStyleSettings.IndentOptions options,
+ @NotNull FormattingProgressCallback progressCallback)
{
- InitialInfoBuilder builder = new InitialInfoBuilder(root, model, affectedRanges, settings, options, interestingOffset, progressCallback);
+ InitialInfoBuilder builder = new InitialInfoBuilder(root, model, formatOptions.myAffectedRanges, settings, options, formatOptions.myInterestingOffset, progressCallback);
+ builder.setCollectAlignmentsInsideFormattingRange(formatOptions.myReformatContext);
builder.buildFrom(root, 0, null, null, null, true);
return builder;
}
@@ -120,8 +122,12 @@ class InitialInfoBuilder {
}
return minOffset;
}
+
+ public FormattingDocumentModel getFormattingDocumentModel() {
+ return myModel;
+ }
- int getEndOffset() {
+ public int getEndOffset() {
int maxDocOffset = myModel.getTextLength();
int maxOffset = myRootBlockWrapper != null ? myRootBlockWrapper.getEndOffset() : 0;
if (myAffectedRanges != null) {
diff --git a/platform/lang-impl/src/com/intellij/formatting/LeafBlockWrapper.java b/platform/lang-impl/src/com/intellij/formatting/LeafBlockWrapper.java
index 8ba16b4ddddd..ab3ef322a786 100644
--- a/platform/lang-impl/src/com/intellij/formatting/LeafBlockWrapper.java
+++ b/platform/lang-impl/src/com/intellij/formatting/LeafBlockWrapper.java
@@ -21,7 +21,7 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import org.jetbrains.annotations.Nullable;
-class LeafBlockWrapper extends AbstractBlockWrapper {
+public class LeafBlockWrapper extends AbstractBlockWrapper {
private static final int CONTAIN_LINE_FEEDS = 4;
private static final int READ_ONLY = 8;
private static final int LEAF = 16;
@@ -126,7 +126,7 @@ class LeafBlockWrapper extends AbstractBlockWrapper {
}
@Override
- protected IndentData getNumberOfSymbolsBeforeBlock() {
+ public IndentData getNumberOfSymbolsBeforeBlock() {
int spaces = getWhiteSpace().getSpaces();
int indentSpaces = getWhiteSpace().getIndentSpaces();
diff --git a/platform/lang-impl/src/com/intellij/formatting/SpacingImpl.java b/platform/lang-impl/src/com/intellij/formatting/SpacingImpl.java
index 06334cb58660..d0009f7aeb5e 100644
--- a/platform/lang-impl/src/com/intellij/formatting/SpacingImpl.java
+++ b/platform/lang-impl/src/com/intellij/formatting/SpacingImpl.java
@@ -16,13 +16,14 @@
package com.intellij.formatting;
+import com.intellij.formatting.engine.BlockRangesMap;
import org.jetbrains.annotations.NonNls;
/**
* Extends {@link Spacing} in order to keep number of additional settings like 'minSpaces', 'minLineFeeds',
* 'prefLineFeeds' etc.
*/
-class SpacingImpl extends Spacing {
+public class SpacingImpl extends Spacing {
private int myMinSpaces;
private int myKeepBlankLines;
private int myMaxSpaces;
@@ -76,7 +77,7 @@ class SpacingImpl extends Spacing {
return myMinLineFeeds;
}
- final boolean isReadOnly(){
+ public final boolean isReadOnly(){
return (myFlags & READ_ONLY_MASK) != 0;
}
@@ -90,10 +91,8 @@ class SpacingImpl extends Spacing {
/**
* Allows to ask to refresh current state using given formatter if necessary.
- *
- * @param formatter formatter to use during state refresh
*/
- public void refresh(FormatProcessor formatter) {
+ public void refresh(BlockRangesMap helper) {
}
public final boolean shouldKeepLineFeeds() {
diff --git a/platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java b/platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java
index 63692a899925..7bee50ca0ea3 100644
--- a/platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java
+++ b/platform/lang-impl/src/com/intellij/formatting/WhiteSpace.java
@@ -16,6 +16,7 @@
package com.intellij.formatting;
+import com.intellij.formatting.engine.BlockRangesMap;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
@@ -45,7 +46,7 @@ import java.util.ArrayList;
*
* Not thread-safe.
*/
-class WhiteSpace {
+public class WhiteSpace {
private static final char LINE_FEED = '\n';
@@ -392,16 +393,13 @@ class WhiteSpace {
/**
* Tries to ensure that number of line feeds managed by the current {@link WhiteSpace} is consistent to the settings
* defined at the given spacing property.
- *
- * @param spaceProperty space settings holder
- * @param formatProcessor format processor to use for space settings state refreshing
*/
- public void arrangeLineFeeds(final SpacingImpl spaceProperty, final FormatProcessor formatProcessor) {
+ public void arrangeLineFeeds(final SpacingImpl spaceProperty, final BlockRangesMap helper) {
performModification(new Runnable() {
@Override
public void run() {
if (spaceProperty != null) {
- spaceProperty.refresh(formatProcessor);
+ spaceProperty.refresh(helper);
if (spaceProperty.getMinLineFeeds() >= 0 && getLineFeeds() < spaceProperty.getMinLineFeeds()) {
setLineFeeds(spaceProperty.getMinLineFeeds());
@@ -541,11 +539,8 @@ class WhiteSpace {
*
* This method may be considered a shortcut for calling {@link #arrangeLineFeeds(SpacingImpl, FormatProcessor)} and
* {@link #arrangeSpaces(SpacingImpl)}.
- *
- * @param spacing spacing settings holder
- * @param formatProcessor format processor to use to refresh state of the given 'spacing' object
*/
- public void removeLineFeeds(final SpacingImpl spacing, final FormatProcessor formatProcessor) {
+ public void removeLineFeeds(final SpacingImpl spacing, final BlockRangesMap helper) {
performModification(new Runnable() {
@Override
public void run() {
@@ -554,7 +549,7 @@ class WhiteSpace {
myIndentSpaces = 0;
}
});
- arrangeLineFeeds(spacing, formatProcessor);
+ arrangeLineFeeds(spacing, helper);
arrangeSpaces(spacing);
}
diff --git a/platform/lang-impl/src/com/intellij/formatting/WrapImpl.java b/platform/lang-impl/src/com/intellij/formatting/WrapImpl.java
index 7be44a8e6816..1eb07bf66265 100644
--- a/platform/lang-impl/src/com/intellij/formatting/WrapImpl.java
+++ b/platform/lang-impl/src/com/intellij/formatting/WrapImpl.java
@@ -20,7 +20,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
-class WrapImpl extends Wrap {
+public class WrapImpl extends Wrap {
/**
* The block where the wrap needs to happen if the CHOP wrap mode is used and the chain of blocks exceeds the right margin.
*/
@@ -124,11 +124,11 @@ class WrapImpl extends Wrap {
myIgnoredWraps.get(wrap).add(currentBlock);
}
- enum Type{
+ public enum Type{
DO_NOT_WRAP, WRAP_AS_NEEDED, CHOP_IF_NEEDED, WRAP_ALWAYS
}
- LeafBlockWrapper getChopStartBlock() {
+ public LeafBlockWrapper getChopStartBlock() {
return myChopStartBlock;
}
@@ -139,7 +139,7 @@ class WrapImpl extends Wrap {
* '{@link #isActive() isActive}' property value is set (to true)
*
*/
- void setActive() {
+ public void setActive() {
myChopStartBlock = null;
myFlags |= ACTIVE_MASK;
}
@@ -150,7 +150,7 @@ class WrapImpl extends Wrap {
*
* @param startOffset new '{@link #getWrapOffset() firstPosition}' property value to use if current value is undefined (negative)
*/
- void setWrapOffset(final int startOffset) {
+ public void setWrapOffset(final int startOffset) {
if (myWrapOffset < 0) {
myWrapOffset = startOffset;
}
@@ -160,7 +160,7 @@ class WrapImpl extends Wrap {
* @return '{@link #getWrapOffset() firstPosition}' property value defined previously via {@link #setWrapOffset(int)} if any;
* '-1' otherwise
*/
- int getWrapOffset() {
+ public int getWrapOffset() {
return myWrapOffset;
}
@@ -180,7 +180,7 @@ class WrapImpl extends Wrap {
myFlags |= (wrapFirstElement ? WRAP_FIRST_ELEMENT_MASK:0) | (myType.ordinal() << TYPE_SHIFT) | (myId << ID_SHIFT);
}
- final Type getType() {
+ public final Type getType() {
return myTypes[(myFlags & TYPE_MASK) >>> TYPE_SHIFT];
}
@@ -190,17 +190,17 @@ class WrapImpl extends Wrap {
*
* @return 'wrapFirstElement' property value
*/
- final boolean isWrapFirstElement() {
+ public final boolean isWrapFirstElement() {
return (myFlags & WRAP_FIRST_ELEMENT_MASK) != 0;
}
- void saveChopBlock(LeafBlockWrapper current) {
+ public void saveChopBlock(LeafBlockWrapper current) {
if (myChopStartBlock == null) {
myChopStartBlock = current;
}
}
- final boolean isActive() {
+ public final boolean isActive() {
return (myFlags & ACTIVE_MASK) != 0;
}
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/AdjustWhiteSpacesState.java b/platform/lang-impl/src/com/intellij/formatting/engine/AdjustWhiteSpacesState.java
new file mode 100644
index 000000000000..1088d66132f8
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/AdjustWhiteSpacesState.java
@@ -0,0 +1,228 @@
+/*
+ * 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.formatting.engine;
+
+import com.intellij.formatting.*;
+import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.util.containers.ContainerUtil;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class AdjustWhiteSpacesState extends State {
+
+ private final FormattingProgressCallback myProgressCallback;
+
+ private WrapBlocksState myWrapBlocksState;
+
+
+ private LeafBlockWrapper myCurrentBlock;
+
+ private DependentSpacingEngine myDependentSpacingEngine;
+ private WrapProcessor myWrapProcessor;
+ private BlockRangesMap myBlockRangesMap;
+ private IndentAdjuster myIndentAdjuster;
+
+
+ private boolean myReformatContext;
+ private Set myAlignmentsInsideRangesToModify = null;
+
+ private final HashSet myAlignAgain = new HashSet();
+ private LeafBlockWrapper myFirstBlock;
+
+ public AdjustWhiteSpacesState(WrapBlocksState state,
+ FormattingProgressCallback progressCallback,
+ boolean isReformatContext) {
+ myWrapBlocksState = state;
+ myProgressCallback = progressCallback;
+ myReformatContext = isReformatContext;
+ }
+
+ @Override
+ public void prepare() {
+ if (myWrapBlocksState != null) {
+ myFirstBlock = myWrapBlocksState.getFirstBlock();
+ myCurrentBlock = myFirstBlock;
+ myDependentSpacingEngine = myWrapBlocksState.getDependentSpacingEngine();
+ myWrapProcessor = myWrapBlocksState.getWrapProcessor();
+ myIndentAdjuster = myWrapBlocksState.getIndentAdjuster();
+ myBlockRangesMap = myWrapBlocksState.getBlockRangesMap();
+ myAlignmentsInsideRangesToModify = myWrapBlocksState.getAlignmentsInsideRangesToModify();
+ }
+ }
+
+ public AdjustWhiteSpacesState(LeafBlockWrapper firstBlock,
+ DependentSpacingEngine dependentSpacingEngine,
+ WrapProcessor wrapProcessor,
+ IndentAdjuster indentAdjuster,
+ BlockRangesMap blockRangesMap,
+ Set alignmentsInsideRangesToModify,
+ boolean isReformatContext,
+ FormattingProgressCallback progressCallback)
+ {
+ myWrapBlocksState = null;
+ myCurrentBlock = firstBlock;
+ myDependentSpacingEngine = dependentSpacingEngine;
+ myWrapProcessor = wrapProcessor;
+ myIndentAdjuster = indentAdjuster;
+ myBlockRangesMap = blockRangesMap;
+ myAlignmentsInsideRangesToModify = alignmentsInsideRangesToModify;
+ myReformatContext = isReformatContext;
+ myProgressCallback = progressCallback;
+ }
+
+ public LeafBlockWrapper getCurrentBlock() {
+ return myCurrentBlock;
+ }
+
+ @Override
+ public void doIteration() {
+ LeafBlockWrapper blockToProcess = myCurrentBlock;
+ processToken();
+ if (blockToProcess != null) {
+ myProgressCallback.afterProcessingBlock(blockToProcess);
+ }
+
+ if (myCurrentBlock != null) {
+ return;
+ }
+
+ if (myAlignAgain.isEmpty()) {
+ setDone(true);
+ }
+ else {
+ myAlignAgain.clear();
+ myDependentSpacingEngine.clear();
+ myCurrentBlock = myFirstBlock;
+ }
+ }
+
+ private boolean isReformatSelectedRangesContext() {
+ return myReformatContext && !ContainerUtil.isEmpty(myAlignmentsInsideRangesToModify);
+ }
+
+ private void defineAlignOffset(final LeafBlockWrapper block) {
+ AbstractBlockWrapper current = myCurrentBlock;
+ while (true) {
+ final AlignmentImpl alignment = current.getAlignment();
+ if (alignment != null) {
+ alignment.setOffsetRespBlock(block);
+ }
+ current = current.getParent();
+ if (current == null) return;
+ if (current.getStartOffset() != myCurrentBlock.getStartOffset()) return;
+ }
+ }
+
+ private void onCurrentLineChanged() {
+ myWrapProcessor.onCurrentLineChanged();
+ }
+
+ private boolean isCurrentBlockAlignmentUsedInRangesToModify() {
+ AbstractBlockWrapper block = myCurrentBlock;
+ AlignmentImpl alignment = myCurrentBlock.getAlignment();
+
+ while (alignment == null) {
+ block = block.getParent();
+ if (block == null || block.getStartOffset() != myCurrentBlock.getStartOffset()) {
+ return false;
+ }
+ alignment = block.getAlignment();
+ }
+
+ return myAlignmentsInsideRangesToModify.contains(alignment);
+ }
+
+ private static List getDependentRegionRangesAfterCurrentWhiteSpace(final SpacingImpl spaceProperty,
+ final WhiteSpace whiteSpace) {
+ if (!(spaceProperty instanceof DependantSpacingImpl)) return ContainerUtil.emptyList();
+
+ if (whiteSpace.isReadOnly() || whiteSpace.isLineFeedsAreReadOnly()) return ContainerUtil.emptyList();
+
+ DependantSpacingImpl spacing = (DependantSpacingImpl)spaceProperty;
+ return ContainerUtil.filter(spacing.getDependentRegionRanges(), new Condition() {
+ @Override
+ public boolean value(TextRange dependencyRange) {
+ return whiteSpace.getStartOffset() < dependencyRange.getEndOffset();
+ }
+ });
+ }
+
+
+ private void processToken() {
+ final SpacingImpl spaceProperty = myCurrentBlock.getSpaceProperty();
+ final WhiteSpace whiteSpace = myCurrentBlock.getWhiteSpace();
+
+ if (isReformatSelectedRangesContext()) {
+ if (isCurrentBlockAlignmentUsedInRangesToModify() &&
+ whiteSpace.isReadOnly() &&
+ spaceProperty != null &&
+ !spaceProperty.isReadOnly()) {
+ whiteSpace.setReadOnly(false);
+ whiteSpace.setLineFeedsAreReadOnly(true);
+ }
+ }
+
+ whiteSpace.arrangeLineFeeds(spaceProperty, myBlockRangesMap);
+
+ if (!whiteSpace.containsLineFeeds()) {
+ whiteSpace.arrangeSpaces(spaceProperty);
+ }
+
+ try {
+ LeafBlockWrapper newBlock = myWrapProcessor.processWrap(myCurrentBlock);
+ if (newBlock != null) {
+ myCurrentBlock = newBlock;
+ return;
+ }
+ }
+ finally {
+ if (whiteSpace.containsLineFeeds()) {
+ onCurrentLineChanged();
+ }
+ }
+
+ LeafBlockWrapper newCurrentBlock = myIndentAdjuster.adjustIndent(myCurrentBlock);
+ if (newCurrentBlock != null) {
+ myCurrentBlock = newCurrentBlock;
+ onCurrentLineChanged();
+ return;
+ }
+
+ defineAlignOffset(myCurrentBlock);
+
+ if (myCurrentBlock.containsLineFeeds()) {
+ onCurrentLineChanged();
+ }
+
+
+ final List ranges = getDependentRegionRangesAfterCurrentWhiteSpace(spaceProperty, whiteSpace);
+ if (!ranges.isEmpty()) {
+ myDependentSpacingEngine.registerUnresolvedDependentSpacingRanges(spaceProperty, ranges);
+ }
+
+ if (!whiteSpace.isIsReadOnly() && myDependentSpacingEngine.shouldReformatPreviouslyLocatedDependentSpacing(whiteSpace)) {
+ myAlignAgain.add(whiteSpace);
+ }
+ else if (!myAlignAgain.isEmpty()) {
+ myAlignAgain.remove(whiteSpace);
+ }
+
+ myCurrentBlock = myCurrentBlock.getNextBlock();
+ }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/AlignmentHelper.java b/platform/lang-impl/src/com/intellij/formatting/engine/AlignmentHelper.java
new file mode 100644
index 000000000000..a5a48b202b15
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/AlignmentHelper.java
@@ -0,0 +1,146 @@
+/*
+ * 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.diagnostic.LogMessageEx;
+import com.intellij.formatting.*;
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.Language;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.MultiMap;
+
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class AlignmentHelper {
+ private static final Logger LOG = Logger.getInstance(AlignmentHelper.class);
+
+ private static final Map ALIGNMENT_PROCESSORS = new EnumMap(Alignment.Anchor.class);
+ static {
+ ALIGNMENT_PROCESSORS.put(Alignment.Anchor.LEFT, new LeftEdgeAlignmentProcessor());
+ ALIGNMENT_PROCESSORS.put(Alignment.Anchor.RIGHT, new RightEdgeAlignmentProcessor());
+ }
+
+ private final Set myAlignmentsToSkip = ContainerUtil.newHashSet();
+ private final Document myDocument;
+ private final BlockIndentOptions myBlockIndentOptions;
+
+ private final AlignmentCyclesDetector myCyclesDetector;
+
+ private final Map> myBackwardShiftedAlignedBlocks = ContainerUtil.newHashMap();
+ private final Map> myAlignmentMappings = ContainerUtil.newHashMap();
+
+ public AlignmentHelper(Document document, MultiMap blocksToAlign, BlockIndentOptions options) {
+ myDocument = document;
+ myBlockIndentOptions = options;
+ int totalBlocks = blocksToAlign.values().size();
+ myCyclesDetector = new AlignmentCyclesDetector(totalBlocks);
+ }
+
+ private static void reportAlignmentProcessingError(BlockAlignmentProcessor.Context context) {
+ ASTNode node = context.targetBlock.getNode();
+ Language language = node != null ? node.getPsi().getLanguage() : null;
+ LogMessageEx.error(LOG,
+ (language != null ? language.getDisplayName() + ": " : "") +
+ "Can't align block " + context.targetBlock, context.document.getText());
+ }
+
+ public LeafBlockWrapper applyAlignment(final AlignmentImpl alignment, final LeafBlockWrapper currentBlock) {
+ BlockAlignmentProcessor alignmentProcessor = ALIGNMENT_PROCESSORS.get(alignment.getAnchor());
+ if (alignmentProcessor == null) {
+ LOG.error(String.format("Can't find alignment processor for alignment anchor %s", alignment.getAnchor()));
+ return null;
+ }
+
+ BlockAlignmentProcessor.Context context = new BlockAlignmentProcessor.Context(
+ myDocument, alignment, currentBlock, myAlignmentMappings, myBackwardShiftedAlignedBlocks,
+ myBlockIndentOptions.getIndentOptions(currentBlock));
+ final LeafBlockWrapper offsetResponsibleBlock = alignment.getOffsetRespBlockBefore(currentBlock);
+ myCyclesDetector.registerOffsetResponsibleBlock(offsetResponsibleBlock);
+ BlockAlignmentProcessor.Result result = alignmentProcessor.applyAlignment(context);
+ switch (result) {
+ case TARGET_BLOCK_PROCESSED_NOT_ALIGNED:
+ return null;
+ case TARGET_BLOCK_ALIGNED:
+ storeAlignmentMapping(currentBlock);
+ return null;
+ case BACKWARD_BLOCK_ALIGNED:
+ if (offsetResponsibleBlock == null) {
+ return null;
+ }
+ Set blocksCausedRealignment = new HashSet();
+ myBackwardShiftedAlignedBlocks.clear();
+ myBackwardShiftedAlignedBlocks.put(offsetResponsibleBlock, blocksCausedRealignment);
+ blocksCausedRealignment.add(currentBlock);
+ storeAlignmentMapping(currentBlock, offsetResponsibleBlock);
+ if (myCyclesDetector.isCycleDetected()) {
+ reportAlignmentProcessingError(context);
+ return null;
+ }
+ myCyclesDetector.registerBlockRollback(currentBlock);
+ return offsetResponsibleBlock.getNextBlock();
+ case RECURSION_DETECTED:
+ myAlignmentsToSkip.add(alignment);
+ return offsetResponsibleBlock; // Fall through to the 'register alignment to skip'.
+ case UNABLE_TO_ALIGN_BACKWARD_BLOCK:
+ myAlignmentsToSkip.add(alignment);
+ return null;
+ default:
+ return null;
+ }
+ }
+
+ public boolean shouldSkip(AlignmentImpl alignment) {
+ return myAlignmentsToSkip.contains(alignment);
+ }
+
+ private void storeAlignmentMapping(AbstractBlockWrapper block1, AbstractBlockWrapper block2) {
+ doStoreAlignmentMapping(block1, block2);
+ doStoreAlignmentMapping(block2, block1);
+ }
+
+ private void doStoreAlignmentMapping(AbstractBlockWrapper key, AbstractBlockWrapper value) {
+ Set wrappers = myAlignmentMappings.get(key);
+ if (wrappers == null) {
+ myAlignmentMappings.put(key, wrappers = new HashSet());
+ }
+ wrappers.add(value);
+ }
+
+ private void storeAlignmentMapping(LeafBlockWrapper currentBlock) {
+ AlignmentImpl alignment = null;
+ AbstractBlockWrapper block = currentBlock;
+ while (alignment == null && block != null) {
+ alignment = block.getAlignment();
+ block = block.getParent();
+ }
+ if (alignment != null) {
+ block = alignment.getOffsetRespBlockBefore(currentBlock);
+ if (block != null) {
+ storeAlignmentMapping(currentBlock, block);
+ }
+ }
+ }
+
+ public void reset() {
+ myBackwardShiftedAlignedBlocks.clear();
+ myAlignmentMappings.clear();
+ }
+}
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/ApplyChangesState.java b/platform/lang-impl/src/com/intellij/formatting/engine/ApplyChangesState.java
new file mode 100644
index 000000000000..3f6eb0968ed6
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/ApplyChangesState.java
@@ -0,0 +1,225 @@
+/*
+ * 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 com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.TextChange;
+import com.intellij.openapi.editor.ex.DocumentEx;
+import com.intellij.openapi.editor.impl.BulkChangesMerger;
+import com.intellij.openapi.editor.impl.TextChangeImpl;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ApplyChangesState extends State {
+
+ /**
+ * There is a possible case that formatting introduced big number of changes to the underlying document. That number may be
+ * big enough for that their subsequent appliance is much slower than direct replacing of the whole document text.
+ *
+ * Current constant holds minimum number of changes that should trigger such 'replace whole text' optimization.
+ */
+ private static final int BULK_REPLACE_OPTIMIZATION_CRITERIA = 3000;
+
+
+ private final FormattingModel myModel;
+
+ private final FormattingProgressCallback myProgressCallback;
+ private final WrapBlocksState myWrapState;
+
+ private List myBlocksToModify;
+ private int myShift;
+ private int myIndex;
+ private boolean myResetBulkUpdateState;
+
+ private BlockIndentOptions myBlockIndentOptions;
+
+ public ApplyChangesState(FormattingModel model, WrapBlocksState state, FormattingProgressCallback callback) {
+ myModel = model;
+ myWrapState = state;
+ myProgressCallback = callback;
+ myBlockIndentOptions = state.getBlockIndentOptions();
+ }
+
+ /**
+ * Performs formatter changes in a series of blocks, for each block a new contents of document is calculated
+ * and whole document is replaced in one operation.
+ *
+ * @param blocksToModify changes introduced by formatter
+ * @param model current formatting model
+ */
+ @SuppressWarnings({"deprecation"})
+ private void applyChangesAtRewriteMode(@NotNull final List blocksToModify,
+ @NotNull final FormattingModel model) {
+ FormattingDocumentModel documentModel = model.getDocumentModel();
+ Document document = documentModel.getDocument();
+ CaretOffsetUpdater caretOffsetUpdater = new CaretOffsetUpdater(document);
+
+ if (document instanceof DocumentEx) ((DocumentEx)document).setInBulkUpdate(true);
+ try {
+ List changes = new ArrayList();
+ int shift = 0;
+ int currentIterationShift = 0;
+ for (LeafBlockWrapper block : blocksToModify) {
+ WhiteSpace whiteSpace = block.getWhiteSpace();
+ CharSequence newWs = documentModel.adjustWhiteSpaceIfNecessary(
+ whiteSpace.generateWhiteSpace(myBlockIndentOptions.getIndentOptions(block)), whiteSpace.getStartOffset(),
+ whiteSpace.getEndOffset(), block.getNode(), false
+ );
+ if (changes.size() > 10000) {
+ caretOffsetUpdater.update(changes);
+ CharSequence mergeResult =
+ BulkChangesMerger.INSTANCE.mergeToCharSequence(document.getChars(), document.getTextLength(), changes);
+ document.replaceString(0, document.getTextLength(), mergeResult);
+ shift += currentIterationShift;
+ currentIterationShift = 0;
+ changes.clear();
+ }
+ TextChangeImpl change = new TextChangeImpl(newWs, whiteSpace.getStartOffset() + shift, whiteSpace.getEndOffset() + shift);
+ currentIterationShift += change.getDiff();
+ changes.add(change);
+ }
+ caretOffsetUpdater.update(changes);
+ CharSequence mergeResult = BulkChangesMerger.INSTANCE.mergeToCharSequence(document.getChars(), document.getTextLength(), changes);
+ document.replaceString(0, document.getTextLength(), mergeResult);
+ }
+ finally {
+ if (document instanceof DocumentEx) ((DocumentEx)document).setInBulkUpdate(false);
+ }
+
+ caretOffsetUpdater.restoreCaretLocations();
+ cleanupBlocks(blocksToModify);
+ }
+
+ private static void cleanupBlocks(List blocks) {
+ for (LeafBlockWrapper block : blocks) {
+ block.getParent().dispose();
+ block.dispose();
+ }
+ blocks.clear();
+ }
+
+ @Nullable
+ private static DocumentEx getAffectedDocument(final FormattingModel model) {
+ final Document document = model.getDocumentModel().getDocument();
+ if (document instanceof DocumentEx) {
+ return (DocumentEx)document;
+ }
+ else {
+ return null;
+ }
+ }
+
+ private List collectBlocksToModify() {
+ List blocksToModify = new ArrayList();
+ LeafBlockWrapper firstBlock = myWrapState.getFirstBlock();
+ for (LeafBlockWrapper block = firstBlock; block != null; block = block.getNextBlock()) {
+ final WhiteSpace whiteSpace = block.getWhiteSpace();
+ if (!whiteSpace.isReadOnly()) {
+ final String newWhiteSpace = whiteSpace.generateWhiteSpace(myBlockIndentOptions.getIndentOptions(block));
+ if (!whiteSpace.equalsToString(newWhiteSpace)) {
+ blocksToModify.add(block);
+ }
+ }
+ }
+ return blocksToModify;
+ }
+
+ @Override
+ public void prepare() {
+ myBlocksToModify = collectBlocksToModify();
+ // call doModifications static method to ensure no access to state
+ // thus we may clear formatting state
+
+ //reset();
+ //myDisposed = true;
+
+ if (myBlocksToModify.isEmpty()) {
+ setDone(true);
+ return;
+ }
+
+ myProgressCallback.beforeApplyingFormatChanges(myBlocksToModify);
+
+ final int blocksToModifyCount = myBlocksToModify.size();
+ if (blocksToModifyCount > BULK_REPLACE_OPTIMIZATION_CRITERIA) {
+ applyChangesAtRewriteMode(myBlocksToModify, myModel);
+ setDone(true);
+ }
+ else if (blocksToModifyCount > 50) {
+ DocumentEx updatedDocument = getAffectedDocument(myModel);
+ if (updatedDocument != null) {
+ updatedDocument.setInBulkUpdate(true);
+ myResetBulkUpdateState = true;
+ }
+ }
+ }
+
+ @Override
+ protected void doIteration() {
+ LeafBlockWrapper blockWrapper = myBlocksToModify.get(myIndex);
+ myShift = FormatProcessorUtils.replaceWhiteSpace(
+ myModel,
+ blockWrapper,
+ myShift,
+ blockWrapper.getWhiteSpace().generateWhiteSpace(myBlockIndentOptions.getIndentOptions(blockWrapper)),
+ myBlockIndentOptions.getIndentOptions()
+ );
+ myProgressCallback.afterApplyingChange(blockWrapper);
+ // block could be gc'd
+ blockWrapper.getParent().dispose();
+ blockWrapper.dispose();
+ myBlocksToModify.set(myIndex, null);
+ myIndex++;
+
+ if (myIndex >= myBlocksToModify.size()) {
+ setDone(true);
+ }
+ }
+
+ @Override
+ protected void setDone(boolean done) {
+ super.setDone(done);
+
+ if (myResetBulkUpdateState) {
+ DocumentEx document = getAffectedDocument(myModel);
+ if (document != null) {
+ document.setInBulkUpdate(false);
+ myResetBulkUpdateState = false;
+ }
+ }
+
+ if (done) {
+ myModel.commitChanges();
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (myIndex > 0) {
+ UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ myModel.commitChanges();
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/BlockIndentOptions.java b/platform/lang-impl/src/com/intellij/formatting/engine/BlockIndentOptions.java
new file mode 100644
index 000000000000..9cdb278fb594
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/BlockIndentOptions.java
@@ -0,0 +1,78 @@
+/*
+ * 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.ASTBlock;
+import com.intellij.formatting.AbstractBlockWrapper;
+import com.intellij.formatting.Block;
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.Language;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.codeStyle.CodeStyleSettings;
+import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
+import org.jetbrains.annotations.NotNull;
+
+public class BlockIndentOptions {
+ private final CodeStyleSettings mySettings;
+ private final CommonCodeStyleSettings.IndentOptions myIndentOptions;
+ private final int myRightMargin;
+
+ public BlockIndentOptions(@NotNull CodeStyleSettings settings, @NotNull CommonCodeStyleSettings.IndentOptions indentOptions, Block block) {
+ mySettings = settings;
+ myIndentOptions = indentOptions;
+ myRightMargin = calcRightMargin(block);
+ }
+
+ public CommonCodeStyleSettings.IndentOptions getIndentOptions() {
+ return myIndentOptions;
+ }
+
+ @NotNull
+ public CommonCodeStyleSettings.IndentOptions getIndentOptions(@NotNull AbstractBlockWrapper block) {
+ final Language language = block.getLanguage();
+ if (language == null) {
+ return myIndentOptions;
+ }
+ final CommonCodeStyleSettings commonSettings = mySettings.getCommonSettings(language);
+ if (commonSettings == null) {
+ return myIndentOptions;
+ }
+ final CommonCodeStyleSettings.IndentOptions result = commonSettings.getIndentOptions();
+ return result == null ? myIndentOptions : result;
+ }
+
+ public int getRightMargin() {
+ return myRightMargin;
+ }
+
+ private int calcRightMargin(Block rootBlock) {
+ Language language = null;
+ if (rootBlock instanceof ASTBlock) {
+ ASTNode node = ((ASTBlock)rootBlock).getNode();
+ if (node != null) {
+ PsiElement psiElement = node.getPsi();
+ if (psiElement.isValid()) {
+ PsiFile psiFile = psiElement.getContainingFile();
+ if (psiFile != null) {
+ language = psiFile.getViewProvider().getBaseLanguage();
+ }
+ }
+ }
+ }
+ return mySettings.getRightMargin(language);
+ }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/BlockRangesMap.java b/platform/lang-impl/src/com/intellij/formatting/engine/BlockRangesMap.java
new file mode 100644
index 000000000000..422cf75a364f
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/BlockRangesMap.java
@@ -0,0 +1,88 @@
+/*
+ * 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.LeafBlockWrapper;
+import com.intellij.openapi.util.TextRange;
+import gnu.trove.TIntObjectHashMap;
+import org.jetbrains.annotations.Nullable;
+
+public class BlockRangesMap {
+ private final LeafBlockWrapper myLastBlock;
+ private TIntObjectHashMap myTextRangeToWrapper;
+
+ public BlockRangesMap(LeafBlockWrapper first, LeafBlockWrapper last) {
+ myLastBlock = last;
+ myTextRangeToWrapper = buildTextRangeToInfoMap(first);
+ }
+
+ private static TIntObjectHashMap buildTextRangeToInfoMap(final LeafBlockWrapper first) {
+ final TIntObjectHashMap result = new TIntObjectHashMap();
+ LeafBlockWrapper current = first;
+ while (current != null) {
+ result.put(current.getStartOffset(), current);
+ current = current.getNextBlock();
+ }
+ return result;
+ }
+
+ public boolean containsLineFeeds(final TextRange dependency) {
+ LeafBlockWrapper child = myTextRangeToWrapper.get(dependency.getStartOffset());
+ if (child == null) return false;
+ if (child.containsLineFeeds()) return true;
+ final int endOffset = dependency.getEndOffset();
+ while (child.getEndOffset() < endOffset) {
+ child = child.getNextBlock();
+ if (child == null) return false;
+ if (child.getWhiteSpace().containsLineFeeds()) return true;
+ if (child.containsLineFeeds()) return true;
+ }
+ return false;
+ }
+
+ @Nullable
+ public LeafBlockWrapper getBlockAtOrAfter(final int startOffset) {
+ int current = startOffset;
+ LeafBlockWrapper result = null;
+ while (current < myLastBlock.getEndOffset()) {
+ final LeafBlockWrapper currentValue = myTextRangeToWrapper.get(current);
+ if (currentValue != null) {
+ result = currentValue;
+ break;
+ }
+ current++;
+ }
+
+ LeafBlockWrapper prevBlock = getPrevBlock(result);
+
+ if (prevBlock != null && prevBlock.contains(startOffset)) {
+ return prevBlock;
+ }
+ else {
+ return result;
+ }
+ }
+
+ @Nullable
+ private LeafBlockWrapper getPrevBlock(@Nullable final LeafBlockWrapper result) {
+ if (result != null) {
+ return result.getPreviousBlock();
+ }
+ else {
+ return myLastBlock;
+ }
+ }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/CaretOffsetUpdater.java b/platform/lang-impl/src/com/intellij/formatting/engine/CaretOffsetUpdater.java
new file mode 100644
index 000000000000..1423dfafb619
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/CaretOffsetUpdater.java
@@ -0,0 +1,51 @@
+/*
+ * 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.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.editor.TextChange;
+import com.intellij.openapi.editor.impl.BulkChangesMerger;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class CaretOffsetUpdater {
+ private final Map myCaretOffsets = new HashMap();
+
+ public CaretOffsetUpdater(@NotNull Document document) {
+ Editor[] editors = EditorFactory.getInstance().getEditors(document);
+ for (Editor editor : editors) {
+ myCaretOffsets.put(editor, editor.getCaretModel().getOffset());
+ }
+ }
+
+ public void update(@NotNull List extends TextChange> changes) {
+ BulkChangesMerger merger = BulkChangesMerger.INSTANCE;
+ for (Map.Entry entry : myCaretOffsets.entrySet()) {
+ entry.setValue(merger.updateOffset(entry.getValue(), changes));
+ }
+ }
+
+ public void restoreCaretLocations() {
+ for (Map.Entry entry : myCaretOffsets.entrySet()) {
+ entry.getKey().getCaretModel().moveToOffset(entry.getValue());
+ }
+ }
+ }
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/DependentSpacingEngine.java b/platform/lang-impl/src/com/intellij/formatting/engine/DependentSpacingEngine.java
new file mode 100644
index 000000000000..c3e1d1ac39cc
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/DependentSpacingEngine.java
@@ -0,0 +1,109 @@
+/*
+ * 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.DependantSpacingImpl;
+import com.intellij.formatting.SpacingImpl;
+import com.intellij.formatting.WhiteSpace;
+import com.intellij.openapi.util.TextRange;
+
+import java.util.*;
+
+/**
+ * Formatter provides a notion of {@link DependantSpacingImpl dependent spacing}, i.e. spacing that insist on line feed if target
+ * dependent region contains line feed.
+ *
+ * Example:
+ *
+ * int[] data = {1, 2, 3};
+ *
+ * We want to keep that in one line if possible but place curly braces on separate lines if the width is not enough:
+ *
+ * int[] data = { | < right margin
+ * 1, 2, 3 |
+ * } |
+ *
+ * There is a possible case that particular block has dependent spacing property that targets region that lays beyond the
+ * current block. E.g. consider example above - '1' block has dependent spacing that targets the whole
+ * '{1, 2, 3}' block. So, it's not possible to answer whether line feed should be used during processing block
+ * '1'.
+ *
+ * We store such 'forward dependencies' at the current collection where the key is the range of the target 'dependent forward
+ * region' and value is dependent spacing object.
+ *
+ * Every time we detect that formatter changes 'has line feeds' status of such dependent region, we
+ * {@link DependantSpacingImpl#setDependentRegionLinefeedStatusChanged() mark} the dependent spacing as changed and schedule one more
+ * formatting iteration.
+ */
+public class DependentSpacingEngine {
+ private final BlockRangesMap myBlockRangesMap;
+
+ private SortedMap myPreviousDependencies =
+ new TreeMap(new Comparator() {
+ @Override
+ public int compare(final TextRange o1, final TextRange o2) {
+ int offsetsDelta = o1.getEndOffset() - o2.getEndOffset();
+
+ if (offsetsDelta == 0) {
+ offsetsDelta = o2.getStartOffset() - o1.getStartOffset(); // starting earlier is greater
+ }
+ return offsetsDelta;
+ }
+ });
+
+ public DependentSpacingEngine(BlockRangesMap helper) {
+ myBlockRangesMap = helper;
+ }
+
+ public boolean shouldReformatPreviouslyLocatedDependentSpacing(WhiteSpace space) {
+ final TextRange changed = space.getTextRange();
+ final SortedMap sortedHeadMap = myPreviousDependencies.tailMap(changed);
+
+ for (final Map.Entry entry : sortedHeadMap.entrySet()) {
+ final TextRange textRange = entry.getKey();
+
+ if (textRange.contains(changed)) {
+ final DependantSpacingImpl spacing = entry.getValue();
+ if (spacing.isDependentRegionLinefeedStatusChanged()) {
+ continue;
+ }
+
+ final boolean containedLineFeeds = spacing.getMinLineFeeds() > 0;
+ final boolean containsLineFeeds = myBlockRangesMap.containsLineFeeds(textRange);
+
+ if (containedLineFeeds != containsLineFeeds) {
+ spacing.setDependentRegionLinefeedStatusChanged();
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public void registerUnresolvedDependentSpacingRanges(final SpacingImpl spaceProperty, List unprocessedRanges) {
+ final DependantSpacingImpl dependantSpaceProperty = (DependantSpacingImpl)spaceProperty;
+ if (dependantSpaceProperty.isDependentRegionLinefeedStatusChanged()) return;
+
+ for (TextRange range: unprocessedRanges) {
+ myPreviousDependencies.put(range, dependantSpaceProperty);
+ }
+ }
+
+ public void clear() {
+ myPreviousDependencies.clear();
+ }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/ExpandChildrenIndentState.java b/platform/lang-impl/src/com/intellij/formatting/engine/ExpandChildrenIndentState.java
new file mode 100644
index 000000000000..296a436760b3
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/ExpandChildrenIndentState.java
@@ -0,0 +1,221 @@
+/*
+ * 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 com.intellij.openapi.editor.Document;
+import com.intellij.util.containers.MultiMap;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+public class ExpandChildrenIndentState extends State {
+ private final Document myDocument;
+ private final WrapBlocksState myWrapState;
+ private IndentAdjuster myIndentAdjuster;
+ private MultiMap myExpandableIndents;
+ private LeafBlockWrapper myCurrentBlock;
+
+ private Iterator myIterator;
+ private MultiMap myBlocksToRealign = new MultiMap();
+
+ public ExpandChildrenIndentState(Document document, WrapBlocksState state) {
+ myDocument = document;
+ myWrapState = state;
+ }
+
+ @Override
+ public void prepare() {
+ myExpandableIndents = myWrapState.getExpandableIndent();
+ myIndentAdjuster = myWrapState.getIndentAdjuster();
+ myIterator = myExpandableIndents.keySet().iterator();
+ }
+
+ @Override
+ protected void doIteration() {
+ if (!myIterator.hasNext()) {
+ setDone(true);
+ return;
+ }
+
+ final ExpandableIndent indent = myIterator.next();
+ Collection blocksToExpandIndent = myExpandableIndents.get(indent);
+ if (shouldExpand(blocksToExpandIndent)) {
+ for (AbstractBlockWrapper block : blocksToExpandIndent) {
+ indent.setEnforceIndent(true);
+ reindentNewLineChildren(block);
+ indent.setEnforceIndent(false);
+ }
+ }
+
+ restoreAlignments(myBlocksToRealign);
+ myBlocksToRealign.clear();
+ }
+
+ private void restoreAlignments(MultiMap blocks) {
+ for (Alignment alignment : blocks.keySet()) {
+ AlignmentImpl alignmentImpl = (AlignmentImpl)alignment;
+ if (!alignmentImpl.isAllowBackwardShift()) continue;
+
+ Set toRealign = alignmentImpl.getOffsetResponsibleBlocks();
+ arrangeSpaces(toRealign);
+
+ LeafBlockWrapper rightMostBlock = getRightMostBlock(toRealign);
+ int maxSpacesBeforeBlock = rightMostBlock.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
+ int rightMostBlockLine = myDocument.getLineNumber(rightMostBlock.getStartOffset());
+
+ for (LeafBlockWrapper block : toRealign) {
+ int currentBlockLine = myDocument.getLineNumber(block.getStartOffset());
+ if (currentBlockLine == rightMostBlockLine) continue;
+
+ int blockIndent = block.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
+ int delta = maxSpacesBeforeBlock - blockIndent;
+ if (delta > 0) {
+ int newSpaces = block.getWhiteSpace().getTotalSpaces() + delta;
+ adjustSpacingToKeepAligned(block, newSpaces);
+ }
+ }
+ }
+ }
+
+ private void adjustSpacingToKeepAligned(LeafBlockWrapper block, int newSpaces) {
+ WhiteSpace space = block.getWhiteSpace();
+ SpacingImpl property = block.getSpaceProperty();
+ if (property == null) return;
+ space.arrangeSpaces(new SpacingImpl(newSpaces, newSpaces,
+ property.getMinLineFeeds(),
+ property.isReadOnly(),
+ property.isSafe(),
+ property.shouldKeepLineFeeds(),
+ property.getKeepBlankLines(),
+ property.shouldKeepFirstColumn(),
+ property.getPrefLineFeeds()));
+ }
+
+ private LeafBlockWrapper getRightMostBlock(Collection toRealign) {
+ int maxSpacesBeforeBlock = -1;
+ LeafBlockWrapper rightMostBlock = null;
+
+ for (LeafBlockWrapper block : toRealign) {
+ int spaces = block.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
+ if (spaces > maxSpacesBeforeBlock) {
+ maxSpacesBeforeBlock = spaces;
+ rightMostBlock = block;
+ }
+ }
+
+ return rightMostBlock;
+ }
+
+ private void arrangeSpaces(Collection toRealign) {
+ for (LeafBlockWrapper block : toRealign) {
+ WhiteSpace whiteSpace = block.getWhiteSpace();
+ SpacingImpl spacing = block.getSpaceProperty();
+ whiteSpace.arrangeSpaces(spacing);
+ }
+ }
+
+ private boolean shouldExpand(Collection blocksToExpandIndent) {
+ AbstractBlockWrapper last = null;
+ for (AbstractBlockWrapper block : blocksToExpandIndent) {
+ if (block.getWhiteSpace().containsLineFeeds()) {
+ return true;
+ }
+ last = block;
+ }
+
+ if (last != null) {
+ AbstractBlockWrapper next = getNextBlock(last);
+ if (next != null && next.getWhiteSpace().containsLineFeeds()) {
+ int nextNewLineBlockIndent = next.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
+ if (nextNewLineBlockIndent >= finMinNewLineIndent(blocksToExpandIndent)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private int finMinNewLineIndent(@NotNull Collection wrappers) {
+ int totalMinimum = Integer.MAX_VALUE;
+ for (AbstractBlockWrapper wrapper : wrappers) {
+ int minNewLineIndent = findMinNewLineIndent(wrapper);
+ if (minNewLineIndent < totalMinimum) {
+ totalMinimum = minNewLineIndent;
+ }
+ }
+ return totalMinimum;
+ }
+
+ private int findMinNewLineIndent(@NotNull AbstractBlockWrapper block) {
+ if (block instanceof LeafBlockWrapper && block.getWhiteSpace().containsLineFeeds()) {
+ return block.getNumberOfSymbolsBeforeBlock().getTotalSpaces();
+ }
+ else if (block instanceof CompositeBlockWrapper) {
+ List children = ((CompositeBlockWrapper)block).getChildren();
+ int currentMin = Integer.MAX_VALUE;
+ for (AbstractBlockWrapper child : children) {
+ int childIndent = findMinNewLineIndent(child);
+ if (childIndent < currentMin) {
+ currentMin = childIndent;
+ }
+ }
+ return currentMin;
+ }
+ return Integer.MAX_VALUE;
+ }
+
+ private AbstractBlockWrapper getNextBlock(AbstractBlockWrapper block) {
+ List children = block.getParent().getChildren();
+ int nextBlockIndex = children.indexOf(block) + 1;
+ if (nextBlockIndex < children.size()) {
+ return children.get(nextBlockIndex);
+ }
+ return null;
+ }
+
+ private void reindentNewLineChildren(final @NotNull AbstractBlockWrapper block) {
+ if (block instanceof LeafBlockWrapper) {
+ WhiteSpace space = block.getWhiteSpace();
+
+ if (space.containsLineFeeds()) {
+ myCurrentBlock = (LeafBlockWrapper)block;
+ myIndentAdjuster.adjustIndent(myCurrentBlock); //since aligned block starts new line, it should not touch any other block
+ storeAlignmentsAfterCurrentBlock();
+ }
+ }
+ else if (block instanceof CompositeBlockWrapper) {
+ List children = ((CompositeBlockWrapper)block).getChildren();
+ for (AbstractBlockWrapper childBlock : children) {
+ reindentNewLineChildren(childBlock);
+ }
+ }
+ }
+
+ private void storeAlignmentsAfterCurrentBlock() {
+ LeafBlockWrapper current = myCurrentBlock.getNextBlock();
+ while (current != null && !current.getWhiteSpace().containsLineFeeds()) {
+ if (current.getAlignment() != null) {
+ myBlocksToRealign.putValue(current.getAlignment(), current);
+ }
+ current = current.getNextBlock();
+ }
+ }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/ExpandableIndent.java b/platform/lang-impl/src/com/intellij/formatting/engine/ExpandableIndent.java
similarity index 92%
rename from platform/lang-impl/src/com/intellij/formatting/ExpandableIndent.java
rename to platform/lang-impl/src/com/intellij/formatting/engine/ExpandableIndent.java
index 8b60c5fb6ba7..fd634ca383d8 100644
--- a/platform/lang-impl/src/com/intellij/formatting/ExpandableIndent.java
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/ExpandableIndent.java
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.intellij.formatting;
+package com.intellij.formatting.engine;
+
+import com.intellij.formatting.IndentImpl;
public class ExpandableIndent extends IndentImpl {
private boolean myEnforceIndent;
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/FormatProcessorUtils.java b/platform/lang-impl/src/com/intellij/formatting/engine/FormatProcessorUtils.java
new file mode 100644
index 000000000000..c6b8066edb26
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/FormatProcessorUtils.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.intellij.openapi.util.TextRange;
+import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
+import org.jetbrains.annotations.NotNull;
+
+public class FormatProcessorUtils {
+
+
+ private static int calcShift(@NotNull final IndentInside oldIndent,
+ @NotNull final IndentInside newIndent,
+ @NotNull final CommonCodeStyleSettings.IndentOptions options)
+ {
+ if (oldIndent.equals(newIndent)) return 0;
+ return newIndent.getSpacesCount(options) - oldIndent.getSpacesCount(options);
+ }
+
+ public static int replaceWhiteSpace(final FormattingModel model,
+ @NotNull final LeafBlockWrapper block,
+ int shift,
+ final CharSequence _newWhiteSpace,
+ final CommonCodeStyleSettings.IndentOptions options
+ ) {
+ final WhiteSpace whiteSpace = block.getWhiteSpace();
+ final TextRange textRange = whiteSpace.getTextRange();
+ final TextRange wsRange = textRange.shiftRight(shift);
+ final String newWhiteSpace = _newWhiteSpace.toString();
+ TextRange newWhiteSpaceRange = model instanceof FormattingModelEx
+ ? ((FormattingModelEx) model).replaceWhiteSpace(wsRange, block.getNode(), newWhiteSpace)
+ : model.replaceWhiteSpace(wsRange, newWhiteSpace);
+
+ shift += newWhiteSpaceRange.getLength() - textRange.getLength();
+
+ if (block.isLeaf() && whiteSpace.containsLineFeeds() && block.containsLineFeeds()) {
+ final TextRange currentBlockRange = block.getTextRange().shiftRight(shift);
+
+ IndentInside oldBlockIndent = whiteSpace.getInitialLastLineIndent();
+ IndentInside whiteSpaceIndent = IndentInside.createIndentOn(IndentInside.getLastLine(newWhiteSpace));
+ final int shiftInside = calcShift(oldBlockIndent, whiteSpaceIndent, options);
+
+ if (shiftInside != 0 || !oldBlockIndent.equals(whiteSpaceIndent)) {
+ final TextRange newBlockRange = model.shiftIndentInsideRange(block.getNode(), currentBlockRange, shiftInside);
+ shift += newBlockRange.getLength() - block.getLength();
+ }
+ }
+ return shift;
+ }
+
+}
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/IndentAdjuster.java b/platform/lang-impl/src/com/intellij/formatting/engine/IndentAdjuster.java
new file mode 100644
index 000000000000..8d4272916928
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/IndentAdjuster.java
@@ -0,0 +1,143 @@
+/*
+ * 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 com.intellij.formatting.FormatProcessor;
+import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
+import org.jetbrains.annotations.Nullable;
+
+public class IndentAdjuster {
+ private final AlignmentHelper myAlignmentHelper;
+ private final BlockIndentOptions myBlockIndentOptions;
+
+ public IndentAdjuster(BlockIndentOptions blockIndentOptions, AlignmentHelper alignmentHelper) {
+ myAlignmentHelper = alignmentHelper;
+ myBlockIndentOptions = blockIndentOptions;
+ }
+
+ /**
+ * Sometimes to align block we adjust whitespace of other block before.
+ * In such a case, we rollback to that block restarting formatting from there.
+ *
+ * @return new current block if we need to rollback, null otherwise
+ */
+ public LeafBlockWrapper adjustIndent(LeafBlockWrapper block) {
+ AlignmentImpl alignment = CoreFormatterUtil.getAlignment(block);
+ WhiteSpace whiteSpace = block.getWhiteSpace();
+
+ if (alignment == null || myAlignmentHelper.shouldSkip(alignment)) {
+ if (whiteSpace.containsLineFeeds()) {
+ adjustSpacingByIndentOffset(block);
+ }
+ else {
+ whiteSpace.arrangeSpaces(block.getSpaceProperty());
+ }
+ return null;
+ }
+
+ return myAlignmentHelper.applyAlignment(alignment, block);
+ }
+
+ private void adjustSpacingByIndentOffset(LeafBlockWrapper block) {
+ CommonCodeStyleSettings.IndentOptions options = myBlockIndentOptions.getIndentOptions(block);
+ IndentData offset = block.calculateOffset(options);
+ block.getWhiteSpace().setSpaces(offset.getSpaces(), offset.getIndentSpaces());
+ }
+
+ public void adjustLineIndent(LeafBlockWrapper myCurrentBlock) {
+ IndentData alignOffset = getAlignOffset(myCurrentBlock);
+
+ if (alignOffset == null) {
+ adjustSpacingByIndentOffset(myCurrentBlock);
+ }
+ else {
+ myCurrentBlock.getWhiteSpace().setSpaces(alignOffset.getSpaces(), alignOffset.getIndentSpaces());
+ }
+ }
+
+ @Nullable
+ private static IndentData getAlignOffset(LeafBlockWrapper myCurrentBlock) {
+ AbstractBlockWrapper current = myCurrentBlock;
+ while (true) {
+ final AlignmentImpl alignment = current.getAlignment();
+ LeafBlockWrapper offsetResponsibleBlock;
+ if (alignment != null && (offsetResponsibleBlock = alignment.getOffsetRespBlockBefore(myCurrentBlock)) != null) {
+ final WhiteSpace whiteSpace = offsetResponsibleBlock.getWhiteSpace();
+ if (whiteSpace.containsLineFeeds()) {
+ return new IndentData(whiteSpace.getIndentSpaces(), whiteSpace.getSpaces());
+ }
+ else {
+ final int offsetBeforeBlock = CoreFormatterUtil.getStartColumn(offsetResponsibleBlock);
+ final AbstractBlockWrapper indentedParentBlock = CoreFormatterUtil.getIndentedParentBlock(myCurrentBlock);
+ if (indentedParentBlock == null) {
+ return new IndentData(0, offsetBeforeBlock);
+ }
+ else {
+ final int parentIndent = indentedParentBlock.getWhiteSpace().getIndentOffset();
+ if (parentIndent > offsetBeforeBlock) {
+ return new IndentData(0, offsetBeforeBlock);
+ }
+ else {
+ return new IndentData(parentIndent, offsetBeforeBlock - parentIndent);
+ }
+ }
+ }
+ }
+ else {
+ current = current.getParent();
+ if (current == null || current.getStartOffset() != myCurrentBlock.getStartOffset()) return null;
+ }
+ }
+ }
+
+ public IndentInfo adjustLineIndent(LeafBlockWrapper currentBlock, FormatProcessor.ChildAttributesInfo info) {
+ AbstractBlockWrapper parent = info.parent;
+ ChildAttributes childAttributes = info.attributes;
+ int index = info.index;
+
+ int alignOffset = getAlignOffsetBefore(childAttributes.getAlignment(), null);
+ if (alignOffset == -1) {
+ return parent.calculateChildOffset(myBlockIndentOptions.getIndentOptions(parent), childAttributes, index).createIndentInfo();
+ }
+ else {
+ AbstractBlockWrapper indentedParentBlock = CoreFormatterUtil.getIndentedParentBlock(currentBlock);
+ if (indentedParentBlock == null) {
+ return new IndentInfo(0, 0, alignOffset);
+ }
+ else {
+ int indentOffset = indentedParentBlock.getWhiteSpace().getIndentOffset();
+ if (indentOffset > alignOffset) {
+ return new IndentInfo(0, 0, alignOffset);
+ }
+ else {
+ return new IndentInfo(0, indentOffset, alignOffset - indentOffset);
+ }
+ }
+ }
+ }
+
+ private static int getAlignOffsetBefore(@Nullable final Alignment alignment, @Nullable final LeafBlockWrapper blockAfter) {
+ if (alignment == null) return -1;
+ final LeafBlockWrapper alignRespBlock = ((AlignmentImpl)alignment).getOffsetRespBlockBefore(blockAfter);
+ if (alignRespBlock != null) {
+ return CoreFormatterUtil.getStartColumn(alignRespBlock);
+ }
+ else {
+ return -1;
+ }
+ }
+}
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/State.java b/platform/lang-impl/src/com/intellij/formatting/engine/State.java
new file mode 100644
index 000000000000..77f1760ae594
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/State.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+public abstract class State {
+
+ private boolean myDone;
+ private Runnable myOnDoneAction;
+
+ public void iteration() {
+ if (!isDone()) {
+ doIteration();
+ }
+ }
+
+ public boolean isDone() {
+ return myDone;
+ }
+
+ protected void setDone(boolean done) {
+ if (!myDone && done && myOnDoneAction != null) {
+ myOnDoneAction.run();
+ }
+ myDone = done;
+ }
+
+ public void stop() {}
+
+ protected abstract void doIteration();
+
+ public void prepare() {}
+
+ public void setOnDone(Runnable onDoneAction) {
+ myOnDoneAction = onDoneAction;
+ }
+
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/StateProcessor.java b/platform/lang-impl/src/com/intellij/formatting/engine/StateProcessor.java
new file mode 100644
index 000000000000..606d96e45826
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/StateProcessor.java
@@ -0,0 +1,57 @@
+/*
+ * 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.util.containers.ContainerUtil;
+
+import java.util.List;
+
+public class StateProcessor {
+
+ private final List myStates = ContainerUtil.newArrayList();
+ private State myCurrentState;
+
+ public StateProcessor(State initial) {
+ myCurrentState = initial;
+ }
+
+ public void setNextState(State state) {
+ myStates.add(state);
+ }
+
+ public boolean isDone() {
+ return myStates.isEmpty() && myCurrentState.isDone();
+ }
+
+ public void iteration() {
+ if (!myCurrentState.isDone()) {
+ myCurrentState.iteration();
+ }
+ shiftStateIfNecessary();
+ }
+
+ private void shiftStateIfNecessary() {
+ if (myCurrentState.isDone() && !myStates.isEmpty()) {
+ myCurrentState = myStates.get(0);
+ myStates.remove(0);
+ myCurrentState.prepare();
+ }
+ }
+
+ public void stop() {
+ myCurrentState.stop();
+ }
+}
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/WrapBlocksState.java b/platform/lang-impl/src/com/intellij/formatting/engine/WrapBlocksState.java
new file mode 100644
index 000000000000..aeb8097c1967
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/WrapBlocksState.java
@@ -0,0 +1,136 @@
+/*
+ * 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 com.intellij.openapi.editor.Document;
+import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
+import com.intellij.util.containers.MultiMap;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.Set;
+
+public class WrapBlocksState extends State {
+ private final InitialInfoBuilder myWrapper;
+ private final BlockIndentOptions myBlockIndentOptions;
+ private WhiteSpace myLastWhiteSpace;
+ private BlockRangesMap myBlockRangesMap;
+ private DependentSpacingEngine myDependentSpacingEngine;
+ private AlignmentHelper myAlignmentHelper;
+ private IndentAdjuster myIndentAdjuster;
+ private WrapProcessor myWrapProcessor;
+
+ public WrapBlocksState(@NotNull InitialInfoBuilder initialInfoBuilder, BlockIndentOptions blockIndentOptions) {
+ myWrapper = initialInfoBuilder;
+ myBlockIndentOptions = blockIndentOptions;
+ }
+
+ @Override
+ public void doIteration() {
+ if (isDone()) {
+ return;
+ }
+ setDone(myWrapper.iteration());
+ }
+
+ public Map getBlockToInfoMap() {
+ return myWrapper.getBlockToInfoMap();
+ }
+
+ public LeafBlockWrapper getFirstBlock() {
+ assertDone();
+ return myWrapper.getFirstTokenBlock();
+ }
+
+ public LeafBlockWrapper getLastBlock() {
+ assertDone();
+ return myWrapper.getLastTokenBlock();
+ }
+
+ public WhiteSpace getLastWhiteSpace() {
+ assertDone();
+ if (myLastWhiteSpace == null) {
+ int lastBlockOffset = getLastBlock().getEndOffset();
+ myLastWhiteSpace = new WhiteSpace(lastBlockOffset, false);
+ FormattingDocumentModel model = myWrapper.getFormattingDocumentModel();
+ CommonCodeStyleSettings.IndentOptions options = myBlockIndentOptions.getIndentOptions();
+ myLastWhiteSpace.append(Math.max(lastBlockOffset, myWrapper.getEndOffset()), model, options);
+ }
+ return myLastWhiteSpace;
+ }
+
+ public BlockRangesMap getBlockRangesMap() {
+ assertDone();
+ if (myBlockRangesMap == null) {
+ myBlockRangesMap = new BlockRangesMap(getFirstBlock(), getLastBlock());
+ }
+ return myBlockRangesMap;
+ }
+
+ public DependentSpacingEngine getDependentSpacingEngine() {
+ assertDone();
+ if (myDependentSpacingEngine == null) {
+ myDependentSpacingEngine = new DependentSpacingEngine(getBlockRangesMap());
+ }
+ return myDependentSpacingEngine;
+ }
+
+ public Set getAlignmentsInsideRangesToModify() {
+ assertDone();
+ return myWrapper.getAlignmentsInsideRangeToModify();
+ }
+
+ public AlignmentHelper getAlignmentHelper() {
+ assertDone();
+ if (myAlignmentHelper == null) {
+ Document document = myWrapper.getFormattingDocumentModel().getDocument();
+ myAlignmentHelper = new AlignmentHelper(document, myWrapper.getBlocksToAlign(), myBlockIndentOptions);
+ }
+ return myAlignmentHelper;
+ }
+
+ public IndentAdjuster getIndentAdjuster() {
+ assertDone();
+ if (myIndentAdjuster == null) {
+ myIndentAdjuster = new IndentAdjuster(myBlockIndentOptions, getAlignmentHelper());
+ }
+ return myIndentAdjuster;
+ }
+
+ public MultiMap getExpandableIndent() {
+ assertDone();
+ return myWrapper.getExpandableIndentsBlocks();
+ }
+
+ public WrapProcessor getWrapProcessor() {
+ assertDone();
+ if (myWrapProcessor == null) {
+ int rightMargin = myBlockIndentOptions.getRightMargin();
+ myWrapProcessor = new WrapProcessor(myBlockRangesMap, getIndentAdjuster(), rightMargin);
+ }
+ return myWrapProcessor;
+ }
+
+ public BlockIndentOptions getBlockIndentOptions() {
+ return myBlockIndentOptions;
+ }
+
+ private void assertDone() {
+ if (!isDone()) throw new IllegalStateException();
+ }
+
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/formatting/engine/WrapProcessor.java b/platform/lang-impl/src/com/intellij/formatting/engine/WrapProcessor.java
new file mode 100644
index 000000000000..acce258962dd
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/formatting/engine/WrapProcessor.java
@@ -0,0 +1,213 @@
+/*
+ * 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);
+ }
+
+ /**
+ * @return true if {@link #myCurrentBlock currently processed wrapped block} doesn't contain line feeds and
+ * exceeds right margin; false otherwise
+ */
+ private boolean lineOver(LeafBlockWrapper currentBlock) {
+ return !currentBlock.containsLineFeeds() &&
+ CoreFormatterUtil.getStartColumn(currentBlock) + currentBlock.getLength() > myRightMargin;
+ }
+
+
+ /**
+ * Ensures that offset of the {@link #myCurrentBlock currently processed block} is not increased if we make a wrap on it.
+ *
+ * @return true if it's ok to wrap at the currently processed block; false otherwise
+ */
+ 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 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 true if wrap of the {@link #myWrapCandidate} object (if any) may be replaced by the given wrap;
+ * 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 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;
+ }
+
+}