private void shiftOffsets(int shift, int changeOffset) { for (int i = myWrappers.size() - 1; i >= 0; i--) { ArrangementEntryWrapper<E> wrapper = myWrappers.get(i); if (wrapper.getStartOffset() < changeOffset) { break; } wrapper.applyShift(shift); } }
@Override public void prepare( @NotNull List<ArrangementEntryWrapper<E>> toArrange, @NotNull Context<E> context) { myWrappers.clear(); myWrappers.addAll(toArrange); for (ArrangementEntryWrapper<E> wrapper : toArrange) { wrapper.updateBlankLines(myDocument); } }
@Override public void prepare( @NotNull List<ArrangementEntryWrapper<E>> toArrange, @NotNull Context<E> context) { ArrangementEntryWrapper<E> parent = toArrange.get(0).getParent(); if (parent == null) { myParentText = context.document.getText(); myParentShift = 0; } else { myParentText = context .document .getCharsSequence() .subSequence(parent.getStartOffset(), parent.getEndOffset()) .toString(); myParentShift = parent.getStartOffset(); } }
@SuppressWarnings("unchecked") private static <E extends ArrangementEntry> void doArrange( @NotNull List<ArrangementEntryWrapper<E>> wrappers, @NotNull Context<E> context) { if (wrappers.isEmpty()) { return; } Map<E, ArrangementEntryWrapper<E>> map = ContainerUtilRt.newHashMap(); List<E> arranged = ContainerUtilRt.newArrayList(); List<E> toArrange = ContainerUtilRt.newArrayList(); for (ArrangementEntryWrapper<E> wrapper : wrappers) { E entry = wrapper.getEntry(); map.put(wrapper.getEntry(), wrapper); if (!entry.canBeMatched()) { // Split entries to arrange by 'can not be matched' rules. // See IDEA-104046 for a problem use-case example. if (toArrange.isEmpty()) { arranged.addAll(arrange(toArrange, context.rules, context.rulesByPriority)); } arranged.add(entry); toArrange.clear(); } else { toArrange.add(entry); } } if (!toArrange.isEmpty()) { arranged.addAll(arrange(toArrange, context.rules, context.rulesByPriority)); } context.changer.prepare(wrappers, context); // We apply changes from the last position to the first position in order not to bother with // offsets shifts. for (int i = arranged.size() - 1; i >= 0; i--) { ArrangementEntryWrapper<E> arrangedWrapper = map.get(arranged.get(i)); ArrangementEntryWrapper<E> initialWrapper = wrappers.get(i); context.changer.replace( arrangedWrapper, initialWrapper, i > 0 ? map.get(arranged.get(i - 1)) : null, context); } }
public static <T extends ArrangementEntry> Context<T> from( @NotNull Rearranger<T> rearranger, @NotNull Document document, @NotNull PsiElement root, @NotNull Collection<TextRange> ranges, @NotNull ArrangementSettings arrangementSettings, @NotNull CodeStyleSettings codeStyleSettings) { Collection<T> entries = rearranger.parse(root, document, ranges, arrangementSettings); Collection<ArrangementEntryWrapper<T>> wrappers = new ArrayList<ArrangementEntryWrapper<T>>(); ArrangementEntryWrapper<T> previous = null; for (T entry : entries) { ArrangementEntryWrapper<T> wrapper = new ArrangementEntryWrapper<T>(entry); if (previous != null) { previous.setNext(wrapper); wrapper.setPrevious(previous); } wrappers.add(wrapper); previous = wrapper; } Changer changer; if (document instanceof DocumentEx) { changer = new RangeMarkerAwareChanger<T>((DocumentEx) document); } else { changer = new DefaultChanger(); } final List<? extends ArrangementMatchRule> rulesByPriority = ArrangementUtil.getRulesSortedByPriority(arrangementSettings); return new Context<T>( rearranger, wrappers, document, arrangementSettings.getRules(), rulesByPriority, codeStyleSettings, changer); }
@SuppressWarnings("AssignmentToForLoopParameter") @Override public void replace( @NotNull ArrangementEntryWrapper<E> newWrapper, @NotNull ArrangementEntryWrapper<E> oldWrapper, @Nullable ArrangementEntryWrapper<E> previous, @NotNull Context<E> context) { // Calculate blank lines before the arrangement. int blankLinesBefore = oldWrapper.getBlankLinesBefore(); ArrangementEntryWrapper<E> parentWrapper = oldWrapper.getParent(); int desiredBlankLinesNumber = context.rearranger.getBlankLines( context.settings, parentWrapper == null ? null : parentWrapper.getEntry(), previous == null ? null : previous.getEntry(), newWrapper.getEntry()); if ((desiredBlankLinesNumber < 0 || desiredBlankLinesNumber == blankLinesBefore) && newWrapper.equals(oldWrapper)) { return; } int lineFeedsDiff = desiredBlankLinesNumber - blankLinesBefore; int insertionOffset = oldWrapper.getStartOffset(); if (oldWrapper.getStartOffset() > newWrapper.getStartOffset()) { insertionOffset -= newWrapper.getEndOffset() - newWrapper.getStartOffset(); } if (newWrapper.getStartOffset() != oldWrapper.getStartOffset() || !newWrapper.equals(oldWrapper)) { context.addMoveInfo( newWrapper.getStartOffset(), newWrapper.getEndOffset(), oldWrapper.getStartOffset()); myDocument.moveText( newWrapper.getStartOffset(), newWrapper.getEndOffset(), oldWrapper.getStartOffset()); for (int i = myWrappers.size() - 1; i >= 0; i--) { ArrangementEntryWrapper<E> w = myWrappers.get(i); if (w == newWrapper) { continue; } if (w.getStartOffset() >= oldWrapper.getStartOffset() && w.getStartOffset() < newWrapper.getStartOffset()) { w.applyShift(newWrapper.getEndOffset() - newWrapper.getStartOffset()); } else if (w.getStartOffset() < oldWrapper.getStartOffset() && w.getStartOffset() > newWrapper.getStartOffset()) { w.applyShift(newWrapper.getStartOffset() - newWrapper.getEndOffset()); } } } if (desiredBlankLinesNumber >= 0 && lineFeedsDiff > 0) { myDocument.insertString(insertionOffset, StringUtil.repeat("\n", lineFeedsDiff)); shiftOffsets(lineFeedsDiff, insertionOffset); } if (desiredBlankLinesNumber >= 0 && lineFeedsDiff < 0) { // Cut exceeding blank lines. int replacementStartOffset = getBlankLineOffset(-lineFeedsDiff, insertionOffset); myDocument.deleteString(replacementStartOffset, insertionOffset); shiftOffsets(replacementStartOffset - insertionOffset, insertionOffset); } // Update wrapper ranges. if (desiredBlankLinesNumber < 0 || lineFeedsDiff == 0 || parentWrapper == null) { return; } Deque<ArrangementEntryWrapper<E>> parents = new ArrayDeque<ArrangementEntryWrapper<E>>(); do { parents.add(parentWrapper); parentWrapper.setEndOffset(parentWrapper.getEndOffset() + lineFeedsDiff); parentWrapper = parentWrapper.getParent(); } while (parentWrapper != null); while (!parents.isEmpty()) { for (ArrangementEntryWrapper<E> wrapper = parents.removeLast().getNext(); wrapper != null; wrapper = wrapper.getNext()) { wrapper.applyShift(lineFeedsDiff); } } }
@SuppressWarnings("AssignmentToForLoopParameter") @Override public void replace( @NotNull ArrangementEntryWrapper<E> newWrapper, @NotNull ArrangementEntryWrapper<E> oldWrapper, @Nullable ArrangementEntryWrapper<E> previous, @NotNull Context<E> context) { // Calculate blank lines before the arrangement. int blankLinesBefore = 0; TIntArrayList lineFeedOffsets = new TIntArrayList(); int oldStartLine = context.document.getLineNumber(oldWrapper.getStartOffset()); if (oldStartLine > 0) { int lastLineFeed = context.document.getLineStartOffset(oldStartLine) - 1; lineFeedOffsets.add(lastLineFeed); for (int i = lastLineFeed - 1 - myParentShift; i >= 0; i--) { i = CharArrayUtil.shiftBackward(myParentText, i, " \t"); if (myParentText.charAt(i) == '\n') { blankLinesBefore++; lineFeedOffsets.add(i + myParentShift); } else { break; } } } ArrangementEntryWrapper<E> parentWrapper = oldWrapper.getParent(); int desiredBlankLinesNumber = context.rearranger.getBlankLines( context.settings, parentWrapper == null ? null : parentWrapper.getEntry(), previous == null ? null : previous.getEntry(), newWrapper.getEntry()); if (desiredBlankLinesNumber == blankLinesBefore && newWrapper.equals(oldWrapper)) { return; } String newEntryText = myParentText.substring( newWrapper.getStartOffset() - myParentShift, newWrapper.getEndOffset() - myParentShift); int lineFeedsDiff = desiredBlankLinesNumber - blankLinesBefore; if (lineFeedsDiff == 0 || desiredBlankLinesNumber < 0) { context.addMoveInfo( newWrapper.getStartOffset() - myParentShift, newWrapper.getEndOffset() - myParentShift, oldWrapper.getStartOffset()); context.document.replaceString( oldWrapper.getStartOffset(), oldWrapper.getEndOffset(), newEntryText); return; } if (lineFeedsDiff > 0) { // Insert necessary number of blank lines. StringBuilder buffer = new StringBuilder(StringUtil.repeat("\n", lineFeedsDiff)); buffer.append(newEntryText); context.document.replaceString( oldWrapper.getStartOffset(), oldWrapper.getEndOffset(), buffer); } else { // Cut exceeding blank lines. int replacementStartOffset = lineFeedOffsets.get(-lineFeedsDiff) + 1; context.document.replaceString( replacementStartOffset, oldWrapper.getEndOffset(), newEntryText); } // Update wrapper ranges. ArrangementEntryWrapper<E> parent = oldWrapper.getParent(); if (parent == null) { return; } Deque<ArrangementEntryWrapper<E>> parents = new ArrayDeque<ArrangementEntryWrapper<E>>(); do { parents.add(parent); parent.setEndOffset(parent.getEndOffset() + lineFeedsDiff); parent = parent.getParent(); } while (parent != null); while (!parents.isEmpty()) { for (ArrangementEntryWrapper<E> wrapper = parents.removeLast().getNext(); wrapper != null; wrapper = wrapper.getNext()) { wrapper.applyShift(lineFeedsDiff); } } }
@SuppressWarnings("unchecked") private static <E extends ArrangementEntry> void doArrange(Context<E> context) { // The general idea is to process entries bottom-up where every processed group belongs to the // same parent. We may not bother // with entries text ranges then. We use a list and a stack for achieving that than. // // Example: // Entry1 Entry2 // / \ / \ // Entry11 Entry12 Entry21 Entry22 // // -------------------------- // Stage 1: // list: Entry1 Entry2 <-- entries to process // stack: [0, 0, 2] <-- holds current iteration info at the following format: // (start entry index at the auxiliary list (inclusive); current // index; end index (exclusive)) // -------------------------- // Stage 2: // list: Entry1 Entry2 Entry11 Entry12 // stack: [0, 1, 2] // [2, 2, 4] // -------------------------- // Stage 3: // list: Entry1 Entry2 Entry11 Entry12 // stack: [0, 1, 2] // [2, 3, 4] // -------------------------- // Stage 4: // list: Entry1 Entry2 Entry11 Entry12 // stack: [0, 1, 2] // [2, 4, 4] // -------------------------- // arrange 'Entry11 Entry12' // -------------------------- // Stage 5: // list: Entry1 Entry2 // stack: [0, 1, 2] // -------------------------- // Stage 6: // list: Entry1 Entry2 Entry21 Entry22 // stack: [0, 2, 2] // [2, 2, 4] // -------------------------- // Stage 7: // list: Entry1 Entry2 Entry21 Entry22 // stack: [0, 2, 2] // [2, 3, 4] // -------------------------- // Stage 8: // list: Entry1 Entry2 Entry21 Entry22 // stack: [0, 2, 2] // [2, 4, 4] // -------------------------- // arrange 'Entry21 Entry22' // -------------------------- // Stage 9: // list: Entry1 Entry2 // stack: [0, 2, 2] // -------------------------- // arrange 'Entry1 Entry2' List<ArrangementEntryWrapper<E>> entries = new ArrayList<ArrangementEntryWrapper<E>>(); Stack<StackEntry> stack = new Stack<StackEntry>(); entries.addAll(context.wrappers); stack.push(new StackEntry(0, context.wrappers.size())); while (!stack.isEmpty()) { StackEntry stackEntry = stack.peek(); if (stackEntry.current >= stackEntry.end) { List<ArrangementEntryWrapper<E>> subEntries = entries.subList(stackEntry.start, stackEntry.end); if (subEntries.size() > 1) { doArrange(subEntries, context); } subEntries.clear(); stack.pop(); } else { ArrangementEntryWrapper<E> wrapper = entries.get(stackEntry.current++); List<ArrangementEntryWrapper<E>> children = wrapper.getChildren(); if (!children.isEmpty()) { entries.addAll(children); stack.push(new StackEntry(stackEntry.end, children.size())); } } } }