private static void adjustIndentationInRange( final PsiFile file, final Document document, final TextRange[] indents, final int indentAdjustment) { final CharSequence charsSequence = document.getCharsSequence(); for (final TextRange indent : indents) { final String oldIndentStr = charsSequence.subSequence(indent.getStartOffset() + 1, indent.getEndOffset()).toString(); final int oldIndent = IndentHelperImpl.getIndent(file.getProject(), file.getFileType(), oldIndentStr, true); final String newIndentStr = IndentHelperImpl.fillIndent( file.getProject(), file.getFileType(), Math.max(oldIndent + indentAdjustment, 0)); document.replaceString(indent.getStartOffset() + 1, indent.getEndOffset(), newIndentStr); } }
/** * This method searches ast node that could be reparsed incrementally and returns pair of target * reparseable node and new replacement node. Returns null if there is no any chance to make * incremental parsing. */ @Nullable public Couple<ASTNode> findReparseableRoots( @NotNull PsiFileImpl file, @NotNull TextRange changedPsiRange, @NotNull CharSequence newFileText) { Project project = file.getProject(); final FileElement fileElement = file.getTreeElement(); final CharTable charTable = fileElement.getCharTable(); int lengthShift = newFileText.length() - fileElement.getTextLength(); if (fileElement.getElementType() instanceof ITemplateDataElementType || isTooDeep(file)) { // unable to perform incremental reparse for template data in JSP, or in exceptionally deep // trees return null; } final ASTNode leafAtStart = fileElement.findLeafElementAt(Math.max(0, changedPsiRange.getStartOffset() - 1)); final ASTNode leafAtEnd = fileElement.findLeafElementAt( Math.min(changedPsiRange.getEndOffset(), fileElement.getTextLength() - 1)); ASTNode node = leafAtStart != null && leafAtEnd != null ? TreeUtil.findCommonParent(leafAtStart, leafAtEnd) : fileElement; Language baseLanguage = file.getViewProvider().getBaseLanguage(); while (node != null && !(node instanceof FileElement)) { IElementType elementType = node.getElementType(); if (elementType instanceof IReparseableElementType) { final TextRange textRange = node.getTextRange(); final IReparseableElementType reparseable = (IReparseableElementType) elementType; if (baseLanguage.isKindOf(reparseable.getLanguage()) && textRange.getLength() + lengthShift > 0) { final int start = textRange.getStartOffset(); final int end = start + textRange.getLength() + lengthShift; if (end > newFileText.length()) { reportInconsistentLength(file, newFileText, node, start, end); break; } CharSequence newTextStr = newFileText.subSequence(start, end); if (reparseable.isParsable(node.getTreeParent(), newTextStr, baseLanguage, project)) { ASTNode chameleon = reparseable.createNode(newTextStr); if (chameleon != null) { DummyHolder holder = DummyHolderFactory.createHolder( file.getManager(), null, node.getPsi(), charTable); holder.getTreeElement().rawAddChildren((TreeElement) chameleon); if (holder.getTextLength() != newTextStr.length()) { String details = ApplicationManager.getApplication().isInternal() ? "text=" + newTextStr + "; treeText=" + holder.getText() + ";" : ""; LOG.error("Inconsistent reparse: " + details + " type=" + elementType); } return Couple.of(node, chameleon); } } } } node = node.getTreeParent(); } return null; }
private List<PostponedAction> normalizeAndReorderPostponedActions( final TreeSet<PostprocessFormattingTask> rangesToProcess, Document document) { final List<PostprocessFormattingTask> freeFormatingActions = new ArrayList<PostprocessFormattingTask>(); final List<ReindentTask> indentActions = new ArrayList<ReindentTask>(); PostprocessFormattingTask accumulatedTask = null; Iterator<PostprocessFormattingTask> iterator = rangesToProcess.iterator(); while (iterator.hasNext()) { final PostprocessFormattingTask currentTask = iterator.next(); if (accumulatedTask == null) { accumulatedTask = currentTask; iterator.remove(); } else if (accumulatedTask.getStartOffset() > currentTask.getEndOffset() || accumulatedTask.getStartOffset() == currentTask.getEndOffset() && !canStickActionsTogether(accumulatedTask, currentTask)) { // action can be pushed if (accumulatedTask instanceof ReindentTask) { indentActions.add((ReindentTask) accumulatedTask); } else { freeFormatingActions.add(accumulatedTask); } accumulatedTask = currentTask; iterator.remove(); } else if (accumulatedTask instanceof ReformatTask && currentTask instanceof ReindentTask) { // split accumulated reformat range into two if (accumulatedTask.getStartOffset() < currentTask.getStartOffset()) { final RangeMarker endOfRange = document.createRangeMarker( accumulatedTask.getStartOffset(), currentTask.getStartOffset()); // add heading reformat part rangesToProcess.add(new ReformatTask(endOfRange)); // and manage heading whitespace because formatter does not edit it in previous action iterator = rangesToProcess.iterator(); //noinspection StatementWithEmptyBody while (iterator.next().getRange() != currentTask.getRange()) ; } final RangeMarker rangeToProcess = document.createRangeMarker(currentTask.getEndOffset(), accumulatedTask.getEndOffset()); freeFormatingActions.add(new ReformatWithHeadingWhitespaceTask(rangeToProcess)); accumulatedTask = currentTask; iterator.remove(); } else { if (!(accumulatedTask instanceof ReindentTask)) { iterator.remove(); boolean withLeadingWhitespace = accumulatedTask instanceof ReformatWithHeadingWhitespaceTask; if (accumulatedTask instanceof ReformatTask && currentTask instanceof ReformatWithHeadingWhitespaceTask && accumulatedTask.getStartOffset() == currentTask.getStartOffset()) { withLeadingWhitespace = true; } else if (accumulatedTask instanceof ReformatWithHeadingWhitespaceTask && currentTask instanceof ReformatTask && accumulatedTask.getStartOffset() < currentTask.getStartOffset()) { withLeadingWhitespace = false; } int newStart = Math.min(accumulatedTask.getStartOffset(), currentTask.getStartOffset()); int newEnd = Math.max(accumulatedTask.getEndOffset(), currentTask.getEndOffset()); RangeMarker rangeMarker; if (accumulatedTask.getStartOffset() == newStart && accumulatedTask.getEndOffset() == newEnd) { rangeMarker = accumulatedTask.getRange(); } else if (currentTask.getStartOffset() == newStart && currentTask.getEndOffset() == newEnd) { rangeMarker = currentTask.getRange(); } else { rangeMarker = document.createRangeMarker(newStart, newEnd); } if (withLeadingWhitespace) { accumulatedTask = new ReformatWithHeadingWhitespaceTask(rangeMarker); } else { accumulatedTask = new ReformatTask(rangeMarker); } } else if (currentTask instanceof ReindentTask) { iterator.remove(); } // TODO[ik]: need to be fixed to correctly process indent inside indent } } if (accumulatedTask != null) { if (accumulatedTask instanceof ReindentTask) { indentActions.add((ReindentTask) accumulatedTask); } else { freeFormatingActions.add(accumulatedTask); } } final List<PostponedAction> result = new ArrayList<PostponedAction>(); Collections.reverse(freeFormatingActions); Collections.reverse(indentActions); if (!freeFormatingActions.isEmpty()) { FormatTextRanges ranges = new FormatTextRanges(); for (PostprocessFormattingTask action : freeFormatingActions) { TextRange range = TextRange.create(action); ranges.add(range, action instanceof ReformatWithHeadingWhitespaceTask); } result.add(new ReformatRangesAction(ranges)); } if (!indentActions.isEmpty()) { ReindentRangesAction reindentRangesAction = new ReindentRangesAction(); for (ReindentTask action : indentActions) { reindentRangesAction.add(action.getRange(), action.getOldIndent()); } result.add(reindentRangesAction); } return result; }