/* * Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC * All rights reserved. * * The source code of this document is proprietary work, and is not licensed for * distribution. For information about licensing, contact Sam Harwell at: * sam@tunnelvisionlabs.com */ package org.antlr.netbeans.editor.fold; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; import org.antlr.netbeans.editor.text.DocumentSnapshot; import org.antlr.netbeans.editor.text.OffsetRegion; import org.antlr.netbeans.editor.text.SnapshotPositionRegion; import org.antlr.netbeans.editor.text.TrackingPositionRegion; import org.antlr.netbeans.editor.text.VersionedDocument; import org.antlr.netbeans.parsing.spi.ParserData; import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.editor.fold.Fold; import org.netbeans.api.editor.fold.FoldHierarchy; import org.netbeans.api.editor.fold.FoldTemplate; import org.netbeans.api.editor.fold.FoldType; import org.netbeans.spi.editor.fold.FoldHierarchyTransaction; import org.netbeans.spi.editor.fold.FoldOperation; /** * * @author Sam Harwell * @param <SemanticData> */ public abstract class AbstractFoldScanner<SemanticData> { // -J-Dorg.antlr.netbeans.editor.fold.AbstractFoldScanner.level=FINE private static final Logger LOGGER = Logger.getLogger(AbstractFoldScanner.class.getName()); @SuppressWarnings("fallthrough") public void run(ParserData<SemanticData> parseResult) { final VersionedDocument versionedDocument = parseResult.getSnapshot().getVersionedDocument(); if (versionedDocument.getDocument() == null) { // no need to calculate folds for unopened documents return; } final AbstractFoldManager foldManager = AbstractFoldManager.getFoldManager(versionedDocument); if (foldManager == null) { return; } // calculate the folds final List<FoldInfo> folds = calculateFolds(parseResult); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { DocumentSnapshot currentSnapshot = versionedDocument.getCurrentSnapshot(); for (int i = 0; i < folds.size(); i++) { folds.set(i, folds.get(i).translateTo(currentSnapshot)); } FoldOperation operation = foldManager.getOperation(); if (operation == null) { return; } FoldHierarchy hierarchy = operation.getHierarchy(); if (hierarchy == null) { return; } hierarchy.lock(); try{ FoldHierarchyTransaction transaction = operation.openTransaction(); synchronized (foldManager.currentFolds) { Collections.sort(foldManager.currentFolds, FoldComparator.DEFAULT); Collections.sort(folds, FoldInfoComparator.DEFAULT); List<Fold> foldsToKeep = new ArrayList<>(); List<Fold> foldsToRemove = new ArrayList<>(); List<FoldInfo> foldsToAdd = new ArrayList<>(); int i = 0; int j = 0; while (i < foldManager.currentFolds.size() && j < folds.size()) { Fold existingFold = foldManager.currentFolds.get(i); FoldInfo existing; Object extraInfo = operation.getExtraInfo(existingFold); if (extraInfo instanceof FoldInfo) { existing = (FoldInfo)extraInfo; existing = existing.translateTo(currentSnapshot); } else { SnapshotPositionRegion existingRegion = new SnapshotPositionRegion(currentSnapshot, OffsetRegion.fromBounds(existingFold.getStartOffset(), existingFold.getEndOffset())); existing = new FoldInfo(existingRegion, existingFold.getDescription()); } FoldInfo next = folds.get(j); int compared = FoldInfoComparator.DEFAULT.compare(existing, next); if (compared == 0) { foldsToKeep.add(foldManager.currentFolds.get(i)); i++; j++; } else if (compared < 0) { // existing doesn't have a match foldsToRemove.add(foldManager.currentFolds.get(i)); i++; } else { // next doesn't have a match foldsToAdd.add(next); j++; } } foldsToRemove.addAll(foldManager.currentFolds.subList(i, foldManager.currentFolds.size())); foldsToAdd.addAll(folds.subList(j, folds.size())); for (Fold fold : foldsToRemove) { operation.removeFromHierarchy(fold, transaction); } foldManager.currentFolds.clear(); foldManager.currentFolds.addAll(foldsToKeep); for (FoldInfo foldInfo : foldsToAdd) { String description = foldInfo.blockHint; boolean collapsed = false; int startOffset = foldInfo.region.getStart().getOffset(); int endOffset = foldInfo.region.getEnd().getOffset(); FoldType foldType = FoldType.create("code-block", "Code Block", FoldTemplate.DEFAULT); try { Fold fold = operation.addToHierarchy(foldType, startOffset, endOffset, collapsed, null, description, foldInfo, transaction); foldManager.currentFolds.add(fold); } catch (BadLocationException ex) { LOGGER.log(Level.WARNING, "An exception occurred while updating code folding.", ex); } } } transaction.commit(); } finally { hierarchy.unlock(); } } }); } protected abstract List<FoldInfo> calculateFolds(ParserData<SemanticData> parseResult); protected Comparator<Fold> getFoldComparator() { return FoldComparator.DEFAULT; } protected Comparator<FoldInfo> getFoldInfoComparator() { return FoldInfoComparator.DEFAULT; } protected static class FoldComparator implements Comparator<Fold> { public static final FoldComparator DEFAULT = new FoldComparator(); @Override public int compare(Fold o1, Fold o2) { if (o1.getStartOffset() != o2.getStartOffset()) { return o1.getStartOffset() - o2.getStartOffset(); } else if (o1.getEndOffset() != o2.getEndOffset()) { return o1.getEndOffset() - o2.getEndOffset(); } else { return o1.getDescription().compareTo(o2.getDescription()); } } } protected static class FoldInfoComparator implements Comparator<FoldInfo> { public static final FoldInfoComparator DEFAULT = new FoldInfoComparator(); @Override public int compare(FoldInfo o1, FoldInfo o2) { if (!o1.region.getStart().equals(o2.region.getStart())) { return o1.region.getStart().compareTo(o2.region.getStart()); } else if (!o1.region.getEnd().equals(o2.region.getEnd())) { return o1.region.getEnd().compareTo(o2.region.getEnd()); } else { return o1.blockHint.compareTo(o2.blockHint); } } } public static class FoldInfo { private final SnapshotPositionRegion region; private final String blockHint; private final String preview; public FoldInfo(@NonNull SnapshotPositionRegion region, @NonNull String blockHint) { this.region = region; this.blockHint = blockHint; this.preview = region.getText(); } public SnapshotPositionRegion getRegion() { return region; } public String getBlockHint() { return blockHint; } public String getPreview() { return preview; } protected FoldInfo translateTo(@NonNull DocumentSnapshot snapshot) { if (snapshot.equals(region.getSnapshot())) { return this; } TrackingPositionRegion trackingRegion = region.getSnapshot().createTrackingRegion(region.getRegion(), TrackingPositionRegion.Bias.Exclusive); return new FoldInfo(trackingRegion.getRegion(snapshot), blockHint); } } }