/** * Arranges given PSI root contents that belong to the given ranges. * * @param file target PSI root * @param ranges target ranges to use within the given root */ @SuppressWarnings("MethodMayBeStatic") public void arrange( @NotNull PsiFile file, @NotNull Collection<TextRange> ranges, @Nullable final ArrangementCallback callback) { final Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); if (document == null) { return; } final Rearranger<?> rearranger = Rearranger.EXTENSION.forLanguage(file.getLanguage()); if (rearranger == null) { return; } final CodeStyleSettings settings = CodeStyleSettingsManager.getInstance(file.getProject()).getCurrentSettings(); ArrangementSettings arrangementSettings = settings.getCommonSettings(file.getLanguage()).getArrangementSettings(); if (arrangementSettings == null && rearranger instanceof ArrangementStandardSettingsAware) { arrangementSettings = ((ArrangementStandardSettingsAware) rearranger).getDefaultSettings(); } if (arrangementSettings == null) { return; } final DocumentEx documentEx; if (document instanceof DocumentEx && !((DocumentEx) document).isInBulkUpdate()) { documentEx = (DocumentEx) document; } else { documentEx = null; } final Context<? extends ArrangementEntry> context = Context.from(rearranger, document, file, ranges, arrangementSettings, settings); ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { if (documentEx != null) { // documentEx.setInBulkUpdate(true); } try { doArrange(context); if (callback != null) { callback.afterArrangement(context.moveInfos); } } finally { if (documentEx != null) { // documentEx.setInBulkUpdate(false); } } } }); }
@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); } } }