diff --git a/csl.api/nbproject/project.properties b/csl.api/nbproject/project.properties
--- a/csl.api/nbproject/project.properties
+++ b/csl.api/nbproject/project.properties
@@ -40,7 +40,7 @@
# Version 2 license, then the option applies only if the new code is
# made subject to such option by the copyright holder.
-spec.version.base=2.34.0
+spec.version.base=2.35.0
is.autoload=true
javac.source=1.6
diff --git a/csl.api/nbproject/project.xml b/csl.api/nbproject/project.xml
--- a/csl.api/nbproject/project.xml
+++ b/csl.api/nbproject/project.xml
@@ -119,7 +119,7 @@
1
- 1.6
+ 1.34
diff --git a/csl.api/src/org/netbeans/modules/csl/api/StructureScanner.java b/csl.api/src/org/netbeans/modules/csl/api/StructureScanner.java
--- a/csl.api/src/org/netbeans/modules/csl/api/StructureScanner.java
+++ b/csl.api/src/org/netbeans/modules/csl/api/StructureScanner.java
@@ -50,6 +50,8 @@
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.modules.csl.spi.ParserResult;
+import org.netbeans.api.editor.fold.FoldType;
+import org.netbeans.spi.editor.fold.FoldTypeProvider;
/**
* Given a parse tree, scan its structure and produce a flat list of
@@ -69,8 +71,20 @@
@NonNull List extends StructureItem> scan(@NonNull ParserResult info);
/**
- * @todo Do this in the same pass as the structure scan?
* Compute a list of foldable regions, named "codeblocks", "comments", "imports", "initial-comment", ...
+ * The returned Map must be keyed by {@link FoldType#code}. For backward compatibility, the following
+ * tokens are temporarily supported although no FoldType is registered explicitly.
+ *
+ *
codeblocks
+ *
comments
+ *
initial-comment
+ *
imports
+ *
tags
+ *
inner-classes
+ *
othercodeblocks
+ *
+ * This additional support will cease to exist after NB-8.0. Language owners are required to register
+ * their {@link FoldTypeProvider} and define their own folding.
*/
@NonNull Map> folds(@NonNull ParserResult info);
diff --git a/csl.api/src/org/netbeans/modules/csl/editor/fold/GsfFoldManager.java b/csl.api/src/org/netbeans/modules/csl/editor/fold/GsfFoldManager.java
--- a/csl.api/src/org/netbeans/modules/csl/editor/fold/GsfFoldManager.java
+++ b/csl.api/src/org/netbeans/modules/csl/editor/fold/GsfFoldManager.java
@@ -51,9 +51,11 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.WeakHashMap;
@@ -65,9 +67,11 @@
import javax.swing.event.DocumentEvent;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
-import javax.swing.text.Position;
import org.netbeans.api.editor.fold.Fold;
+import org.netbeans.spi.editor.fold.FoldInfo;
+import org.netbeans.api.editor.fold.FoldTemplate;
import org.netbeans.api.editor.fold.FoldType;
+import org.netbeans.api.editor.fold.FoldUtilities;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.csl.api.Severity;
@@ -92,7 +96,9 @@
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.api.UserTask;
import org.netbeans.modules.parsing.spi.*;
-import org.openide.text.NbDocument;
+import org.openide.util.NbBundle;
+
+import static org.netbeans.modules.csl.editor.fold.Bundle.*;
/**
* This file is originally from Retouche, the Java Support
@@ -100,7 +106,23 @@
* as possible to make merging Retouche fixes back as simple as
* possible.
*
- * Copied from both JavaFoldManager and JavaElementFoldManager
+ * Copied from both JavaFoldManager and JavaElementFoldManager.
+ *
+ *
+ * From introduction of {@link FoldType}, this Manager accepts these Strings
+ * from {@link StructureScanner#folds}:
+ *
codes of FoldTypes registered for the language, or
+ *
legacy FoldType codes.
+ *
+ * The registered FoldTypes take precedence; that is if the SPI returns
+ * "code-block" and a FoldType is registered with that code, that FoldType (and its FoldTemplate)
+ * will be used to create the Fold. If no such FoldType exists, the GsfFoldManager will still
+ * create the fold in a bw compatible mode with an unregistered fold type. But because
+ * of implementation peculiarities, the FoldType will eventually default to one of the generic
+ * settings.
+ *
+ * If a code other than registered FoldTypes and bw compatible constants is used,
+ * a warning will be printed - to detect programmer errors.
*
*
* @author Jan Lahoda
@@ -110,129 +132,60 @@
private static final Logger LOG = Logger.getLogger(GsfFoldManager.class.getName());
- public static final FoldType CODE_BLOCK_FOLD_TYPE = new FoldType("code-block"); // NOI18N
- public static final FoldType INITIAL_COMMENT_FOLD_TYPE = new FoldType("initial-comment"); // NOI18N
- public static final FoldType IMPORTS_FOLD_TYPE = new FoldType("imports"); // NOI18N
- public static final FoldType JAVADOC_FOLD_TYPE = new FoldType("javadoc"); // NOI18N
- public static final FoldType TAG_FOLD_TYPE = new FoldType("tag"); // NOI18N
- public static final FoldType INNER_CLASS_FOLD_TYPE = new FoldType("inner-class"); // NOI18N
-
+ private static final FoldTemplate TEMPLATE_CODEBLOCK = new org.netbeans.api.editor.fold.FoldTemplate(1, 1, "{...}"); // NOI18N
+
+ /**
+ * This definition and all Fold types defined in CSL are deprecated and for backward
+ * compatibility only.
+ */
+ @Deprecated
+ public static final FoldType CODE_BLOCK_FOLD_TYPE = FoldType.CODE_BLOCK;
- private static final String IMPORTS_FOLD_DESCRIPTION = "..."; // NOI18N
-
- private static final String COMMENT_FOLD_DESCRIPTION = "..."; // NOI18N
-
- private static final String JAVADOC_FOLD_DESCRIPTION = "..."; // NOI18N
-
- private static final String CODE_BLOCK_FOLD_DESCRIPTION = "{...}"; // NOI18N
-
- private static final String TAG_FOLD_DESCRIPTION = "<.../>"; // NOI18N
-
-
- public static final FoldTemplate CODE_BLOCK_FOLD_TEMPLATE
- = new FoldTemplate(CODE_BLOCK_FOLD_TYPE, CODE_BLOCK_FOLD_DESCRIPTION, 1, 1);
-
- public static final FoldTemplate INITIAL_COMMENT_FOLD_TEMPLATE
- = new FoldTemplate(INITIAL_COMMENT_FOLD_TYPE, COMMENT_FOLD_DESCRIPTION, 2, 2);
-
- public static final FoldTemplate IMPORTS_FOLD_TEMPLATE
- = new FoldTemplate(IMPORTS_FOLD_TYPE, IMPORTS_FOLD_DESCRIPTION, 0, 0);
-
- public static final FoldTemplate JAVADOC_FOLD_TEMPLATE
- = new FoldTemplate(JAVADOC_FOLD_TYPE, JAVADOC_FOLD_DESCRIPTION, 3, 2);
-
- public static final FoldTemplate TAG_FOLD_TEMPLATE
- = new FoldTemplate(TAG_FOLD_TYPE, TAG_FOLD_DESCRIPTION, 0, 0);
-
- public static final FoldTemplate INNER_CLASS_FOLD_TEMPLATE
- = new FoldTemplate(INNER_CLASS_FOLD_TYPE, CODE_BLOCK_FOLD_DESCRIPTION, 0, 0);
-
-
- public static final String CODE_FOLDING_ENABLE = "code-folding-enable"; //NOI18N
-
- /** Collapse methods by default
- * NOTE: This must be kept in sync with string literal in editor/options
- */
- public static final String CODE_FOLDING_COLLAPSE_METHOD = "code-folding-collapse-method"; //NOI18N
+ @Deprecated
+ public static final FoldType INITIAL_COMMENT_FOLD_TYPE = FoldType.INITIAL_COMMENT;
/**
- * Collapse inner classes by default
- * NOTE: This must be kept in sync with string literal in editor/options
+ * Note: this FoldType's code was changed from 'imports' to 'import' to match the used preference key.
*/
- public static final String CODE_FOLDING_COLLAPSE_INNERCLASS = "code-folding-collapse-innerclass"; //NOI18N
+ @NbBundle.Messages("FT_label_imports=Imports or Includes")
+ @Deprecated
+ public static final FoldType IMPORTS_FOLD_TYPE = FoldType.IMPORT;
+
+ @Deprecated
+ public static final FoldType JAVADOC_FOLD_TYPE = FoldType.DOCUMENTATION;
+
+ @Deprecated
+ public static final FoldType TAG_FOLD_TYPE = FoldType.TAG;
/**
- * Collapse import section default
- * NOTE: This must be kept in sync with string literal in editor/options
+ * This type's code was renamed from inner-class to innerclass to match the preference value
*/
- public static final String CODE_FOLDING_COLLAPSE_IMPORT = "code-folding-collapse-import"; //NOI18N
+ @NbBundle.Messages("FT_label_innerclass=Inner Classes")
+ @Deprecated
+ public static final FoldType INNER_CLASS_FOLD_TYPE = FoldType.create("innerclass", FT_label_innerclass(), TEMPLATE_CODEBLOCK);
- /**
- * Collapse javadoc comment by default
- * NOTE: This must be kept in sync with string literal in editor/options
- */
- public static final String CODE_FOLDING_COLLAPSE_JAVADOC = "code-folding-collapse-javadoc"; //NOI18N
-
- /**
- * Collapse initial comment by default
- * NOTE: This must be kept in sync with string literal in editor/options
- */
- public static final String CODE_FOLDING_COLLAPSE_INITIAL_COMMENT = "code-folding-collapse-initial-comment"; //NOI18N
+ @NbBundle.Messages("FT_label_othercodeblocks=Other code blocks")
+ @Deprecated
+ public static final FoldType OTHER_CODEBLOCKS_FOLD_TYPE = FoldType.TAG.derive("othercodeblocks", FT_label_othercodeblocks(),
+ TEMPLATE_CODEBLOCK);
- /**
- * Collapse tags by default
- * NOTE: This must be kept in sync with string literal in editor/options
- */
- public static final String CODE_FOLDING_COLLAPSE_TAGS = "code-folding-collapse-tags"; //NOI18N
-
+ private static final Set LEGACY_FOLD_TAGS = new HashSet(11);
- protected static final class FoldTemplate {
-
- private FoldType type;
-
- private String description;
-
- private int startGuardedLength;
-
- private int endGuardedLength;
-
- protected FoldTemplate(FoldType type, String description,
- int startGuardedLength, int endGuardedLength) {
- this.type = type;
- this.description = description;
- this.startGuardedLength = startGuardedLength;
- this.endGuardedLength = endGuardedLength;
- }
-
- public FoldType getType() {
- return type;
- }
-
- public String getDescription() {
- return description;
- }
-
- public int getStartGuardedLength() {
- return startGuardedLength;
- }
-
- public int getEndGuardedLength() {
- return endGuardedLength;
- }
-
+ static {
+ LEGACY_FOLD_TAGS.add(OTHER_CODEBLOCKS_FOLD_TYPE.code());
+ LEGACY_FOLD_TAGS.add(INNER_CLASS_FOLD_TYPE.code());
+ LEGACY_FOLD_TAGS.add(TAG_FOLD_TYPE.code());
+ LEGACY_FOLD_TAGS.add(JAVADOC_FOLD_TYPE.code());
+ LEGACY_FOLD_TAGS.add(IMPORTS_FOLD_TYPE.code());
+ LEGACY_FOLD_TAGS.add(INITIAL_COMMENT_FOLD_TYPE.code());
+ LEGACY_FOLD_TAGS.add(CODE_BLOCK_FOLD_TYPE.code());
}
private FoldOperation operation;
private FileObject file;
private JavaElementFoldTask task;
-// // Folding presets
-// private boolean foldImportsPreset;
-// private boolean foldInnerClassesPreset;
-// private boolean foldJavadocsPreset;
-// private boolean foldCodeBlocksPreset;
-// private boolean foldInitialCommentsPreset;
- private static volatile Preferences prefs;
+ private volatile Preferences prefs;
/** Creates a new instance of GsfFoldManager */
public GsfFoldManager() {
@@ -253,7 +206,6 @@
file = DataLoadersBridge.getDefault().getFileObject(doc);
if (file != null) {
- currentFolds = new HashMap();
task = JavaElementFoldTask.getTask(file);
task.setGsfFoldManager(GsfFoldManager.this, file);
}
@@ -278,7 +230,6 @@
@Override
public void removeDamagedNotify(Fold damagedFold) {
- currentFolds.remove(operation.getExtraInfo(damagedFold));
if (importsFold == damagedFold) {
importsFold = null;//not sure if this is correct...
}
@@ -297,28 +248,18 @@
task = null;
file = null;
- currentFolds = null;
importsFold = null;
initialCommentFold = null;
}
-// public void settingsChange(SettingsChangeEvent evt) {
-// // Get folding presets
-// foldInitialCommentsPreset = getSetting(GsfOptions.CODE_FOLDING_COLLAPSE_INITIAL_COMMENT);
-// foldImportsPreset = getSetting(GsfOptions.CODE_FOLDING_COLLAPSE_IMPORT);
-// foldCodeBlocksPreset = getSetting(GsfOptions.CODE_FOLDING_COLLAPSE_METHOD);
-// foldInnerClassesPreset = getSetting(GsfOptions.CODE_FOLDING_COLLAPSE_INNERCLASS);
-// foldJavadocsPreset = getSetting(GsfOptions.CODE_FOLDING_COLLAPSE_JAVADOC);
-// }
-
- private static boolean getSetting(String settingName) {
- return prefs.getBoolean(settingName, false);
- }
-
static final class JavaElementFoldTask extends IndexingAwareParserResultTask {
private final AtomicBoolean cancelled = new AtomicBoolean(false);
+ private FoldInfo initComment;
+
+ private FoldInfo imports;
+
public JavaElementFoldTask() {
super(TaskIndexingMode.ALLOWED_DURING_SCAN);
}
@@ -397,7 +338,7 @@
return;
}
- final TreeSet folds = new TreeSet();
+ final Collection folds = new HashSet();
final Document doc = info.getSnapshot().getSource().getDocument(false);
if (doc == null) {
return;
@@ -408,13 +349,16 @@
}
if (mgrs instanceof GsfFoldManager) {
- SwingUtilities.invokeLater(((GsfFoldManager)mgrs).new CommitFolds(folds, doc, info.getSnapshot().getSource()));
+ SwingUtilities.invokeLater(((GsfFoldManager)mgrs).createCommit(
+ folds,
+ initComment, imports, doc, info.getSnapshot().getSource()));
} else {
SwingUtilities.invokeLater(new Runnable() {
Collection jefms = (Collection)mgrs;
public void run() {
for (GsfFoldManager jefm : jefms) {
- jefm.new CommitFolds(folds, doc, info.getSnapshot().getSource()).run();
+ jefm.createCommit(folds, initComment, imports,
+ doc, info.getSnapshot().getSource()).run();
}
}});
}
@@ -430,7 +374,7 @@
*
* @return true If folds were found, false if cancelled
*/
- private boolean gsfFoldScan(final Document doc, ParserResult info, final TreeSet folds) {
+ private boolean gsfFoldScan(final Document doc, ParserResult info, final Collection folds) {
final boolean [] success = new boolean [] { false };
Source source = info.getSnapshot().getSource();
@@ -482,7 +426,7 @@
return success[0];
}
- private boolean checkInitialFold(final Document doc, final TreeSet folds) {
+ private boolean checkInitialFold(final Document doc, final Collection folds) {
final boolean[] ret = new boolean[1];
ret[0] = true;
final TokenHierarchy> th = TokenHierarchy.get(doc);
@@ -492,52 +436,44 @@
doc.render(new Runnable() {
@Override
public void run() {
- try {
- TokenSequence> ts = th.tokenSequence();
- if (ts == null) {
+ TokenSequence> ts = th.tokenSequence();
+ if (ts == null) {
+ return;
+ }
+ while (ts.moveNext()) {
+ Token> token = ts.token();
+ String category = token.id().primaryCategory();
+ if ("comment".equals(category)) { // NOI18N
+ int startOffset = ts.offset();
+ int endOffset = startOffset + token.length();
+
+ // Find end - could be a block of single-line statements
+ while (ts.moveNext()) {
+ token = ts.token();
+ category = token.id().primaryCategory();
+ if ("comment".equals(category)) { // NOI18N
+ endOffset = ts.offset() + token.length();
+ } else if (!"whitespace".equals(category)) { // NOI18N
+ break;
+ }
+ }
+
+ try {
+ // Start the fold at the END of the line
+ startOffset = org.netbeans.editor.Utilities.getRowEnd((BaseDocument) doc, startOffset);
+ if (startOffset >= endOffset) {
+ return;
+ }
+ } catch (BadLocationException ex) {
+ LOG.log(Level.WARNING, null, ex);
+ }
+
+ folds.add(initComment = FoldInfo.range(startOffset, endOffset, INITIAL_COMMENT_FOLD_TYPE));
return;
}
- while (ts.moveNext()) {
- Token> token = ts.token();
- String category = token.id().primaryCategory();
- if ("comment".equals(category)) { // NOI18N
- int startOffset = ts.offset();
- int endOffset = startOffset + token.length();
- boolean collapsed = getSetting(CODE_FOLDING_COLLAPSE_INITIAL_COMMENT); //foldInitialCommentsPreset;
-
-
- // Find end - could be a block of single-line statements
-
- while (ts.moveNext()) {
- token = ts.token();
- category = token.id().primaryCategory();
- if ("comment".equals(category)) { // NOI18N
- endOffset = ts.offset() + token.length();
- } else if (!"whitespace".equals(category)) { // NOI18N
- break;
- }
- }
-
- try {
- // Start the fold at the END of the line
- startOffset = org.netbeans.editor.Utilities.getRowEnd((BaseDocument) doc, startOffset);
- if (startOffset >= endOffset) {
- return;
- }
- } catch (BadLocationException ex) {
- LOG.log(Level.WARNING, null, ex);
- }
-
- folds.add(new FoldInfo(doc, startOffset, endOffset, INITIAL_COMMENT_FOLD_TEMPLATE, collapsed));
- return;
- }
- if (!"whitespace".equals(category)) { // NOI18N
- break;
- }
-
+ if (!"whitespace".equals(category)) { // NOI18N
+ break;
}
- } catch (BadLocationException e) {
- ret[0] = false;
}
}
});
@@ -545,7 +481,7 @@
}
private void scan(final ParserResult info,
- final TreeSet folds, final Document doc, final
+ final Collection folds, final Document doc, final
StructureScanner scanner) {
doc.render(new Runnable() {
@@ -556,68 +492,82 @@
});
}
- private void addFoldsOfType(
- StructureScanner scanner,
+ private boolean addFoldsOfType(
String type, Map> folds,
- TreeSet result,
+ Collection result,
Document doc,
- String collapsedOptionName,
- FoldTemplate template) {
+ FoldType foldType) {
List ranges = folds.get(type); //NOI18N
if (ranges != null) {
- boolean collapseByDefault = getSetting(collapsedOptionName);
if (LOG.isLoggable(Level.FINEST)) {
- LOG.log(Level.FINEST, "Creating folds {0}, collapsed: {1}", new Object[] {
- type, collapseByDefault
+ LOG.log(Level.FINEST, "Creating folds {0}", new Object[] {
+ type
});
}
for (OffsetRange range : ranges) {
- try {
- if (LOG.isLoggable(Level.FINEST)) {
- LOG.log(Level.FINEST, "Fold: {0}", range);
- }
- addFold(range, result, doc, collapseByDefault, template);
- } catch (BadLocationException ble) {
- LOG.log(Level.WARNING, "StructureScanner " + scanner + " supplied invalid fold " + range, ble); //NOI18N
+ if (LOG.isLoggable(Level.FINEST)) {
+ LOG.log(Level.FINEST, "Fold: {0}", range);
}
+ addFold(range, result, doc, foldType);
}
+ folds.remove(type);
+ return true;
} else {
LOG.log(Level.FINEST, "No folds of type {0}", type);
+ return false;
}
}
- private void addTree(TreeSet result, ParserResult info, Document doc, StructureScanner scanner) {
+ private void addTree(Collection result, ParserResult info, Document doc, StructureScanner scanner) {
// #217322, disabled folding -> no folds will be created
- if (!getSetting(CODE_FOLDING_ENABLE)) {
+ String mime = info.getSnapshot().getMimeType();
+
+ if (!FoldUtilities.isFoldingEnabled(mime)) {
return;
}
Map> folds = scanner.folds(info);
if (cancelled.get()) {
return;
}
- addFoldsOfType(scanner, "codeblocks", folds, result, doc,
- CODE_FOLDING_COLLAPSE_METHOD, CODE_BLOCK_FOLD_TEMPLATE);
- addFoldsOfType(scanner, "comments", folds, result, doc,
- CODE_FOLDING_COLLAPSE_JAVADOC, JAVADOC_FOLD_TEMPLATE);
- addFoldsOfType(scanner, "initial-comment", folds, result, doc,
- CODE_FOLDING_COLLAPSE_INITIAL_COMMENT, INITIAL_COMMENT_FOLD_TEMPLATE);
- addFoldsOfType(scanner, "imports", folds, result, doc,
- CODE_FOLDING_COLLAPSE_IMPORT, IMPORTS_FOLD_TEMPLATE);
- addFoldsOfType(scanner, "tags", folds, result, doc,
- CODE_FOLDING_COLLAPSE_TAGS, TAG_FOLD_TEMPLATE);
- addFoldsOfType(scanner, "othercodeblocks", folds, result, doc,
- CODE_FOLDING_COLLAPSE_TAGS, CODE_BLOCK_FOLD_TEMPLATE);
- addFoldsOfType(scanner, "inner-classes", folds, result, doc,
- CODE_FOLDING_COLLAPSE_INNERCLASS, INNER_CLASS_FOLD_TEMPLATE);
+ Collection extends FoldType> ftypes = FoldUtilities.getFoldTypes(mime).values();
+ for (FoldType ft : ftypes) {
+ addFoldsOfType(ft.code(), folds, result, doc, ft);
+ }
+ // fallback: transform the legacy keys into FoldInfos
+ addFoldsOfType("codeblocks", folds, result, doc,
+ CODE_BLOCK_FOLD_TYPE);
+ addFoldsOfType("comments", folds, result, doc,
+ JAVADOC_FOLD_TYPE);
+ addFoldsOfType("initial-comment", folds, result, doc,
+ INITIAL_COMMENT_FOLD_TYPE);
+ addFoldsOfType("imports", folds, result, doc,
+ IMPORTS_FOLD_TYPE);
+ addFoldsOfType("tags", folds, result, doc,
+ TAG_FOLD_TYPE);
+ addFoldsOfType("othercodeblocks", folds, result, doc,
+ CODE_BLOCK_FOLD_TYPE);
+ addFoldsOfType("inner-classes", folds, result, doc,
+ INNER_CLASS_FOLD_TYPE);
+
+ if (folds.size() > 0) {
+ LOG.log(Level.WARNING, "Undefined fold types used in {0}: {1}", new Object[] {
+ info, folds.keySet()
+ });
+ }
}
- private void addFold(OffsetRange range, TreeSet folds, Document doc, boolean collapseByDefault, FoldTemplate template) throws BadLocationException {
+ private void addFold(OffsetRange range, Collection folds, Document doc, FoldType type) {
if (range != OffsetRange.NONE) {
int start = range.getStart();
int end = range.getEnd();
if (start != (-1) && end != (-1) && end <= doc.getLength()) {
- folds.add(new FoldInfo(doc, start, end, template, collapseByDefault));
+ FoldInfo fi = FoldInfo.range(start, end, type);
+ // hack for singular imports fold
+ if (fi.getType() == IMPORTS_FOLD_TYPE && imports == null) {
+ imports = fi;
+ }
+ folds.add(fi);
}
}
}
@@ -639,17 +589,25 @@
}
+ private Runnable createCommit(Collection folds, FoldInfo initComment, FoldInfo imports, Document d, Source s) {
+ return new CommitFolds(folds, initComment, imports, d, s);
+ }
+
private class CommitFolds implements Runnable {
- private Document scannedDocument;
- private Source scanSource;
+ private final Document scannedDocument;
+ private Source scanSource;
private boolean insideRender;
- private TreeSet infos;
+ private Collection infos;
private long startTime;
+ private FoldInfo initComment;
+ private FoldInfo imports;
- public CommitFolds(TreeSet infos, Document scanedDocument, Source s) {
+ public CommitFolds(Collection infos, FoldInfo initComment, FoldInfo imports, Document scannedDocument, Source s) {
this.infos = infos;
- this.scannedDocument = scanedDocument;
+ this.initComment = initComment;
+ this.imports = imports;
+ this.scannedDocument = scannedDocument;
this.scanSource = s;
}
@@ -658,17 +616,10 @@
* ignores the default state, and takes it from the actual state of
* existing fold.
*/
- private boolean mergeSpecialFoldState(FoldInfo fi) {
- if (fi.template == IMPORTS_FOLD_TEMPLATE) {
- if (importsFold != null) {
- return importsFold.isCollapsed();
- }
- } else if (fi.template == INITIAL_COMMENT_FOLD_TEMPLATE) {
- if (initialCommentFold != null) {
- return initialCommentFold.isCollapsed();
- }
+ private void mergeSpecialFoldState(FoldInfo fi, Fold f) {
+ if (fi != null && f != null) {
+ fi.collapsed(f.isCollapsed());
}
- return fi.collapseByDefault;
}
public void run() {
@@ -682,6 +633,9 @@
}
operation.getHierarchy().lock();
+ if (task == null) {
+ return;
+ }
if (operation.getHierarchy().getComponent().getDocument() != this.scannedDocument) {
Throwable t = (Throwable)scannedDocument.getProperty("Issue-222763-debug");
StringWriter sw = new StringWriter();
@@ -704,71 +658,19 @@
return;
}
try {
- FoldHierarchyTransaction tr = operation.openTransaction();
-
try {
- if (currentFolds == null) {
- return;
+ mergeSpecialFoldState(imports, importsFold);
+ mergeSpecialFoldState(initComment, initialCommentFold);
+
+ Map newState = operation.update(infos, null, null);
+ if (imports != null) {
+ importsFold = newState.get(imports);
}
-
- Map added = new TreeMap();
- // TODO - use map duplication here instead?
- TreeSet removed = new TreeSet(currentFolds.keySet());
- int documentLength = document.getLength();
-
- for (FoldInfo i : infos) {
- if (removed.remove(i)) {
- continue ;
- }
-
- int start = i.start.getOffset();
- int end = i.end.getOffset();
-
- if (end > documentLength) {
- continue;
- }
-
- if (end > start && (end - start) > (i.template.getStartGuardedLength() + i.template.getEndGuardedLength())) {
- Fold f = operation.addToHierarchy(i.template.getType(),
- i.template.getDescription(),
- mergeSpecialFoldState(i),
- start,
- end,
- i.template.getStartGuardedLength(),
- i.template.getEndGuardedLength(),
- i,
- tr);
-
- added.put(i, f);
-
- if (i.template == IMPORTS_FOLD_TEMPLATE) {
- importsFold = f;
- }
- if (i.template == INITIAL_COMMENT_FOLD_TEMPLATE) {
- initialCommentFold = f;
- }
- }
+ if (initComment != null) {
+ initialCommentFold = newState.get(initComment);
}
-
- for (FoldInfo i : removed) {
- Fold f = currentFolds.remove(i);
-
- operation.removeFromHierarchy(f, tr);
-
- if (importsFold == f ) {
- importsFold = null;
- }
-
- if (initialCommentFold == f) {
- initialCommentFold = f;
- }
- }
-
- currentFolds.putAll(added);
} catch (BadLocationException e) {
LOG.log(Level.WARNING, null, e);
- } finally {
- tr.commit();
}
} finally {
operation.getHierarchy().unlock();
@@ -781,63 +683,8 @@
}
}
- private Map currentFolds;
private Fold initialCommentFold;
private Fold importsFold;
-
- protected static final class FoldInfo implements Comparable {
-
- private Position start;
- private Position end;
- private FoldTemplate template;
- private boolean collapseByDefault;
-
- public FoldInfo(Document doc, int start, int end, FoldTemplate template, boolean collapseByDefault) throws BadLocationException {
- this.start = doc.createPosition(start);
- // see issue #216378; while Fold.end Position is manually updated by FoldHierarchyTransactionImpl, the
- // FoldInfos are left alone, and end marker must stick with the content, so characters typed after it does
- // not extend the FoldInfo
- this.end = NbDocument.createPosition(doc, end, Position.Bias.Backward);
- this.template = template;
- this.collapseByDefault = collapseByDefault;
- }
-
- @Override
- public int hashCode() {
- return 1;
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof FoldInfo)) {
- return false;
- }
-
- return compareTo(o) == 0;
- }
-
- public int compareTo(Object o) {
- FoldInfo remote = (FoldInfo) o;
-
- if (start.getOffset() < remote.start.getOffset()) {
- return -1;
- }
-
- if (start.getOffset() > remote.start.getOffset()) {
- return 1;
- }
-
- if (end.getOffset() < remote.end.getOffset()) {
- return -1;
- }
-
- if (end.getOffset() > remote.end.getOffset()) {
- return 1;
- }
-
- return 0;
- }
- }
private static boolean hasErrors(ParserResult r) {
for(org.netbeans.modules.csl.api.Error e : r.getDiagnostics()) {
diff --git a/editor.codetemplates/nbproject/org-netbeans-modules-editor-codetemplates.sig b/editor.codetemplates/nbproject/org-netbeans-modules-editor-codetemplates.sig
--- a/editor.codetemplates/nbproject/org-netbeans-modules-editor-codetemplates.sig
+++ b/editor.codetemplates/nbproject/org-netbeans-modules-editor-codetemplates.sig
@@ -1,5 +1,5 @@
#Signature file v4.1
-#Version 1.26.1
+#Version 1.31.0
CLSS public java.lang.Object
cons public init()
diff --git a/editor.codetemplates/nbproject/project.properties b/editor.codetemplates/nbproject/project.properties
--- a/editor.codetemplates/nbproject/project.properties
+++ b/editor.codetemplates/nbproject/project.properties
@@ -45,6 +45,6 @@
#javadoc.name=EditorCodeTemplates
javadoc.apichanges=${basedir}/apichanges.xml
javadoc.arch=${basedir}/arch.xml
-spec.version.base=1.31.0
+spec.version.base=1.32.0
test.config.stableBTD.includes=**/*Test.class
diff --git a/editor.codetemplates/nbproject/project.xml b/editor.codetemplates/nbproject/project.xml
--- a/editor.codetemplates/nbproject/project.xml
+++ b/editor.codetemplates/nbproject/project.xml
@@ -99,7 +99,7 @@
1
- 1.20
+ 1.31
diff --git a/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/storage/SettingsProvider.java b/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/storage/SettingsProvider.java
--- a/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/storage/SettingsProvider.java
+++ b/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/storage/SettingsProvider.java
@@ -106,11 +106,11 @@
this.ic = ic;
// Start listening
- MimePath [] allPaths = computeInheritedMimePaths(mimePath);
- this.allCtsi = new CodeTemplateSettingsImpl[allPaths.length];
+ List allPaths = mimePath.getIncludedPaths();
+ this.allCtsi = new CodeTemplateSettingsImpl[allPaths.size()];
- for(int i = 0; i < allPaths.length; i++) {
- this.allCtsi[i] = CodeTemplateSettingsImpl.get(allPaths[i]);
+ for(int i = 0; i < allPaths.size(); i++) {
+ this.allCtsi[i] = CodeTemplateSettingsImpl.get(allPaths.get(i));
this.allCtsi[i].addPropertyChangeListener(WeakListeners.propertyChange(this, this.allCtsi[i]));
}
}
@@ -170,30 +170,4 @@
}
} // End of CompositeCTS class
-
- private static MimePath [] computeInheritedMimePaths(MimePath mimePath) {
- List paths = null;
- try {
- Method m = MimePath.class.getDeclaredMethod("getInheritedPaths", String.class, String.class); //NOI18N
- m.setAccessible(true);
- @SuppressWarnings("unchecked")
- List ret = (List) m.invoke(mimePath, null, null);
- paths = ret;
- } catch (Exception e) {
- LOG.log(Level.WARNING, "Can't call org.netbeans.api.editor.mimelookup.MimePath.getInheritedPaths method.", e); //NOI18N
- }
-
- if (paths != null) {
- ArrayList mimePaths = new ArrayList(paths.size());
-
- for (String path : paths) {
- mimePaths.add(MimePath.parse(path));
- }
-
- return mimePaths.toArray(new MimePath[mimePaths.size()]);
- } else {
- return new MimePath [] { mimePath, MimePath.EMPTY };
- }
- }
-
}
diff --git a/editor.fold/arch.xml b/editor.fold/arch.xml
--- a/editor.fold/arch.xml
+++ b/editor.fold/arch.xml
@@ -450,6 +450,41 @@
}
+
Updating Fold hierarchy
+In the preceding cases, maintaining Folds was the FoldManager's responsibility. The FoldManager typically
+held a copy of the Folds added to the hierarchy, and during the refresh, it compared them to the new data
+and decided what folds to remove.
+For simple cases, which only create/remove folds based on text positions, part of the work can be offloaded to the
+FoldOperation:
+
+
+ // create new fold positional information for all folds.
+ Collection<FoldInfo> newInfos = ...;
+
+ // create FoldInfo for each of the fold
+ newInfos.add(
+ FoldInfo.range(start, end, type).
+ withTemplate(customTemplate).
+ withDescription(veryCustomDescription).
+ collapse(true)
+ );
+
+ // the hierarchy must be locked prior to update
+
+ doc.readLock();
+ hierarchy.lock();
+ try {
+ operation.update(newInfos, null, null);
+ } finally {
+ }
+
+
+The update() operation performs a diff, creates new folds, discards old ones, and updates the folds, which
+prevailed.
+
+
Accessing folds
+Instead of keeping a copy of created folds, the FoldManager may call operation.foldIterator. The iterator
+will enumerate all folds, including (recursively) blocked ones.
@@ -1204,4 +1239,85 @@
No.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ XXX no answer for compat-deprecation
+
+
+
+
+
+
+
+
+ XXX no answer for exec-ant-tasks
+
+
+
+
+
+
+
+
+ XXX no answer for resources-preferences
+
+
+
diff --git a/editor.fold/nbproject/org-netbeans-modules-editor-fold.sig b/editor.fold/nbproject/org-netbeans-modules-editor-fold.sig
--- a/editor.fold/nbproject/org-netbeans-modules-editor-fold.sig
+++ b/editor.fold/nbproject/org-netbeans-modules-editor-fold.sig
@@ -1,5 +1,5 @@
#Signature file v4.1
-#Version 1.29.2
+#Version 1.33.0
CLSS public abstract interface java.io.Serializable
diff --git a/editor.fold/nbproject/project.properties b/editor.fold/nbproject/project.properties
--- a/editor.fold/nbproject/project.properties
+++ b/editor.fold/nbproject/project.properties
@@ -46,6 +46,6 @@
#cp.extra=
javac.source=1.6
-spec.version.base=1.33.0
+spec.version.base=1.34.0
test.config.stableBTD.includes=**/*Test.class
diff --git a/editor.fold/nbproject/project.xml b/editor.fold/nbproject/project.xml
--- a/editor.fold/nbproject/project.xml
+++ b/editor.fold/nbproject/project.xml
@@ -50,6 +50,15 @@
org.netbeans.modules.editor.fold
+ org.netbeans.modules.editor.lib
+
+
+
+ 3
+ 3.33
+
+
+ org.netbeans.modules.editor.lib2
@@ -64,7 +73,7 @@
1
- 1.20
+ 1.31
@@ -77,6 +86,15 @@
+ org.netbeans.modules.editor.settings.storage
+
+
+
+ 1
+ 1.38
+
+
+ org.netbeans.modules.editor.util
@@ -85,6 +103,32 @@
+ org.netbeans.modules.lexer
+
+
+
+ 2
+ 1.49
+
+
+
+ org.netbeans.modules.options.api
+
+
+
+ 1
+ 1.31
+
+
+
+ org.openide.awt
+
+
+
+ 7.55
+
+
+ org.openide.filesystems
@@ -147,8 +191,12 @@
org.netbeans.api.editor.fold
+ org.netbeans.editororg.netbeans.spi.editor.fold
+
+ initially
+
diff --git a/editor.fold/src/org/netbeans/api/editor/fold/Fold.java b/editor.fold/src/org/netbeans/api/editor/fold/Fold.java
--- a/editor.fold/src/org/netbeans/api/editor/fold/Fold.java
+++ b/editor.fold/src/org/netbeans/api/editor/fold/Fold.java
@@ -48,6 +48,7 @@
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Position;
+import org.netbeans.modules.editor.fold.ApiPackageAccessor;
import org.netbeans.modules.editor.fold.FoldOperationImpl;
import org.netbeans.modules.editor.fold.FoldChildren;
import org.netbeans.modules.editor.fold.FoldUtilitiesImpl;
@@ -108,7 +109,6 @@
private Object extraInfo;
-
Fold(FoldOperationImpl operation,
FoldType type, String description, boolean collapsed,
Document doc, int startOffset, int endOffset,
@@ -404,6 +404,40 @@
public int getFoldIndex(Fold child) {
return (children != null) ? children.getFoldIndex(child) : -1;
}
+
+ /**
+ * Start of the fold content past the guarded area.
+ * For root fold, returns 0. For other folds, returns the offset
+ * following the guarded start of the fold (typically a delimiter).
+ *
+ * @return offset in document
+ */
+ public int getGuardedStart() {
+ if (isRootFold()) {
+ return 0;
+ } else if (isZeroStartGuardedLength()) {
+ return getStartOffset();
+ } else {
+ return getStartOffset() + startGuardedLength;
+ }
+ }
+
+ /**
+ * End of the fold content before the guarded area.
+ * For root fold, returns 0. For other folds, returns the last offset
+ * preceding the guarded start of the fold (typically a delimiter).
+ *
+ * @return offset in document
+ */
+ public int getGuardedEnd() {
+ if (isRootFold()) {
+ return getEndOffset();
+ } else if (isZeroStartGuardedLength()) {
+ return getEndOffset();
+ } else {
+ return getEndOffset() - endGuardedLength;
+ }
+ }
private void updateGuardedStartPos(Document doc, int startOffset) throws BadLocationException {
if (!isRootFold()) {
@@ -530,7 +564,6 @@
this.rawIndex += rawIndexDelta;
}
-
public String toString() {
return FoldUtilitiesImpl.foldToString(this) + ", [" + getInnerStartOffset() // NOI18N
+ ", " + getInnerEndOffset() + "] {" // NOI18N
diff --git a/editor.fold/src/org/netbeans/api/editor/fold/FoldHierarchy.java b/editor.fold/src/org/netbeans/api/editor/fold/FoldHierarchy.java
--- a/editor.fold/src/org/netbeans/api/editor/fold/FoldHierarchy.java
+++ b/editor.fold/src/org/netbeans/api/editor/fold/FoldHierarchy.java
@@ -53,6 +53,7 @@
import org.netbeans.modules.editor.fold.ApiPackageAccessor;
import org.netbeans.modules.editor.fold.FoldHierarchyExecution;
import org.netbeans.modules.editor.fold.FoldOperationImpl;
+import org.netbeans.spi.editor.fold.FoldTypeProvider;
/**
* Hierarchy of the folds for a single text component represents
@@ -122,7 +123,7 @@
static {
ensureApiAccessorRegistered();
}
-
+
private static void ensureApiAccessorRegistered() {
if (!apiPackageAccessorRegistered) {
apiPackageAccessorRegistered = true;
@@ -458,6 +459,10 @@
fsc.endOffsetChanged(originalEndOffset);
}
+ public FoldHierarchyExecution foldGetExecution(FoldHierarchy h) {
+ return h.execution;
+ }
+
}
}
diff --git a/editor.fold/src/org/netbeans/api/editor/fold/FoldTemplate.java b/editor.fold/src/org/netbeans/api/editor/fold/FoldTemplate.java
new file mode 100644
--- /dev/null
+++ b/editor.fold/src/org/netbeans/api/editor/fold/FoldTemplate.java
@@ -0,0 +1,125 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2011 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2011 Sun Microsystems, Inc.
+ */
+package org.netbeans.api.editor.fold;
+
+import org.openide.util.NbBundle;
+
+import static org.netbeans.api.editor.fold.Bundle.*;
+
+/**
+ * Template that describes how the fold should appear and interact with the
+ * user. The FoldTemplate defines how many characters at the start (end) should
+ * act as a fold guard: change to that area will remove the fold because it
+ * becomes damaged. It also defines what placeholder should appear in place of the
+ * folded code.
+ *
+ * A template may be attached to a {@link FoldType} instance. Folds of that kind will
+ * automatically use the attached Template, if not overriden explicitly.
+ *
+ * The string {@code "..."} ({@link #CONTENT_PLACEHOLDER}) is treated as a placeholder. If a
+ * ContentReader is available for the {@link FoldType}, the placeholder will be replaced by
+ * the product of the Reader. Otherwise the placeholder will remain in the displayText
+ * and will be presented.
+ *
+ * @since 1.34
+ * @author sdedic
+ */
+public final class FoldTemplate {
+ /**
+ * The default template for folded text: no markers before+after, ellipsis
+ * shown.
+ */
+ @NbBundle.Messages("FT_DefaultTemplate=...")
+ public static final FoldTemplate DEFAULT = new FoldTemplate(0, 0, FT_DefaultTemplate()); // NOI18N
+
+ /**
+ * A standard template, which represents a block of something.
+ */
+ @NbBundle.Messages("FT_DefaultBlockTemplate={...}")
+ public static final FoldTemplate DEFAULT_BLOCK = new FoldTemplate(0, 0, FT_DefaultBlockTemplate()); // NOI18N
+
+ /**
+ * This string is interpreted as a placeholder for the infrastructure to inject
+ */
+ public static final String CONTENT_PLACEHOLDER = FT_DefaultTemplate();
+
+ /**
+ * # of characters at the start of the fold, which serve as a marker that the fold
+ * has been damaged/destroyed
+ */
+ private int guardedStart;
+
+ /**
+ * The guarded portion at the end of the fold
+ */
+ private int guardedEnd;
+
+ /**
+ * Description that appears in the folded area.
+ */
+ private String displayText;
+
+ /**
+ * Creates a FoldTemplate with a fixed description.
+ *
+ * @param guardedStart length of the start marker, or -1 if no start marker is present
+ * @param guardedEnd length of the end marker, or -1 if no end marker is present
+ * @param displayText text which should be displayed in place of the folded content
+ */
+ public FoldTemplate(int guardedStart, int guardedEnd, String displayText) {
+ this.guardedStart = guardedStart;
+ this.guardedEnd = guardedEnd;
+ this.displayText = displayText;
+ }
+
+ public String getDescription() {
+ return displayText;
+ }
+
+ public int getGuardedEnd() {
+ return guardedEnd;
+ }
+
+ public int getGuardedStart() {
+ return guardedStart;
+ }
+}
diff --git a/editor.fold/src/org/netbeans/api/editor/fold/FoldType.java b/editor.fold/src/org/netbeans/api/editor/fold/FoldType.java
--- a/editor.fold/src/org/netbeans/api/editor/fold/FoldType.java
+++ b/editor.fold/src/org/netbeans/api/editor/fold/FoldType.java
@@ -44,48 +44,339 @@
package org.netbeans.api.editor.fold;
+import java.util.Collection;
+import javax.swing.event.ChangeListener;
+import org.openide.util.NbBundle;
+import org.openide.util.Parameters;
+
+import static org.netbeans.api.editor.fold.Bundle.*;
+
/**
* Each {@link Fold} is identified by its fold type.
*
* Each fold type presents a fold type acceptor as well
* accepting just itself.
*
- *
+ *
+ *
* As the equals() method is declared final
* (delegating to super) the fold types
* can directly be compared by using ==
* operator.
+ *
+ *
+ * FoldTypes are MIME-type specific. Sets of FoldType, which apply
+ * to a certain MIME type are collected in a {@link FoldType.Domain},
+ * which can be obtained by {@link FoldUtilities#getFoldTypes}. FoldTypes for a MIME type are
+ * collected with the help of {@link org.netbeans.spi.editor.fold.FoldTypeProvider}.
+ *
+ * Several generalized types of folds are defined in this class as constants.
+ * When creating custom FoldTypes, please check carefully whether the new FoldType is not,
+ * in fact, one of these general ones,
+ * or is not a specialization of it. If a general action (e.g. auto-collapse all folds XXX) would
+ * make sense for the new fold, consider to {@link #derive} it from an existing one.
+ *
+ * Each FoldType has a specific FoldTemplate instance that controls how it is presented. If a
+ * new Fold has same semantics as an existing one, but a different {@link FoldTemplate} is needed,
+ * use {@link #override} to create a new FoldType with appropriate properties. FoldTypes
+ * defined here can be reused in individual MIME types, if their configuration is appropriate.
+ * In general, FoldTypes from more general MIME types (e.g. text/xml) can be reused in specialized ones
+ * (e.g. text/x-ant+xml).
+ *
+ * FoldTypes can form a hierarchy of generalization. For example java Method fold can have a more general
+ * 'parent', {@link #MEMBER}.
+ *
+ * The generalization is designed to allow several kinds of the same concept operated separately at the
+ * level of the language, but managed at once using general options or operations.
+ * If no settings are specified for Method folding, {@link #MEMBER} settings are
+ * applied. Likewise, a general-purpose folding operation can work on {@link #MEMBER} fold type,
+ * which will affect both FUNCTION in PHP or METHOD in java (and other fold types in other languages).
+ *
+ * Because of this, please be careful
+ * to use {@link #isKindOf} to check whether a FoldType is of a specific type. This method respects the
+ * generalization. == can be still used to check for exact type match. When using ==, javadoc comments
+ * may be evaluated as different from general 'documentation' and yet different from PHP docs. Comparison using
+ * == are safe only when applied on FoldType instances defined for the same MIME type
+*
*
- *
- * Fold providers should export all the fold types
- * that they provide in their APIs so that the
- * clients can use these fold types in operations
- * with the fold hierarchy.
- *
+ * @author sdedic
* @author Miloslav Metelka
* @version 1.00
*/
public final class FoldType {
+ /**
+ * A generic code block. Note that other categories are defined that represent
+ * method, class, member or nested symbol levels
+ * @since 1.34
+ */
+ @NbBundle.Messages({
+ "FT_Label_code-block=Code Blocks",
+ "FT_display_code-block={...}",
+ "FT_display_default=..."
+ })
+ public static final FoldType CODE_BLOCK = create("code-block", FT_Label_code_block(),
+ new FoldTemplate(1, 1, FT_display_code_block()));
+
+ /**
+ * Documentation comment, as defined by the language
+ * @since 1.34
+ */
+ @NbBundle.Messages("FT_Label_javadoc=Documentation")
+ public static final FoldType DOCUMENTATION = create("documentation", FT_Label_javadoc(),
+ FoldTemplate.DEFAULT);
- private final String description;
+ /**
+ * Unspecified type of comment
+ * @since 1.34
+ */
+ @NbBundle.Messages("FT_Label_comment=Comments")
+ public static final FoldType COMMENT = create("comment", FT_Label_comment(),
+ FoldTemplate.DEFAULT);
/**
+ * Initial file-level comment. Either documentation comment or comment containing
+ * copyright. Defaults to 'comment'
+ * @since 1.34
+ */
+ @NbBundle.Messages("FT_Label_initial-comment=Initial Comment")
+ public static final FoldType INITIAL_COMMENT = create("initial-comment", FT_Label_initial_comment(),
+ FoldTemplate.DEFAULT);
+
+ /**
+ * Tag in markup languages
+ * @since 1.34
+ */
+ @NbBundle.Messages({
+ "FT_Label_tag=Tags and Markup",
+ "FT_display_tag=<.../>"
+ })
+ public static final FoldType TAG = create("tag", FT_Label_tag(),
+ new FoldTemplate(1, 2, FT_display_tag()));
+
+ /**
+ * Nested part; for example an embedded type, or nested namespace. Do not use
+ * this category for top-level symbols. This type is different from a 'member',
+ * as type may have many member types, nested types form worlds of their own
+ * @since 1.34
+ */
+ @NbBundle.Messages("FT_Label_inner-class=Nested Blocks")
+ public static final FoldType NESTED = create("nested", FT_Label_inner_class(),
+ FoldTemplate.DEFAULT);
+
+ /**
+ * Member of a symbol (type, class, object)
+ * @since 1.34
+ */
+ @NbBundle.Messages("FT_Label_member=Member")
+ public static final FoldType MEMBER = create("member", FT_Label_member(),
+ FoldTemplate.DEFAULT);
+
+ /**
+ * Various import, includes and references to sibling files.
+ *
+ * @since 1.34
+ */
+ @NbBundle.Messages("FT_Label_import=Imports and Includes")
+ public static final FoldType IMPORT = create("import", FT_Label_import(),
+ FoldTemplate.DEFAULT);
+
+ /**
+ * User-defined fold, recoded using a special comment
+ * @since 1.34
+ */
+ @NbBundle.Messages("FT_Label_user-defined=User defined")
+ public static final FoldType USER = create("user", FT_Label_user_defined(), null);
+
+ /**
* Construct fold type with the given description.
*
* @param description textual description of the fold type.
* Two fold types with the same description are not equal.
+ * @deprecated Use the {link create} static method.
*/
+ @Deprecated
public FoldType(String description) {
- this.description = description;
+ this(description, null, null, null);
}
- public boolean accepts(FoldType type) {
- return (type == this);
+ private FoldType(String code, String label, FoldTemplate template, FoldType parent) {
+ this.code = code;
+ this.parent = parent;
+ this.label = label;
+ this.template = template != null ? template : FoldTemplate.DEFAULT;
+ }
+
+ /**
+ * Creates an instance of FoldType.
+ * The code, label and template parameters will be assigned to the FoldType's properties.
+ *
+ * @param code code used to form, e.g. persistent keys for info related to the FoldType
+ * @param label human-readable label that identifies the FoldType
+ * @param template describes how the fold is presented
+ * @return FoldType instance
+ * @since 1.34
+ */
+ public static FoldType create(String code, String label, FoldTemplate template) {
+ Parameters.notWhitespace("code", code);
+ Parameters.notWhitespace("label", label);
+ return new FoldType(code, label, template, null);
}
+ /**
+ * Creates a FoldType, overriding its appearance.
+ * The new FoldType will use the same code, but label and/or template can be changed.
+ *
+ * @param label human-readable label that describes the FoldType
+ * @param template human-readable label that describes the FoldType
+ * @return an instance of FoldType initialized according to parameters
+ * @since 1.34
+ */
+ public FoldType override(String label, FoldTemplate template) {
+ return derive(code(), label, template);
+ }
+
+ /**
+ * Derives a FoldType which acts as a child of this instance.
+ * The FoldType will be treated as a special case of this instance. If A is the returned
+ * instance and B is this instance, then A.isKindOf(B) will return true.
+ *
+ *
+ * @param code new code for the FoldType
+ * @param label human-readable label that describes the FoldType
+ * @param template human-readable label that describes the FoldType
+ * @return an instance of child FoldType
+ * @since 1.34
+ */
+ public FoldType derive(String code, String label, FoldTemplate template) {
+ Parameters.notWhitespace("code", code);
+ Parameters.notWhitespace("label", label);
+ return new FoldType(code, label, template, this);
+ }
+
+ /**
+ * @param type
+ * @return
+ * @deprecated Use {@link #isKindOf}
+ */
+ @Deprecated
+ public boolean accepts(FoldType type) {
+ return isKindOf(type);
+ }
+
+ @Override
public String toString() {
- return description;
+ return code();
+ }
+
+ /**
+ * Provides human-readable label for the type
+ * @return
+ * @since 1.34
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * Provides FoldTemplate instance for this type.
+ * The FoldTemplate is the default template used when creating a Fold. Specific FoldTemplate
+ * may be provided when creating the Fold instance.
+ *
+ * @return fold template for the type.
+ */
+ public FoldTemplate getTemplate() {
+ return template;
+ }
+
+ /**
+ * Checks whether the fold can act as the 'other' type.
+ * This check respect parent relationship (see {@link #parent}). The method returns true,
+ * if semantics, operations, settings,... applicable to 'other' could be also applied on
+ * this FoldType.
+ *
+ * @param other the type to check
+ * @return true, if this FoldType should be affected
+ * @since 1.34
+ */
+ public boolean isKindOf(FoldType other) {
+ return other == this || parent != null && parent.isKindOf(other);
+ }
+
+ /**
+ * Provides parent of this FoldType. Returns {@code null}, if no parent is defined.
+ * The parent is a FoldType, which is more general and could be used as a fallback when no information
+ * is provided/available on this FoldType instance.
+ *
+ * @return parent instance or {@code null}
+ * @since 1.34
+ */
+ public FoldType parent() {
+ return parent;
+ }
+
+ /**
+ * Persistable value of FoldType.
+ * Use {@link FoldType.Domain#valueOf} to convert the value back to FoldType instance. The value
+ * can (and is) used to compose preference setting keys.
+ *
+ * @return value of FoldType.
+ * @since 1.34
+ */
+ public String code() {
+ return code;
}
+ /**
+ * Represents a value set of {@link FoldType}s for one MIME type.
+ * The instance collects all FoldTypes defined for a certain MIME type. "" mime type
+ * represents 'global' FoldTypes.
+ *
+ * The instance will fire change events when the set of fold types change, e.g. as a result
+ * of enabled/disabled modules (appearance of {@link FoldTypeProvider}).
+ * @since 1.34
+ */
+ public interface Domain {
+ /**
+ * Enumerates FoldTypes. Returns empty collection, if no fold types are known.
+ * @return fold types.
+ */
+ Collection values();
+
+ /**
+ * Translates String code into FoldType. Returns null if the fold type is not found.
+ * The String should have been obtained by a call to FoldType.code().
+ *
+ * @param s code value
+ * @return the FoldType instance.
+ */
+ FoldType valueOf(String s);
+
+ /**
+ * Attaches listener to be notified when set of FoldTypes change.
+ *
+ * @param l listener instance.
+ */
+ void addChangeListener(ChangeListener l);
+
+ /**
+ * Detaches the change listener
+ * @param l listener instance.
+ */
+ void removeChangeListener(ChangeListener l);
+ }
+
+ private final String code;
+
+ private final FoldType parent;
+
+ /**
+ * Display name for the fold type, for presentation in UI
+ */
+ private final String label;
+
+ /**
+ * The default fold template
+ */
+ private final FoldTemplate template;
}
diff --git a/editor.fold/src/org/netbeans/api/editor/fold/FoldUtilities.java b/editor.fold/src/org/netbeans/api/editor/fold/FoldUtilities.java
--- a/editor.fold/src/org/netbeans/api/editor/fold/FoldUtilities.java
+++ b/editor.fold/src/org/netbeans/api/editor/fold/FoldUtilities.java
@@ -48,6 +48,8 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import org.netbeans.api.editor.mimelookup.MimePath;
+import org.netbeans.modules.editor.fold.FoldRegistry;
import org.netbeans.modules.editor.fold.FoldUtilitiesImpl;
/**
@@ -440,4 +442,41 @@
return FoldUtilitiesImpl.collapsedFoldIterator(hierarchy, startOffset, endOffset);
}
+ /**
+ * Obtains available FoldType values for the specified MIME type.
+ *
+ * @param mimeType mime type of the content
+ * @return available FoldTypes
+ * @since 1.34
+ */
+ public static FoldType.Domain getFoldTypes(String mimeType) {
+ return FoldRegistry.get().getDomain(MimePath.parse(mimeType));
+ }
+
+ /**
+ * Determines whether folds of that type are should be initially collapsed.
+ * The FoldType is evaluated in the context of a specific FoldHierarchy, that is a Component
+ * with a content of a certain MIME type.
+ *
+ * @param ft FoldType to inspect
+ * @param hierarchy context for evaluation
+ * @return true, if folds of FoldType should be initially collapsed.
+ * @since 1.34
+ */
+ public static boolean isAutoCollapsed(FoldType ft, FoldHierarchy hierarchy) {
+ return FoldUtilitiesImpl.isAutoCollapsed(ft, hierarchy);
+ }
+
+ /**
+ * Determines whether folding is enabled for a given MIME type.
+ * Use {@link MimePath#EMPTY}.getMimeType() to query for the default (all languages)
+ * setting.
+ *
+ * @param mimeType the MIME type of the content.
+ * @return true, if folding is enabled, false otherwise.
+ * @since 1.34
+ */
+ public static boolean isFoldingEnabled(String mimeType) {
+ return FoldUtilitiesImpl.isFoldingEnabled(mimeType);
+ }
}
diff --git a/editor.fold/src/org/netbeans/api/editor/fold/FoldingSupport.java b/editor.fold/src/org/netbeans/api/editor/fold/FoldingSupport.java
new file mode 100644
--- /dev/null
+++ b/editor.fold/src/org/netbeans/api/editor/fold/FoldingSupport.java
@@ -0,0 +1,194 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2013 Sun Microsystems, Inc.
+ */
+package org.netbeans.api.editor.fold;
+
+import java.util.Map;
+import javax.swing.JComponent;
+import javax.swing.text.JTextComponent;
+import org.netbeans.editor.SideBarFactory;
+import org.netbeans.modules.editor.fold.CustomFoldManager;
+import org.netbeans.modules.editor.fold.JavadocReader;
+import org.netbeans.modules.editor.fold.ui.CodeFoldingSideBar;
+import org.netbeans.spi.editor.fold.ContentReader;
+import org.netbeans.spi.editor.fold.FoldManager;
+import org.netbeans.spi.editor.fold.FoldManagerFactory;
+import org.openide.util.Parameters;
+
+/**
+ * This utility class collects APIs to create default implementations
+ * of various pieces of infrastructure.
+ *
+ * @author sdedic
+ */
+public final class FoldingSupport {
+ private static SideBarFactory FACTORY = null;
+
+ private FoldingSupport() {}
+
+ /**
+ * Creates a default implementation of {@link ContentReader}.
+ *
+ * The default implementation is modeled to work with Javadoc-like comments. It will
+ * ignore markers at line start (first non-whitespace) - 'start' parameter. If the reader
+ * encounters the 'stop' regex pattern, it stops scanning and returns {@code null}. Typically
+ * some tags are placed at the end of the doc comment, and they are not informative enough
+ * to put them into folded preview.
+ *
+ * Finally, if a suitable content is found, and it contains the 'terminator' pattern,
+ * the content is only returned up to (excluding) the terminator.
+ *
+ * Javadoc (PHPdoc) reader can be constructed as defaultReader("*", "\\.", "@");
+ *
+ * @param start character sequence, which will be ignored at the beginning of the line. Can be {@code null}
+ * @param terminator pattern, which marks the end of the summary line/sentence. Can be {@code null}
+ * @param stop content search stop at the 'stop' pattern. Can be {@code null}
+ */
+ public static ContentReader contentReader(String start, String terminator, String stop, String prefix) {
+ return new JavadocReader(start, terminator, stop, prefix);
+ }
+
+ /**
+ * A variant of {@link #defaultReader} usable from FS layer.
+ * See {@link #defaultReader} documentation for parameter explanation. The
+ * method expects those parameters as keys in the Map (values are all Strings).
+ * An additional entry (key: 'type') is required in the map, it identifies the
+ * FoldType for which the reader should work.
+ *
+ * @param m configuration parameters for the reader factory.
+ * @see #contentReader
+ */
+ public static ContentReader.Factory contentReaderFactory(Map m) {
+ final String start = (String) m.get("start"); // NOI18N
+ final String terminator = (String) m.get("terminator"); // NOI18N
+ final String stop = (String) m.get("stop"); // NOI18N
+ final String foldType = (String) m.get("type"); // NOI18N
+ final String prefix = (String)m.get("prefix"); // NOI18N
+ return new ContentReader.Factory() {
+ @Override
+ public ContentReader createReader(FoldType ft) {
+ if (foldType != null && foldType.equals(ft.code()) || (foldType == null && ft.isKindOf(FoldType.DOCUMENTATION))) {
+ return contentReader(start, terminator, stop, prefix);
+ } else {
+ return null;
+ }
+ }
+ };
+ }
+
+ /**
+ * Creates a component for the code folding sidebar.
+ * Returns a standard folding sidebar component, which displays code folds. This sidebar implementation
+ * can be created only once per text component.
+ *
+ * @param textComponent the text component which should work with the Sidebar
+ * @return Sidebar instance.
+ */
+ public static JComponent sidebarComponent(JTextComponent textComponent) {
+ return new CodeFoldingSideBar(textComponent);
+ }
+
+ /**
+ * Creates a user-defined fold manager, that processes specific token type.
+ * The manager tries to find start/end markers within the token text. If found,
+ * a Fold is created. The manager only looks in tokens, whose {@code primaryCategory}
+ * starts with a String, which is stored under 'tokenId' key in the params map.
+ *
+ * The method is designed to be called from the filesystem layer as follows:
+ *
+ *
+ * @param map the configuration parameters.
+ * @return FoldManagerFactory instance
+ */
+ public static FoldManagerFactory userFoldManagerFactory(Map params) {
+ final String s = (String) params.get("tokenId");
+ return new FoldManagerFactory() {
+ @Override
+ public FoldManager createFoldManager() {
+ return userFoldManager(s);
+ }
+ };
+ }
+
+ /**
+ * Creates a user-defined fold manager, that processes specific token type.
+ * The manager tries to find start/end markers within the token text. If found,
+ * a Fold is created. The manager only looks in tokens, whose {@code primaryCategory}
+ * starts with tokenId string.
+ *
+ * {@code Null} value of 'tokenId' means the default "comment" will be used.
+ *
+ * @param tokenId filter for prefix of the token's primaryCategory.
+ * @return FoldManager instance
+ */
+ public static FoldManager userFoldManager(String tokenId) {
+ if (tokenId != null) {
+ return new CustomFoldManager(tokenId);
+ } else {
+ return new CustomFoldManager();
+ }
+ }
+
+ /**
+ * Obtains an instance of folding sidebar factory. This method should
+ * be used in layer, in the MIME lookup area, to register a sidebar with
+ * an editor for a specific MIMEtype.
+ *
+ * There's a default sidebar instance registered for all MIME types.
+ *
+ * @return shared sidebar factory instance
+ */
+ public static SideBarFactory foldingSidebarFactory() {
+ if (FACTORY != null) {
+ return FACTORY;
+ }
+ return FACTORY = new CodeFoldingSideBar.Factory();
+ }
+
+ public static void disableCodeFoldingSidebar(JTextComponent text) {
+ text.putClientProperty(CodeFoldingSideBar.PROP_SIDEBAR_MARK, true);
+ }
+}
diff --git a/editor.fold/src/org/netbeans/editor/CodeFoldingSideBar.java b/editor.fold/src/org/netbeans/editor/CodeFoldingSideBar.java
new file mode 100644
--- /dev/null
+++ b/editor.fold/src/org/netbeans/editor/CodeFoldingSideBar.java
@@ -0,0 +1,1482 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
+ * Microsystems, Inc. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+
+package org.netbeans.editor;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Stroke;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.prefs.PreferenceChangeEvent;
+import java.util.prefs.PreferenceChangeListener;
+import java.util.prefs.Preferences;
+import javax.accessibility.Accessible;
+import javax.accessibility.AccessibleContext;
+import javax.accessibility.AccessibleRole;
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.AbstractDocument;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.JTextComponent;
+import javax.swing.text.View;
+import org.netbeans.api.editor.fold.Fold;
+import org.netbeans.api.editor.fold.FoldHierarchy;
+import org.netbeans.api.editor.fold.FoldHierarchyEvent;
+import org.netbeans.api.editor.fold.FoldHierarchyListener;
+import org.netbeans.api.editor.fold.FoldUtilities;
+import org.netbeans.api.editor.mimelookup.MimeLookup;
+import org.netbeans.api.editor.settings.AttributesUtilities;
+import org.netbeans.api.editor.settings.FontColorNames;
+import org.netbeans.api.editor.settings.FontColorSettings;
+import org.netbeans.api.editor.settings.SimpleValueNames;
+import org.netbeans.modules.editor.lib2.EditorPreferencesDefaults;
+import org.netbeans.modules.editor.lib2.view.LockedViewHierarchy;
+import org.netbeans.modules.editor.lib2.view.ParagraphViewDescriptor;
+import org.netbeans.modules.editor.lib2.view.ViewHierarchy;
+import org.netbeans.modules.editor.lib2.view.ViewHierarchyEvent;
+import org.netbeans.modules.editor.lib2.view.ViewHierarchyListener;
+import org.openide.util.Lookup;
+import org.openide.util.LookupEvent;
+import org.openide.util.LookupListener;
+import org.openide.util.NbBundle;
+import org.openide.util.WeakListeners;
+
+/**
+ * Code Folding Side Bar. Component responsible for drawing folding signs and responding
+ * on user fold/unfold action.
+ *
+ * @author Martin Roskanin
+ * @deprecated You should use {@link FoldUtilities#createSidebarComponent(javax.swing.text.JTextComponent)} or
+ * {@link FoldUtilities#getFoldingSidebarFactory()} instead. Subclassing CodeFoldingSidebar
+ * is no longer actively supported, though still working.
+ */
+@Deprecated
+public class CodeFoldingSideBar extends JComponent implements Accessible {
+
+ private static final Logger LOG = Logger.getLogger(CodeFoldingSideBar.class.getName());
+
+ /** This field should be treated as final. Subclasses are forbidden to change it.
+ * @deprecated Without any replacement.
+ */
+ protected Color backColor;
+ /** This field should be treated as final. Subclasses are forbidden to change it.
+ * @deprecated Without any replacement.
+ */
+ protected Color foreColor;
+ /** This field should be treated as final. Subclasses are forbidden to change it.
+ * @deprecated Without any replacement.
+ */
+ protected Font font;
+
+ /** This field should be treated as final. Subclasses are forbidden to change it. */
+ protected /*final*/ JTextComponent component;
+ private volatile AttributeSet attribs;
+ private Lookup.Result extends FontColorSettings> fcsLookupResult;
+ private final LookupListener fcsTracker = new LookupListener() {
+ public void resultChanged(LookupEvent ev) {
+ attribs = null;
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ //EMI: This is needed as maybe the DEFAULT_COLORING is changed, the font is different
+ // and while getMarkSize() is used in paint() and will make the artifacts bigger,
+ // the component itself will be the same size and it must be changed.
+ // See http://www.netbeans.org/issues/show_bug.cgi?id=153316
+ updatePreferredSize();
+ CodeFoldingSideBar.this.repaint();
+ }
+ });
+ }
+ };
+ private final Listener listener = new Listener();
+
+ private boolean enabled = false;
+
+ protected List visibleMarks = new ArrayList();
+
+ /**
+ * Mouse moved point, possibly {@code null}. Set from mouse-moved, mouse-entered
+ * handlers, so that painting will paint this fold in bold. -1, if mouse is not
+ * in the sidebar region. The value is used to compute highlighted portions of the
+ * folding outline.
+ */
+ private int mousePoint = -1;
+
+ /**
+ * if true, the {@link #mousePoint} has been already used to make a PaintInfo active.
+ * The flag is tested by {@link #traverseForward} and {@link #traverseBackward} after children
+ * of the current fold are processed and cleared if the {@link #mousePoint} falls to the fold area -
+ * fields of PaintInfo are set accordingly.
+ * It's also used to compute (current) mouseBoundary, so mouse movement does not trigger
+ * refreshes eagerly
+ */
+ private boolean mousePointConsumed;
+
+ /**
+ * Boundaries of the current area under the mouse. Can be eiher the span of the
+ * current fold (or part of it), or the span not occupied by any fold. Serves as an optimization
+ * for mouse handler, which does not trigger painting (refresh) unless mouse
+ * leaves this region.
+ */
+ private Rectangle mouseBoundary;
+
+ /**
+ * Y-end of the nearest fold that ends above the {@link #mousePoint}. Undefined if mousePoint is null.
+ * These two variables are initialized at each level of folds, and help to compute {@link #mouseBoundary} for
+ * the case the mousePointer is OUTSIDE all children (or outside all folds).
+ */
+ private int lowestAboveMouse = -1;
+
+ /**
+ * Y-begin of the nearest fold, which starts below the {@link #mousePoint}. Undefined if mousePoint is null
+ */
+ private int topmostBelowMouse = Integer.MAX_VALUE;
+
+ /** Paint operations */
+ public static final int PAINT_NOOP = 0;
+ /**
+ * Normal opening +- marker
+ */
+ public static final int PAINT_MARK = 1;
+
+ /**
+ * Vertical line - typically at the end of the screen
+ */
+ public static final int PAINT_LINE = 2;
+
+ /**
+ * End angled line, without a sign
+ */
+ public static final int PAINT_END_MARK = 3;
+
+ /**
+ * Single-line marker, both start and end
+ */
+ public static final int SINGLE_PAINT_MARK = 4;
+
+ /**
+ * Marker value for {@link #mousePoint} indicating that mouse is outside the Component.
+ */
+ private static final int NO_MOUSE_POINT = -1;
+
+ /**
+ * Stroke used to draw inactive (regular) fold outlines.
+ */
+ private static Stroke LINE_DASHED = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
+ 1f, new float[] { 1f, 1f }, 0f);
+
+ private boolean alreadyPresent;
+
+ /**
+ * Stroke used to draw outlines for 'active' fold
+ */
+ private static final Stroke LINE_BOLD = new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
+
+ private final Preferences prefs;
+ private final PreferenceChangeListener prefsListener = new PreferenceChangeListener() {
+ public void preferenceChange(PreferenceChangeEvent evt) {
+ String key = evt == null ? null : evt.getKey();
+ if (key == null || SimpleValueNames.CODE_FOLDING_ENABLE.equals(key)) {
+ updateColors();
+
+ boolean newEnabled = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_ENABLE, EditorPreferencesDefaults.defaultCodeFoldingEnable);
+ if (enabled != newEnabled) {
+ enabled = newEnabled;
+ updatePreferredSize();
+ }
+ }
+ }
+ };
+
+ private void checkRepaint(ViewHierarchyEvent vhe) {
+ if (!vhe.isChangeY()) {
+ // does not obscur sidebar graphics
+ return;
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ updatePreferredSize();
+ CodeFoldingSideBar.this.repaint();
+ }
+ });
+ }
+
+ /**
+ * @deprecated Don't use this constructor, it does nothing!
+ */
+ public CodeFoldingSideBar() {
+ component = null;
+ prefs = null;
+ throw new IllegalStateException("Do not use this constructor!"); //NOI18N
+ }
+
+ public CodeFoldingSideBar(JTextComponent component){
+ super();
+ this.component = component;
+
+ if (component.getClientProperty("org.netbeans.editor.CodeFoldingSidebar") == null) {
+ component.putClientProperty("org.netbeans.editor.CodeFoldingSidebar", Boolean.TRUE);
+ } else {
+ alreadyPresent = true;
+ }
+
+ addMouseListener(listener);
+ addMouseMotionListener(listener);
+
+ FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
+ foldHierarchy.addFoldHierarchyListener(WeakListeners.create(FoldHierarchyListener.class, listener, foldHierarchy));
+
+ Document doc = getDocument();
+ doc.addDocumentListener(WeakListeners.document(listener, doc));
+ setOpaque(true);
+
+ prefs = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component)).lookup(Preferences.class);
+ prefs.addPreferenceChangeListener(WeakListeners.create(PreferenceChangeListener.class, prefsListener, prefs));
+ prefsListener.preferenceChange(null);
+
+ ViewHierarchy.get(component).addViewHierarchyListener(new ViewHierarchyListener() {
+
+ @Override
+ public void viewHierarchyChanged(ViewHierarchyEvent evt) {
+ checkRepaint(evt);
+ }
+
+ });
+ }
+
+ private void updatePreferredSize() {
+ if (enabled && !alreadyPresent) {
+ setPreferredSize(new Dimension(getColoring().getFont().getSize(), component.getHeight()));
+ setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
+ }else{
+ setPreferredSize(new Dimension(0,0));
+ setMaximumSize(new Dimension(0,0));
+ }
+ revalidate();
+ }
+
+ private void updateColors() {
+ Coloring c = getColoring();
+ this.backColor = c.getBackColor();
+ this.foreColor = c.getForeColor();
+ this.font = c.getFont();
+ }
+
+ /**
+ * This method should be treated as final. Subclasses are forbidden to override it.
+ * @return The background color used for painting this component.
+ * @deprecated Without any replacement.
+ */
+ protected Color getBackColor() {
+ if (backColor == null) {
+ updateColors();
+ }
+ return backColor;
+ }
+
+ /**
+ * This method should be treated as final. Subclasses are forbidden to override it.
+ * @return The foreground color used for painting this component.
+ * @deprecated Without any replacement.
+ */
+ protected Color getForeColor() {
+ if (foreColor == null) {
+ updateColors();
+ }
+ return foreColor;
+ }
+
+ /**
+ * This method should be treated as final. Subclasses are forbidden to override it.
+ * @return The font used for painting this component.
+ * @deprecated Without any replacement.
+ */
+ protected Font getColoringFont() {
+ if (font == null) {
+ updateColors();
+ }
+ return font;
+ }
+
+ // overriding due to issue #60304
+ public @Override void update(Graphics g) {
+ }
+
+ protected void collectPaintInfos(
+ View rootView, Fold fold, Map map, int level, int startIndex, int endIndex
+ ) throws BadLocationException {
+ //never called
+ }
+
+ /**
+ * Adjust lowest/topmost boundaries from the Fold range y1-y2.
+ * @param y1
+ * @param y2
+ * @param level
+ */
+ private void setMouseBoundaries(int y1, int y2, int level) {
+ if (!hasMousePoint() || mousePointConsumed) {
+ return;
+ }
+ int y = mousePoint;
+ if (y2 < y && lowestAboveMouse < y2) {
+ LOG.log(Level.FINEST, "lowestAbove at {1}: {0}", new Object[] { y2, level });
+ lowestAboveMouse = y2;
+ }
+ if (y1 > y && topmostBelowMouse > y1) {
+ LOG.log(Level.FINEST, "topmostBelow at {1}: {0}", new Object[] { y1, level });
+ topmostBelowMouse = y1;
+ }
+ }
+
+ /*
+ * Even collapsed fold MAY contain a continuation line, IF one of folds on the same line is NOT collapsed. Such a fold should
+ * then visually span multiple lines && be marked as collapsed.
+ */
+
+ protected List extends PaintInfo> getPaintInfo(Rectangle clip) throws BadLocationException {
+ javax.swing.plaf.TextUI textUI = component.getUI();
+ if (!(textUI instanceof BaseTextUI)) {
+ return Collections.emptyList();
+ }
+ BaseTextUI baseTextUI = (BaseTextUI)textUI;
+ BaseDocument bdoc = Utilities.getDocument(component);
+ if (bdoc == null) {
+ return Collections.emptyList();
+ }
+ mousePointConsumed = false;
+ mouseBoundary = null;
+ topmostBelowMouse = Integer.MAX_VALUE;
+ lowestAboveMouse = -1;
+ bdoc.readLock();
+ try {
+ int startPos = baseTextUI.getPosFromY(clip.y);
+ int endPos = baseTextUI.viewToModel(component, Short.MAX_VALUE / 2, clip.y + clip.height);
+
+ if (startPos < 0 || endPos < 0) {
+ // editor window is not properly sized yet; return no infos
+ return Collections.emptyList();
+ }
+
+ // #218282: if the view hierarchy is not yet updated, the Y coordinate may map to an incorrect offset outside
+ // the document.
+ int docLen = bdoc.getLength();
+ if (startPos >= docLen || endPos > docLen) {
+ return Collections.emptyList();
+ }
+
+ startPos = Utilities.getRowStart(bdoc, startPos);
+ endPos = Utilities.getRowEnd(bdoc, endPos);
+
+ FoldHierarchy hierarchy = FoldHierarchy.get(component);
+ hierarchy.lock();
+ try {
+ View rootView = Utilities.getDocumentView(component);
+ if (rootView != null) {
+ Object [] arr = getFoldList(hierarchy.getRootFold(), startPos, endPos);
+ @SuppressWarnings("unchecked")
+ List extends Fold> foldList = (List extends Fold>) arr[0];
+ int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
+
+ /*
+ * Note:
+ *
+ * The Map is keyed by Y-VISUAL position of the fold mark, not the textual offset of line start.
+ * This is because several folds may occupy the same line, while only one + sign is displayed,
+ * and affect the last fold in the row.
+ */
+ NavigableMap map = new TreeMap();
+ // search backwards
+ for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
+ Fold fold = foldList.get(i);
+ if (!traverseBackwards(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
+ break;
+ }
+ }
+
+ // search forward
+ for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
+ Fold fold = foldList.get(i);
+ if (!traverseForward(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
+ break;
+ }
+ }
+
+ if (map.isEmpty() && foldList.size() > 0) {
+ assert foldList.size() == 1;
+ PaintInfo pi = new PaintInfo(PAINT_LINE, 0, clip.y, clip.height, -1, -1);
+ mouseBoundary = new Rectangle(0, 0, 0, clip.height);
+ LOG.log(Level.FINEST, "Mouse boundary for full side line set to: {0}", mouseBoundary);
+ if (hasMousePoint()) {
+ pi.markActive(true, true, true);
+ }
+ return Collections.singletonList(pi);
+ } else {
+ if (mouseBoundary == null) {
+ mouseBoundary = makeMouseBoundary(clip.y, clip.y + clip.height);
+ LOG.log(Level.FINEST, "Mouse boundary not set, defaulting to: {0}", mouseBoundary);
+ }
+ return new ArrayList(map.values());
+ }
+ } else {
+ return Collections.emptyList();
+ }
+ } finally {
+ hierarchy.unlock();
+ }
+ } finally {
+ bdoc.readUnlock();
+ }
+ }
+
+ /**
+ * Adds a paint info to the map. If a paintinfo already exists, it merges
+ * the structures, so the painting process can just follow the instructions.
+ *
+ * @param infos
+ * @param yOffset
+ * @param nextInfo
+ */
+ private void addPaintInfo(Map infos, int yOffset, PaintInfo nextInfo) {
+ PaintInfo prevInfo = infos.get(yOffset);
+ nextInfo.mergeWith(prevInfo);
+ infos.put(yOffset, nextInfo);
+ }
+
+ private boolean traverseForward(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary,int level, NavigableMap infos) throws BadLocationException {
+// System.out.println("~~~ traverseForward<" + lowerBoundary + ", " + upperBoundary
+// + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
+// + (f.getStartOffset() > upperBoundary ? ", f.gSO > uB" : "")
+// + ", level=" + level);
+
+ if (f.getStartOffset() > upperBoundary) {
+ return false;
+ }
+
+ int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
+ int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
+ int y1 = btui.getYFromPos(lineStartOffset1);
+ int h = btui.getEditorUI().getLineHeight();
+ int y2 = btui.getYFromPos(lineStartOffset2);
+
+ // the 'active' flags can be set only after children are processed; highlights
+ // correspond to the innermost expanded child.
+ boolean activeMark = false;
+ boolean activeIn = false;
+ boolean activeOut = false;
+ PaintInfo spi;
+ boolean activated;
+
+ if (y1 == y2) {
+ // whole fold is on a single line
+ spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
+ if (activated = isActivated(y1, y1 + h)) {
+ activeMark = true;
+ }
+ addPaintInfo(infos, y1, spi);
+ } else {
+ // fold spans multiple lines
+ spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
+ if (activated = isActivated(y1, y2 + h / 2)) {
+ activeMark = true;
+ activeOut = true;
+ }
+ addPaintInfo(infos, y1, spi);
+ }
+
+ setMouseBoundaries(y1, y2 + h / 2, level);
+
+ // Handle end mark after possible inner folds were processed because
+ // otherwise if there would be two nested folds both ending at the same line
+ // then the end mark for outer one would be replaced by an end mark for inner one
+ // (same key in infos map) and the painting code would continue to paint line marking a fold
+ // until next fold is reached (or end of doc).
+ PaintInfo epi = null;
+ if (y1 != y2 && !f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
+ epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
+ addPaintInfo(infos, y2, epi);
+ }
+
+ // save the topmost/lowest information, reset for child processing
+ int topmost = topmostBelowMouse;
+ int lowest = lowestAboveMouse;
+ topmostBelowMouse = y2 + h / 2;
+ lowestAboveMouse = y1;
+
+ try {
+ if (!f.isCollapsed()) {
+ Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
+ @SuppressWarnings("unchecked")
+ List extends Fold> foldList = (List extends Fold>) arr[0];
+ int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
+
+ // search backwards
+ for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
+ Fold fold = foldList.get(i);
+ if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
+ break;
+ }
+ }
+
+ // search forward
+ for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
+ Fold fold = foldList.get(i);
+ if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
+ return false;
+ }
+ }
+ }
+ if (!mousePointConsumed && activated) {
+ mousePointConsumed = true;
+ mouseBoundary = makeMouseBoundary(y1, y2 + h);
+ LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
+ spi.markActive(activeMark, activeIn, activeOut);
+ if (epi != null) {
+ epi.markActive(true, true, false);
+ }
+ markDeepChildrenActive(infos, y1, y2, level);
+ }
+ } finally {
+ topmostBelowMouse = topmost;
+ lowestAboveMouse = lowest;
+ }
+ return true;
+ }
+
+ /**
+ * Sets outlines of all children to 'active'. Assuming yFrom and yTo are from-to Y-coordinates of the parent
+ * fold, it finds all nested folds (folds, which are in between yFrom and yTo) and changes their in/out lines
+ * as active.
+ * The method returns Y start coordinate of the 1st child found.
+ *
+ * @param infos fold infos collected so far
+ * @param yFrom upper Y-coordinate of the parent fold
+ * @param yTo lower Y-coordinate of the parent fold
+ * @param level level of the parent fold
+ * @return Y-coordinate of the 1st child.
+ */
+ private int markDeepChildrenActive(NavigableMap infos, int yFrom, int yTo, int level) {
+ int result = Integer.MAX_VALUE;
+ Map m = infos.subMap(yFrom, yTo);
+ for (Map.Entry me : m.entrySet()) {
+ PaintInfo pi = me.getValue();
+ int y = pi.getPaintY();
+ if (y > yFrom && y < yTo) {
+ if (LOG.isLoggable(Level.FINEST)) {
+ LOG.log(Level.FINEST, "Marking chind as active: {0}", pi);
+ }
+ pi.markActive(false, true, true);
+ if (y < result) {
+ y = result;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns stroke appropriate for painting (in)active outlines
+ * @param s the default stroke
+ * @param active true for active outlines
+ * @return value of 's' or a Stroke which should be used to paint the outline.
+ */
+ private static Stroke getStroke(Stroke s, boolean active) {
+ if (active) {
+ return LINE_BOLD;
+ } else {
+ return s;
+ }
+ }
+
+ private boolean traverseBackwards(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary, int level, NavigableMap infos) throws BadLocationException {
+// System.out.println("~~~ traverseBackwards<" + lowerBoundary + ", " + upperBoundary
+// + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
+// + (f.getEndOffset() < lowerBoundary ? ", f.gEO < lB" : "")
+// + ", level=" + level);
+
+ if (f.getEndOffset() < lowerBoundary) {
+ return false;
+ }
+
+ int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
+ int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
+ int h = btui.getEditorUI().getLineHeight();
+
+ boolean activeMark = false;
+ boolean activeIn = false;
+ boolean activeOut = false;
+ PaintInfo spi = null;
+ PaintInfo epi = null;
+ boolean activated = false;
+ int y1 = 0;
+ int y2 = 0;
+
+ if (lineStartOffset1 == lineStartOffset2) {
+ // whole fold is on a single line
+ y2 = y1 = btui.getYFromPos(lineStartOffset1);
+ spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset1);
+ if (activated = isActivated(y1, y1 + h)) {
+ activeMark = true;
+ }
+ addPaintInfo(infos, y1, spi);
+ } else {
+ y2 = btui.getYFromPos(lineStartOffset2);
+ // fold spans multiple lines
+ y1 = btui.getYFromPos(lineStartOffset1);
+ activated = isActivated(y1, y2 + h / 2);
+ if (f.getStartOffset() >= upperBoundary) {
+ spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
+ if (activated) {
+ activeMark = true;
+ activeOut = true;
+ }
+ addPaintInfo(infos, y1, spi);
+ }
+
+ if (!f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
+ activated |= isActivated(y1, y2 + h / 2);
+ epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
+ addPaintInfo(infos, y2, epi);
+ }
+ }
+
+ setMouseBoundaries(y1, y2 + h / 2, level);
+
+ // save the topmost/lowest information, reset for child processing
+ int topmost = topmostBelowMouse;
+ int lowest = lowestAboveMouse;
+ topmostBelowMouse = y2 + h /2;
+ lowestAboveMouse = y1;
+
+ try {
+ if (!f.isCollapsed()) {
+ Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
+ @SuppressWarnings("unchecked")
+ List extends Fold> foldList = (List extends Fold>) arr[0];
+ int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
+
+ // search backwards
+ for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
+ Fold fold = foldList.get(i);
+ if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
+ return false;
+ }
+ }
+
+ // search forward
+ for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
+ Fold fold = foldList.get(i);
+ if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
+ break;
+ }
+ }
+ }
+ if (!mousePointConsumed && activated) {
+ mousePointConsumed = true;
+ mouseBoundary = makeMouseBoundary(y1, y2 + h);
+ LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
+ if (spi != null) {
+ spi.markActive(activeMark, activeIn, activeOut);
+ }
+ if (epi != null) {
+ epi.markActive(true, true, false);
+ }
+ int lowestChild = markDeepChildrenActive(infos, y1, y2, level);
+ if (lowestChild < Integer.MAX_VALUE && lineStartOffset1 < upperBoundary) {
+ // the fold starts above the screen clip region, and is 'activated'. We need to setup instructions to draw activated line up to the
+ // 1st child marker.
+ epi = new PaintInfo(PAINT_LINE, level, y1, y2 - y1, false, lineStartOffset1, lineStartOffset2);
+ epi.markActive(true, true, false);
+ addPaintInfo(infos, y1, epi);
+ }
+ }
+ } finally {
+ topmostBelowMouse = topmost;
+ lowestAboveMouse = lowest;
+ }
+ return true;
+ }
+
+ private Rectangle makeMouseBoundary(int y1, int y2) {
+ if (!hasMousePoint()) {
+ return null;
+ }
+ if (topmostBelowMouse < Integer.MAX_VALUE) {
+ y2 = topmostBelowMouse;
+ }
+ if (lowestAboveMouse > -1) {
+ y1 = lowestAboveMouse;
+ }
+ return new Rectangle(0, y1, 0, y2 - y1);
+ }
+
+ protected EditorUI getEditorUI(){
+ return Utilities.getEditorUI(component);
+ }
+
+ protected Document getDocument(){
+ return component.getDocument();
+ }
+
+
+ private Fold getLastLineFold(FoldHierarchy hierarchy, int rowStart, int rowEnd, boolean shift){
+ Fold fold = FoldUtilities.findNearestFold(hierarchy, rowStart);
+ Fold prevFold = fold;
+ while (fold != null && fold.getStartOffset()= startY && mouseY <= nextY) {
+ LOG.log(Level.FINEST, "Starting line clicked, ignoring. MouseY={0}, startY={1}, nextY={2}",
+ new Object[] { mouseY, startY, nextY });
+ return;
+ }
+
+ startY = textUI.getYFromPos(endOffset);
+ nextLineOffset = Utilities.getRowStart(bdoc, endOffset, 1);
+ nextY = textUI.getYFromPos(nextLineOffset);
+
+ if (mouseY >= startY && mouseY <= nextY) {
+ // the mouse can be positioned above the marker (the fold found above), or
+ // below it; in that case, the immediate enclosing fold should be used - should be the fold
+ // that corresponds to the nextLineOffset, if any
+ int h2 = (startY + nextY) / 2;
+ if (mouseY >= h2) {
+ Fold f2 = f;
+
+ f = FoldUtilities.findOffsetFold(hierarchy, nextLineOffset);
+ if (f == null) {
+ // fold does not exist for the position below end-of-fold indicator
+ return;
+ }
+ }
+
+ }
+
+ LOG.log(Level.FINEST, "Collapsing fold: {0}", f);
+ hierarchy.collapse(f);
+ } finally {
+ hierarchy.unlock();
+ }
+ } finally {
+ bdoc.readUnlock();
+ }
+ }
+
+ private void performAction(final Mark mark, final boolean shiftFold) {
+ Document doc = component.getDocument();
+ doc.render(new Runnable() {
+ @Override
+ public void run() {
+ ViewHierarchy vh = ViewHierarchy.get(component);
+ LockedViewHierarchy lockedVH = vh.lock();
+ try {
+ int pViewIndex = lockedVH.yToParagraphViewIndex(mark.y + mark.size / 2);
+ if (pViewIndex >= 0) {
+ ParagraphViewDescriptor pViewDesc = lockedVH.getParagraphViewDescriptor(pViewIndex);
+ int pViewStartOffset = pViewDesc.getStartOffset();
+ int pViewEndOffset = pViewStartOffset + pViewDesc.getLength();
+ // Find corresponding fold
+ FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
+ foldHierarchy.lock();
+ try {
+ int rowStart = javax.swing.text.Utilities.getRowStart(component, pViewStartOffset);
+ int rowEnd = javax.swing.text.Utilities.getRowEnd(component, pViewStartOffset);
+ Fold clickedFold = getLastLineFold(foldHierarchy, rowStart, rowEnd, shiftFold);//FoldUtilities.findNearestFold(foldHierarchy, viewStartOffset);
+ if (clickedFold != null && clickedFold.getStartOffset() < pViewEndOffset) {
+ foldHierarchy.toggle(clickedFold);
+ }
+ } catch (BadLocationException ble) {
+ LOG.log(Level.WARNING, null, ble);
+ } finally {
+ foldHierarchy.unlock();
+ }
+ }
+ } finally {
+ lockedVH.unlock();
+ }
+ }
+ });
+ }
+
+ protected int getMarkSize(Graphics g){
+ if (g != null){
+ FontMetrics fm = g.getFontMetrics(getColoring().getFont());
+ if (fm != null){
+ int ret = fm.getAscent() - fm.getDescent();
+ return ret - ret%2;
+ }
+ }
+ return -1;
+ }
+
+ private boolean hasMousePoint() {
+ return mousePoint >= 0;
+ }
+
+ private boolean isActivated(int y1, int y2) {
+ return hasMousePoint() &&
+ (mousePoint >= y1 && mousePoint < y2);
+ }
+
+ private void drawFoldLine(Graphics2D g2d, boolean active, int x1, int y1, int x2, int y2) {
+ Stroke origStroke = g2d.getStroke();
+ g2d.setStroke(getStroke(origStroke, active));
+ g2d.drawLine(x1, y1, x2, y2);
+ g2d.setStroke(origStroke);
+ }
+
+ protected @Override void paintComponent(Graphics g) {
+ if (!enabled) {
+ return;
+ }
+
+ Rectangle clip = getVisibleRect();//g.getClipBounds();
+ visibleMarks.clear();
+
+ Coloring coloring = getColoring();
+ g.setColor(coloring.getBackColor());
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+ g.setColor(coloring.getForeColor());
+
+ AbstractDocument adoc = (AbstractDocument)component.getDocument();
+ adoc.readLock();
+ try {
+ List extends PaintInfo> ps = getPaintInfo(clip);
+ Font defFont = coloring.getFont();
+ int markSize = getMarkSize(g);
+ int halfMarkSize = markSize / 2;
+ int markX = (defFont.getSize() - markSize) / 2; // x position of mark rectangle
+ int plusGap = (int)Math.round(markSize / 3.8); // distance between mark rectangle vertical side and start/end of minus sign
+ int lineX = markX + halfMarkSize; // x position of the centre of mark
+
+ LOG.fine("CFSBar: PAINT START ------\n");
+ int descent = g.getFontMetrics(defFont).getDescent();
+ PaintInfo previousInfo = null;
+ Graphics2D g2d = (Graphics2D)g;
+ LOG.log(Level.FINEST, "MousePoint: {0}", mousePoint);
+
+ for(PaintInfo paintInfo : ps) {
+ boolean isFolded = paintInfo.isCollapsed();
+ int y = paintInfo.getPaintY();
+ int height = paintInfo.getPaintHeight();
+ int markY = y + descent; // y position of mark rectangle
+ int paintOperation = paintInfo.getPaintOperation();
+
+ if (previousInfo == null) {
+ if (paintInfo.hasLineIn()) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("prevInfo=NULL; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
+ }
+ drawFoldLine(g2d, paintInfo.lineInActive, lineX, clip.y, lineX, y);
+ }
+ } else {
+ if (previousInfo.hasLineOut() || paintInfo.hasLineIn()) {
+ // Draw middle vertical line
+ int prevY = previousInfo.getPaintY();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.log(Level.FINE, "prevInfo={0}; y=" + y + ", PI:" + paintInfo + "\n", previousInfo); // NOI18N
+ }
+ drawFoldLine(g2d, previousInfo.lineOutActive || paintInfo.lineInActive, lineX, prevY + previousInfo.getPaintHeight(), lineX, y);
+ }
+ }
+
+ if (paintInfo.hasSign()) {
+ g.drawRect(markX, markY, markSize, markSize);
+ g.drawLine(plusGap + markX, markY + halfMarkSize, markSize + markX - plusGap, markY + halfMarkSize);
+ String opStr = (paintOperation == PAINT_MARK) ? "PAINT_MARK" : "SINGLE_PAINT_MARK"; // NOI18N
+ if (isFolded) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine(opStr + ": folded; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
+ }
+ g.drawLine(lineX, markY + plusGap, lineX, markY + markSize - plusGap);
+ }
+ if (paintOperation != SINGLE_PAINT_MARK) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine(opStr + ": non-single; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
+ }
+ }
+ if (paintInfo.hasLineIn()) { //[PENDING]
+ drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, markY);
+ }
+ if (paintInfo.hasLineOut()) {
+ // This is an error in case there's a next paint info at the same y which is an end mark
+ // for this mark (it must be cleared explicitly).
+ drawFoldLine(g2d, paintInfo.lineOutActive, lineX, markY + markSize, lineX, y + height);
+ }
+ visibleMarks.add(new Mark(markX, markY, markSize, isFolded));
+
+ } else if (paintOperation == PAINT_LINE) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("PAINT_LINE: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
+ }
+ // FIXME !!
+ drawFoldLine(g2d, paintInfo.signActive, lineX, y, lineX, y + height );
+ } else if (paintOperation == PAINT_END_MARK) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("PAINT_END_MARK: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
+ }
+ if (previousInfo == null || y != previousInfo.getPaintY()) {
+ drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, y + height / 2);
+ drawFoldLine(g2d, paintInfo.signActive, lineX, y + height / 2, lineX + halfMarkSize, y + height / 2);
+ if (paintInfo.getInnerLevel() > 0) {//[PENDING]
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine(" PAINT middle-line\n"); // NOI18N
+ }
+ drawFoldLine(g2d, paintInfo.lineOutActive, lineX, y + height / 2, lineX, y + height);
+ }
+ }
+ }
+
+ previousInfo = paintInfo;
+ }
+
+ if (previousInfo != null &&
+ (previousInfo.getInnerLevel() > 0 ||
+ (previousInfo.getPaintOperation() == PAINT_MARK && !previousInfo.isCollapsed()))
+ ) {
+ drawFoldLine(g2d, previousInfo.lineOutActive,
+ lineX, previousInfo.getPaintY() + previousInfo.getPaintHeight(), lineX, clip.y + clip.height);
+ }
+
+ } catch (BadLocationException ble) {
+ LOG.log(Level.WARNING, null, ble);
+ } finally {
+ LOG.fine("CFSBar: PAINT END ------\n\n");
+ adoc.readUnlock();
+ }
+ }
+
+ private static Object [] getFoldList(Fold parentFold, int start, int end) {
+ List ret = new ArrayList();
+
+ int index = FoldUtilities.findFoldEndIndex(parentFold, start);
+ int foldCount = parentFold.getFoldCount();
+ int idxOfFirstFoldStartingInside = -1;
+ while (index < foldCount) {
+ Fold f = parentFold.getFold(index);
+ if (f.getStartOffset() <= end) {
+ ret.add(f);
+ } else {
+ break; // no more relevant folds
+ }
+ if (idxOfFirstFoldStartingInside == -1 && f.getStartOffset() >= start) {
+ idxOfFirstFoldStartingInside = ret.size() - 1;
+ }
+ index++;
+ }
+
+ return new Object [] { ret, idxOfFirstFoldStartingInside != -1 ? idxOfFirstFoldStartingInside : ret.size() };
+ }
+
+ /**
+ * This class should be never used by other code; will be made private
+ */
+ public class PaintInfo {
+
+ int paintOperation;
+ /**
+ * level of the 1st marker on the line
+ */
+ int innerLevel;
+
+ /**
+ * Y-coordinate of the cell
+ */
+ int paintY;
+
+ /**
+ * Height of the paint cell
+ */
+ int paintHeight;
+
+ /**
+ * State of the marker (+/-)
+ */
+ boolean isCollapsed;
+
+ /**
+ * all markers on the line are collapsed
+ */
+ boolean allCollapsed;
+ int startOffset;
+ int endOffset;
+ /**
+ * nesting level of the last marker on the line
+ */
+ int outgoingLevel;
+
+ /**
+ * Force incoming line (from above) to be present
+ */
+ boolean lineIn;
+
+ /**
+ * Force outgoing line (down from marker) to be present
+ */
+ boolean lineOut;
+
+ /**
+ * The 'incoming' (upper) line should be painted as active
+ */
+ boolean lineInActive;
+
+ /**
+ * The 'outgoing' (down) line should be painted as active
+ */
+ boolean lineOutActive;
+
+ /**
+ * The sign/marker itself should be painted as active
+ */
+ boolean signActive;
+
+ public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, boolean isCollapsed, int startOffset, int endOffset){
+ this.paintOperation = paintOperation;
+ this.innerLevel = this.outgoingLevel = innerLevel;
+ this.paintY = paintY;
+ this.paintHeight = paintHeight;
+ this.isCollapsed = this.allCollapsed = isCollapsed;
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+
+ switch (paintOperation) {
+ case PAINT_MARK:
+ lineIn = false;
+ lineOut = true;
+ outgoingLevel++;
+ break;
+ case SINGLE_PAINT_MARK:
+ lineIn = false;
+ lineOut = false;
+ break;
+ case PAINT_END_MARK:
+ lineIn = true;
+ lineOut = false;
+ isCollapsed = true;
+ allCollapsed = true;
+ break;
+ case PAINT_LINE:
+ lineIn = lineOut = true;
+ break;
+ }
+ }
+
+ /**
+ * Sets active flags on inidivual parts of the mark
+ * @param mark
+ * @param lineIn
+ * @param lineOut S
+ */
+ void markActive(boolean mark, boolean lineIn, boolean lineOut) {
+ this.signActive |= mark;
+ this.lineInActive |= lineIn;
+ this.lineOutActive |= lineOut;
+ }
+
+ boolean hasLineIn() {
+ return lineIn || innerLevel > 0;
+ }
+
+ boolean hasLineOut() {
+ return lineOut || outgoingLevel > 0 || (paintOperation != SINGLE_PAINT_MARK && !isAllCollapsed());
+ }
+
+ public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, int startOffset, int endOffset){
+ this(paintOperation, innerLevel, paintY, paintHeight, false, startOffset, endOffset);
+ }
+
+ public int getPaintOperation(){
+ return paintOperation;
+ }
+
+ public int getInnerLevel(){
+ return innerLevel;
+ }
+
+ public int getPaintY(){
+ return paintY;
+ }
+
+ public int getPaintHeight(){
+ return paintHeight;
+ }
+
+ public boolean isCollapsed(){
+ return isCollapsed;
+ }
+
+ boolean isAllCollapsed() {
+ return allCollapsed;
+ }
+
+ public void setPaintOperation(int paintOperation){
+ this.paintOperation = paintOperation;
+ }
+
+ public void setInnerLevel(int innerLevel){
+ this.innerLevel = innerLevel;
+ }
+
+ public @Override String toString(){
+ StringBuffer sb = new StringBuffer("");
+ if (paintOperation == PAINT_MARK){
+ sb.append("PAINT_MARK"); // NOI18N
+ }else if (paintOperation == PAINT_LINE){
+ sb.append("PAINT_LINE"); // NOI18N
+ }else if (paintOperation == PAINT_END_MARK) {
+ sb.append("PAINT_END_MARK"); // NOI18N
+ }else if (paintOperation == SINGLE_PAINT_MARK) {
+ sb.append("SINGLE_PAINT_MARK");
+ }
+ sb.append(",L:").append(innerLevel).append("/").append(outgoingLevel); // NOI18N
+ sb.append(',').append(isCollapsed ? "C" : "E"); // NOI18N
+ sb.append(", start=").append(startOffset).append(", end=").append(endOffset);
+ sb.append(", lineIn=").append(lineIn).append(", lineOut=").append(lineOut);
+ return sb.toString();
+ }
+
+ boolean hasSign() {
+ return paintOperation == PAINT_MARK || paintOperation == SINGLE_PAINT_MARK;
+ }
+
+
+ void mergeWith(PaintInfo prevInfo) {
+ if (prevInfo == null) {
+ return;
+ }
+
+ int operation = this.paintOperation;
+ boolean lineIn = prevInfo.lineIn;
+ boolean lineOut = prevInfo.lineOut;
+
+ LOG.log(Level.FINE, "Merging {0} with {1}: ", new Object[] { this, prevInfo });
+ if (prevInfo.getPaintOperation() == PAINT_END_MARK) {
+ // merge with start|single -> start mark + line-in
+ lineIn = true;
+ } else {
+ operation = PAINT_MARK;
+ }
+
+ int level1 = Math.min(prevInfo.innerLevel, innerLevel);
+ int level2 = prevInfo.outgoingLevel;
+
+ if (getPaintOperation() == PAINT_END_MARK
+ && innerLevel == prevInfo.outgoingLevel) {
+ // if merging end marker at the last level, update to the new outgoing level
+ level2 = outgoingLevel;
+ } else if (!isCollapsed) {
+ level2 = Math.max(prevInfo.outgoingLevel, outgoingLevel);
+ }
+
+ if (prevInfo.getInnerLevel() < getInnerLevel()) {
+ int paintFrom = Math.min(prevInfo.paintY, paintY);
+ int paintTo = Math.max(prevInfo.paintY + prevInfo.paintHeight, paintY + paintHeight);
+ // at least one collapsed -> paint plus sign
+ boolean collapsed = prevInfo.isCollapsed() || isCollapsed();
+ int offsetFrom = Math.min(prevInfo.startOffset, startOffset);
+ int offsetTo = Math.max(prevInfo.endOffset, endOffset);
+
+ this.paintY = paintFrom;
+ this.paintHeight = paintTo - paintFrom;
+ this.isCollapsed = collapsed;
+ this.startOffset = offsetFrom;
+ this.endOffset = offsetTo;
+ }
+ this.paintOperation = operation;
+ this.allCollapsed = prevInfo.allCollapsed && allCollapsed;
+ this.innerLevel = level1;
+ this.outgoingLevel = level2;
+ this.lineIn |= lineIn;
+ this.lineOut |= lineOut;
+
+ this.signActive |= prevInfo.signActive;
+ this.lineInActive |= prevInfo.lineInActive;
+ this.lineOutActive |= prevInfo.lineOutActive;
+
+ LOG.log(Level.FINE, "Merged result: {0}", this);
+ }
+ }
+
+ /** Keeps info of visible folding mark */
+ public class Mark{
+ public int x;
+ public int y;
+ public int size;
+ public boolean isFolded;
+
+ public Mark(int x, int y, int size, boolean isFolded){
+ this.x = x;
+ this.y = y;
+ this.size = size;
+ this.isFolded = isFolded;
+ }
+ }
+
+ private final class Listener extends MouseAdapter implements FoldHierarchyListener, DocumentListener, Runnable {
+
+ public Listener(){
+ }
+
+ // --------------------------------------------------------------------
+ // FoldHierarchyListener implementation
+ // --------------------------------------------------------------------
+
+ public void foldHierarchyChanged(FoldHierarchyEvent evt) {
+ refresh();
+ }
+
+ // --------------------------------------------------------------------
+ // DocumentListener implementation
+ // --------------------------------------------------------------------
+
+ public void insertUpdate(DocumentEvent evt) {
+ if (!(evt instanceof BaseDocumentEvent)) return;
+
+ BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
+ if (bevt.getLFCount() > 0) { // one or more lines inserted
+ refresh();
+ }
+ }
+
+ public void removeUpdate(DocumentEvent evt) {
+ if (!(evt instanceof BaseDocumentEvent)) return;
+
+ BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
+ if (bevt.getLFCount() > 0) { // one or more lines removed
+ refresh();
+ }
+ }
+
+ public void changedUpdate(DocumentEvent evt) {
+ }
+
+ // --------------------------------------------------------------------
+ // MouseListener implementation
+ // --------------------------------------------------------------------
+
+ @Override
+ public void mousePressed (MouseEvent e) {
+ Mark mark = getClickedMark(e);
+ if (mark!=null){
+ e.consume();
+ performAction(mark, (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) > 0);
+ }
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ // #102288 - missing event consuming caused quick doubleclicks to break
+ // fold expanding/collapsing and move caret to the particular line
+ if (e.getClickCount() > 1) {
+ LOG.log(Level.FINEST, "Mouse {0}click at {1}", new Object[] { e.getClickCount(), e.getY()});
+ Mark mark = getClickedMark(e);
+ try {
+ performActionAt(mark, e.getY());
+ } catch (BadLocationException ex) {
+ LOG.log(Level.WARNING, "Error during fold expansion using sideline", ex);
+ }
+ } else {
+ e.consume();
+ }
+ }
+
+ private void refreshIfMouseOutside(Point pt) {
+ mousePoint = (int)pt.getY();
+ if (LOG.isLoggable(Level.FINEST)) {
+ if (mouseBoundary == null) {
+ LOG.log(Level.FINEST, "Mouse boundary not set, refreshing: {0}", mousePoint);
+ } else {
+ LOG.log(Level.FINEST, "Mouse {0} inside known mouse boundary: {1}-{2}",
+ new Object[] { mousePoint, mouseBoundary.y, mouseBoundary.getMaxY() });
+ }
+ }
+ if (mouseBoundary == null || mousePoint < mouseBoundary.y || mousePoint > mouseBoundary.getMaxY()) {
+ refresh();
+ }
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ refreshIfMouseOutside(e.getPoint());
+ }
+
+ public void mouseEntered(MouseEvent e) {
+ refreshIfMouseOutside(e.getPoint());
+ }
+
+ public void mouseExited(MouseEvent e) {
+ mousePoint = NO_MOUSE_POINT;
+ refresh();
+ }
+
+
+ // --------------------------------------------------------------------
+ // private implementation
+ // --------------------------------------------------------------------
+
+ private Mark getClickedMark(MouseEvent e){
+ if (e == null || !SwingUtilities.isLeftMouseButton(e)) {
+ return null;
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+ for (Mark mark : visibleMarks) {
+ if (x >= mark.x && x <= (mark.x + mark.size) && y >= mark.y && y <= (mark.y + mark.size)) {
+ return mark;
+ }
+ }
+ return null;
+ }
+
+ private void refresh() {
+ SwingUtilities.invokeLater(this);
+ }
+
+ @Override
+ public void run() {
+ repaint();
+ }
+ } // End of Listener class
+
+ @Override
+ public AccessibleContext getAccessibleContext() {
+ if (accessibleContext == null) {
+ accessibleContext = new AccessibleJComponent() {
+ public @Override AccessibleRole getAccessibleRole() {
+ return AccessibleRole.PANEL;
+ }
+ };
+ accessibleContext.setAccessibleName(NbBundle.getMessage(CodeFoldingSideBar.class, "ACSN_CodeFoldingSideBar")); //NOI18N
+ accessibleContext.setAccessibleDescription(NbBundle.getMessage(CodeFoldingSideBar.class, "ACSD_CodeFoldingSideBar")); //NOI18N
+ }
+ return accessibleContext;
+ }
+
+ private Coloring getColoring() {
+ if (attribs == null) {
+ if (fcsLookupResult == null) {
+ fcsLookupResult = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component))
+ .lookupResult(FontColorSettings.class);
+ fcsLookupResult.addLookupListener(WeakListeners.create(LookupListener.class, fcsTracker, fcsLookupResult));
+ }
+
+ FontColorSettings fcs = fcsLookupResult.allInstances().iterator().next();
+ AttributeSet attr = fcs.getFontColors(FontColorNames.CODE_FOLDING_BAR_COLORING);
+ if (attr == null) {
+ attr = fcs.getFontColors(FontColorNames.DEFAULT_COLORING);
+ } else {
+ attr = AttributesUtilities.createComposite(attr, fcs.getFontColors(FontColorNames.DEFAULT_COLORING));
+ }
+ attribs = attr;
+ }
+ return Coloring.fromAttributeSet(attribs);
+ }
+
+}
diff --git a/editor.fold/src/org/netbeans/editor/CustomFoldManager.java b/editor.fold/src/org/netbeans/editor/CustomFoldManager.java
new file mode 100644
--- /dev/null
+++ b/editor.fold/src/org/netbeans/editor/CustomFoldManager.java
@@ -0,0 +1,769 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
+ * Microsystems, Inc. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+
+package org.netbeans.editor;
+
+import org.netbeans.modules.editor.fold.*;
+import javax.swing.text.Document;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Position;
+import javax.swing.event.DocumentEvent;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import org.netbeans.api.editor.fold.Fold;
+import org.netbeans.api.editor.fold.FoldHierarchy;
+import org.netbeans.api.editor.fold.FoldType;
+import org.netbeans.api.lexer.Token;
+import org.netbeans.api.lexer.TokenHierarchy;
+import org.netbeans.api.lexer.TokenSequence;
+import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
+import org.netbeans.spi.editor.fold.FoldManager;
+import org.netbeans.spi.editor.fold.FoldManagerFactory;
+import org.netbeans.spi.editor.fold.FoldOperation;
+import org.openide.util.Parameters;
+import org.openide.util.RequestProcessor;
+
+/**
+ * Fold maintainer that creates and updates custom folds.
+ *
+ * @author Dusan Balek, Miloslav Metelka
+ * @version 1.00
+ * @deprecated Please use {@link org.netbeans.api.editor.fold.FoldingSupport#userFoldManager} to create an instance of
+ * the fold manager, or {@link org.netbeans.api.editor.fold.FoldingSupport#userFoldManagerFactory} to register factory
+ * instance in a layer.
+ */
+@Deprecated
+public final class CustomFoldManager implements FoldManager, Runnable {
+
+ private static final Logger LOG = Logger.getLogger(CustomFoldManager.class.getName());
+
+ public static final FoldType CUSTOM_FOLD_TYPE = new FoldType("custom-fold"); // NOI18N
+
+ private FoldOperation operation;
+ private Document doc;
+ private org.netbeans.editor.GapObjectArray markArray = new org.netbeans.editor.GapObjectArray();
+ private int minUpdateMarkOffset = Integer.MAX_VALUE;
+ private int maxUpdateMarkOffset = -1;
+ private List removedFoldList;
+ private HashMap customFoldId = new HashMap();
+
+ private static final RequestProcessor RP = new RequestProcessor(CustomFoldManager.class.getName(),
+ 1, false, false);
+ private final RequestProcessor.Task task = RP.create(this);
+
+ private final String tokenId;
+
+ public CustomFoldManager() {
+ this.tokenId = "comment";
+ }
+
+ public void init(FoldOperation operation) {
+ this.operation = operation;
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.log(Level.FINE, "Initialized: {0}", System.identityHashCode(this));
+ }
+ }
+
+ private FoldOperation getOperation() {
+ return operation;
+ }
+
+ public void initFolds(FoldHierarchyTransaction transaction) {
+ doc = getOperation().getHierarchy().getComponent().getDocument();
+ task.schedule(300);
+ }
+
+ public void insertUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
+ processRemovedFolds(transaction);
+ task.schedule(300);
+ }
+
+ public void removeUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
+ processRemovedFolds(transaction);
+ removeAffectedMarks(evt, transaction);
+ task.schedule(300);
+ }
+
+ public void changedUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
+ }
+
+ public void removeEmptyNotify(Fold emptyFold) {
+ removeFoldNotify(emptyFold);
+ }
+
+ public void removeDamagedNotify(Fold damagedFold) {
+ removeFoldNotify(damagedFold);
+ }
+
+ public void expandNotify(Fold expandedFold) {
+
+ }
+
+ public void release() {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.log(Level.FINE, "Released: {0}", System.identityHashCode(this));
+ }
+ }
+
+ public void run() {
+ if (operation.isReleased()) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.log(Level.FINE, "Update skipped, already relaesed: {0}", System.identityHashCode(this));
+ }
+ return;
+ }
+ ((BaseDocument) doc).readLock();
+ try {
+ TokenHierarchy th = TokenHierarchy.get(doc);
+ if (th != null && th.isActive()) {
+ FoldHierarchy hierarchy = getOperation().getHierarchy();
+ hierarchy.lock();
+ try {
+ if (operation.isReleased()) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.log(Level.FINE, "Update skipped, already relaesed: {0}", System.identityHashCode(this));
+ }
+ return;
+ }
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.log(Level.FINE, "Updating: {0}", System.identityHashCode(this));
+ }
+ FoldHierarchyTransaction transaction = getOperation().openTransaction();
+ try {
+ updateFolds(th.tokenSequence(), transaction);
+ } finally {
+ transaction.commit();
+ }
+ } finally {
+ hierarchy.unlock();
+ }
+ }
+ } finally {
+ ((BaseDocument) doc).readUnlock();
+ }
+ }
+
+ private void removeFoldNotify(Fold removedFold) {
+ if (removedFoldList == null) {
+ removedFoldList = new ArrayList(3);
+ }
+ removedFoldList.add(removedFold);
+ }
+
+ private void removeAffectedMarks(DocumentEvent evt, FoldHierarchyTransaction transaction) {
+ int removeOffset = evt.getOffset();
+ int markIndex = findMarkIndex(removeOffset);
+ if (markIndex < getMarkCount()) {
+ FoldMarkInfo mark;
+ while (markIndex >= 0 && (mark = getMark(markIndex)).getOffset() == removeOffset) {
+ mark.release(false, transaction);
+ removeMark(markIndex);
+ markIndex--;
+ }
+ }
+ }
+
+ private void processRemovedFolds(FoldHierarchyTransaction transaction) {
+ if (removedFoldList != null) {
+ for (int i = removedFoldList.size() - 1; i >= 0; i--) {
+ Fold removedFold = (Fold)removedFoldList.get(i);
+ FoldMarkInfo startMark = (FoldMarkInfo)getOperation().getExtraInfo(removedFold);
+ if (startMark.getId() != null)
+ customFoldId.put(startMark.getId(), Boolean.valueOf(removedFold.isCollapsed())); // remember the last fold's state before remove
+ FoldMarkInfo endMark = startMark.getPairMark(); // get prior releasing
+ if (getOperation().isStartDamaged(removedFold)) { // start mark area was damaged
+ startMark.release(true, transaction); // forced remove
+ }
+ if (getOperation().isEndDamaged(removedFold)) {
+ endMark.release(true, transaction);
+ }
+ }
+ }
+ removedFoldList = null;
+ }
+
+ private void markUpdate(FoldMarkInfo mark) {
+ markUpdate(mark.getOffset());
+ }
+
+ private void markUpdate(int offset) {
+ if (offset < minUpdateMarkOffset) {
+ minUpdateMarkOffset = offset;
+ }
+ if (offset > maxUpdateMarkOffset) {
+ maxUpdateMarkOffset = offset;
+ }
+ }
+
+ private FoldMarkInfo getMark(int index) {
+ return (FoldMarkInfo)markArray.getItem(index);
+ }
+
+ private int getMarkCount() {
+ return markArray.getItemCount();
+ }
+
+ private void removeMark(int index) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Removing mark from ind=" + index + ": " + getMark(index)); // NOI18N
+ }
+ markArray.remove(index, 1);
+ }
+
+ private void insertMark(int index, FoldMarkInfo mark) {
+ markArray.insertItem(index, mark);
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Inserted mark at ind=" + index + ": " + mark); // NOI18N
+ }
+ }
+
+ private int findMarkIndex(int offset) {
+ int markCount = getMarkCount();
+ int low = 0;
+ int high = markCount - 1;
+
+ while (low <= high) {
+ int mid = (low + high) / 2;
+ int midMarkOffset = getMark(mid).getOffset();
+
+ if (midMarkOffset < offset) {
+ low = mid + 1;
+ } else if (midMarkOffset > offset) {
+ high = mid - 1;
+ } else {
+ // mark starting exactly at the given offset found
+ // If multiple -> find the one with highest index
+ mid++;
+ while (mid < markCount && getMark(mid).getOffset() == offset) {
+ mid++;
+ }
+ mid--;
+ return mid;
+ }
+ }
+ return low; // return higher index (e.g. for insert)
+ }
+
+ private List getMarkList(TokenSequence seq) {
+ List markList = null;
+
+ for(seq.moveStart(); seq.moveNext(); ) {
+ Token token = seq.token();
+ FoldMarkInfo info;
+ try {
+ info = scanToken(token);
+ } catch (BadLocationException e) {
+ LOG.log(Level.WARNING, null, e);
+ info = null;
+ }
+
+ if (info != null) {
+ if (markList == null) {
+ markList = new ArrayList();
+ }
+ markList.add(info);
+ }
+ }
+
+ return markList;
+ }
+
+ private void processTokenList(TokenSequence seq, FoldHierarchyTransaction transaction) {
+ List markList = getMarkList(seq);
+ int markListSize;
+ if (markList != null && ((markListSize = markList.size()) > 0)) {
+ // Find the index for insertion
+ int offset = ((FoldMarkInfo)markList.get(0)).getOffset();
+ int arrayMarkIndex = findMarkIndex(offset);
+ // Remember the corresponding mark in the array as well
+ FoldMarkInfo arrayMark;
+ int arrayMarkOffset;
+ if (arrayMarkIndex < getMarkCount()) {
+ arrayMark = getMark(arrayMarkIndex);
+ arrayMarkOffset = arrayMark.getOffset();
+ } else { // at last mark
+ arrayMark = null;
+ arrayMarkOffset = Integer.MAX_VALUE;
+ }
+
+ for (int i = 0; i < markListSize; i++) {
+ FoldMarkInfo listMark = (FoldMarkInfo)markList.get(i);
+ int listMarkOffset = listMark.getOffset();
+ if (i == 0 || i == markListSize - 1) {
+ // Update the update-offsets by the first and last marks in the list
+ markUpdate(listMarkOffset);
+ }
+ while (listMarkOffset >= arrayMarkOffset) {
+ if (listMarkOffset == arrayMarkOffset) {
+ // At the same offset - likely the same mark
+ // -> retain the collapsed state
+ listMark.setCollapsed(arrayMark.isCollapsed());
+ }
+ if (!arrayMark.isReleased()) { // make sure that the mark is released
+ arrayMark.release(false, transaction);
+ }
+ removeMark(arrayMarkIndex);
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Removed dup mark from ind=" + arrayMarkIndex + ": " + arrayMark); // NOI18N
+ }
+ if (arrayMarkIndex < getMarkCount()) {
+ arrayMark = getMark(arrayMarkIndex);
+ arrayMarkOffset = arrayMark.getOffset();
+ } else { // no more marks
+ arrayMark = null;
+ arrayMarkOffset = Integer.MAX_VALUE;
+ }
+ }
+ // Insert the listmark
+ insertMark(arrayMarkIndex, listMark);
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Inserted mark at ind=" + arrayMarkIndex + ": " + listMark); // NOI18N
+ }
+ arrayMarkIndex++;
+ }
+ }
+ }
+
+ private void updateFolds(TokenSequence seq, FoldHierarchyTransaction transaction) {
+
+ if (seq != null && !seq.isEmpty()) {
+ processTokenList(seq, transaction);
+ }
+
+ if (maxUpdateMarkOffset == -1) { // no updates
+ return;
+ }
+
+ // Find the first mark to update and init the prevMark and parentMark prior the loop
+ int index = findMarkIndex(minUpdateMarkOffset);
+ FoldMarkInfo prevMark;
+ FoldMarkInfo parentMark;
+ if (index == 0) { // start from begining
+ prevMark = null;
+ parentMark = null;
+ } else {
+ prevMark = getMark(index - 1);
+ parentMark = prevMark.getParentMark();
+ }
+
+ // Iterate through the changed marks in the mark array
+ int markCount = getMarkCount();
+ while (index < markCount) { // process the marks
+ FoldMarkInfo mark = getMark(index);
+
+ // If the mark was released then it must be removed
+ if (mark.isReleased()) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Removing released mark at ind=" + index + ": " + mark); // NOI18N
+ }
+ removeMark(index);
+ markCount--;
+ continue;
+ }
+
+ // Update mark's status (folds, parentMark etc.)
+ if (mark.isStartMark()) { // starting a new fold
+ if (prevMark == null || prevMark.isStartMark()) { // new level
+ mark.setParentMark(prevMark); // prevMark == null means root level
+ parentMark = prevMark;
+
+ } // same level => parent to the parent of the prevMark
+
+ } else { // end mark
+ if (prevMark != null) {
+ if (prevMark.isStartMark()) { // closing nearest fold
+ prevMark.setEndMark(mark, false, transaction);
+
+ } else { // prevMark is end mark - closing its parent fold
+ if (parentMark != null) {
+ // mark's parent gets set as well
+ parentMark.setEndMark(mark, false, transaction);
+ parentMark = parentMark.getParentMark();
+
+ } else { // prevMark's parentMark is null (top level)
+ mark.makeSolitaire(false, transaction);
+ }
+ }
+
+ } else { // prevMark is null
+ mark.makeSolitaire(false, transaction);
+ }
+ }
+
+ // Set parent mark of the mark
+ mark.setParentMark(parentMark);
+
+
+ prevMark = mark;
+ index++;
+ }
+
+ minUpdateMarkOffset = Integer.MAX_VALUE;
+ maxUpdateMarkOffset = -1;
+
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("MARKS DUMP:\n" + this); //NOI18N
+ }
+ }
+
+ public @Override String toString() {
+ StringBuffer sb = new StringBuffer();
+ int markCount = getMarkCount();
+ int markCountDigitCount = Integer.toString(markCount).length();
+ for (int i = 0; i < markCount; i++) {
+ sb.append("["); // NOI18N
+ String iStr = Integer.toString(i);
+ appendSpaces(sb, markCountDigitCount - iStr.length());
+ sb.append(iStr);
+ sb.append("]:"); // NOI18N
+ FoldMarkInfo mark = getMark(i);
+
+ // Add extra indent regarding the depth in hierarchy
+ int indent = 0;
+ FoldMarkInfo parentMark = mark.getParentMark();
+ while (parentMark != null) {
+ indent += 4;
+ parentMark = parentMark.getParentMark();
+ }
+ appendSpaces(sb, indent);
+
+ sb.append(mark);
+ sb.append('\n');
+ }
+ return sb.toString();
+ }
+
+ private static void appendSpaces(StringBuffer sb, int spaces) {
+ while (--spaces >= 0) {
+ sb.append(' ');
+ }
+ }
+
+ private static Pattern pattern = Pattern.compile(
+ "(<\\s*editor-fold" +
+ // id="x"[opt] defaultstate="y"[opt] desc="z"[opt] defaultstate="a"[opt]
+ // id must be first, the rest of attributes in random order
+ "(?:(?:\\s+id=\"(\\S*)\")?(?:\\s+defaultstate=\"(\\S*?)\")?(?:\\s+desc=\"([\\S \\t]*?)\")?(?:\\s+defaultstate=\"(\\S*?)\")?)" +
+ "\\s*>)|(?:\\s*editor-fold\\s*>)"); // NOI18N
+
+ private FoldMarkInfo scanToken(Token token) throws BadLocationException {
+ // ignore any token that is not comment
+ if (token.id().primaryCategory() != null && token.id().primaryCategory().startsWith(tokenId)) { //NOI18N
+ Matcher matcher = pattern.matcher(token.text());
+ if (matcher.find()) {
+ if (matcher.group(1) != null) { // fold's start mark found
+ boolean state;
+ if (matcher.group(3) != null) {
+ state = "collapsed".equals(matcher.group(3)); // remember the defaultstate // NOI18N
+ } else {
+ state = "collapsed".equals(matcher.group(5));
+ }
+
+ if (matcher.group(2) != null) { // fold's id exists
+ Boolean collapsed = (Boolean)customFoldId.get(matcher.group(2));
+ if (collapsed != null)
+ state = collapsed.booleanValue(); // fold's state is already known from the past
+ else
+ customFoldId.put(matcher.group(2), Boolean.valueOf(state));
+ }
+ return new FoldMarkInfo(true, token.offset(null), matcher.end(0), matcher.group(2), state, matcher.group(4)); // NOI18N
+ } else { // fold's end mark found
+ return new FoldMarkInfo(false, token.offset(null), matcher.end(0), null, false, null);
+ }
+ }
+ }
+ return null;
+ }
+
+ private final class FoldMarkInfo {
+
+ private boolean startMark;
+ private Position pos;
+ private int length;
+ private String id;
+ private boolean collapsed;
+ private String description;
+
+ /** Matching pair mark used for fold construction */
+ private FoldMarkInfo pairMark;
+
+ /** Parent mark defining nesting in the mark hierarchy. */
+ private FoldMarkInfo parentMark;
+
+ /**
+ * Fold that corresponds to this mark (if it's start mark).
+ * It can be null if this mark is end mark or if it currently
+ * does not have the fold assigned.
+ */
+ private Fold fold;
+
+ private boolean released;
+
+ private FoldMarkInfo(boolean startMark, int offset,
+ int length, String id, boolean collapsed, String description)
+ throws BadLocationException {
+
+ this.startMark = startMark;
+ this.pos = doc.createPosition(offset);
+ this.length = length;
+ this.id = id;
+ this.collapsed = collapsed;
+ this.description = description;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public boolean isStartMark() {
+ return startMark;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public int getOffset() {
+ return pos.getOffset();
+ }
+
+ public int getEndOffset() {
+ return getOffset() + getLength();
+ }
+
+ public boolean isCollapsed() {
+ return (fold != null) ? fold.isCollapsed() : collapsed;
+ }
+
+ public boolean hasFold() {
+ return (fold != null);
+ }
+
+ public void setCollapsed(boolean collapsed) {
+ this.collapsed = collapsed;
+ }
+
+ public boolean isSolitaire() {
+ return (pairMark == null);
+ }
+
+ public void makeSolitaire(boolean forced, FoldHierarchyTransaction transaction) {
+ if (!isSolitaire()) {
+ if (isStartMark()) {
+ setEndMark(null, forced, transaction);
+ } else { // end mark
+ getPairMark().setEndMark(null, forced, transaction);
+ }
+ }
+ }
+
+ public boolean isReleased() {
+ return released;
+ }
+
+ /**
+ * Release this mark and mark for update.
+ */
+ public void release(boolean forced, FoldHierarchyTransaction transaction) {
+ if (!released) {
+ makeSolitaire(forced, transaction);
+ released = true;
+ markUpdate(this);
+ }
+ }
+
+ public FoldMarkInfo getPairMark() {
+ return pairMark;
+ }
+
+ private void setPairMark(FoldMarkInfo pairMark) {
+ this.pairMark = pairMark;
+ }
+
+ public void setEndMark(FoldMarkInfo endMark, boolean forced,
+ FoldHierarchyTransaction transaction) {
+ if (!isStartMark()) {
+ throw new IllegalStateException("Not start mark"); // NOI18N
+ }
+ if (pairMark == endMark) {
+ return;
+ }
+
+ if (pairMark != null) { // is currently paired to an end mark
+ releaseFold(forced, transaction);
+ pairMark.setPairMark(null);
+ }
+
+ pairMark = endMark;
+ if (endMark != null) {
+ if (!endMark.isSolitaire()) { // make solitaire first
+ endMark.makeSolitaire(false, transaction); // not forced here
+ }
+ endMark.setPairMark(this);
+ endMark.setParentMark(this.getParentMark());
+ ensureFoldExists(transaction);
+ }
+ }
+
+ public FoldMarkInfo getParentMark() {
+ return parentMark;
+ }
+
+ public void setParentMark(FoldMarkInfo parentMark) {
+ this.parentMark = parentMark;
+ }
+
+ private void releaseFold(boolean forced, FoldHierarchyTransaction transaction) {
+ if (isSolitaire() || !isStartMark()) {
+ throw new IllegalStateException();
+ }
+
+ if (fold != null) {
+ setCollapsed(fold.isCollapsed()); // serialize the collapsed info
+ if (!forced) {
+ getOperation().removeFromHierarchy(fold, transaction);
+ }
+ fold = null;
+ }
+ }
+
+ public Fold getFold() {
+ if (isSolitaire()) {
+ return null;
+ }
+ if (!isStartMark()) {
+ return pairMark.getFold();
+ }
+ return fold;
+ }
+
+ public void ensureFoldExists(FoldHierarchyTransaction transaction) {
+ if (isSolitaire() || !isStartMark()) {
+ throw new IllegalStateException();
+ }
+
+ if (fold == null) {
+ try {
+ if (!startMark) {
+ throw new IllegalStateException("Not start mark: " + this); // NOI18N
+ }
+ if (pairMark == null) {
+ throw new IllegalStateException("No pairMark for mark:" + this); // NOI18N
+ }
+ int startOffset = getOffset();
+ int startGuardedLength = getLength();
+ int endGuardedLength = pairMark.getLength();
+ int endOffset = pairMark.getOffset() + endGuardedLength;
+ fold = getOperation().addToHierarchy(
+ CUSTOM_FOLD_TYPE, getDescription(), collapsed,
+ startOffset, endOffset,
+ startGuardedLength, endGuardedLength,
+ this,
+ transaction
+ );
+ } catch (BadLocationException e) {
+ LOG.log(Level.WARNING, null, e);
+ }
+ }
+ }
+
+ public @Override String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(isStartMark() ? 'S' : 'E'); // NOI18N
+
+ // Check whether this mark (or its pair) has fold
+ if (hasFold() || (!isSolitaire() && getPairMark().hasFold())) {
+ sb.append("F"); // NOI18N
+
+ // Check fold's status
+ if (isStartMark() && (isSolitaire()
+ || getOffset() != fold.getStartOffset()
+ || getPairMark().getEndOffset() != fold.getEndOffset())
+ ) {
+ sb.append("!!<"); // NOI18N
+ sb.append(fold.getStartOffset());
+ sb.append(","); // NOI18N
+ sb.append(fold.getEndOffset());
+ sb.append(">!!"); // NOI18N
+ }
+ }
+
+ // Append mark's internal status
+ sb.append(" ("); // NOI18N
+ sb.append("o="); // NOI18N
+ sb.append(pos.getOffset());
+ sb.append(", l="); // NOI18N
+ sb.append(length);
+ sb.append(", d='"); // NOI18N
+ sb.append(description);
+ sb.append('\'');
+ if (getPairMark() != null) {
+ sb.append(", <->"); // NOI18N
+ sb.append(getPairMark().getOffset());
+ }
+ if (getParentMark() != null) {
+ sb.append(", ^"); // NOI18N
+ sb.append(getParentMark().getOffset());
+ }
+ sb.append(')');
+
+ return sb.toString();
+ }
+
+ }
+
+ public static final class Factory implements FoldManagerFactory {
+
+ public FoldManager createFoldManager() {
+ return new CustomFoldManager();
+ }
+ }
+}
diff --git a/editor.fold/src/org/netbeans/modules/editor/fold/ApiPackageAccessor.java b/editor.fold/src/org/netbeans/modules/editor/fold/ApiPackageAccessor.java
--- a/editor.fold/src/org/netbeans/modules/editor/fold/ApiPackageAccessor.java
+++ b/editor.fold/src/org/netbeans/modules/editor/fold/ApiPackageAccessor.java
@@ -44,6 +44,7 @@
package org.netbeans.modules.editor.fold;
+import org.netbeans.modules.editor.fold.ui.FoldViewFactory;
import javax.swing.event.DocumentEvent;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
@@ -142,5 +143,7 @@
public abstract void foldStateChangeEndOffsetChanged(FoldStateChange fsc,
int originalEndOffset);
+
+ public abstract FoldHierarchyExecution foldGetExecution(FoldHierarchy fh);
}
diff --git a/editor.lib/src/org/netbeans/editor/CustomFoldManager.java b/editor.fold/src/org/netbeans/modules/editor/fold/CustomFoldManager.java
rename from editor.lib/src/org/netbeans/editor/CustomFoldManager.java
rename to editor.fold/src/org/netbeans/modules/editor/fold/CustomFoldManager.java
--- a/editor.lib/src/org/netbeans/editor/CustomFoldManager.java
+++ b/editor.fold/src/org/netbeans/modules/editor/fold/CustomFoldManager.java
@@ -42,7 +42,7 @@
* made subject to such option by the copyright holder.
*/
-package org.netbeans.editor;
+package org.netbeans.modules.editor.fold;
import javax.swing.text.Document;
import javax.swing.text.BadLocationException;
@@ -59,10 +59,12 @@
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
+import org.netbeans.editor.BaseDocument;
import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
import org.netbeans.spi.editor.fold.FoldManager;
import org.netbeans.spi.editor.fold.FoldManagerFactory;
import org.netbeans.spi.editor.fold.FoldOperation;
+import org.openide.util.Parameters;
import org.openide.util.RequestProcessor;
/**
@@ -72,7 +74,7 @@
* @version 1.00
*/
-final class CustomFoldManager implements FoldManager, Runnable {
+public final class CustomFoldManager implements FoldManager, Runnable {
private static final Logger LOG = Logger.getLogger(CustomFoldManager.class.getName());
@@ -89,7 +91,18 @@
private static final RequestProcessor RP = new RequestProcessor(CustomFoldManager.class.getName(),
1, false, false);
private final RequestProcessor.Task task = RP.create(this);
+
+ private final String tokenId;
+
+ public CustomFoldManager() {
+ this.tokenId = "comment";
+ }
+ public CustomFoldManager(String tokenId) {
+ Parameters.notNull("tokenId", tokenId);
+ this.tokenId = tokenId;
+ }
+
public void init(FoldOperation operation) {
this.operation = operation;
if (LOG.isLoggable(Level.FINE)) {
@@ -481,7 +494,7 @@
private FoldMarkInfo scanToken(Token token) throws BadLocationException {
// ignore any token that is not comment
- if (token.id().primaryCategory() != null && token.id().primaryCategory().startsWith("comment")) { //NOI18N
+ if (token.id().primaryCategory() != null && token.id().primaryCategory().startsWith(tokenId)) { //NOI18N
Matcher matcher = pattern.matcher(token.text());
if (matcher.find()) {
if (matcher.group(1) != null) { // fold's start mark found
diff --git a/editor.fold/src/org/netbeans/modules/editor/fold/DefaultFoldProvider.java b/editor.fold/src/org/netbeans/modules/editor/fold/DefaultFoldProvider.java
new file mode 100644
--- /dev/null
+++ b/editor.fold/src/org/netbeans/modules/editor/fold/DefaultFoldProvider.java
@@ -0,0 +1,83 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2013 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.editor.fold;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import org.netbeans.api.editor.fold.FoldType;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.spi.editor.fold.FoldTypeProvider;
+
+/**
+ * Default folds, provided by the infrastructure.
+ * These fold types do not propagate to individual languages, only serve as
+ * a common base for "all languages" configuration.
+ *
+ * @author sdedic
+ */
+@MimeRegistration(mimeType = "", service = FoldTypeProvider.class)
+public class DefaultFoldProvider implements FoldTypeProvider {
+ private final Collection defaultTypes;
+
+ public DefaultFoldProvider() {
+ defaultTypes = new ArrayList(7);
+ defaultTypes.add(FoldType.CODE_BLOCK);
+ defaultTypes.add(FoldType.COMMENT);
+ defaultTypes.add(FoldType.DOCUMENTATION);
+ defaultTypes.add(FoldType.INITIAL_COMMENT);
+ defaultTypes.add(FoldType.MEMBER);
+ defaultTypes.add(FoldType.NESTED);
+ defaultTypes.add(FoldType.TAG);
+ defaultTypes.add(FoldType.USER);
+ }
+
+ @Override
+ public Collection getValues(Class type) {
+ return type == FoldType.class ? defaultTypes : null;
+ }
+
+ @Override
+ public boolean inheritable() {
+ return false;
+ }
+
+}
diff --git a/editor.fold/src/org/netbeans/modules/editor/fold/FoldContentReaders.java b/editor.fold/src/org/netbeans/modules/editor/fold/FoldContentReaders.java
new file mode 100644
--- /dev/null
+++ b/editor.fold/src/org/netbeans/modules/editor/fold/FoldContentReaders.java
@@ -0,0 +1,161 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2013 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.editor.fold;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import org.netbeans.api.editor.fold.Fold;
+import org.netbeans.api.editor.fold.FoldTemplate;
+import org.netbeans.api.editor.fold.FoldType;
+import org.netbeans.api.editor.fold.FoldUtilities;
+import org.netbeans.api.editor.mimelookup.MimeLookup;
+import org.netbeans.spi.editor.fold.ContentReader;
+import org.openide.util.Lookup;
+import org.openide.util.LookupEvent;
+import org.openide.util.LookupListener;
+
+/**
+ * Manages registered ContentReaders for individual Mime types.
+ * The cache is flushed iff the set of ContentReader.Factory changes (e.g. during module (un)installation).
+ *
+ * @author sdedic
+ */
+public final class FoldContentReaders {
+ private static FoldContentReaders INSTANCE = new FoldContentReaders();
+
+ private volatile Map mimeNodes = new HashMap();
+
+ public static FoldContentReaders get() {
+ return INSTANCE;
+ }
+
+ public CharSequence readContent(String mime, Document d, Fold f, FoldTemplate ft) throws BadLocationException {
+ List readers = getReaders(mime, f.getType());
+ for (ContentReader r : readers) {
+ CharSequence chs = r.read(d, f, ft);
+ if (chs != null) {
+ return chs;
+ }
+ }
+ return null;
+ }
+
+ private class N implements LookupListener {
+ String mime;
+ Lookup.Result result;
+ Map> readers = new HashMap>();
+
+ public N(String mime, Lookup mimeLookup) {
+ this.mime = mime;
+ init(mimeLookup);
+ }
+
+ void clear() {
+ result.removeLookupListener(this);
+ }
+
+ @Override
+ public void resultChanged(LookupEvent ev) {
+ flush();
+ }
+
+ private void init(Lookup mimeLookup) {
+ Collection extends FoldType> types =
+ new ArrayList((FoldUtilities.getFoldTypes(mime).values()));
+
+ result = mimeLookup.lookupResult(ContentReader.Factory.class);
+ Collection extends ContentReader.Factory> factories = result.allInstances();
+
+ List l;
+ for (FoldType ft : types) {
+ l = null;
+ for (ContentReader.Factory f : factories) {
+ ContentReader cr = f.createReader(ft);
+ if (cr != null) {
+ if (l == null) {
+ l = new ArrayList(3);
+ }
+ l.add(cr);
+ }
+ }
+ if (l != null) {
+ readers.put(ft, l);
+ }
+ }
+ result.addLookupListener(this);
+ }
+
+ List readers(FoldType ft) {
+ List r = readers.get(ft);
+ return r == null ? Collections.emptyList() : r;
+ }
+ }
+
+ public List getReaders(String mime, FoldType ft) {
+ N node = mimeNodes.get(mime);
+ if (node == null) {
+ synchronized (mimeNodes) {
+ node = mimeNodes.get(mime);
+ if (node == null) {
+ node = new N(mime, MimeLookup.getLookup(mime));
+ mimeNodes.put(mime, node);
+ }
+ }
+ }
+ return node.readers(ft);
+ }
+
+ private void flush() {
+ synchronized (mimeNodes) {
+ for (N n : mimeNodes.values()) {
+ n.clear();
+ }
+ mimeNodes.clear();
+ }
+ }
+}
diff --git a/editor.fold/src/org/netbeans/modules/editor/fold/FoldHierarchyExecution.java b/editor.fold/src/org/netbeans/modules/editor/fold/FoldHierarchyExecution.java
--- a/editor.fold/src/org/netbeans/modules/editor/fold/FoldHierarchyExecution.java
+++ b/editor.fold/src/org/netbeans/modules/editor/fold/FoldHierarchyExecution.java
@@ -57,8 +57,10 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.logging.Level;
+import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
+import java.util.prefs.PreferenceChangeEvent;
+import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
@@ -67,13 +69,17 @@
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
+import javax.swing.text.EditorKit;
import javax.swing.text.JTextComponent;
import org.netbeans.api.editor.fold.Fold;
import org.netbeans.api.editor.fold.FoldHierarchy;
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
import org.netbeans.api.editor.fold.FoldHierarchyListener;
import org.netbeans.api.editor.fold.FoldStateChange;
+import org.netbeans.api.editor.fold.FoldType;
+import org.netbeans.api.editor.fold.FoldUtilities;
import org.netbeans.api.editor.mimelookup.MimeLookup;
+import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.lib.editor.util.swing.DocumentListenerPriority;
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
import org.netbeans.spi.editor.fold.FoldManager;
@@ -83,6 +89,7 @@
import org.openide.ErrorManager;
import org.openide.util.RequestProcessor;
import org.openide.util.Task;
+import org.openide.util.WeakListeners;
/**
* Class backing the FoldHierarchy in one-to-one relationship.
@@ -117,7 +124,7 @@
private static final String PROPERTY_FOLD_HIERARCHY_MUTEX = "foldHierarchyMutex"; //NOI18N
- private static final String PROPERTY_FOLDING_ENABLED = "code-folding-enable"; //NOI18N
+ private static final String PROPERTY_FOLDING_ENABLED = FoldUtilitiesImpl.PREF_CODE_FOLDING_ENABLED; //NOI18N
private static final boolean debug
= Boolean.getBoolean("netbeans.debug.editor.fold"); //NOI18N
@@ -174,6 +181,10 @@
private Task initTask;
+ private Preferences foldPreferences;
+
+ private FoldingEditorSupport editSupport;
+
public static synchronized FoldHierarchy getOrCreateFoldHierarchy(JTextComponent component) {
return getOrCreateFoldExecution(component).getHierarchy();
}
@@ -256,6 +267,9 @@
} catch (BadLocationException e) {
ErrorManager.getDefault().notify(e);
}
+
+ editSupport = new FoldingEditorSupport(hierarchy, component);
+
}
/* testing only */
@@ -263,6 +277,11 @@
getOrCreateFoldExecution(panel).getInitTask().waitFinished();
}
+ /* testing only */
+ static boolean waitAllTasks() throws InterruptedException {
+ return RP.awaitTermination(30, TimeUnit.SECONDS);
+ }
+
private Task getInitTask() {
return initTask;
}
@@ -314,6 +333,10 @@
mutex.lock();
}
+ public final boolean isLockedByCaller() {
+ return mutex.getLockThread() == Thread.currentThread();
+ }
+
/**
* Unlock the hierarchy from exclusive use. This method must only
* be used together with {@link #lock()} in try..finally block.
@@ -454,6 +477,10 @@
: null;
}
+ Set getBlockedFolds(Fold f) {
+ return (Set)block2blockedSet.get(f);
+ }
+
/**
* Mark given fold as blocked by the block fold.
*/
@@ -689,6 +716,18 @@
}
}
}
+
+ private String getMimeType() {
+ EditorKit ek = component.getUI().getEditorKit(component);
+ String mimeType;
+
+ if (ek != null) {
+ mimeType = ek.getContentType();
+ } else {
+ mimeType = "";
+ }
+ return mimeType;
+ }
/**
* Rebuild (or release) the root folds of the hierarchy in the event dispatch thread.
@@ -721,12 +760,22 @@
boolean ok = false;
try {
operations = new FoldOperationImpl[factoryListLength];
- for (int i = 0; i < factoryListLength; i++) {
+ int i;
+ for (i = 0; i < factoryListLength; i++) {
FoldManagerFactory factory = (FoldManagerFactory)factoryList.get(i);
FoldManager manager = factory.createFoldManager();
+ if (manager == null) {
+ continue;
+ }
operations[i] = new FoldOperationImpl(this, manager, priority);
priority--;
}
+ // trim the array in the unlikely case
+ if (i < factoryListLength) {
+ FoldOperationImpl[] ops = new FoldOperationImpl[i];
+ System.arraycopy(operations, 0, ops, 0, i);
+ operations = ops;
+ }
ok = true;
} finally {
if (!ok) {
@@ -878,7 +927,7 @@
if (b == null && component.getDocument() != null) {
String mime = DocumentUtilities.getMimeType(component.getDocument());
if (mime != null) {
- Preferences prefs = MimeLookup.getLookup(mime).lookup(Preferences.class);
+ Preferences prefs = getFoldPreferences();
b = prefs.getBoolean(PROPERTY_FOLDING_ENABLED, true);
}
}
@@ -996,5 +1045,70 @@
return sb.toString();
}
+
+ public boolean hasProviders() {
+ return operations.length > 0;
+ }
+
+ /**
+ * Cache for initial states for individual FoldTypes.
+ * The cache is invalidated iff the Preferences change in a key which starts with the collapse- prefix.
+ * The cache is NOT invalidated on the FoldType set change, as if the foldtype set changes, either new FoldTypes
+ * appear (they will enter the cache eventually), or the obsolete FoldTypes will not be used in the future,
+ * so they may rote in the cache until the Component is closed.
+ */
+ private volatile Map initialFoldState = new HashMap();
+
+ /**
+ * Listener on fold preferences.
+ */
+ private PreferenceChangeListener weakPrefL;
+
+ /**
+ * Returns the cached value for initial folding state of the specific type. The method may be only
+ * called under a lock, since it populates the cache; no concurrency is permitted.
+ *
+ * @param ft the FoldType to inspect
+ * @return true, if the fold should be collapsed initially.
+ */
+ public boolean getInitialFoldState(FoldType ft) {
+ Boolean b = initialFoldState.get(ft);
+ if (b != null) {
+ return b;
+ }
+ b = FoldUtilities.isAutoCollapsed(ft, hierarchy);
+ initialFoldState.put(ft, b);
+ return b;
+ }
+
+ /**
+ * Obtains Preferences that control folding for this Hierarchy.
+ *
+ * @return Preferences object
+ */
+ public Preferences getFoldPreferences() {
+ if (foldPreferences == null) {
+ String mimeType = getMimeType();
+ // internally does MimeLookup lookup(Preferences.class)
+ Preferences prefs = LegacySettingsSync.get().processMime(mimeType);
+ if ("".equals(mimeType)) {
+ // do not cache; typically the editor kit will be changed to something other
+ return prefs;
+ }
+ foldPreferences = prefs;
+ weakPrefL = WeakListeners.create(PreferenceChangeListener.class, new PreferenceChangeListener() {
+ @Override
+ public void preferenceChange(PreferenceChangeEvent evt) {
+ if (evt.getKey().startsWith(FoldUtilitiesImpl.PREF_COLLAPSE_PREFIX)) {
+ if (!initialFoldState.isEmpty()) {
+ initialFoldState = new HashMap();
+ }
+ }
+ }
+ }, foldPreferences);
+ foldPreferences.addPreferenceChangeListener(weakPrefL);
+ }
+ return foldPreferences;
+ }
}
diff --git a/editor.fold/src/org/netbeans/modules/editor/fold/FoldHierarchyTransactionImpl.java b/editor.fold/src/org/netbeans/modules/editor/fold/FoldHierarchyTransactionImpl.java
--- a/editor.fold/src/org/netbeans/modules/editor/fold/FoldHierarchyTransactionImpl.java
+++ b/editor.fold/src/org/netbeans/modules/editor/fold/FoldHierarchyTransactionImpl.java
@@ -242,7 +242,7 @@
updateAffectedOffsets(fold);
int startOffset = change.getOriginalStartOffset();
int endOffset = change.getOriginalEndOffset();
- assert (startOffset <= endOffset) : "startOffset=" + startOffset + " > endOffset=" + endOffset; // NOI18N;
+ assert (endOffset < 0 || startOffset <= endOffset) : "startOffset=" + startOffset + " > endOffset=" + endOffset; // NOI18N;
if (startOffset != -1) {
updateAffectedStartOffset(startOffset);
}
diff --git a/editor.fold/src/org/netbeans/modules/editor/fold/FoldOperationImpl.java b/editor.fold/src/org/netbeans/modules/editor/fold/FoldOperationImpl.java
--- a/editor.fold/src/org/netbeans/modules/editor/fold/FoldOperationImpl.java
+++ b/editor.fold/src/org/netbeans/modules/editor/fold/FoldOperationImpl.java
@@ -44,16 +44,39 @@
package org.netbeans.modules.editor.fold;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.prefs.Preferences;
import javax.swing.event.DocumentEvent;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
+import javax.swing.text.EditorKit;
+import javax.swing.text.JTextComponent;
import org.netbeans.spi.editor.fold.FoldManager;
import org.netbeans.api.editor.fold.Fold;
import org.netbeans.api.editor.fold.FoldHierarchy;
+import org.netbeans.spi.editor.fold.FoldInfo;
+import org.netbeans.api.editor.fold.FoldStateChange;
+import org.netbeans.api.editor.fold.FoldTemplate;
import org.netbeans.spi.editor.fold.FoldOperation;
import org.netbeans.api.editor.fold.FoldType;
+import org.netbeans.api.editor.mimelookup.MimeLookup;
+import org.netbeans.editor.BaseDocument;
+import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
+import org.openide.util.Exceptions;
/**
@@ -204,6 +227,9 @@
throws BadLocationException {
checkFoldOperation(fold);
int origEndOffset = fold.getEndOffset();
+ if (origEndOffset == endOffset) {
+ return;
+ }
ApiPackageAccessor api = getAccessor();
api.foldSetEndOffset(fold, getDocument(), endOffset);
api.foldStateChangeEndOffsetChanged(transaction.getFoldStateChange(fold), origEndOffset);
@@ -235,7 +261,16 @@
public boolean isReleased() {
return released;
}
-
+
+ /**
+ * Enumerates all folds contributed by this Operation, whether blocked or active.
+ *
+ * @return
+ */
+ public Iterator foldIterator() {
+ return new BI(new DFSI(execution.getRootFold()));
+ }
+
private void checkFoldOperation(Fold fold) {
FoldOperationImpl foldOperation = getAccessor().foldGetOperation(fold);
if (foldOperation != this) {
@@ -250,5 +285,446 @@
private static ApiPackageAccessor getAccessor() {
return ApiPackageAccessor.get();
}
+
+ /**
+ * Compares two folds A, B. Fold "A" precedes "B", if and only if:
+ *
+ *
A fully encloses B, or
+ *
A starts before B, or
+ *
A and B occupy the same range, and A's priority is lower
+ *
+ *
+ * @param a fold to compare
+ * @param b fold to compare
+ * @return -1, 1, 0 as appropriate for a Comparator
+ */
+ private static final Comparator FOLD_COMPARATOR = new Comparator() {
+ @Override
+ public int compare(Fold a, Fold b) {
+ int diff = a.getStartOffset() - b.getStartOffset();
+ if (diff < 0) {
+ return -1;
+ }
+ int diff2 = b.getEndOffset() - a.getEndOffset();
+ if (diff2 != 0 || diff != 0) {
+ return 1;
+ }
+ ApiPackageAccessor accessor = getAccessor();
+ return accessor.foldGetOperation(a).getPriority() - accessor.foldGetOperation(b).getPriority();
+ }
+ };
+ /**
+ * Level of depth-first traversal
+ */
+ static class PS {
+ private Fold parent;
+ private int childIndex = -1;
+ private PS next;
+
+ PS(Fold parent, PS next) {
+ this.parent = parent;
+ this.next = next;
+ }
+ }
+
+ /**
+ * Implmentation of depth-first pre-order traversal through Fold hierarchy.
+ * Each level is iterated in the fold order = start offset order.
+ */
+ private class DFSI implements Iterator {
+ PS level;
+
+ private DFSI(Fold root) {
+ level = new PS(root, null);
+ }
+
+ @Override
+ public Fold next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ if (level.childIndex == -1) {
+ level.childIndex++;
+ return level.parent;
+ }
+ // note that hasNext also pops levels, as necessary, so there's a
+ // level, which is not yet exhausted.
+ Fold f = level.parent.getFold(level.childIndex++);
+ if (f.getFoldCount() > 0) {
+ level = new PS(f, level);
+ level.childIndex++;
+ return level.parent;
+ }
+ return f;
+ }
+
+ @Override
+ public boolean hasNext() {
+ while (level != null) {
+ if (level.childIndex == -1) {
+ return true;
+ } else if (level.childIndex >= level.parent.getFoldCount()) {
+ level = level.next;
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void remove() {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Iterator, which processes all blocked folds along with their blocker.
+ * The blocker + blockers folds are ordered using the fold order. Results
+ * are filtered to contain just Folds produced by this Operation. Folds
+ * owned by other operations/executions are skipped.
+ *
+ * Note that blocked folds do not form a hierarchy; they were removed from
+ * the fold hierarchy when it was decided to block those folds. So prior to
+ * iterating further in FoldHierarchy, all (recursively) blocked folds must
+ * be processed.
+ */
+ private class BI implements Iterator {
+ private Iterator dfsi;
+ private Iterator blockedFolds;
+ private Fold ret;
+ private Stack
- org.netbeans.modules.editor.fold
-
-
-
- 1
-
-
- org.netbeans.modules.editor.indent
diff --git a/editor.lib/src/org/netbeans/editor/ActionFactory.java b/editor.lib/src/org/netbeans/editor/ActionFactory.java
--- a/editor.lib/src/org/netbeans/editor/ActionFactory.java
+++ b/editor.lib/src/org/netbeans/editor/ActionFactory.java
@@ -84,10 +84,6 @@
import org.netbeans.api.editor.EditorActionNames;
import org.netbeans.api.editor.EditorActionRegistration;
import org.netbeans.api.editor.EditorActionRegistrations;
-import org.netbeans.api.editor.fold.Fold;
-import org.netbeans.api.editor.fold.FoldHierarchy;
-import org.netbeans.api.editor.fold.FoldUtilities;
-import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.progress.ProgressUtils;
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
import org.netbeans.lib.editor.util.swing.PositionRegion;
@@ -2106,192 +2102,6 @@
}
- /** Returns the fold that should be collapsed/expanded in the caret row
- * @param hierarchy hierarchy under which all folds should be collapsed/expanded.
- * @param dot caret position offset
- * @param lineStart offset of the start of line
- * @param lineEnd offset of the end of line
- * @return the fold that meet common criteria in accordance with the caret position
- */
- private static Fold getLineFold(FoldHierarchy hierarchy, int dot, int lineStart, int lineEnd){
- Fold caretOffsetFold = FoldUtilities.findOffsetFold(hierarchy, dot);
-
- // beginning searching from the lineStart
- Fold fold = FoldUtilities.findNearestFold(hierarchy, lineStart);
-
- while (fold!=null &&
- (fold.getEndOffset()<=dot || // find next available fold if the 'fold' is one-line
- // or it has children and the caret is in the fold body
- // i.e. class A{ |public void method foo(){}}
- (!fold.isCollapsed() && fold.getFoldCount() > 0 && fold.getStartOffset()+10) ? fold.getStartOffset()+1 : fold.getEndOffset());
- if (nextFold!=null && nextFold.getStartOffset()lineEnd) {
-
- // in the case:
- // class A{
- // } |
- // try to find an offset fold on the offset of the line beginning
- if (caretOffsetFold == null){
- caretOffsetFold = FoldUtilities.findOffsetFold(hierarchy, lineStart);
- }
-
- return caretOffsetFold;
- }
-
- // no fold at offset found, in this case return the fold
- if (caretOffsetFold == null) return fold;
-
- // skip possible inner class members validating if the innerclass fold is collapsed
- if (caretOffsetFold.isCollapsed()) return caretOffsetFold;
-
- // in the case:
- // class A{
- // public vo|id foo(){} }
- // 'fold' (in this case fold of the method foo) will be returned
- if ( caretOffsetFold.getEndOffset()>fold.getEndOffset() &&
- fold.getEndOffset()>dot){
- return fold;
- }
-
- // class A{
- // |} public void method foo(){}
- // inner class fold will be returned
- if (fold.getStartOffset()>caretOffsetFold.getEndOffset()) return caretOffsetFold;
-
- // class A{
- // public void foo(){} |}
- // returning innerclass fold
- if (fold.getEndOffset() dot || foldRowEnd < dot) return false; // it's not fold encapsulating dot
- return true;
- }
-
-
- public void actionPerformed(ActionEvent evt, final JTextComponent target) {
- Document doc = target.getDocument();
- doc.render(new Runnable() {
- @Override
- public void run() {
- FoldHierarchy hierarchy = FoldHierarchy.get(target);
- int dot = target.getCaret().getDot();
- hierarchy.lock();
- try{
- try{
- int rowStart = javax.swing.text.Utilities.getRowStart(target, dot);
- int rowEnd = javax.swing.text.Utilities.getRowEnd(target, dot);
- Fold fold = FoldUtilities.findNearestFold(hierarchy, rowStart);
- fold = getLineFold(hierarchy, dot, rowStart, rowEnd);
- if (fold==null){
- return; // no success
- }
- // ensure we' got the right fold
- if (dotInFoldArea(target, fold, dot)){
- hierarchy.collapse(fold);
- }
- }catch(BadLocationException ble){
- ble.printStackTrace();
- }
- }finally {
- hierarchy.unlock();
- }
- }
- });
- }
- }
-
- /** Expand a fold. Depends on the current caret position. */
- @EditorActionRegistration(name = BaseKit.expandFoldAction,
- menuText = "#" + BaseKit.expandFoldAction + "_menu_text")
- public static class ExpandFold extends LocalBaseAction {
- public ExpandFold(){
- }
-
- public void actionPerformed(ActionEvent evt, final JTextComponent target) {
- Document doc = target.getDocument();
- doc.render(new Runnable() {
- @Override
- public void run() {
- FoldHierarchy hierarchy = FoldHierarchy.get(target);
- int dot = target.getCaret().getDot();
- hierarchy.lock();
- try {
- try {
- int rowStart = javax.swing.text.Utilities.getRowStart(target, dot);
- int rowEnd = javax.swing.text.Utilities.getRowEnd(target, dot);
- Fold fold = getLineFold(hierarchy, dot, rowStart, rowEnd);
- if (fold != null) {
- hierarchy.expand(fold);
- }
- } catch (BadLocationException ble) {
- ble.printStackTrace();
- }
- } finally {
- hierarchy.unlock();
- }
- }
- });
- }
- }
-
- /** Collapse all existing folds in the document. */
- @EditorActionRegistration(name = BaseKit.collapseAllFoldsAction,
- menuText = "#" + BaseKit.collapseAllFoldsAction + "_menu_text")
- public static class CollapseAllFolds extends LocalBaseAction {
- public CollapseAllFolds(){
- }
-
- public void actionPerformed(ActionEvent evt, JTextComponent target) {
- FoldHierarchy hierarchy = FoldHierarchy.get(target);
- // Hierarchy locking done in the utility method
- FoldUtilities.collapseAll(hierarchy);
- }
- }
-
- /** Expand all existing folds in the document. */
- @EditorActionRegistration(name = BaseKit.expandAllFoldsAction,
- menuText = "#" + BaseKit.expandAllFoldsAction + "_menu_text")
- public static class ExpandAllFolds extends LocalBaseAction {
- public ExpandAllFolds(){
- }
-
- public void actionPerformed(ActionEvent evt, JTextComponent target) {
- FoldHierarchy hierarchy = FoldHierarchy.get(target);
- // Hierarchy locking done in the utility method
- FoldUtilities.expandAll(hierarchy);
- }
- }
-
/** Expand all existing folds in the document. */
@EditorActionRegistration(name = "dump-view-hierarchy")
public static class DumpViewHierarchyAction extends LocalBaseAction {
@@ -2303,15 +2113,19 @@
public void actionPerformed(ActionEvent evt, JTextComponent target) {
AbstractDocument adoc = (AbstractDocument)target.getDocument();
+ /*
+ * Folding has moved to editor.fold module. It must somehow hook
+ * into this dump to provide the information.
+
// Dump fold hierarchy
FoldHierarchy hierarchy = FoldHierarchy.get(target);
adoc.readLock();
try {
hierarchy.lock();
try {
- /*DEBUG*/System.err.println("FOLD HIERARCHY DUMP:\n" + hierarchy); // NOI18N
+ /*DEBUG* /System.err.println("FOLD HIERARCHY DUMP:\n" + hierarchy); // NOI18N
TokenHierarchy> th = TokenHierarchy.get(adoc);
- /*DEBUG*/System.err.println("TOKEN HIERARCHY DUMP:\n" + (th != null ? th : "")); // NOI18N
+ /*DEBUG* /System.err.println("TOKEN HIERARCHY DUMP:\n" + (th != null ? th : "")); // NOI18N
} finally {
hierarchy.unlock();
@@ -2319,7 +2133,7 @@
} finally {
adoc.readUnlock();
}
-
+ */
View rootView = null;
TextUI textUI = target.getUI();
if (textUI != null) {
diff --git a/editor.lib/src/org/netbeans/editor/BaseCaret.java b/editor.lib/src/org/netbeans/editor/BaseCaret.java
--- a/editor.lib/src/org/netbeans/editor/BaseCaret.java
+++ b/editor.lib/src/org/netbeans/editor/BaseCaret.java
@@ -78,6 +78,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.PreferenceChangeEvent;
@@ -105,12 +106,6 @@
import javax.swing.text.AttributeSet;
import javax.swing.text.Position;
import javax.swing.text.StyleConstants;
-import org.netbeans.api.editor.fold.Fold;
-import org.netbeans.api.editor.fold.FoldHierarchy;
-import org.netbeans.api.editor.fold.FoldHierarchyEvent;
-import org.netbeans.api.editor.fold.FoldHierarchyListener;
-import org.netbeans.api.editor.fold.FoldStateChange;
-import org.netbeans.api.editor.fold.FoldUtilities;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.settings.FontColorNames;
import org.netbeans.api.editor.settings.FontColorSettings;
@@ -136,7 +131,7 @@
public class BaseCaret implements Caret,
MouseListener, MouseMotionListener, PropertyChangeListener,
DocumentListener, ActionListener,
-AtomicLockListener, FoldHierarchyListener {
+AtomicLockListener {
/** Caret type representing block covering current character */
public static final String BLOCK_CARET = EditorPreferencesDefaults.BLOCK_CARET; // NOI18N
@@ -279,7 +274,6 @@
* its relative visual position on the screen.
*/
private boolean updateAfterFoldHierarchyChange;
- private FoldHierarchyListener weakFHListener;
/**
* Whether at least one typing change occurred during possibly several atomic operations.
@@ -530,13 +524,6 @@
EditorUI editorUI = Utilities.getEditorUI(c);
editorUI.removePropertyChangeListener(this);
- if (weakFHListener != null) {
- FoldHierarchy hierarchy = FoldHierarchy.get(c);
- if (hierarchy != null) {
- hierarchy.removeFoldHierarchyListener(weakFHListener);
- }
- }
-
modelChanged(listenDoc, null);
}
@@ -1231,27 +1218,20 @@
caretPos = doc.createPosition(offset);
markPos = doc.createPosition(offset);
- FoldHierarchy hierarchy = FoldHierarchy.get(c);
- // hook the listener if not already done
- if (weakFHListener == null) {
- weakFHListener = WeakListeners.create(FoldHierarchyListener.class, this, hierarchy);
- hierarchy.addFoldHierarchyListener(weakFHListener);
- }
-
- // Unfold fold
- hierarchy.lock();
- try {
- Fold collapsed = null;
- while (expandFold && (collapsed = FoldUtilities.findCollapsedFold(hierarchy, offset, offset)) != null && collapsed.getStartOffset() < offset &&
- collapsed.getEndOffset() > offset) {
- hierarchy.expand(collapsed);
+ Callable cc = (Callable)c.getClientProperty("org.netbeans.api.fold.expander");
+ if (cc != null) {
+ // the caretPos/markPos were already called.
+ // nothing except the document is locked at this moment.
+ try {
+ cc.call();
+ } catch (Exception ex) {
+ Exceptions.printStackTrace(ex);
}
- } finally {
- hierarchy.unlock();
}
if (rectangularSelection) {
setRectangularSelectionToDotAndMark();
}
+
} catch (BadLocationException e) {
throw new IllegalStateException(e.toString());
// setting the caret to wrong position leaves it at current position
@@ -1510,37 +1490,25 @@
// Disable drag which would otherwise occur when mouse would be over text
c.setDragEnabled(false);
// Check possible fold expansion
- FoldHierarchy hierarchy = FoldHierarchy.get(c);
- Document doc = c.getDocument();
- if (doc instanceof AbstractDocument) {
- AbstractDocument adoc = (AbstractDocument) doc;
- adoc.readLock();
- try {
- hierarchy.lock();
- try {
- Fold collapsed = FoldUtilities.findCollapsedFold(
- hierarchy, offset, offset);
- if (collapsed != null && collapsed.getStartOffset() <= offset
- && collapsed.getEndOffset() >= offset) {
- hierarchy.expand(collapsed);
- } else {
- if (selectWordAction == null) {
- selectWordAction = ((BaseKit) c.getUI().getEditorKit(
- c)).getActionByName(BaseKit.selectWordAction);
- }
- if (selectWordAction != null) {
- selectWordAction.actionPerformed(null);
- }
- // Select word action selects forward i.e. dot > mark
- minSelectionStartOffset = getMark();
- minSelectionEndOffset = getDot();
- }
- } finally {
- hierarchy.unlock();
+ try {
+ // hack, to get knowledge of possible expansion. Editor depends on Folding, so it's not really possible
+ // to have Folding depend on BaseCaret (= a cycle). If BaseCaret moves to editor.lib2, this contract
+ // can be formalized as an interface.
+ Callable cc = (Callable)c.getClientProperty("org.netbeans.api.fold.expander");
+ if (cc == null || !cc.call()) {
+ if (selectWordAction == null) {
+ selectWordAction = ((BaseKit) c.getUI().getEditorKit(
+ c)).getActionByName(BaseKit.selectWordAction);
}
- } finally {
- adoc.readUnlock();
+ if (selectWordAction != null) {
+ selectWordAction.actionPerformed(null);
+ }
+ // Select word action selects forward i.e. dot > mark
+ minSelectionStartOffset = getMark();
+ minSelectionEndOffset = getDot();
}
+ } catch (Exception ex) {
+ Exceptions.printStackTrace(ex);
}
break;
@@ -1617,37 +1585,48 @@
JTextComponent c = component;
if (c != null) {
if (isMiddleMouseButtonExt(evt)) {
- if (evt.getClickCount() == 1) {
- if (c == null) return;
+ if (evt.getClickCount() == 1) {
+ if (c == null) {
+ return;
+ }
Clipboard buffer = getSystemSelection();
-
- if (buffer == null) return;
+
+ if (buffer == null) {
+ return;
+ }
Transferable trans = buffer.getContents(null);
- if (trans == null) return;
+ if (trans == null) {
+ return;
+ }
- final BaseDocument doc = (BaseDocument)c.getDocument();
- if (doc == null) return;
-
- final int offset = ((BaseTextUI)c.getUI()).viewToModel(c,
- evt.getX(), evt.getY());
+ final BaseDocument doc = (BaseDocument) c.getDocument();
+ if (doc == null) {
+ return;
+ }
- try{
- final String pastingString = (String)trans.getTransferData(DataFlavor.stringFlavor);
- if (pastingString == null) return;
- doc.runAtomicAsUser (new Runnable () {
- public @Override void run () {
- try {
- doc.insertString(offset, pastingString, null);
- setDot(offset+pastingString.length());
- } catch( BadLocationException exc ) {
- }
+ final int offset = ((BaseTextUI) c.getUI()).viewToModel(c,
+ evt.getX(), evt.getY());
+
+ try {
+ final String pastingString = (String) trans.getTransferData(DataFlavor.stringFlavor);
+ if (pastingString == null) {
+ return;
+ }
+ doc.runAtomicAsUser(new Runnable() {
+ public @Override
+ void run() {
+ try {
+ doc.insertString(offset, pastingString, null);
+ setDot(offset + pastingString.length());
+ } catch (BadLocationException exc) {
+ }
}
});
- }catch(UnsupportedFlavorException ufe){
- }catch(IOException ioe){
+ } catch (UnsupportedFlavorException ufe) {
+ } catch (IOException ioe) {
}
- }
+ }
}
}
}
@@ -2110,67 +2089,6 @@
}
}
- public @Override void foldHierarchyChanged(FoldHierarchyEvent evt) {
- int caretOffset = getDot();
- final int addedFoldCnt = evt.getAddedFoldCount();
- final boolean scrollToView;
- LOG.finest("Received fold hierarchy change");
- if (addedFoldCnt > 0) {
- FoldHierarchy hierarchy = (FoldHierarchy) evt.getSource();
- Fold collapsed = null;
- boolean wasExpanded = false;
- while ((collapsed = FoldUtilities.findCollapsedFold(hierarchy, caretOffset, caretOffset)) != null && collapsed.getStartOffset() < caretOffset &&
- collapsed.getEndOffset() > caretOffset) {
- hierarchy.expand(collapsed);
- wasExpanded = true;
- }
- // prevent unneeded scrolling; the user may have scrolled out using mouse already
- // so scroll only if the added fold may affect Y axis. Actually it's unclear why
- // we should reveal the current position on fold events except when caret is positioned in now-collapsed fold
- scrollToView = wasExpanded;
- } else {
- int startOffset = Integer.MAX_VALUE;
- // Set the caret's offset to the end of just collapsed fold if necessary
- if (evt.getAffectedStartOffset() <= caretOffset && evt.getAffectedEndOffset() >= caretOffset) {
- for (int i = 0; i < evt.getFoldStateChangeCount(); i++) {
- FoldStateChange change = evt.getFoldStateChange(i);
- if (change.isCollapsedChanged()) {
- Fold fold = change.getFold();
- if (fold.isCollapsed() && fold.getStartOffset() <= caretOffset && fold.getEndOffset() >= caretOffset) {
- if (fold.getStartOffset() < startOffset) {
- startOffset = fold.getStartOffset();
- }
- }
- }
- }
- if (startOffset != Integer.MAX_VALUE) {
- setDot(startOffset, false);
- }
- }
- scrollToView = false;
- }
- // Update caret's visual position
- // Post the caret update asynchronously since the fold hierarchy is updated before
- // the view hierarchy and the views so the dispatchUpdate() could be picking obsolete
- // view information.
- SwingUtilities.invokeLater(new Runnable() {
- public @Override void run() {
- LOG.finest("Updating after fold hierarchy change");
- if (component == null) {
- return;
- }
- // see #217867
- Rectangle b = caretBounds;
- updateAfterFoldHierarchyChange = b != null;
- boolean wasInView = b != null && component.getVisibleRect().intersects(b);
- // scroll if:
- // a/ a fold was added and the caret was originally in the view
- // b/ scrollToView is true (= caret was positioned within a new now collapsed fold)
- dispatchUpdate((addedFoldCnt > 1 && wasInView) || scrollToView);
- }
- });
- }
-
void scheduleCaretUpdate() {
if (!caretUpdatePending) {
caretUpdatePending = true;
@@ -2304,5 +2222,22 @@
DRAG_SELECTION // Drag is being done (text selection existed at the mouse press)
}
-
+
+ /**
+ * Refreshes caret display on the screen.
+ * Some height or view changes may result in the caret going off the screen. In some cases, this is not desirable,
+ * as the user's work may be interrupted by e.g. an automatic refresh. This method repositions the view so the
+ * caret remains visible.
+ *
+ * The method has two modes: it can reposition the view just if it originally displayed the caret and the caret became
+ * invisible, and it can scroll the caret into view unconditionally.
+ * @param retainInView true to scroll only if the caret was visible. False to refresh regardless of visibility.
+ */
+ public void refresh(boolean retainInView) {
+ Rectangle b = caretBounds;
+ updateAfterFoldHierarchyChange = b != null;
+ boolean wasInView = b != null && component.getVisibleRect().intersects(b);
+ update(!retainInView || wasInView);
+ }
+
}
diff --git a/editor.lib/src/org/netbeans/editor/Bundle.properties b/editor.lib/src/org/netbeans/editor/Bundle.properties
--- a/editor.lib/src/org/netbeans/editor/Bundle.properties
+++ b/editor.lib/src/org/netbeans/editor/Bundle.properties
@@ -59,14 +59,6 @@
bracket-match=Match Bracket
build-popup-menu=Build Popup Menu
dump-view-hierarchy=Dump View Hierarchy
-collapse-all-folds_menu_text=Co&llapse All
-collapse-fold_menu_text=&Collapse Fold
-expand-all-folds_menu_text=E&xpand All
-expand-fold_menu_text=&Expand Fold
-collapse-all-folds=Collapse All
-collapse-fold=Collapse Fold
-expand-all-folds=Expand All
-expand-fold=Expand Fold
build-tool-tip=Build Tool Tip
caret-backward=Insertion Point Backward
caret-begin-line=Insertion Point to Beginning of Text on Line
@@ -311,10 +303,6 @@
## ext.Completion.java
ext.Completion.wait=Please wait...
-# CodeFoldingSideBar
-ACSN_CodeFoldingSideBar=Code folding side bar
-ACSD_CodeFoldingSideBar=Code folding side bar shows text folds and allows their collapsing and expanding.
-
# Format Action
Format_in_progress=Formatting text, please wait...
diff --git a/editor.lib/src/org/netbeans/editor/CodeFoldingSideBar.java b/editor.lib/src/org/netbeans/editor/CodeFoldingSideBar.java
deleted file mode 100644
--- a/editor.lib/src/org/netbeans/editor/CodeFoldingSideBar.java
+++ /dev/null
@@ -1,1472 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
- *
- * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
- * Other names may be trademarks of their respective owners.
- *
- * The contents of this file are subject to the terms of either the GNU
- * General Public License Version 2 only ("GPL") or the Common
- * Development and Distribution License("CDDL") (collectively, the
- * "License"). You may not use this file except in compliance with the
- * License. You can obtain a copy of the License at
- * http://www.netbeans.org/cddl-gplv2.html
- * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
- * specific language governing permissions and limitations under the
- * License. When distributing the software, include this License Header
- * Notice in each file and include the License file at
- * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the GPL Version 2 section of the License file that
- * accompanied this code. If applicable, add the following below the
- * License Header, with the fields enclosed by brackets [] replaced by
- * your own identifying information:
- * "Portions Copyrighted [year] [name of copyright owner]"
- *
- * Contributor(s):
- *
- * The Original Software is NetBeans. The Initial Developer of the Original
- * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
- * Microsystems, Inc. All Rights Reserved.
- *
- * If you wish your version of this file to be governed by only the CDDL
- * or only the GPL Version 2, indicate your decision by adding
- * "[Contributor] elects to include this software in this distribution
- * under the [CDDL or GPL Version 2] license." If you do not indicate a
- * single choice of license, a recipient has the option to distribute
- * your version of this file under either the CDDL, the GPL Version 2 or
- * to extend the choice of license to its licensees as provided above.
- * However, if you add GPL Version 2 code and therefore, elected the GPL
- * Version 2 license, then the option applies only if the new code is
- * made subject to such option by the copyright holder.
- */
-
-package org.netbeans.editor;
-
-import java.awt.BasicStroke;
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Point;
-import java.awt.Rectangle;
-import java.awt.Stroke;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.NavigableMap;
-import java.util.TreeMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.prefs.PreferenceChangeEvent;
-import java.util.prefs.PreferenceChangeListener;
-import java.util.prefs.Preferences;
-import javax.accessibility.Accessible;
-import javax.accessibility.AccessibleContext;
-import javax.accessibility.AccessibleRole;
-import javax.swing.JComponent;
-import javax.swing.SwingUtilities;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-import javax.swing.text.AbstractDocument;
-import javax.swing.text.AttributeSet;
-import javax.swing.text.BadLocationException;
-import javax.swing.text.Document;
-import javax.swing.text.JTextComponent;
-import javax.swing.text.View;
-import org.netbeans.api.editor.fold.Fold;
-import org.netbeans.api.editor.fold.FoldHierarchy;
-import org.netbeans.api.editor.fold.FoldHierarchyEvent;
-import org.netbeans.api.editor.fold.FoldHierarchyListener;
-import org.netbeans.api.editor.fold.FoldUtilities;
-import org.netbeans.api.editor.mimelookup.MimeLookup;
-import org.netbeans.api.editor.settings.AttributesUtilities;
-import org.netbeans.api.editor.settings.FontColorNames;
-import org.netbeans.api.editor.settings.FontColorSettings;
-import org.netbeans.api.editor.settings.SimpleValueNames;
-import org.netbeans.modules.editor.lib2.EditorPreferencesDefaults;
-import org.netbeans.modules.editor.lib.SettingsConversions;
-import org.netbeans.modules.editor.lib2.view.LockedViewHierarchy;
-import org.netbeans.modules.editor.lib2.view.ParagraphViewDescriptor;
-import org.netbeans.modules.editor.lib2.view.ViewHierarchy;
-import org.netbeans.modules.editor.lib2.view.ViewHierarchyEvent;
-import org.netbeans.modules.editor.lib2.view.ViewHierarchyListener;
-import org.openide.util.Lookup;
-import org.openide.util.LookupEvent;
-import org.openide.util.LookupListener;
-import org.openide.util.NbBundle;
-import org.openide.util.WeakListeners;
-
-/**
- * Code Folding Side Bar. Component responsible for drawing folding signs and responding
- * on user fold/unfold action.
- *
- * @author Martin Roskanin
- */
-public class CodeFoldingSideBar extends JComponent implements Accessible {
-
- private static final Logger LOG = Logger.getLogger(CodeFoldingSideBar.class.getName());
-
- /** This field should be treated as final. Subclasses are forbidden to change it.
- * @deprecated Without any replacement.
- */
- protected Color backColor;
- /** This field should be treated as final. Subclasses are forbidden to change it.
- * @deprecated Without any replacement.
- */
- protected Color foreColor;
- /** This field should be treated as final. Subclasses are forbidden to change it.
- * @deprecated Without any replacement.
- */
- protected Font font;
-
- /** This field should be treated as final. Subclasses are forbidden to change it. */
- protected /*final*/ JTextComponent component;
- private volatile AttributeSet attribs;
- private Lookup.Result extends FontColorSettings> fcsLookupResult;
- private final LookupListener fcsTracker = new LookupListener() {
- public void resultChanged(LookupEvent ev) {
- attribs = null;
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- //EMI: This is needed as maybe the DEFAULT_COLORING is changed, the font is different
- // and while getMarkSize() is used in paint() and will make the artifacts bigger,
- // the component itself will be the same size and it must be changed.
- // See http://www.netbeans.org/issues/show_bug.cgi?id=153316
- updatePreferredSize();
- CodeFoldingSideBar.this.repaint();
- }
- });
- }
- };
- private final Listener listener = new Listener();
-
- private boolean enabled = false;
-
- protected List visibleMarks = new ArrayList();
-
- /**
- * Mouse moved point, possibly {@code null}. Set from mouse-moved, mouse-entered
- * handlers, so that painting will paint this fold in bold. -1, if mouse is not
- * in the sidebar region. The value is used to compute highlighted portions of the
- * folding outline.
- */
- private int mousePoint = -1;
-
- /**
- * if true, the {@link #mousePoint} has been already used to make a PaintInfo active.
- * The flag is tested by {@link #traverseForward} and {@link #traverseBackward} after children
- * of the current fold are processed and cleared if the {@link #mousePoint} falls to the fold area -
- * fields of PaintInfo are set accordingly.
- * It's also used to compute (current) mouseBoundary, so mouse movement does not trigger
- * refreshes eagerly
- */
- private boolean mousePointConsumed;
-
- /**
- * Boundaries of the current area under the mouse. Can be eiher the span of the
- * current fold (or part of it), or the span not occupied by any fold. Serves as an optimization
- * for mouse handler, which does not trigger painting (refresh) unless mouse
- * leaves this region.
- */
- private Rectangle mouseBoundary;
-
- /**
- * Y-end of the nearest fold that ends above the {@link #mousePoint}. Undefined if mousePoint is null.
- * These two variables are initialized at each level of folds, and help to compute {@link #mouseBoundary} for
- * the case the mousePointer is OUTSIDE all children (or outside all folds).
- */
- private int lowestAboveMouse = -1;
-
- /**
- * Y-begin of the nearest fold, which starts below the {@link #mousePoint}. Undefined if mousePoint is null
- */
- private int topmostBelowMouse = Integer.MAX_VALUE;
-
- /** Paint operations */
- public static final int PAINT_NOOP = 0;
- /**
- * Normal opening +- marker
- */
- public static final int PAINT_MARK = 1;
-
- /**
- * Vertical line - typically at the end of the screen
- */
- public static final int PAINT_LINE = 2;
-
- /**
- * End angled line, without a sign
- */
- public static final int PAINT_END_MARK = 3;
-
- /**
- * Single-line marker, both start and end
- */
- public static final int SINGLE_PAINT_MARK = 4;
-
- /**
- * Marker value for {@link #mousePoint} indicating that mouse is outside the Component.
- */
- private static final int NO_MOUSE_POINT = -1;
-
- /**
- * Stroke used to draw inactive (regular) fold outlines.
- */
- private static Stroke LINE_DASHED = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
- 1f, new float[] { 1f, 1f }, 0f);
-
- /**
- * Stroke used to draw outlines for 'active' fold
- */
- private static final Stroke LINE_BOLD = new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
-
- private final Preferences prefs;
- private final PreferenceChangeListener prefsListener = new PreferenceChangeListener() {
- public void preferenceChange(PreferenceChangeEvent evt) {
- String key = evt == null ? null : evt.getKey();
- if (key == null || SimpleValueNames.CODE_FOLDING_ENABLE.equals(key)) {
- updateColors();
-
- boolean newEnabled = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_ENABLE, EditorPreferencesDefaults.defaultCodeFoldingEnable);
- if (enabled != newEnabled) {
- enabled = newEnabled;
- updatePreferredSize();
- }
- }
- SettingsConversions.callSettingsChange(CodeFoldingSideBar.this);
- }
- };
-
- private void checkRepaint(ViewHierarchyEvent vhe) {
- if (!vhe.isChangeY()) {
- // does not obscur sidebar graphics
- return;
- }
-
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- updatePreferredSize();
- CodeFoldingSideBar.this.repaint();
- }
- });
- }
-
- /**
- * @deprecated Don't use this constructor, it does nothing!
- */
- public CodeFoldingSideBar() {
- component = null;
- prefs = null;
- throw new IllegalStateException("Do not use this constructor!"); //NOI18N
- }
-
- public CodeFoldingSideBar(JTextComponent component){
- super();
- this.component = component;
-
- addMouseListener(listener);
- addMouseMotionListener(listener);
-
- FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
- foldHierarchy.addFoldHierarchyListener(WeakListeners.create(FoldHierarchyListener.class, listener, foldHierarchy));
-
- Document doc = getDocument();
- doc.addDocumentListener(WeakListeners.document(listener, doc));
- setOpaque(true);
-
- prefs = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component)).lookup(Preferences.class);
- prefs.addPreferenceChangeListener(WeakListeners.create(PreferenceChangeListener.class, prefsListener, prefs));
- prefsListener.preferenceChange(null);
-
- ViewHierarchy.get(component).addViewHierarchyListener(new ViewHierarchyListener() {
-
- @Override
- public void viewHierarchyChanged(ViewHierarchyEvent evt) {
- checkRepaint(evt);
- }
-
- });
- }
-
- private void updatePreferredSize() {
- if (enabled) {
- setPreferredSize(new Dimension(getColoring().getFont().getSize(), component.getHeight()));
- setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
- }else{
- setPreferredSize(new Dimension(0,0));
- setMaximumSize(new Dimension(0,0));
- }
- revalidate();
- }
-
- private void updateColors() {
- Coloring c = getColoring();
- this.backColor = c.getBackColor();
- this.foreColor = c.getForeColor();
- this.font = c.getFont();
- }
-
- /**
- * This method should be treated as final. Subclasses are forbidden to override it.
- * @return The background color used for painting this component.
- * @deprecated Without any replacement.
- */
- protected Color getBackColor() {
- if (backColor == null) {
- updateColors();
- }
- return backColor;
- }
-
- /**
- * This method should be treated as final. Subclasses are forbidden to override it.
- * @return The foreground color used for painting this component.
- * @deprecated Without any replacement.
- */
- protected Color getForeColor() {
- if (foreColor == null) {
- updateColors();
- }
- return foreColor;
- }
-
- /**
- * This method should be treated as final. Subclasses are forbidden to override it.
- * @return The font used for painting this component.
- * @deprecated Without any replacement.
- */
- protected Font getColoringFont() {
- if (font == null) {
- updateColors();
- }
- return font;
- }
-
- // overriding due to issue #60304
- public @Override void update(Graphics g) {
- }
-
- protected void collectPaintInfos(
- View rootView, Fold fold, Map map, int level, int startIndex, int endIndex
- ) throws BadLocationException {
- //never called
- }
-
- /**
- * Adjust lowest/topmost boundaries from the Fold range y1-y2.
- * @param y1
- * @param y2
- * @param level
- */
- private void setMouseBoundaries(int y1, int y2, int level) {
- if (!hasMousePoint() || mousePointConsumed) {
- return;
- }
- int y = mousePoint;
- if (y2 < y && lowestAboveMouse < y2) {
- LOG.log(Level.FINEST, "lowestAbove at {1}: {0}", new Object[] { y2, level });
- lowestAboveMouse = y2;
- }
- if (y1 > y && topmostBelowMouse > y1) {
- LOG.log(Level.FINEST, "topmostBelow at {1}: {0}", new Object[] { y1, level });
- topmostBelowMouse = y1;
- }
- }
-
- /*
- * Even collapsed fold MAY contain a continuation line, IF one of folds on the same line is NOT collapsed. Such a fold should
- * then visually span multiple lines && be marked as collapsed.
- */
-
- protected List extends PaintInfo> getPaintInfo(Rectangle clip) throws BadLocationException {
- javax.swing.plaf.TextUI textUI = component.getUI();
- if (!(textUI instanceof BaseTextUI)) {
- return Collections.emptyList();
- }
- BaseTextUI baseTextUI = (BaseTextUI)textUI;
- BaseDocument bdoc = Utilities.getDocument(component);
- if (bdoc == null) {
- return Collections.emptyList();
- }
- mousePointConsumed = false;
- mouseBoundary = null;
- topmostBelowMouse = Integer.MAX_VALUE;
- lowestAboveMouse = -1;
- bdoc.readLock();
- try {
- int startPos = baseTextUI.getPosFromY(clip.y);
- int endPos = baseTextUI.viewToModel(component, Short.MAX_VALUE / 2, clip.y + clip.height);
-
- if (startPos < 0 || endPos < 0) {
- // editor window is not properly sized yet; return no infos
- return Collections.emptyList();
- }
-
- // #218282: if the view hierarchy is not yet updated, the Y coordinate may map to an incorrect offset outside
- // the document.
- int docLen = bdoc.getLength();
- if (startPos >= docLen || endPos > docLen) {
- return Collections.emptyList();
- }
-
- startPos = Utilities.getRowStart(bdoc, startPos);
- endPos = Utilities.getRowEnd(bdoc, endPos);
-
- FoldHierarchy hierarchy = FoldHierarchy.get(component);
- hierarchy.lock();
- try {
- View rootView = Utilities.getDocumentView(component);
- if (rootView != null) {
- Object [] arr = getFoldList(hierarchy.getRootFold(), startPos, endPos);
- @SuppressWarnings("unchecked")
- List extends Fold> foldList = (List extends Fold>) arr[0];
- int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
-
- /*
- * Note:
- *
- * The Map is keyed by Y-VISUAL position of the fold mark, not the textual offset of line start.
- * This is because several folds may occupy the same line, while only one + sign is displayed,
- * and affect the last fold in the row.
- */
- NavigableMap map = new TreeMap();
- // search backwards
- for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
- Fold fold = foldList.get(i);
- if (!traverseBackwards(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
- break;
- }
- }
-
- // search forward
- for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
- Fold fold = foldList.get(i);
- if (!traverseForward(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
- break;
- }
- }
-
- if (map.isEmpty() && foldList.size() > 0) {
- assert foldList.size() == 1;
- PaintInfo pi = new PaintInfo(PAINT_LINE, 0, clip.y, clip.height, -1, -1);
- mouseBoundary = new Rectangle(0, 0, 0, clip.height);
- LOG.log(Level.FINEST, "Mouse boundary for full side line set to: {0}", mouseBoundary);
- if (hasMousePoint()) {
- pi.markActive(true, true, true);
- }
- return Collections.singletonList(pi);
- } else {
- if (mouseBoundary == null) {
- mouseBoundary = makeMouseBoundary(clip.y, clip.y + clip.height);
- LOG.log(Level.FINEST, "Mouse boundary not set, defaulting to: {0}", mouseBoundary);
- }
- return new ArrayList(map.values());
- }
- } else {
- return Collections.emptyList();
- }
- } finally {
- hierarchy.unlock();
- }
- } finally {
- bdoc.readUnlock();
- }
- }
-
- /**
- * Adds a paint info to the map. If a paintinfo already exists, it merges
- * the structures, so the painting process can just follow the instructions.
- *
- * @param infos
- * @param yOffset
- * @param nextInfo
- */
- private void addPaintInfo(Map infos, int yOffset, PaintInfo nextInfo) {
- PaintInfo prevInfo = infos.get(yOffset);
- nextInfo.mergeWith(prevInfo);
- infos.put(yOffset, nextInfo);
- }
-
- private boolean traverseForward(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary,int level, NavigableMap infos) throws BadLocationException {
-// System.out.println("~~~ traverseForward<" + lowerBoundary + ", " + upperBoundary
-// + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
-// + (f.getStartOffset() > upperBoundary ? ", f.gSO > uB" : "")
-// + ", level=" + level);
-
- if (f.getStartOffset() > upperBoundary) {
- return false;
- }
-
- int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
- int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
- int y1 = btui.getYFromPos(lineStartOffset1);
- int h = btui.getEditorUI().getLineHeight();
- int y2 = btui.getYFromPos(lineStartOffset2);
-
- // the 'active' flags can be set only after children are processed; highlights
- // correspond to the innermost expanded child.
- boolean activeMark = false;
- boolean activeIn = false;
- boolean activeOut = false;
- PaintInfo spi;
- boolean activated;
-
- if (y1 == y2) {
- // whole fold is on a single line
- spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
- if (activated = isActivated(y1, y1 + h)) {
- activeMark = true;
- }
- addPaintInfo(infos, y1, spi);
- } else {
- // fold spans multiple lines
- spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
- if (activated = isActivated(y1, y2 + h / 2)) {
- activeMark = true;
- activeOut = true;
- }
- addPaintInfo(infos, y1, spi);
- }
-
- setMouseBoundaries(y1, y2 + h / 2, level);
-
- // Handle end mark after possible inner folds were processed because
- // otherwise if there would be two nested folds both ending at the same line
- // then the end mark for outer one would be replaced by an end mark for inner one
- // (same key in infos map) and the painting code would continue to paint line marking a fold
- // until next fold is reached (or end of doc).
- PaintInfo epi = null;
- if (y1 != y2 && !f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
- epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
- addPaintInfo(infos, y2, epi);
- }
-
- // save the topmost/lowest information, reset for child processing
- int topmost = topmostBelowMouse;
- int lowest = lowestAboveMouse;
- topmostBelowMouse = y2 + h / 2;
- lowestAboveMouse = y1;
-
- try {
- if (!f.isCollapsed()) {
- Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
- @SuppressWarnings("unchecked")
- List extends Fold> foldList = (List extends Fold>) arr[0];
- int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
-
- // search backwards
- for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
- Fold fold = foldList.get(i);
- if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
- break;
- }
- }
-
- // search forward
- for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
- Fold fold = foldList.get(i);
- if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
- return false;
- }
- }
- }
- if (!mousePointConsumed && activated) {
- mousePointConsumed = true;
- mouseBoundary = makeMouseBoundary(y1, y2 + h);
- LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
- spi.markActive(activeMark, activeIn, activeOut);
- if (epi != null) {
- epi.markActive(true, true, false);
- }
- markDeepChildrenActive(infos, y1, y2, level);
- }
- } finally {
- topmostBelowMouse = topmost;
- lowestAboveMouse = lowest;
- }
- return true;
- }
-
- /**
- * Sets outlines of all children to 'active'. Assuming yFrom and yTo are from-to Y-coordinates of the parent
- * fold, it finds all nested folds (folds, which are in between yFrom and yTo) and changes their in/out lines
- * as active.
- * The method returns Y start coordinate of the 1st child found.
- *
- * @param infos fold infos collected so far
- * @param yFrom upper Y-coordinate of the parent fold
- * @param yTo lower Y-coordinate of the parent fold
- * @param level level of the parent fold
- * @return Y-coordinate of the 1st child.
- */
- private int markDeepChildrenActive(NavigableMap infos, int yFrom, int yTo, int level) {
- int result = Integer.MAX_VALUE;
- Map m = infos.subMap(yFrom, yTo);
- for (Map.Entry me : m.entrySet()) {
- PaintInfo pi = me.getValue();
- int y = pi.getPaintY();
- if (y > yFrom && y < yTo) {
- if (LOG.isLoggable(Level.FINEST)) {
- LOG.log(Level.FINEST, "Marking chind as active: {0}", pi);
- }
- pi.markActive(false, true, true);
- if (y < result) {
- y = result;
- }
- }
- }
- return result;
- }
-
- /**
- * Returns stroke appropriate for painting (in)active outlines
- * @param s the default stroke
- * @param active true for active outlines
- * @return value of 's' or a Stroke which should be used to paint the outline.
- */
- private static Stroke getStroke(Stroke s, boolean active) {
- if (active) {
- return LINE_BOLD;
- } else {
- return s;
- }
- }
-
- private boolean traverseBackwards(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary, int level, NavigableMap infos) throws BadLocationException {
-// System.out.println("~~~ traverseBackwards<" + lowerBoundary + ", " + upperBoundary
-// + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
-// + (f.getEndOffset() < lowerBoundary ? ", f.gEO < lB" : "")
-// + ", level=" + level);
-
- if (f.getEndOffset() < lowerBoundary) {
- return false;
- }
-
- int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
- int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
- int h = btui.getEditorUI().getLineHeight();
-
- boolean activeMark = false;
- boolean activeIn = false;
- boolean activeOut = false;
- PaintInfo spi = null;
- PaintInfo epi = null;
- boolean activated = false;
- int y1 = 0;
- int y2 = 0;
-
- if (lineStartOffset1 == lineStartOffset2) {
- // whole fold is on a single line
- y2 = y1 = btui.getYFromPos(lineStartOffset1);
- spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset1);
- if (activated = isActivated(y1, y1 + h)) {
- activeMark = true;
- }
- addPaintInfo(infos, y1, spi);
- } else {
- y2 = btui.getYFromPos(lineStartOffset2);
- // fold spans multiple lines
- y1 = btui.getYFromPos(lineStartOffset1);
- activated = isActivated(y1, y2 + h / 2);
- if (f.getStartOffset() >= upperBoundary) {
- spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
- if (activated) {
- activeMark = true;
- activeOut = true;
- }
- addPaintInfo(infos, y1, spi);
- }
-
- if (!f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
- activated |= isActivated(y1, y2 + h / 2);
- epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
- addPaintInfo(infos, y2, epi);
- }
- }
-
- setMouseBoundaries(y1, y2 + h / 2, level);
-
- // save the topmost/lowest information, reset for child processing
- int topmost = topmostBelowMouse;
- int lowest = lowestAboveMouse;
- topmostBelowMouse = y2 + h /2;
- lowestAboveMouse = y1;
-
- try {
- if (!f.isCollapsed()) {
- Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
- @SuppressWarnings("unchecked")
- List extends Fold> foldList = (List extends Fold>) arr[0];
- int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
-
- // search backwards
- for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
- Fold fold = foldList.get(i);
- if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
- return false;
- }
- }
-
- // search forward
- for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
- Fold fold = foldList.get(i);
- if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
- break;
- }
- }
- }
- if (!mousePointConsumed && activated) {
- mousePointConsumed = true;
- mouseBoundary = makeMouseBoundary(y1, y2 + h);
- LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
- if (spi != null) {
- spi.markActive(activeMark, activeIn, activeOut);
- }
- if (epi != null) {
- epi.markActive(true, true, false);
- }
- int lowestChild = markDeepChildrenActive(infos, y1, y2, level);
- if (lowestChild < Integer.MAX_VALUE && lineStartOffset1 < upperBoundary) {
- // the fold starts above the screen clip region, and is 'activated'. We need to setup instructions to draw activated line up to the
- // 1st child marker.
- epi = new PaintInfo(PAINT_LINE, level, y1, y2 - y1, false, lineStartOffset1, lineStartOffset2);
- epi.markActive(true, true, false);
- addPaintInfo(infos, y1, epi);
- }
- }
- } finally {
- topmostBelowMouse = topmost;
- lowestAboveMouse = lowest;
- }
- return true;
- }
-
- private Rectangle makeMouseBoundary(int y1, int y2) {
- if (!hasMousePoint()) {
- return null;
- }
- if (topmostBelowMouse < Integer.MAX_VALUE) {
- y2 = topmostBelowMouse;
- }
- if (lowestAboveMouse > -1) {
- y1 = lowestAboveMouse;
- }
- return new Rectangle(0, y1, 0, y2 - y1);
- }
-
- protected EditorUI getEditorUI(){
- return Utilities.getEditorUI(component);
- }
-
- protected Document getDocument(){
- return component.getDocument();
- }
-
-
- private Fold getLastLineFold(FoldHierarchy hierarchy, int rowStart, int rowEnd, boolean shift){
- Fold fold = FoldUtilities.findNearestFold(hierarchy, rowStart);
- Fold prevFold = fold;
- while (fold != null && fold.getStartOffset()= startY && mouseY <= nextY) {
- LOG.log(Level.FINEST, "Starting line clicked, ignoring. MouseY={0}, startY={1}, nextY={2}",
- new Object[] { mouseY, startY, nextY });
- return;
- }
-
- startY = textUI.getYFromPos(endOffset);
- nextLineOffset = Utilities.getRowStart(bdoc, endOffset, 1);
- nextY = textUI.getYFromPos(nextLineOffset);
-
- if (mouseY >= startY && mouseY <= nextY) {
- // the mouse can be positioned above the marker (the fold found above), or
- // below it; in that case, the immediate enclosing fold should be used - should be the fold
- // that corresponds to the nextLineOffset, if any
- int h2 = (startY + nextY) / 2;
- if (mouseY >= h2) {
- Fold f2 = f;
-
- f = FoldUtilities.findOffsetFold(hierarchy, nextLineOffset);
- if (f == null) {
- // fold does not exist for the position below end-of-fold indicator
- return;
- }
- }
-
- }
-
- LOG.log(Level.FINEST, "Collapsing fold: {0}", f);
- hierarchy.collapse(f);
- } finally {
- hierarchy.unlock();
- }
- } finally {
- bdoc.readUnlock();
- }
- }
-
- private void performAction(final Mark mark, final boolean shiftFold) {
- Document doc = component.getDocument();
- doc.render(new Runnable() {
- @Override
- public void run() {
- ViewHierarchy vh = ViewHierarchy.get(component);
- LockedViewHierarchy lockedVH = vh.lock();
- try {
- int pViewIndex = lockedVH.yToParagraphViewIndex(mark.y + mark.size / 2);
- if (pViewIndex >= 0) {
- ParagraphViewDescriptor pViewDesc = lockedVH.getParagraphViewDescriptor(pViewIndex);
- int pViewStartOffset = pViewDesc.getStartOffset();
- int pViewEndOffset = pViewStartOffset + pViewDesc.getLength();
- // Find corresponding fold
- FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
- foldHierarchy.lock();
- try {
- int rowStart = javax.swing.text.Utilities.getRowStart(component, pViewStartOffset);
- int rowEnd = javax.swing.text.Utilities.getRowEnd(component, pViewStartOffset);
- Fold clickedFold = getLastLineFold(foldHierarchy, rowStart, rowEnd, shiftFold);//FoldUtilities.findNearestFold(foldHierarchy, viewStartOffset);
- if (clickedFold != null && clickedFold.getStartOffset() < pViewEndOffset) {
- foldHierarchy.toggle(clickedFold);
- }
- } catch (BadLocationException ble) {
- LOG.log(Level.WARNING, null, ble);
- } finally {
- foldHierarchy.unlock();
- }
- }
- } finally {
- lockedVH.unlock();
- }
- }
- });
- }
-
- protected int getMarkSize(Graphics g){
- if (g != null){
- FontMetrics fm = g.getFontMetrics(getColoring().getFont());
- if (fm != null){
- int ret = fm.getAscent() - fm.getDescent();
- return ret - ret%2;
- }
- }
- return -1;
- }
-
- private boolean hasMousePoint() {
- return mousePoint >= 0;
- }
-
- private boolean isActivated(int y1, int y2) {
- return hasMousePoint() &&
- (mousePoint >= y1 && mousePoint < y2);
- }
-
- private void drawFoldLine(Graphics2D g2d, boolean active, int x1, int y1, int x2, int y2) {
- Stroke origStroke = g2d.getStroke();
- g2d.setStroke(getStroke(origStroke, active));
- g2d.drawLine(x1, y1, x2, y2);
- g2d.setStroke(origStroke);
- }
-
- protected @Override void paintComponent(Graphics g) {
- if (!enabled) {
- return;
- }
-
- Rectangle clip = getVisibleRect();//g.getClipBounds();
- visibleMarks.clear();
-
- Coloring coloring = getColoring();
- g.setColor(coloring.getBackColor());
- g.fillRect(clip.x, clip.y, clip.width, clip.height);
- g.setColor(coloring.getForeColor());
-
- AbstractDocument adoc = (AbstractDocument)component.getDocument();
- adoc.readLock();
- try {
- List extends PaintInfo> ps = getPaintInfo(clip);
- Font defFont = coloring.getFont();
- int markSize = getMarkSize(g);
- int halfMarkSize = markSize / 2;
- int markX = (defFont.getSize() - markSize) / 2; // x position of mark rectangle
- int plusGap = (int)Math.round(markSize / 3.8); // distance between mark rectangle vertical side and start/end of minus sign
- int lineX = markX + halfMarkSize; // x position of the centre of mark
-
- LOG.fine("CFSBar: PAINT START ------\n");
- int descent = g.getFontMetrics(defFont).getDescent();
- PaintInfo previousInfo = null;
- Graphics2D g2d = (Graphics2D)g;
- LOG.log(Level.FINEST, "MousePoint: {0}", mousePoint);
-
- for(PaintInfo paintInfo : ps) {
- boolean isFolded = paintInfo.isCollapsed();
- int y = paintInfo.getPaintY();
- int height = paintInfo.getPaintHeight();
- int markY = y + descent; // y position of mark rectangle
- int paintOperation = paintInfo.getPaintOperation();
-
- if (previousInfo == null) {
- if (paintInfo.hasLineIn()) {
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("prevInfo=NULL; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
- }
- drawFoldLine(g2d, paintInfo.lineInActive, lineX, clip.y, lineX, y);
- }
- } else {
- if (previousInfo.hasLineOut() || paintInfo.hasLineIn()) {
- // Draw middle vertical line
- int prevY = previousInfo.getPaintY();
- if (LOG.isLoggable(Level.FINE)) {
- LOG.log(Level.FINE, "prevInfo={0}; y=" + y + ", PI:" + paintInfo + "\n", previousInfo); // NOI18N
- }
- drawFoldLine(g2d, previousInfo.lineOutActive || paintInfo.lineInActive, lineX, prevY + previousInfo.getPaintHeight(), lineX, y);
- }
- }
-
- if (paintInfo.hasSign()) {
- g.drawRect(markX, markY, markSize, markSize);
- g.drawLine(plusGap + markX, markY + halfMarkSize, markSize + markX - plusGap, markY + halfMarkSize);
- String opStr = (paintOperation == PAINT_MARK) ? "PAINT_MARK" : "SINGLE_PAINT_MARK"; // NOI18N
- if (isFolded) {
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine(opStr + ": folded; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
- }
- g.drawLine(lineX, markY + plusGap, lineX, markY + markSize - plusGap);
- }
- if (paintOperation != SINGLE_PAINT_MARK) {
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine(opStr + ": non-single; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
- }
- }
- if (paintInfo.hasLineIn()) { //[PENDING]
- drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, markY);
- }
- if (paintInfo.hasLineOut()) {
- // This is an error in case there's a next paint info at the same y which is an end mark
- // for this mark (it must be cleared explicitly).
- drawFoldLine(g2d, paintInfo.lineOutActive, lineX, markY + markSize, lineX, y + height);
- }
- visibleMarks.add(new Mark(markX, markY, markSize, isFolded));
-
- } else if (paintOperation == PAINT_LINE) {
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("PAINT_LINE: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
- }
- // FIXME !!
- drawFoldLine(g2d, paintInfo.signActive, lineX, y, lineX, y + height );
- } else if (paintOperation == PAINT_END_MARK) {
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("PAINT_END_MARK: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
- }
- if (previousInfo == null || y != previousInfo.getPaintY()) {
- drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, y + height / 2);
- drawFoldLine(g2d, paintInfo.signActive, lineX, y + height / 2, lineX + halfMarkSize, y + height / 2);
- if (paintInfo.getInnerLevel() > 0) {//[PENDING]
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine(" PAINT middle-line\n"); // NOI18N
- }
- drawFoldLine(g2d, paintInfo.lineOutActive, lineX, y + height / 2, lineX, y + height);
- }
- }
- }
-
- previousInfo = paintInfo;
- }
-
- if (previousInfo != null &&
- (previousInfo.getInnerLevel() > 0 ||
- (previousInfo.getPaintOperation() == PAINT_MARK && !previousInfo.isCollapsed()))
- ) {
- drawFoldLine(g2d, previousInfo.lineOutActive,
- lineX, previousInfo.getPaintY() + previousInfo.getPaintHeight(), lineX, clip.y + clip.height);
- }
-
- } catch (BadLocationException ble) {
- LOG.log(Level.WARNING, null, ble);
- } finally {
- LOG.fine("CFSBar: PAINT END ------\n\n");
- adoc.readUnlock();
- }
- }
-
- private static Object [] getFoldList(Fold parentFold, int start, int end) {
- List ret = new ArrayList();
-
- int index = FoldUtilities.findFoldEndIndex(parentFold, start);
- int foldCount = parentFold.getFoldCount();
- int idxOfFirstFoldStartingInside = -1;
- while (index < foldCount) {
- Fold f = parentFold.getFold(index);
- if (f.getStartOffset() <= end) {
- ret.add(f);
- } else {
- break; // no more relevant folds
- }
- if (idxOfFirstFoldStartingInside == -1 && f.getStartOffset() >= start) {
- idxOfFirstFoldStartingInside = ret.size() - 1;
- }
- index++;
- }
-
- return new Object [] { ret, idxOfFirstFoldStartingInside != -1 ? idxOfFirstFoldStartingInside : ret.size() };
- }
-
- /**
- * This class should be never used by other code; will be made private
- */
- public class PaintInfo {
-
- int paintOperation;
- /**
- * level of the 1st marker on the line
- */
- int innerLevel;
-
- /**
- * Y-coordinate of the cell
- */
- int paintY;
-
- /**
- * Height of the paint cell
- */
- int paintHeight;
-
- /**
- * State of the marker (+/-)
- */
- boolean isCollapsed;
-
- /**
- * all markers on the line are collapsed
- */
- boolean allCollapsed;
- int startOffset;
- int endOffset;
- /**
- * nesting level of the last marker on the line
- */
- int outgoingLevel;
-
- /**
- * Force incoming line (from above) to be present
- */
- boolean lineIn;
-
- /**
- * Force outgoing line (down from marker) to be present
- */
- boolean lineOut;
-
- /**
- * The 'incoming' (upper) line should be painted as active
- */
- boolean lineInActive;
-
- /**
- * The 'outgoing' (down) line should be painted as active
- */
- boolean lineOutActive;
-
- /**
- * The sign/marker itself should be painted as active
- */
- boolean signActive;
-
- public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, boolean isCollapsed, int startOffset, int endOffset){
- this.paintOperation = paintOperation;
- this.innerLevel = this.outgoingLevel = innerLevel;
- this.paintY = paintY;
- this.paintHeight = paintHeight;
- this.isCollapsed = this.allCollapsed = isCollapsed;
- this.startOffset = startOffset;
- this.endOffset = endOffset;
-
- switch (paintOperation) {
- case PAINT_MARK:
- lineIn = false;
- lineOut = true;
- outgoingLevel++;
- break;
- case SINGLE_PAINT_MARK:
- lineIn = false;
- lineOut = false;
- break;
- case PAINT_END_MARK:
- lineIn = true;
- lineOut = false;
- isCollapsed = true;
- allCollapsed = true;
- break;
- case PAINT_LINE:
- lineIn = lineOut = true;
- break;
- }
- }
-
- /**
- * Sets active flags on inidivual parts of the mark
- * @param mark
- * @param lineIn
- * @param lineOut S
- */
- void markActive(boolean mark, boolean lineIn, boolean lineOut) {
- this.signActive |= mark;
- this.lineInActive |= lineIn;
- this.lineOutActive |= lineOut;
- }
-
- boolean hasLineIn() {
- return lineIn || innerLevel > 0;
- }
-
- boolean hasLineOut() {
- return lineOut || outgoingLevel > 0 || (paintOperation != SINGLE_PAINT_MARK && !isAllCollapsed());
- }
-
- public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, int startOffset, int endOffset){
- this(paintOperation, innerLevel, paintY, paintHeight, false, startOffset, endOffset);
- }
-
- public int getPaintOperation(){
- return paintOperation;
- }
-
- public int getInnerLevel(){
- return innerLevel;
- }
-
- public int getPaintY(){
- return paintY;
- }
-
- public int getPaintHeight(){
- return paintHeight;
- }
-
- public boolean isCollapsed(){
- return isCollapsed;
- }
-
- boolean isAllCollapsed() {
- return allCollapsed;
- }
-
- public void setPaintOperation(int paintOperation){
- this.paintOperation = paintOperation;
- }
-
- public void setInnerLevel(int innerLevel){
- this.innerLevel = innerLevel;
- }
-
- public @Override String toString(){
- StringBuffer sb = new StringBuffer("");
- if (paintOperation == PAINT_MARK){
- sb.append("PAINT_MARK"); // NOI18N
- }else if (paintOperation == PAINT_LINE){
- sb.append("PAINT_LINE"); // NOI18N
- }else if (paintOperation == PAINT_END_MARK) {
- sb.append("PAINT_END_MARK"); // NOI18N
- }else if (paintOperation == SINGLE_PAINT_MARK) {
- sb.append("SINGLE_PAINT_MARK");
- }
- sb.append(",L:").append(innerLevel).append("/").append(outgoingLevel); // NOI18N
- sb.append(',').append(isCollapsed ? "C" : "E"); // NOI18N
- sb.append(", start=").append(startOffset).append(", end=").append(endOffset);
- sb.append(", lineIn=").append(lineIn).append(", lineOut=").append(lineOut);
- return sb.toString();
- }
-
- boolean hasSign() {
- return paintOperation == PAINT_MARK || paintOperation == SINGLE_PAINT_MARK;
- }
-
-
- void mergeWith(PaintInfo prevInfo) {
- if (prevInfo == null) {
- return;
- }
-
- int operation = this.paintOperation;
- boolean lineIn = prevInfo.lineIn;
- boolean lineOut = prevInfo.lineOut;
-
- LOG.log(Level.FINE, "Merging {0} with {1}: ", new Object[] { this, prevInfo });
- if (prevInfo.getPaintOperation() == PAINT_END_MARK) {
- // merge with start|single -> start mark + line-in
- lineIn = true;
- } else {
- operation = PAINT_MARK;
- }
-
- int level1 = Math.min(prevInfo.innerLevel, innerLevel);
- int level2 = prevInfo.outgoingLevel;
-
- if (getPaintOperation() == PAINT_END_MARK
- && innerLevel == prevInfo.outgoingLevel) {
- // if merging end marker at the last level, update to the new outgoing level
- level2 = outgoingLevel;
- } else if (!isCollapsed) {
- level2 = Math.max(prevInfo.outgoingLevel, outgoingLevel);
- }
-
- if (prevInfo.getInnerLevel() < getInnerLevel()) {
- int paintFrom = Math.min(prevInfo.paintY, paintY);
- int paintTo = Math.max(prevInfo.paintY + prevInfo.paintHeight, paintY + paintHeight);
- // at least one collapsed -> paint plus sign
- boolean collapsed = prevInfo.isCollapsed() || isCollapsed();
- int offsetFrom = Math.min(prevInfo.startOffset, startOffset);
- int offsetTo = Math.max(prevInfo.endOffset, endOffset);
-
- this.paintY = paintFrom;
- this.paintHeight = paintTo - paintFrom;
- this.isCollapsed = collapsed;
- this.startOffset = offsetFrom;
- this.endOffset = offsetTo;
- }
- this.paintOperation = operation;
- this.allCollapsed = prevInfo.allCollapsed && allCollapsed;
- this.innerLevel = level1;
- this.outgoingLevel = level2;
- this.lineIn |= lineIn;
- this.lineOut |= lineOut;
-
- this.signActive |= prevInfo.signActive;
- this.lineInActive |= prevInfo.lineInActive;
- this.lineOutActive |= prevInfo.lineOutActive;
-
- LOG.log(Level.FINE, "Merged result: {0}", this);
- }
- }
-
- /** Keeps info of visible folding mark */
- public class Mark{
- public int x;
- public int y;
- public int size;
- public boolean isFolded;
-
- public Mark(int x, int y, int size, boolean isFolded){
- this.x = x;
- this.y = y;
- this.size = size;
- this.isFolded = isFolded;
- }
- }
-
- private final class Listener extends MouseAdapter implements FoldHierarchyListener, DocumentListener, Runnable {
-
- public Listener(){
- }
-
- // --------------------------------------------------------------------
- // FoldHierarchyListener implementation
- // --------------------------------------------------------------------
-
- public void foldHierarchyChanged(FoldHierarchyEvent evt) {
- refresh();
- }
-
- // --------------------------------------------------------------------
- // DocumentListener implementation
- // --------------------------------------------------------------------
-
- public void insertUpdate(DocumentEvent evt) {
- if (!(evt instanceof BaseDocumentEvent)) return;
-
- BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
- if (bevt.getLFCount() > 0) { // one or more lines inserted
- refresh();
- }
- }
-
- public void removeUpdate(DocumentEvent evt) {
- if (!(evt instanceof BaseDocumentEvent)) return;
-
- BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
- if (bevt.getLFCount() > 0) { // one or more lines removed
- refresh();
- }
- }
-
- public void changedUpdate(DocumentEvent evt) {
- }
-
- // --------------------------------------------------------------------
- // MouseListener implementation
- // --------------------------------------------------------------------
-
- @Override
- public void mousePressed (MouseEvent e) {
- Mark mark = getClickedMark(e);
- if (mark!=null){
- e.consume();
- performAction(mark, (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) > 0);
- }
- }
-
- @Override
- public void mouseClicked(MouseEvent e) {
- // #102288 - missing event consuming caused quick doubleclicks to break
- // fold expanding/collapsing and move caret to the particular line
- if (e.getClickCount() > 1) {
- LOG.log(Level.FINEST, "Mouse {0}click at {1}", new Object[] { e.getClickCount(), e.getY()});
- Mark mark = getClickedMark(e);
- try {
- performActionAt(mark, e.getY());
- } catch (BadLocationException ex) {
- LOG.log(Level.WARNING, "Error during fold expansion using sideline", ex);
- }
- } else {
- e.consume();
- }
- }
-
- private void refreshIfMouseOutside(Point pt) {
- mousePoint = (int)pt.getY();
- if (LOG.isLoggable(Level.FINEST)) {
- if (mouseBoundary == null) {
- LOG.log(Level.FINEST, "Mouse boundary not set, refreshing: {0}", mousePoint);
- } else {
- LOG.log(Level.FINEST, "Mouse {0} inside known mouse boundary: {1}-{2}",
- new Object[] { mousePoint, mouseBoundary.y, mouseBoundary.getMaxY() });
- }
- }
- if (mouseBoundary == null || mousePoint < mouseBoundary.y || mousePoint > mouseBoundary.getMaxY()) {
- refresh();
- }
- }
-
- @Override
- public void mouseMoved(MouseEvent e) {
- refreshIfMouseOutside(e.getPoint());
- }
-
- public void mouseEntered(MouseEvent e) {
- refreshIfMouseOutside(e.getPoint());
- }
-
- public void mouseExited(MouseEvent e) {
- mousePoint = NO_MOUSE_POINT;
- refresh();
- }
-
-
- // --------------------------------------------------------------------
- // private implementation
- // --------------------------------------------------------------------
-
- private Mark getClickedMark(MouseEvent e){
- if (e == null || !SwingUtilities.isLeftMouseButton(e)) {
- return null;
- }
-
- int x = e.getX();
- int y = e.getY();
- for (Mark mark : visibleMarks) {
- if (x >= mark.x && x <= (mark.x + mark.size) && y >= mark.y && y <= (mark.y + mark.size)) {
- return mark;
- }
- }
- return null;
- }
-
- private void refresh() {
- SwingUtilities.invokeLater(this);
- }
-
- @Override
- public void run() {
- repaint();
- }
- } // End of Listener class
-
- @Override
- public AccessibleContext getAccessibleContext() {
- if (accessibleContext == null) {
- accessibleContext = new AccessibleJComponent() {
- public @Override AccessibleRole getAccessibleRole() {
- return AccessibleRole.PANEL;
- }
- };
- accessibleContext.setAccessibleName(NbBundle.getMessage(CodeFoldingSideBar.class, "ACSN_CodeFoldingSideBar")); //NOI18N
- accessibleContext.setAccessibleDescription(NbBundle.getMessage(CodeFoldingSideBar.class, "ACSD_CodeFoldingSideBar")); //NOI18N
- }
- return accessibleContext;
- }
-
- private Coloring getColoring() {
- if (attribs == null) {
- if (fcsLookupResult == null) {
- fcsLookupResult = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component))
- .lookupResult(FontColorSettings.class);
- fcsLookupResult.addLookupListener(WeakListeners.create(LookupListener.class, fcsTracker, fcsLookupResult));
- }
-
- FontColorSettings fcs = fcsLookupResult.allInstances().iterator().next();
- AttributeSet attr = fcs.getFontColors(FontColorNames.CODE_FOLDING_BAR_COLORING);
- if (attr == null) {
- attr = fcs.getFontColors(FontColorNames.DEFAULT_COLORING);
- } else {
- attr = AttributesUtilities.createComposite(attr, fcs.getFontColors(FontColorNames.DEFAULT_COLORING));
- }
- attribs = attr;
- }
- return Coloring.fromAttributeSet(attribs);
- }
-
-}
diff --git a/editor.lib/src/org/netbeans/editor/GlyphGutter.java b/editor.lib/src/org/netbeans/editor/GlyphGutter.java
--- a/editor.lib/src/org/netbeans/editor/GlyphGutter.java
+++ b/editor.lib/src/org/netbeans/editor/GlyphGutter.java
@@ -88,9 +88,6 @@
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.View;
-import org.netbeans.api.editor.fold.FoldHierarchy;
-import org.netbeans.api.editor.fold.FoldHierarchyEvent;
-import org.netbeans.api.editor.fold.FoldHierarchyListener;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.settings.EditorStyleConstants;
import org.netbeans.api.editor.settings.FontColorNames;
@@ -182,9 +179,7 @@
/** Property change listener on AnnotationTypes changes */
private PropertyChangeListener annoTypesListener;
private PropertyChangeListener editorUIListener;
- private GlyphGutter.GlyphGutterFoldHierarchyListener glyphGutterFoldHierarchyListener;
private GutterMouseListener gutterMouseListener;
- private FoldHierarchy foldHierarchy;
private ColoringMap coloringMap;
private final PropertyChangeListener coloringMapListener = new PropertyChangeListener() {
@@ -234,9 +229,6 @@
init();
update();
setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
- foldHierarchy = FoldHierarchy.get(eui.getComponent());
- glyphGutterFoldHierarchyListener = new GlyphGutterFoldHierarchyListener();
- foldHierarchy.addFoldHierarchyListener(glyphGutterFoldHierarchyListener);
editorUIListener = new EditorUIListener();
eui.addPropertyChangeListener(editorUIListener);
eui.getComponent().addPropertyChangeListener(editorUIListener);
@@ -1096,24 +1088,6 @@
}
- class GlyphGutterFoldHierarchyListener implements FoldHierarchyListener, Runnable {
-
- public GlyphGutterFoldHierarchyListener(){
- }
-
- public @Override void foldHierarchyChanged(FoldHierarchyEvent evt) {
- // Do not react to fold changes since fold expanding/collapsing should trigger
- // corresponding view hierarchy changes covered by view hierarchy listener.
-
-// SwingUtilities.invokeLater(this);
- }
-
- @Override
- public void run() {
- repaint();
- }
- }
-
/** Listening to EditorUI to properly deinstall attached listeners */
class EditorUIListener implements PropertyChangeListener{
public @Override void propertyChange (PropertyChangeEvent evt) {
@@ -1126,7 +1100,6 @@
((JTextComponent) evt.getOldValue()).removePropertyChangeListener(this);
}
annos.removeAnnotationsListener(GlyphGutter.this);
- foldHierarchy.removeFoldHierarchyListener(glyphGutterFoldHierarchyListener);
if (gutterMouseListener!=null){
removeMouseListener(gutterMouseListener);
removeMouseMotionListener(gutterMouseListener);
@@ -1134,8 +1107,6 @@
if (annoTypesListener !=null){
AnnotationTypes.getTypes().removePropertyChangeListener(annoTypesListener);
}
- foldHierarchy.removeFoldHierarchyListener(glyphGutterFoldHierarchyListener);
- foldHierarchy = null;
editorUI = null;
annos = null;
}
diff --git a/editor.lib/src/org/netbeans/modules/editor/lib/drawing/DrawEngineDocView.java b/editor.lib/src/org/netbeans/modules/editor/lib/drawing/DrawEngineDocView.java
--- a/editor.lib/src/org/netbeans/modules/editor/lib/drawing/DrawEngineDocView.java
+++ b/editor.lib/src/org/netbeans/modules/editor/lib/drawing/DrawEngineDocView.java
@@ -48,20 +48,12 @@
import java.awt.Rectangle;
import java.awt.Shape;
import java.beans.PropertyChangeListener;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
import javax.swing.plaf.TextUI;
import javax.swing.text.AbstractDocument;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
-import org.netbeans.api.editor.fold.Fold;
-import org.netbeans.api.editor.fold.FoldHierarchy;
-import org.netbeans.api.editor.fold.FoldUtilities;
-import org.netbeans.api.editor.fold.FoldHierarchyEvent;
-import org.netbeans.api.editor.fold.FoldHierarchyListener;
import org.netbeans.editor.BaseTextUI;
import org.netbeans.editor.EditorUI;
import org.netbeans.lib.editor.view.GapDocumentView;
@@ -73,19 +65,15 @@
* @author Miloslav Metelka
*/
public class DrawEngineDocView extends GapDocumentView
-implements FoldHierarchyListener, PropertyChangeListener {
+implements PropertyChangeListener {
private static final boolean debugRebuild
= Boolean.getBoolean("netbeans.debug.editor.view.rebuild"); // NOI18N
- private FoldHierarchy foldHierarchy;
/** Editor UI listening to */
private EditorUI editorUI;
- private Iterator collapsedFoldIterator;
- private Fold collapsedFold;
private int collapsedFoldStartOffset;
- private int collapsedFoldEndOffset;
private boolean collapsedFoldsInPresentViews;
@@ -104,8 +92,6 @@
public @Override void setParent(View parent) {
if (parent != null) { // start listening
JTextComponent component = (JTextComponent)parent.getContainer();
- foldHierarchy = FoldHierarchy.get(component);
- foldHierarchy.addFoldHierarchyListener(this);
TextUI tui = component.getUI();
if (tui instanceof BaseTextUI){
editorUI = ((BaseTextUI)tui).getEditorUI();
@@ -118,8 +104,6 @@
super.setParent(parent);
if (parent == null) {
- foldHierarchy.removeFoldHierarchyListener(this);
- foldHierarchy = null;
if (editorUI!=null){
editorUI.removePropertyChangeListener(this);
editorUI = null;
@@ -128,111 +112,12 @@
}
protected void attachListeners(){
- if (foldHierarchy != null) {
- }
- }
-
- private FoldHierarchy getFoldHierarchy() {
- return foldHierarchy;
}
protected @Override boolean useCustomReloadChildren() {
return true;
}
- /**
- * Find next collapsed fold in the given offset range.
- * @param lastCollapsedFold last collapsed fold returned by this method.
- * @param startOffset starting offset of the area in which the collapsed folds
- * should be searched.
- * @param endOffset ending offset of the area in which the collapsed folds
- * should be searched.
- */
- protected Fold nextCollapsedFold() {
- while (true) {
- Fold fold = collapsedFoldIterator.hasNext() ? (Fold)collapsedFoldIterator.next() : null;
-
- // Check whether the fold is not past the doc
- if (fold != null) {
- collapsedFoldStartOffset = fold.getStartOffset();
- collapsedFoldEndOffset = fold.getEndOffset();
- /* Ignore the empty folds as they would make up
- * no visible view anyway.
- * Although the fold hierarchy removes the empty views
- * automatically it may happen that the document listener
- * that the fold hierarchy attaches may not be notified yet.
- */
- if (collapsedFoldStartOffset == collapsedFoldEndOffset) {
- if (debugRebuild) {
- /*DEBUG*/System.err.println(
- "GapBoxView.nextCollapsedFold(): ignored empty fold " // NOI18N
- + fold
- );
- }
- continue; // skip empty fold
- }
-
- if (collapsedFoldEndOffset > getDocument().getLength()) {
- /* The fold is past the end of the document.
- * If a document is going to be switched in the component
- * the view hierarchy may be notified sooner
- * than fold hierarchy about that change which
- * can lead to this state.
- * That fold is ignored together with the rest of the folds
- * that would follow it.
- */
- fold = null;
- }
- }
-
- if (fold != null) {
- collapsedFoldsInPresentViews = true;
- }
-
- return fold;
- }
- }
-
- /**
- * Extra initialization for custom reload of children.
- */
- protected void initCustomReloadChildren(FoldHierarchy hierarchy,
- int startOffset, int endOffset) {
- collapsedFoldIterator = FoldUtilities.collapsedFoldIterator(hierarchy, startOffset, endOffset);
- collapsedFold = nextCollapsedFold();
- }
-
- /**
- * Free any resources required for custom reload of children.
- */
- protected void finishCustomReloadChildren(FoldHierarchy hierarchy) {
- collapsedFoldIterator = null;
- collapsedFold = null;
- }
-
- protected @Override void customReloadChildren(int index, int removeLength, int startOffset, int endOffset) {
- // if removing all the views reset the flag
- if (index == 0 && removeLength == getViewCount()) {
- collapsedFoldsInPresentViews = false; // suppose there will be no folds in line views
- }
-
- FoldHierarchy hierarchy = getFoldHierarchy();
- // Assuming the document lock was already acquired
- if (hierarchy != null) {
- hierarchy.lock();
- try {
- initCustomReloadChildren(hierarchy, startOffset, endOffset);
-
- super.customReloadChildren(index, removeLength, startOffset, endOffset);
-
- finishCustomReloadChildren(hierarchy);
-
- } finally {
- hierarchy.unlock();
- }
- }
- }
-
protected @Override View createCustomView(ViewFactory f,
int startOffset, int maxEndOffset, int elementIndex) {
if (elementIndex == -1) {
@@ -243,94 +128,10 @@
Element elem = getElement();
Element lineElem = elem.getElement(elementIndex);
- boolean createCollapsed = (collapsedFold != null);
-
- if (createCollapsed) { // collapsedFold != null
- int lineElemEndOffset = lineElem.getEndOffset();
- createCollapsed = (collapsedFoldStartOffset < lineElemEndOffset);
- if (createCollapsed) { // need to find end of collapsed area
- Element firstLineElem = lineElem;
- List foldAndEndLineElemList = new ArrayList();
-
- while (true) {
- int _collapsedFoldEndOffset = collapsedFold.getEndOffset();
- // Find line element index of the line in which the collapsed fold ends
- while (_collapsedFoldEndOffset > lineElemEndOffset) {
- elementIndex++;
- lineElem = elem.getElement(elementIndex);
- lineElemEndOffset = lineElem.getEndOffset();
- }
-
- foldAndEndLineElemList.add(collapsedFold);
- foldAndEndLineElemList.add(lineElem);
-
- collapsedFold = nextCollapsedFold();
-
- // No more collapsed or next collapsed does not start on current line
- if (collapsedFold == null || collapsedFoldStartOffset >= lineElemEndOffset) {
- break;
- }
- }
-
- // Create the multi-line-view with collapsed fold(s)
- view = new FoldMultiLineView(firstLineElem, foldAndEndLineElemList);
- }
- }
-
- if (!createCollapsed) {
- view = f.create(lineElem);
- }
-
+ view = f.create(lineElem);
return view;
}
- public void foldHierarchyChanged(FoldHierarchyEvent evt) {
- LockView lockView = LockView.get(this);
- lockView.lock();
- try {
- layoutLock();
- try {
- FoldHierarchy hierarchy = (FoldHierarchy)evt.getSource();
- if (hierarchy.getComponent().getDocument() != lockView.getDocument()) {
- // Comonent already has a different document assigned
- // so this view will be abandoned anyway => do not rebuild
- // the current chilren because of this change
- return;
- }
-
- boolean rebuildViews = true;
- int affectedStartOffset = evt.getAffectedStartOffset();
- int affectedEndOffset = evt.getAffectedEndOffset();
-
- // Check whether it is not a case when there were
- // no collapsed folds before and no collapsed folds now
- if (!collapsedFoldsInPresentViews) { // no collapsed folds previously
- // TODO Could Integer.MAX_VALUE be used?
- if (FoldUtilities.findCollapsedFold(hierarchy,
- affectedStartOffset, affectedEndOffset) == null
- ) { // no collapsed folds => no need to rebuild
- rebuildViews = false;
- }
- }
-
- if (rebuildViews) {
- /**
- * Check the affected offsets against the current document boundaries
- */
- int docLength = getDocument().getLength();
- int rebuildStartOffset = Math.min(affectedStartOffset, docLength);
- int rebuildEndOffset = Math.min(affectedEndOffset, docLength);
- offsetRebuild(rebuildStartOffset, rebuildEndOffset);
- }
- } finally {
- updateLayout();
- layoutUnlock();
- }
- } finally {
- lockView.unlock();
- }
- }
-
public @Override void paint(Graphics g, Shape allocation) {
java.awt.Component c = getContainer();
if (c instanceof javax.swing.text.JTextComponent){
diff --git a/editor.lib/src/org/netbeans/modules/editor/lib/drawing/DrawEngineFakeDocView.java b/editor.lib/src/org/netbeans/modules/editor/lib/drawing/DrawEngineFakeDocView.java
--- a/editor.lib/src/org/netbeans/modules/editor/lib/drawing/DrawEngineFakeDocView.java
+++ b/editor.lib/src/org/netbeans/modules/editor/lib/drawing/DrawEngineFakeDocView.java
@@ -45,7 +45,6 @@
package org.netbeans.modules.editor.lib.drawing;
import javax.swing.text.Element;
-import org.netbeans.api.editor.fold.Fold;
/**
* Fake view of the whole document supporting the code folding, operating from given startOffset
@@ -83,11 +82,6 @@
}
@Override
- protected Fold nextCollapsedFold() {
- return null; // simulate no collapsed folds
- }
-
- @Override
protected void attachListeners(){
}
diff --git a/editor.lib/src/org/netbeans/modules/editor/lib/drawing/FoldMultiLineView.java b/editor.lib/src/org/netbeans/modules/editor/lib/drawing/FoldMultiLineView.java
--- a/editor.lib/src/org/netbeans/modules/editor/lib/drawing/FoldMultiLineView.java
+++ b/editor.lib/src/org/netbeans/modules/editor/lib/drawing/FoldMultiLineView.java
@@ -46,14 +46,10 @@
import java.util.ArrayList;
import java.util.List;
-import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
-import javax.swing.text.Position;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
-import org.netbeans.api.editor.fold.Fold;
-import org.netbeans.editor.BaseDocument;
import org.netbeans.lib.editor.view.GapMultiLineView;
/**
@@ -109,40 +105,7 @@
// begining of the first line
int lastViewEndOffset = lineElem.getStartOffset();
- int cnt = foldAndEndLineElemList.size();
List childViews = new ArrayList();
- for (int i = 0; i < cnt; i++) {
- Fold fold = (Fold)foldAndEndLineElemList.get(i);
- int foldStartOffset = fold.getStartOffset();
- int foldEndOffset = fold.getEndOffset();
-
- BaseDocument doc = (BaseDocument) lineElem.getDocument();
- try {
- if (foldStartOffset > lastViewEndOffset) { // need to insert intra-line fragment
- View lineView = f.create(lineElem); // normal line view
- View intraFrag = lineView.createFragment(lastViewEndOffset, foldStartOffset);
- childViews.add(intraFrag);
- lastViewEndOffset = foldStartOffset;
- }
-
- // Create collapsed view
- Position viewStartPos = doc.createPosition(foldStartOffset);
- Position viewEndPos = doc.createPosition(foldEndOffset);
- CollapsedView collapsedView = new CollapsedView(lineElem,
- viewStartPos, viewEndPos, fold.getDescription());
- childViews.add(collapsedView);
- lastViewEndOffset = foldEndOffset;
-
- } catch (BadLocationException e) {
- throw new IllegalStateException("Failed to create view boundary positions"); // NOI18N
- }
-
- // Fetch line element where the fold ends
- i++;
- lineElem = (Element)foldAndEndLineElemList.get(i);
- lineElemEndOffset = lineElem.getEndOffset();
- }
-
// Append ending fragment if necessary
// asserted non-empty list => foldEndOffset populated
if (lastViewEndOffset < lineElemEndOffset) { // need ending fragment
diff --git a/editor.mimelookup/manifest.mf b/editor.mimelookup/manifest.mf
--- a/editor.mimelookup/manifest.mf
+++ b/editor.mimelookup/manifest.mf
@@ -1,7 +1,7 @@
Manifest-Version: 1.0
OpenIDE-Module: org.netbeans.modules.editor.mimelookup/1
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/mimelookup/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.30
+OpenIDE-Module-Specification-Version: 1.31
OpenIDE-Module-Recommends: org.netbeans.spi.editor.mimelookup.MimeDataProvider
AutoUpdate-Essential-Module: true
diff --git a/editor.mimelookup/nbproject/org-netbeans-modules-editor-mimelookup.sig b/editor.mimelookup/nbproject/org-netbeans-modules-editor-mimelookup.sig
--- a/editor.mimelookup/nbproject/org-netbeans-modules-editor-mimelookup.sig
+++ b/editor.mimelookup/nbproject/org-netbeans-modules-editor-mimelookup.sig
@@ -1,9 +1,10 @@
#Signature file v4.1
-#Version 1.26.1
+#Version 1.30
CLSS public abstract interface !annotation java.lang.Deprecated
anno 0 java.lang.annotation.Documented()
anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
+ anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE])
intf java.lang.annotation.Annotation
CLSS public java.lang.Object
@@ -124,6 +125,6 @@
meth public abstract <%0 extends java.lang.Object> {%%0} lookup(java.lang.Class<{%%0}>)
meth public static org.openide.util.Lookup getDefault()
supr java.lang.Object
-hfds defaultLookup
+hfds LOG,defaultLookup
hcls DefLookup,Empty
diff --git a/editor.mimelookup/src/org/netbeans/api/editor/mimelookup/MimePath.java b/editor.mimelookup/src/org/netbeans/api/editor/mimelookup/MimePath.java
--- a/editor.mimelookup/src/org/netbeans/api/editor/mimelookup/MimePath.java
+++ b/editor.mimelookup/src/org/netbeans/api/editor/mimelookup/MimePath.java
@@ -532,6 +532,80 @@
return true;
}
+ /**
+ * Returns the inherited Mime type.
+ * For {@link #EMPTY}, returns {@code null}. For most other mime types, returns
+ * {@code ""}. If the mime type derives from another one, such as text/ant+xml derives
+ * from xml, the return value will be the base mime type (text/xml in the example case).
+ *
+ * For MimePaths that identified embedded content (more components on the MimePath),
+ * the method returns the parent MIME of the last MIME type on the path
+ *
+ * @return inherited mime type, or {@code null}, if no parent exists (for {@link #EMPTY})
+ */
+ public String getInheritedType() {
+ if ("".equals(mimeType)) {
+ return null;
+ }
+ MimePath lastType = (size() == 1) ? this : MimePath.parse(mimeType);
+ List inheritedPaths = lastType.getInheritedPaths(null, null);
+ if (inheritedPaths.size() > 1) {
+ return inheritedPaths.get(1);
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Returns the included Mime paths.
+ * For MimePath, which nests several MIME types (i.e. text/php/text/html/text/css), it enumerates
+ * sub-paths so that a following element represents one level of nesting of the content.
+ * For the example example, the return would be:
+ *
+ *
text/php/text/html/text/css -- the full path
+ *
text/html/text/css -- outer content is removed
+ *
text/css -- the mime type of the identified content itself
+ *
(empty string)
+ *
+ *
+ * If a MIME type on the path has a generic MIME type (i.e. text/x-ant+xml
+ * has a generic MIME type text/xml), that generic type will be inserted. For example,
+ * for text/java/text/x-ant+xml/text/javascript, the result will list:
+ *
+ *
text/java/text/x-ant+xml/text/javascript, -- the full MimePath
+ *
text/x-ant+xml/text/javascript -- a prefix
+ *
text/xml/text/javascript -- ant+xml is generalized to xml
+ *
text/javascript
+ *
(empty string)
+ *
+ * For all but {@link #EMPTY} MimePaths, the list contains at least one entry, and the last
+ * entry is the {@link #EMPTY}. Note also, that the complete MimePath is always returned
+ * as the 1st member of the list.
+ *
+ * The returned sequence of MimePaths is suitable for searching settings or services
+ * for the (embedded) content whose type is described by MimePath as it is ordered from the
+ * most specific to the least specific paths (including generalization) and always contains
+ * the mime type of the identified contents. The last component ({@link ""}) represents
+ * default settings (services).
+ *
+ * Note that for MimePaths created from a mime type (not empty!) string, the
+ * getInheritedPaths().get(1) is a parent mime type. Either empty,
+ * or the generalized MIME.
+ *
+ * The caller should not modify the returned List.
+ *
+ * @return list of inherited Mime paths
+ */
+ public List getIncludedPaths() {
+ List paths = getInheritedPaths(null, null);
+ List mpaths = new ArrayList(paths.size());
+ for (String p : paths) {
+ mpaths.add(MimePath.parse(p));
+ }
+ return mpaths;
+ }
+
// XXX: This is currently called from editor/settings/storage (SettingsProvider)
// and editor/mimelookup/impl via reflection.
// We will eventually make it friend API. In the meantime just
diff --git a/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/MimePathLookup.java b/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/MimePathLookup.java
--- a/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/MimePathLookup.java
+++ b/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/MimePathLookup.java
@@ -138,20 +138,8 @@
// See also http://www.netbeans.org/issues/show_bug.cgi?id=118099
// Add lookups from deprecated MimeLookupInitializers
- List paths;
- try {
- Method m = MimePath.class.getDeclaredMethod("getInheritedPaths", String.class, String.class); //NOI18N
- m.setAccessible(true);
- @SuppressWarnings("unchecked")
- List ret = (List) m.invoke(mimePath, null, null);
- paths = ret;
- } catch (Exception e) {
- LOG.log(Level.WARNING, "Can't call org.netbeans.api.editor.mimelookup.MimePath.getInheritedPaths method.", e); //NOI18N
- paths = Collections.singletonList(mimePath.getPath());
- }
-
- for(String path : paths) {
- MimePath mp = MimePath.parse(path);
+ List paths = mimePath.getIncludedPaths();
+ for(MimePath mp : paths) {
Collection extends MimeLookupInitializer> initializers = mimeInitializers.allInstances();
for(int i = 0; i < mp.size(); i++) {
@@ -166,7 +154,7 @@
for(MimeLookupInitializer mli : initializers) {
if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("- Querying MimeLookupInitializer(" + path + "): " + mli); //NOI18N
+ LOG.fine("- Querying MimeLookupInitializer(" + mp.getPath() + "): " + mli); //NOI18N
}
Lookup mimePathLookup = mli.lookup();
if (mimePathLookup != null) {
diff --git a/editor.mimelookup/test/unit/src/org/netbeans/api/editor/mimelookup/MimePathTest.java b/editor.mimelookup/test/unit/src/org/netbeans/api/editor/mimelookup/MimePathTest.java
--- a/editor.mimelookup/test/unit/src/org/netbeans/api/editor/mimelookup/MimePathTest.java
+++ b/editor.mimelookup/test/unit/src/org/netbeans/api/editor/mimelookup/MimePathTest.java
@@ -84,6 +84,8 @@
MimePath mpPrefix = mp.getPrefix(2);
assertTrue("text/x-java/text/x-ant+xml".equals(mpPrefix.getPath()));
+ List ll = mp.getInheritedPaths(null, null);
+
// Force exceed size of the internal LRU cache and release the created and cached MPs
for (int op = 0; op < 2; op++) {
for (int i = 0; i < 10; i++) {
diff --git a/editor.settings.storage/manifest.mf b/editor.settings.storage/manifest.mf
--- a/editor.settings.storage/manifest.mf
+++ b/editor.settings.storage/manifest.mf
@@ -2,6 +2,6 @@
OpenIDE-Module: org.netbeans.modules.editor.settings.storage/1
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/settings/storage/Bundle.properties
OpenIDE-Module-Provides: org.netbeans.api.editor.settings.implementation
-OpenIDE-Module-Specification-Version: 1.37
+OpenIDE-Module-Specification-Version: 1.38
OpenIDE-Module-Layer: org/netbeans/modules/editor/settings/storage/layer.xml
AutoUpdate-Show-In-Client: false
diff --git a/editor.settings.storage/nbproject/project.xml b/editor.settings.storage/nbproject/project.xml
--- a/editor.settings.storage/nbproject/project.xml
+++ b/editor.settings.storage/nbproject/project.xml
@@ -55,7 +55,7 @@
1
- 1.0
+ 1.31
@@ -176,6 +176,7 @@
org.netbeans.modules.jviorg.netbeans.modules.languagesorg.netbeans.modules.options.editor
+ org.netbeans.modules.editor.foldorg.netbeans.modules.parsing.apiorg.netbeans.modules.editor.settings.storage.apiorg.netbeans.modules.editor.settings.storage.spi
diff --git a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/EditorSettings.java b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/EditorSettings.java
--- a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/EditorSettings.java
+++ b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/EditorSettings.java
@@ -48,6 +48,7 @@
import java.util.Collection;
import java.util.Map;
import java.util.Set;
+import java.util.prefs.Preferences;
import javax.swing.text.AttributeSet;
import org.netbeans.modules.editor.settings.storage.EditorSettingsImpl;
import org.netbeans.modules.editor.settings.storage.MimeTypesTracker;
@@ -321,4 +322,22 @@
String propertyName,
PropertyChangeListener l
);
+
+ /**
+ * Provides a temporary storage for Preferences, which can be
+ * flushed to the delegate.
+ * The returned object collects changes made through the {@link Preferences}
+ * interface, but does not write them to the 'delegate' instance immediately.
+ * It writes the changes (adds new keys, changes existing ones, removes the
+ * obsolete keys) during {@link Preferences#flush}.
+ *
+ * The token controls the lifecycle of the Preferences tree. If left unattended,
+ * it will be destroyed when the 'token' object becomes GCed. Each token instance
+ * has its own full Preferences tree.
+ *
+ * @param token token to control lifecycle and separate instances
+ * @param delegate the backing Preferences
+ * @return Preferences with temporary storage.
+ */
+ // public abstract MemoryPreferences getProxyPreferences(Object token, Preferences delegate);
}
diff --git a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/MemoryPreferences.java b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/MemoryPreferences.java
new file mode 100644
--- /dev/null
+++ b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/MemoryPreferences.java
@@ -0,0 +1,171 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2013 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.editor.settings.storage.api;
+
+import java.util.prefs.Preferences;
+import org.netbeans.modules.editor.settings.storage.preferences.InheritedPreferences;
+import org.netbeans.modules.editor.settings.storage.preferences.ProxyPreferencesImpl;
+
+/**
+ * Preferences with a temporary storage, backed by another Preferences
+ * object. The instance tracks modifications done through the
+ * {@link Preferences} interface, but do not change the backing store
+ * until {@link Preferences#flush} is called.
+ *
+ * The MemoryPreferences object serves as an accessor, and offers some
+ * additional control for the Preferences tree. It should not be handed
+ * away, only the creator who manages the lifecycle should possess
+ * the MemoryPreferences instance. Other clients should be given just the
+ * Preferences object from {@link #getPreferences}.
+ *
+ * The returned Preferences object implements {@link LocalPreferences} extension
+ * interface.
+ *
+ * This implementation does not support sub-nodes.
+ *
+ * @author sdedic
+ * @author Vita Stejskal
+ */
+public final class MemoryPreferences {
+ /**
+ * Returns an instance of Preferences backed by the delegate.
+ * A token is used to identify the desired Preferences set. As long as {@link #destroy} is not called,
+ * calls which use the same token & delegate will receive the same Preferences objects (though their
+ * MemoryPreferences may differ). The returned object implements {@link LocalPreferences}
+ * interface.
+ *
+ * @param token token that determines the tree of Preferences.
+ * @param delegate
+ * @return MemoryPreferences accessor instance
+ */
+ public static MemoryPreferences get(Object token, Preferences delegate) {
+ return new MemoryPreferences(ProxyPreferencesImpl.getProxyPreferences(token, delegate));
+ }
+
+ /**
+ * Creates Preferences, which delegates to both persistent storage and parent (inherited) preferences.
+ * The persistent storage takes precedence over the parent. The {@link Preferences#remove} call is redefined
+ * for this case to just remove the key-value from the 'delegate', so that 'parent' value (if any) can become
+ * effective. Before {@link Preferences#flush}, the returned Preferences object delegates to both 'parent'
+ * and 'delegate' so that effective values can be seen. The returned object implements {@link LocalPreferences}
+ * interface.
+ *
+ * @param token
+ * @param parent
+ * @param delegate
+ * @return
+ */
+ public static MemoryPreferences getWithInherited(Object token, Preferences parent, Preferences delegate) {
+ if (parent == null) {
+ return get(token, delegate);
+ }
+ InheritedPreferences inh = new InheritedPreferences(parent, delegate);
+ return new MemoryPreferences(ProxyPreferencesImpl.getProxyPreferences(token, inh));
+ }
+
+ /**
+ * Provides the Preferences instance.
+ * The instance will collect writes in memory, as described in {@link MemoryPreferences} doc.
+ *
+ * @return instance of Preferences
+ */
+ public Preferences getPreferences() {
+ return prefInstance;
+ }
+
+ /**
+ * Destroys the whole tree this Preferences belongs to.
+ * Individual Preferences node will not be flushed or cleared, but will become
+ * inaccessible from their token
+ *
+ * @see {@link EditorSettings#getProxyPreferences}
+ */
+ public void destroy() {
+ prefInstance.destroy();
+ }
+
+ /**
+ * Suppresses events from this Preferences node.
+ * During the Runnable execution, the Preferences node will not
+ * fire any events.
+ *
+ * @param r runnable to execute
+ */
+ public void runWithoutEvents(Runnable r) {
+ try {
+ prefInstance.silence();
+ r.run();
+ } finally {
+ prefInstance.noise();
+ }
+ }
+
+ /**
+ * Checks whether the Preferences node is changed.
+ * Only value provided by the {@link #getPreferences} and values derived by call to {@link Preferences#node}
+ * on that instance are supported. In other words, Preferences object from the tree managed by this
+ * MemoryPreferences object. IllegalArgumentException can be thrown when an incompatible Preferences object
+ * is used.
+ *
+ * True will be returned, if the Preferences object is dirty and not flushed.
+ *
+ * @param pref preferences node to check
+ * @return true, if the preferences was modified, and not flushed
+ * @throws IllegalArgumentException if the pref node is not from the managed tree.
+ */
+ public boolean isDirty(Preferences pref) {
+ if (!(pref instanceof ProxyPreferencesImpl)) {
+ throw new IllegalArgumentException("Incompatible PreferencesImpl");
+ }
+ ProxyPreferencesImpl impl = (ProxyPreferencesImpl)pref;
+ if (impl.node(prefInstance.absolutePath()) != prefInstance) {
+ throw new IllegalArgumentException("The preferences tree root is not reachable");
+ }
+ return impl.isDirty();
+ }
+
+ private ProxyPreferencesImpl prefInstance;
+
+ private MemoryPreferences(ProxyPreferencesImpl delegate) {
+ this.prefInstance = delegate;
+ }
+}
diff --git a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/OverridePreferences.java b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/OverridePreferences.java
new file mode 100644
--- /dev/null
+++ b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/OverridePreferences.java
@@ -0,0 +1,64 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2013 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.editor.settings.storage.api;
+
+/**
+ * Mixin interface to detect if a value is inherited (defaulted) or not.
+ * The interface is to be implemented on Preferences objects (e.g. Mime Preferences),
+ * which support some sort of fallback, inheritance or default. It allows
+ * clients to determine whether a preference key is defined at the level represented
+ * by the Preferences object, or whether the value produced by {@link java.util.prefs.Preferences#get}
+ * originates in some form of default or inherited values.
+ *
+ * @author sdedic
+ */
+public interface OverridePreferences {
+ /**
+ * Determines whether the value is defined locally.
+ * If the value comes from an inherited or default set of values,
+ * the method returns {@code false}.
+ *
+ * @param key key to check
+ * @return true, if the value is defined locally, false if inherited.
+ */
+ public boolean isOverriden(String key);
+}
diff --git a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/fontscolors/CompositeFCS.java b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/fontscolors/CompositeFCS.java
--- a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/fontscolors/CompositeFCS.java
+++ b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/fontscolors/CompositeFCS.java
@@ -113,12 +113,12 @@
mimePath = mimePath.getPrefix(mimePath.size() - 1);
}
- MimePath[] allPaths = computeInheritedMimePaths(mimePath);
- assert allPaths.length > 0 : "allPaths should always contain at least MimePath.EMPTY"; //NOI18N
+ List allPaths = mimePath.getIncludedPaths();
+ assert allPaths.size() > 0 : "allPaths should always contain at least MimePath.EMPTY"; //NOI18N
- this.allFcsi = new FontColorSettingsImpl[allPaths.length];
- for (int i = 0; i < allPaths.length; i++) {
- allFcsi[i] = FontColorSettingsImpl.get(allPaths[i]);
+ this.allFcsi = new FontColorSettingsImpl[allPaths.size()];
+ for (int i = 0; i < allPaths.size(); i++) {
+ allFcsi[i] = FontColorSettingsImpl.get(allPaths.get(i));
}
this.profile = profile;
@@ -275,31 +275,6 @@
System.out.println(sb.toString());
}
- private static MimePath[] computeInheritedMimePaths(MimePath mimePath) {
- List paths = null;
- try {
- Method m = MimePath.class.getDeclaredMethod("getInheritedPaths", String.class, String.class); //NOI18N
- m.setAccessible(true);
- @SuppressWarnings("unchecked")
- List ret = (List) m.invoke(mimePath, null, null);
- paths = ret;
- } catch (Exception e) {
- LOG.log(Level.WARNING, "Can't call org.netbeans.api.editor.mimelookup.MimePath.getInheritedPaths method.", e); //NOI18N
- }
-
- if (paths != null) {
- ArrayList mimePaths = new ArrayList(paths.size());
-
- for (String path : paths) {
- mimePaths.add(MimePath.parse(path));
- }
-
- return mimePaths.toArray(new MimePath[mimePaths.size()]);
- } else {
- return new MimePath[]{mimePath, MimePath.EMPTY};
- }
- }
-
private Map, ?> getRenderingHints() {
// This property was introduced in JDK1.6, see http://java.sun.com/javase/6/docs/api/java/awt/doc-files/DesktopProperties.html
// We should probably also listen on the default toolkit for changes in this
diff --git a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/InheritedPreferences.java b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/InheritedPreferences.java
new file mode 100644
--- /dev/null
+++ b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/InheritedPreferences.java
@@ -0,0 +1,258 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2013 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.editor.settings.storage.preferences;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.prefs.AbstractPreferences;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.PreferenceChangeEvent;
+import java.util.prefs.PreferenceChangeListener;
+import java.util.prefs.Preferences;
+import org.netbeans.modules.editor.settings.storage.api.OverridePreferences;
+
+/**
+ * Support for inheriting Preferences, while still working with stored ones.
+ *
+ * This class solves the 'diamond inheritance', which is present during editing:
+ * a MIME-type preferences derive from BOTH its persistent values (preferred) and
+ * from the parent, whose actual values are potentially transient, and also persistent.
+ *
+ * Let us assume the following assignment:
+ *
+ *
TC (this current) = currently added/changed/removed values
+ *
TP (this persistent) = persistent values, the getLocal() part of the Mime PreferencesImpl object
+ *
PC (parent current) = currently added/changed/removed values of the parent
+ *
PP (parent persistent) = persistent values, the getLocal() part of the parent MimePreferences
+ *
+ * The desired priority to find a value is: TC, TP, PC, PP. Because of {@link MemoryPreferences}, the
+ * PC, PP (and potentially fallback to a grandparent) we already have, if we use the parent's {@link MemoryPreferences}
+ * preferences as 'inherited'. The "TC" is handled by ProxyPreferences for this Mime node. In InheritedPreferences,
+ * we must only inject the TP in between TC and the parent's preferences (PC, PP, ...)
+ *
+ * The object is intended to act as a ProxyPreferences delegate, all writes go directly to the stored
+ * Mime preferences.
+ *
+ * @author sdedic
+ */
+public final class InheritedPreferences extends AbstractPreferences implements PreferenceChangeListener, OverridePreferences {
+ /**
+ * Preferences inherited, ie from a parent Mime type
+ */
+ private Preferences inherited;
+
+ /**
+ * Stored preferences,
+ */
+ private Preferences stored;
+
+ public InheritedPreferences(Preferences inherited, Preferences stored) {
+ super(null, ""); // NOI18N
+ this.inherited = inherited;
+ if (!(stored instanceof OverridePreferences)) {
+ throw new IllegalArgumentException();
+ }
+ this.stored = stored;
+
+ inherited.addPreferenceChangeListener(this);
+ }
+
+ /* package */ Preferences getParent() {
+ return inherited;
+ }
+
+ @Override
+ protected void putSpi(String key, String value) {
+ // do nothing, the AbstractPref then just fires an event
+ }
+
+ @Override
+ public void put(String key, String value) {
+ if (Boolean.TRUE != ignorePut.get()) {
+ stored.put(key, value);
+ }
+ super.put(key, value);
+ }
+
+ @Override
+ public void putInt(String key, int value) {
+ if (Boolean.TRUE != ignorePut.get()) {
+ stored.putInt(key, value);
+ }
+ super.putInt(key, value);
+ }
+
+ @Override
+ public void putLong(String key, long value) {
+ if (Boolean.TRUE != ignorePut.get()) {
+ stored.putLong(key, value);
+ }
+ super.putLong(key, value);
+ }
+
+ @Override
+ public void putBoolean(String key, boolean value) {
+ if (Boolean.TRUE != ignorePut.get()) {
+ stored.putBoolean(key, value);
+ }
+ super.putBoolean(key, value);
+ }
+
+ @Override
+ public void putFloat(String key, float value) {
+ if (Boolean.TRUE != ignorePut.get()) {
+ stored.putFloat(key, value);
+ }
+ super.putFloat(key, value);
+ }
+
+ @Override
+ public void putDouble(String key, double value) {
+ if (Boolean.TRUE != ignorePut.get()) {
+ stored.putDouble(key, value);
+ }
+ super.putDouble(key, value);
+ }
+
+ @Override
+ public void putByteArray(String key, byte[] value) {
+ if (Boolean.TRUE != ignorePut.get()) {
+ stored.putByteArray(key, value);
+ }
+ super.putByteArray(key, value);
+ }
+
+ private ThreadLocal ignorePut = new ThreadLocal();
+
+ @Override
+ public void preferenceChange(PreferenceChangeEvent evt) {
+ if (!isOverriden(evt.getKey())) {
+ // jusr refires an event
+ ignorePut.set(true);
+ try {
+ put(evt.getKey(), evt.getNewValue());
+ } finally {
+ ignorePut.set(false);
+ }
+ }
+ }
+
+ /**
+ * The value is defined locally, if the stored prefs define the value
+ * locally. The parent definitions do not count. It is expected, that the
+ * ProxyPreferences will report its local overrides as local in front of this
+ * InheritedPreferences.
+ *
+ * @param k
+ * @return
+ */
+ public @Override boolean isOverriden(String k) {
+ if (stored instanceof OverridePreferences) {
+ return ((OverridePreferences)stored).isOverriden(k);
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ protected String getSpi(String key) {
+ // check the stored values
+ OverridePreferences localStored = (OverridePreferences)stored;
+ if (localStored.isOverriden(key)) {
+ return stored.get(key, null);
+ }
+ // fall back to the inherited prefs, potentially its stored values etc.
+ return inherited.get(key, null);
+ }
+
+ @Override
+ protected void removeSpi(String key) {
+ stored.remove(key);
+ }
+
+ @Override
+ protected void removeNodeSpi() throws BackingStoreException {
+ stored.removeNode();
+ }
+
+ @Override
+ protected String[] keysSpi() throws BackingStoreException {
+ Collection names = new HashSet();
+ names.addAll(Arrays.asList(stored.keys()));
+ names.addAll(Arrays.asList(inherited.keys()));
+ return names.toArray(new String[names.size()]);
+ }
+
+ @Override
+ protected String[] childrenNamesSpi() throws BackingStoreException {
+ if (stored != null) {
+ return stored.childrenNames();
+ } else {
+ return new String[0];
+ }
+ }
+
+ @Override
+ protected AbstractPreferences childSpi(String name) {
+ Preferences storedNode = stored != null ? stored.node(name) : null;
+ if (storedNode != null) {
+ return new InheritedPreferences(null, storedNode);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected void syncSpi() throws BackingStoreException {
+ stored.sync();
+ }
+
+ @Override
+ protected void flushSpi() throws BackingStoreException {
+ stored.flush();
+ }
+
+
+}
diff --git a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/PreferencesImpl.java b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/PreferencesImpl.java
--- a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/PreferencesImpl.java
+++ b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/PreferencesImpl.java
@@ -63,6 +63,7 @@
import java.util.prefs.Preferences;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.modules.editor.settings.storage.api.EditorSettingsStorage;
+import org.netbeans.modules.editor.settings.storage.api.OverridePreferences;
import org.netbeans.modules.editor.settings.storage.spi.TypedValue;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
@@ -71,7 +72,7 @@
*
* @author vita
*/
-public final class PreferencesImpl extends AbstractPreferences implements PreferenceChangeListener {
+public final class PreferencesImpl extends AbstractPreferences implements PreferenceChangeListener, OverridePreferences {
// the constant bellow is used in o.n.e.Settings!!
private static final String JAVATYPE_KEY_PREFIX = "nbeditor-javaType-for-legacy-setting_"; //NOI18N
@@ -154,6 +155,18 @@
}
}
}
+
+ public @Override boolean isOverriden(String key) {
+ synchronized (lock) {
+ String bareKey;
+ if (key.startsWith(JAVATYPE_KEY_PREFIX)) {
+ bareKey = key.substring(JAVATYPE_KEY_PREFIX.length());
+ } else {
+ bareKey = key;
+ }
+ return getLocal().containsKey(bareKey);
+ }
+ }
public @Override void remove(String key) {
synchronized(lock) {
@@ -473,20 +486,9 @@
private Preferences getInherited() {
if (inherited == null && mimePath.length() > 0) {
- List paths = null;
- try {
- Method m = MimePath.class.getDeclaredMethod("getInheritedPaths", String.class, String.class); //NOI18N
- m.setAccessible(true);
- @SuppressWarnings("unchecked")
- List ret = (List) m.invoke(MimePath.parse(mimePath), null, null);
- paths = ret;
- } catch (Exception e) {
- LOG.log(Level.WARNING, "Can't call org.netbeans.api.editor.mimelookup.MimePath.getInheritedPaths method.", e); //NOI18N
- }
-
- if (paths != null) {
- assert paths.size() > 1 : "Wrong getInheritedPaths result size: " + paths.size(); //NOI18N
- inherited = get(MimePath.parse(paths.get(1)));
+ String type = MimePath.parse(mimePath).getInheritedType();
+ if (type != null) {
+ inherited = get(MimePath.parse(type));
} else {
inherited = get(MimePath.EMPTY);
}
diff --git a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/ProxyPreferencesImpl.java b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/ProxyPreferencesImpl.java
new file mode 100644
--- /dev/null
+++ b/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/ProxyPreferencesImpl.java
@@ -0,0 +1,1124 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2008 Sun Microsystems, Inc.
+ */
+
+package org.netbeans.modules.editor.settings.storage.preferences;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.NodeChangeEvent;
+import java.util.prefs.NodeChangeListener;
+import java.util.prefs.PreferenceChangeEvent;
+import java.util.prefs.PreferenceChangeListener;
+import java.util.prefs.Preferences;
+import javax.xml.bind.DatatypeConverter;
+import org.netbeans.modules.editor.settings.storage.api.OverridePreferences;
+import org.netbeans.modules.editor.settings.storage.spi.TypedValue;
+import org.openide.util.WeakListeners;
+
+/**
+ * Preferences impl that stores changes locally, and propagates them upon flush().
+ * The implementation is an adapted (former) implementation from org.netbeans.modules.options.indentation.ProxyPreferences.
+ * The original was moved here, and adapted to work with 'diamond' double defaulting: 1st default is
+ * the persistent Preferences object, where the changes will be finally propagated. The 2nd default
+ * is the Preferences object for the MIMEtype parent (if it exists). Keys that do not exist
+ * in the stored Preferences should delegate to the MIME parent. During editing, the MIME parent Preferences
+ * may get also changed, so we cannot rely on delegation between stored Mime Preferences, but must
+ * inject an additional path - see {@link InheritedPreferences}.
+ *
+ * @author sdedic
+ * @author vita
+ */
+public final class ProxyPreferencesImpl extends Preferences implements PreferenceChangeListener, NodeChangeListener,
+ OverridePreferences {
+
+ /**
+ * Inherited preferences, for the case that key does not exist at our Node.
+ * Special handling for diamond inheritance.
+ */
+ private Preferences inheritedPrefs;
+
+ public static ProxyPreferencesImpl getProxyPreferences(Object token, Preferences delegate) {
+ return Tree.getTree(token, delegate).get(null, delegate.name(), delegate); //NOI18N
+ }
+
+ public boolean isDirty() {
+ synchronized (tree.treeLock()) {
+ return !(data.isEmpty() && removedKeys.isEmpty() && children.isEmpty() && removedChildren.isEmpty()) || removed;
+ }
+ }
+
+ @Override
+ public void put(String key, String value) {
+ _put(key, value, String.class.getName());
+ }
+
+ @Override
+ public String get(String key, String def) {
+ synchronized (tree.treeLock()) {
+ checkNotNull(key, "key"); //NOI18N
+ checkRemoved();
+
+ if (removedKeys.contains(key)) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Key '" + key + "' removed, using default '" + def + "'"); //NOI18N
+ }
+ // removes will be flushed to the preferences, but now we need to see the defaults
+ // that WILL become effective after flush of this object.
+ if (inheritedPrefs != null) {
+ return inheritedPrefs.get(key, def);
+ } else {
+ return def;
+ }
+ } else {
+ TypedValue typedValue = data.get(key);
+ if (typedValue != null) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Key '" + key + "' modified, local value '" + typedValue.getValue() + "'"); //NOI18N
+ }
+ return typedValue.getValue();
+ } else if (delegate != null) {
+ String value = delegate.get(key, def);
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Key '" + key + "' undefined, original value '" + value + "'"); //NOI18N
+ }
+ return value;
+ } else {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Key '" + key + "' undefined, '" + name + "' is a new node, using default '" + def + "'"); //NOI18N
+ }
+ return def;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void remove(String key) {
+ EventBag bag = null;
+
+ synchronized (tree.treeLock()) {
+ checkNotNull(key, "key"); //NOI18N
+ checkRemoved();
+
+ if (removedKeys.add(key)) {
+ data.remove(key);
+ bag = new EventBag();
+ bag.addListeners(prefListeners);
+ if (inheritedPrefs != null) {
+ bag.addEvent(new PreferenceChangeEvent(this, key,
+ inheritedPrefs.get(key, null)));
+ } else {
+ bag.addEvent(new PreferenceChangeEvent(this, key, null));
+ }
+ }
+ }
+
+ if (bag != null) {
+ firePrefEvents(Collections.singletonList(bag));
+ }
+ }
+
+ @Override
+ public void clear() throws BackingStoreException {
+ EventBag bag = new EventBag();
+
+ synchronized (tree.treeLock()) {
+ checkRemoved();
+
+ // Determine modified or added keys
+ Set keys = new HashSet();
+ keys.addAll(data.keySet());
+ keys.removeAll(removedKeys);
+ if (!keys.isEmpty()) {
+ for(String key : keys) {
+ String value = delegate == null ? null : delegate.get(key, null);
+ bag.addEvent(new PreferenceChangeEvent(this, key, value));
+ }
+ }
+
+ // Determine removed keys
+ if (delegate != null) {
+ for(String key : removedKeys) {
+ String value = delegate.get(key, null);
+ if (value != null) {
+ bag.addEvent(new PreferenceChangeEvent(this, key, value));
+ }
+ }
+ }
+
+ // Initialize bag's listeners
+ bag.addListeners(prefListeners);
+
+ // Finally, remove the data
+ data.clear();
+ removedKeys.clear();
+ }
+
+ firePrefEvents(Collections.singletonList(bag));
+ }
+
+ @Override
+ public void putInt(String key, int value) {
+ _put(key, Integer.toString(value), Integer.class.getName());
+ }
+
+ @Override
+ public int getInt(String key, int def) {
+ String value = get(key, null);
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException nfe) {
+ // ignore
+ }
+ }
+ return def;
+ }
+
+ @Override
+ public void putLong(String key, long value) {
+ _put(key, Long.toString(value), Long.class.getName());
+ }
+
+ @Override
+ public long getLong(String key, long def) {
+ String value = get(key, null);
+ if (value != null) {
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException nfe) {
+ // ignore
+ }
+ }
+ return def;
+ }
+
+ @Override
+ public void putBoolean(String key, boolean value) {
+ _put(key, Boolean.toString(value), Boolean.class.getName());
+ }
+
+ @Override
+ public boolean getBoolean(String key, boolean def) {
+ String value = get(key, null);
+ if (value != null) {
+ return Boolean.parseBoolean(value);
+ } else {
+ return def;
+ }
+ }
+
+ @Override
+ public void putFloat(String key, float value) {
+ _put(key, Float.toString(value), Float.class.getName());
+ }
+
+ @Override
+ public float getFloat(String key, float def) {
+ String value = get(key, null);
+ if (value != null) {
+ try {
+ return Float.parseFloat(value);
+ } catch (NumberFormatException nfe) {
+ // ignore
+ }
+ }
+ return def;
+ }
+
+ @Override
+ public void putDouble(String key, double value) {
+ _put(key, Double.toString(value), Double.class.getName());
+ }
+
+ @Override
+ public double getDouble(String key, double def) {
+ String value = get(key, null);
+ if (value != null) {
+ try {
+ return Double.parseDouble(value);
+ } catch (NumberFormatException nfe) {
+ // ignore
+ }
+ }
+ return def;
+ }
+
+ @Override
+ public void putByteArray(String key, byte[] value) {
+ _put(key, DatatypeConverter.printBase64Binary(value), value.getClass().getName());
+ }
+
+ @Override
+ public byte[] getByteArray(String key, byte[] def) {
+ String value = get(key, null);
+ if (value != null) {
+ byte [] decoded = DatatypeConverter.parseBase64Binary(value);
+ if (decoded != null) {
+ return decoded;
+ }
+ }
+ return def;
+ }
+
+ @Override
+ public String[] keys() throws BackingStoreException {
+ synchronized (tree.treeLock()) {
+ checkRemoved();
+ HashSet keys = new HashSet();
+ if (delegate != null) {
+ keys.addAll(Arrays.asList(delegate.keys()));
+ }
+ keys.addAll(data.keySet());
+ keys.removeAll(removedKeys);
+ return keys.toArray(new String [keys.size()]);
+ }
+ }
+
+ @Override
+ public String[] childrenNames() throws BackingStoreException {
+ synchronized (tree.treeLock()) {
+ checkRemoved();
+ HashSet names = new HashSet();
+ if (delegate != null) {
+ names.addAll(Arrays.asList(delegate.childrenNames()));
+ }
+ names.addAll(children.keySet());
+ names.removeAll(removedChildren);
+ return names.toArray(new String [names.size()]);
+ }
+ }
+
+ @Override
+ public Preferences parent() {
+ synchronized (tree.treeLock()) {
+ checkRemoved();
+ return parent;
+ }
+ }
+
+ @Override
+ public Preferences node(String pathName) {
+ Preferences node;
+ LinkedList> events = new LinkedList>();
+
+ synchronized (tree.treeLock()) {
+ checkNotNull(pathName, "pathName"); //NOI18N
+ checkRemoved();
+ node = node(pathName, true, events);
+ }
+
+ fireNodeEvents(events);
+ return node;
+ }
+
+ @Override
+ public boolean nodeExists(String pathName) throws BackingStoreException {
+ synchronized (tree.treeLock()) {
+ if (pathName.length() == 0) {
+ return !removed;
+ } else {
+ checkRemoved();
+ return node(pathName, false, null) != null;
+ }
+ }
+ }
+
+ @Override
+ public void removeNode() throws BackingStoreException {
+ synchronized (tree.treeLock()) {
+ checkRemoved();
+ ProxyPreferencesImpl p = parent;
+ if (p != null) {
+ p.removeChild(this);
+ } else {
+ throw new UnsupportedOperationException("Can't remove the root."); //NOI18N
+ }
+ }
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public String absolutePath() {
+ synchronized (tree.treeLock()) {
+ ProxyPreferencesImpl pp = parent;
+ if (pp != null) {
+ if (pp.parent == null) {
+ // pp is the root, we don't want two consecutive slashes in the path
+ return "/" + name(); //NOI18N
+ } else {
+ return pp.absolutePath() + "/" + name(); //NOI18N
+ }
+ } else {
+ return "/"; //NOI18N
+ }
+ }
+ }
+
+ @Override
+ public boolean isUserNode() {
+ synchronized (tree.treeLock()) {
+ if (delegate != null) {
+ return delegate.isUserNode();
+ } else {
+ ProxyPreferencesImpl pp = parent;
+ if (pp != null) {
+ return pp.isUserNode();
+ } else {
+ return true;
+ }
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return (isUserNode() ? "User" : "System") + " Preference Node: " + absolutePath(); //NOI18N
+ }
+
+ @Override
+ public void flush() throws BackingStoreException {
+ synchronized (tree.treeLock()) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Flushing " + absolutePath());
+ }
+
+ checkRemoved();
+ for(ProxyPreferencesImpl pp : children.values()) {
+ pp.flush();
+ }
+
+ if (delegate == null) {
+ ProxyPreferencesImpl proxyRoot = parent.node("/", false, null); //NOI18N
+ assert proxyRoot != null : "Root must always exist"; //NOI18N
+
+ Preferences delegateRoot = proxyRoot.delegate;
+ assert delegateRoot != null : "Root must always have its corresponding delegate"; //NOI18N
+
+ Preferences nueDelegate = delegateRoot.node(absolutePath());
+ changeDelegate(nueDelegate);
+ }
+
+ delegate.removeNodeChangeListener(weakNodeListener);
+ delegate.removePreferenceChangeListener(weakPrefListener);
+ try {
+ // remove all removed children
+ for(String childName : removedChildren) {
+ if (delegate.nodeExists(childName)) {
+ delegate.node(childName).removeNode();
+ }
+ }
+
+ // write all valid key-value pairs
+ for(String key : data.keySet()) {
+ if (!removedKeys.contains(key)) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Writing " + absolutePath() + "/" + key + "=" + data.get(key));
+ }
+
+ TypedValue typedValue = data.get(key);
+ if (String.class.getName().equals(typedValue.getJavaType())) {
+ delegate.put(key, typedValue.getValue());
+
+ } else if (Integer.class.getName().equals(typedValue.getJavaType())) {
+ delegate.putInt(key, Integer.parseInt(typedValue.getValue()));
+
+ } else if (Long.class.getName().equals(typedValue.getJavaType())) {
+ delegate.putLong(key, Long.parseLong(typedValue.getValue()));
+
+ } else if (Boolean.class.getName().equals(typedValue.getJavaType())) {
+ delegate.putBoolean(key, Boolean.parseBoolean(typedValue.getValue()));
+
+ } else if (Float.class.getName().equals(typedValue.getJavaType())) {
+ delegate.putFloat(key, Float.parseFloat(typedValue.getValue()));
+
+ } else if (Double.class.getName().equals(typedValue.getJavaType())) {
+ delegate.putDouble(key, Double.parseDouble(typedValue.getValue()));
+
+ } else {
+ delegate.putByteArray(key, DatatypeConverter.parseBase64Binary(typedValue.getValue()));
+ }
+ }
+ }
+ data.clear();
+
+ // remove all removed keys
+ for(String key : removedKeys) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Removing " + absolutePath() + "/" + key);
+ }
+ delegate.remove(key);
+ }
+ removedKeys.clear();
+ } finally {
+ delegate.addNodeChangeListener(weakNodeListener);
+ delegate.addPreferenceChangeListener(weakPrefListener);
+ }
+ }
+ }
+
+ @Override
+ public void sync() throws BackingStoreException {
+ ArrayList> prefEvents = new ArrayList>();
+ ArrayList> nodeEvents = new ArrayList>();
+
+ synchronized (tree.treeLock()) {
+ _sync(prefEvents, nodeEvents);
+ }
+
+ fireNodeEvents(nodeEvents);
+ firePrefEvents(prefEvents);
+ }
+
+ @Override
+ public void addPreferenceChangeListener(PreferenceChangeListener pcl) {
+ synchronized (tree.treeLock()) {
+ prefListeners.add(pcl);
+ }
+ }
+
+ @Override
+ public void removePreferenceChangeListener(PreferenceChangeListener pcl) {
+ synchronized (tree.treeLock()) {
+ prefListeners.remove(pcl);
+ }
+ }
+
+ @Override
+ public void addNodeChangeListener(NodeChangeListener ncl) {
+ synchronized (tree.treeLock()) {
+ nodeListeners.add(ncl);
+ }
+ }
+
+ @Override
+ public void removeNodeChangeListener(NodeChangeListener ncl) {
+ synchronized (tree.treeLock()) {
+ nodeListeners.remove(ncl);
+ }
+ }
+
+ @Override
+ public void exportNode(OutputStream os) throws IOException, BackingStoreException {
+ throw new UnsupportedOperationException("exportNode not supported");
+ }
+
+ @Override
+ public void exportSubtree(OutputStream os) throws IOException, BackingStoreException {
+ throw new UnsupportedOperationException("exportSubtree not supported");
+ }
+
+ // ------------------------------------------------------------------------
+ // PreferenceChangeListener implementation
+ // ------------------------------------------------------------------------
+
+ public void preferenceChange(PreferenceChangeEvent evt) {
+ PreferenceChangeListener [] listeners;
+ String nValue = evt.getNewValue();
+ String k = evt.getKey();
+ synchronized (tree.treeLock()) {
+ if (removed || data.containsKey(k)) {
+ return;
+ }
+ if (removedKeys.contains(k)) {
+ if (inheritedPrefs == null) {
+ return;
+ } else {
+ // if removed && there are inherited preferences, we must report the 'new value'
+ // from the inherited prefs, as the override in our preferences is not in effect now.
+ nValue = inheritedPrefs.get(k, null);
+ }
+ }
+ listeners = prefListeners.toArray(new PreferenceChangeListener[prefListeners.size()]);
+ }
+
+ PreferenceChangeEvent myEvt = null;
+ for(PreferenceChangeListener l : listeners) {
+ if (myEvt == null) {
+ myEvt = new PreferenceChangeEvent(this, k, nValue);
+ }
+ l.preferenceChange(myEvt);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // NodeChangeListener implementation
+ // ------------------------------------------------------------------------
+
+ public void childAdded(NodeChangeEvent evt) {
+ NodeChangeListener [] listeners;
+ Preferences childNode;
+
+ synchronized (tree.treeLock()) {
+ String childName = evt.getChild().name();
+ if (removed || removedChildren.contains(childName)) {
+ return;
+ }
+
+ childNode = children.get(childName);
+ if (childNode != null) {
+ // swap delegates
+ ((ProxyPreferencesImpl) childNode).changeDelegate(evt.getChild());
+ } else {
+ childNode = node(evt.getChild().name());
+ }
+
+ listeners = nodeListeners.toArray(new NodeChangeListener[nodeListeners.size()]);
+ }
+
+ NodeChangeEvent myEvt = null;
+ for(NodeChangeListener l : listeners) {
+ if (myEvt == null) {
+ myEvt = new NodeChangeEvent(this, childNode);
+ }
+ l.childAdded(evt);
+ }
+ }
+
+ public void childRemoved(NodeChangeEvent evt) {
+ NodeChangeListener [] listeners;
+ Preferences childNode;
+
+ synchronized (tree.treeLock()) {
+ String childName = evt.getChild().name();
+ if (removed || removedChildren.contains(childName)) {
+ return;
+ }
+
+ childNode = children.get(childName);
+ if (childNode != null) {
+ // swap delegates
+ ((ProxyPreferencesImpl) childNode).changeDelegate(null);
+ } else {
+ // nobody has accessed the child yet
+ return;
+ }
+
+ listeners = nodeListeners.toArray(new NodeChangeListener[nodeListeners.size()]);
+ }
+
+ NodeChangeEvent myEvt = null;
+ for(NodeChangeListener l : listeners) {
+ if (myEvt == null) {
+ myEvt = new NodeChangeEvent(this, childNode);
+ }
+ l.childAdded(evt);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Other public implementation
+ // ------------------------------------------------------------------------
+
+ /**
+ * Destroys whole preferences tree as if called on the root.
+ */
+ public void destroy() {
+ synchronized (tree.treeLock()) {
+ tree.destroy();
+ }
+ }
+
+ public void silence() {
+ synchronized (tree.treeLock()) {
+ noEvents = true;
+ }
+ }
+
+ public void noise() {
+ synchronized (tree.treeLock()) {
+ noEvents = false;
+ }
+ }
+
+ @Override
+ public boolean isOverriden(String key) {
+ return data.containsKey(key);
+ }
+
+ // ------------------------------------------------------------------------
+ // private implementation
+ // ------------------------------------------------------------------------
+
+ private static final Logger LOG = Logger.getLogger(ProxyPreferencesImpl.class.getName());
+
+ private final ProxyPreferencesImpl parent;
+ private final String name;
+ private Preferences delegate;
+ private final Tree tree;
+ private boolean removed;
+
+ private final Map data = new HashMap();
+ private final Set removedKeys = new HashSet();
+ private final Map children = new HashMap();
+ private final Set removedChildren = new HashSet();
+
+ private boolean noEvents = false;
+ private PreferenceChangeListener weakPrefListener;
+ private final Set prefListeners = new HashSet();
+ private NodeChangeListener weakNodeListener;
+ private final Set nodeListeners = new HashSet();
+
+ private ProxyPreferencesImpl(ProxyPreferencesImpl parent, String name, Preferences delegate, Tree tree) {
+ assert name != null;
+
+ this.parent = parent;
+ this.name = name;
+ this.delegate = delegate;
+ if (delegate instanceof InheritedPreferences) {
+ this.inheritedPrefs = ((InheritedPreferences)delegate).getParent();
+ }
+ if (delegate != null) {
+ assert name.equals(delegate.name());
+
+ weakPrefListener = WeakListeners.create(PreferenceChangeListener.class, this, delegate);
+ delegate.addPreferenceChangeListener(weakPrefListener);
+
+ weakNodeListener = WeakListeners.create(NodeChangeListener.class, this, delegate);
+ delegate.addNodeChangeListener(weakNodeListener);
+ }
+ this.tree = tree;
+ }
+
+ private void _put(String key, String value, String javaType) {
+ EventBag bag = null;
+
+ synchronized (tree.treeLock()) {
+ checkNotNull(key, "key"); //NOI18N
+ checkNotNull(value, "value"); //NOI18N
+ checkRemoved();
+
+ String orig = get(key, null);
+ if (orig == null || !orig.equals(value)) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Overwriting '" + key + "' = '" + value + "'"); //NOI18N
+ }
+
+ data.put(key, new TypedValue(value, javaType));
+ removedKeys.remove(key);
+
+ bag = new EventBag();
+ bag.addListeners(prefListeners);
+ bag.addEvent(new PreferenceChangeEvent(this, key, value));
+ }
+ }
+
+ if (bag != null) {
+ firePrefEvents(Collections.singletonList(bag));
+ }
+ }
+
+ private ProxyPreferencesImpl node(String pathName, boolean create, List> events) {
+ if (pathName.length() > 0 && pathName.charAt(0) == '/') { //NOI18N
+ // absolute path, if this is not the root then find the root
+ // and pass the call to it
+ if (parent != null) {
+ Preferences root = this;
+ while (root.parent() != null) {
+ root = root.parent();
+ }
+ return ((ProxyPreferencesImpl) root).node(pathName, create, events);
+ } else {
+ // this is the root, change the pathName to a relative path and proceed
+ pathName = pathName.substring(1);
+ }
+ }
+
+ if (pathName.length() > 0) {
+ String childName;
+ String pathFromChild;
+
+ int idx = pathName.indexOf('/'); //NOI18N
+ if (idx != -1) {
+ childName = pathName.substring(0, idx);
+ pathFromChild = pathName.substring(idx + 1);
+ } else {
+ childName = pathName;
+ pathFromChild = null;
+ }
+
+ ProxyPreferencesImpl child = children.get(childName);
+ if (child == null) {
+ if (removedChildren.contains(childName) && !create) {
+ // this child has been removed
+ return null;
+ }
+
+ Preferences childDelegate = null;
+ try {
+ if (delegate != null && delegate.nodeExists(childName)) {
+ childDelegate = delegate.node(childName);
+ }
+ } catch (BackingStoreException bse) {
+ // ignore
+ }
+
+ if (childDelegate != null || create) {
+ child = tree.get(this, childName, childDelegate);
+ children.put(childName, child);
+ removedChildren.remove(childName);
+
+ // fire event if we really created the new child node
+ if (childDelegate == null) {
+ EventBag bag = new EventBag();
+ bag.addListeners(nodeListeners);
+ bag.addEvent(new NodeChangeEventExt(this, child, false));
+ events.add(bag);
+ }
+ } else {
+ // childDelegate == null && !create
+ return null;
+ }
+ } else {
+ assert !child.removed;
+ }
+
+ return pathFromChild != null ? child.node(pathFromChild, create, events) : child;
+ } else {
+ return this;
+ }
+ }
+
+ private void addChild(ProxyPreferencesImpl child) {
+ ProxyPreferencesImpl pp = children.get(child.name());
+ if (pp == null) {
+ children.put(child.name(), child);
+ } else {
+ assert pp == child;
+ }
+ }
+
+ private void removeChild(ProxyPreferencesImpl child) {
+ assert child != null;
+ assert children.get(child.name()) == child;
+
+ child.nodeRemoved();
+ children.remove(child.name());
+ removedChildren.add(child.name());
+ }
+
+ private void nodeRemoved() {
+ for(ProxyPreferencesImpl pp : children.values()) {
+ pp.nodeRemoved();
+ }
+
+ data.clear();
+ removedKeys.clear();
+ children.clear();
+ removedChildren.clear();
+ tree.removeNode(this);
+
+ removed = true;
+ }
+
+ private void checkNotNull(Object paramValue, String paramName) {
+ if (paramValue == null) {
+ throw new NullPointerException("The " + paramName + " must not be null");
+ }
+ }
+
+ private void checkRemoved() {
+ if (removed) {
+ throw new IllegalStateException("The node '" + this + " has already been removed."); //NOI18N
+ }
+ }
+
+ private void changeDelegate(Preferences nueDelegate) {
+ if (delegate != null) {
+ try {
+ if (delegate.nodeExists("")) { //NOI18N
+ assert weakPrefListener != null;
+ assert weakNodeListener != null;
+ delegate.removePreferenceChangeListener(weakPrefListener);
+ delegate.removeNodeChangeListener(weakNodeListener);
+ }
+ } catch (BackingStoreException bse) {
+ LOG.log(Level.WARNING, null, bse);
+ }
+ }
+
+ delegate = nueDelegate;
+ weakPrefListener = null;
+ weakNodeListener = null;
+
+ if (delegate != null) {
+ weakPrefListener = WeakListeners.create(PreferenceChangeListener.class, this, delegate);
+ delegate.addPreferenceChangeListener(weakPrefListener);
+
+ weakNodeListener = WeakListeners.create(NodeChangeListener.class, this, delegate);
+ delegate.addNodeChangeListener(weakNodeListener);
+ }
+ }
+
+ private void _sync(
+ List> prefEvents,
+ List> nodeEvents
+ ) {
+ // synchronize all children firts
+ for(ProxyPreferencesImpl pp : children.values()) {
+ pp._sync(prefEvents, nodeEvents);
+ }
+
+ // report all new children as removed
+ EventBag nodeBag = new EventBag();
+ nodeBag.addListeners(nodeListeners);
+
+ for(ProxyPreferencesImpl pp : children.values()) {
+ if (pp.delegate == null) {
+ // new node that does not have corresponding node in the original hierarchy
+ nodeBag.addEvent(new NodeChangeEventExt(this, pp, true));
+ }
+ }
+
+ if (!nodeBag.getEvents().isEmpty()) {
+ nodeEvents.add(nodeBag);
+ }
+
+ // report all modified keys
+ if (delegate != null) {
+ EventBag prefBag = new EventBag();
+ prefBag.addListeners(prefListeners);
+ prefEvents.add(prefBag);
+
+ for(String key : data.keySet()) {
+ prefBag.addEvent(new PreferenceChangeEvent(this, key, delegate.get(key, data.get(key).getValue())));
+ }
+ } // else there is no corresponding node in the orig hierarchy and this node
+ // will be reported as removed
+
+ // erase modified data
+ for(NodeChangeEvent nce : nodeBag.getEvents()) {
+ children.remove(nce.getChild().name());
+ }
+ data.clear();
+ }
+
+ private void firePrefEvents(List> events) {
+ if (noEvents) {
+ return;
+ }
+
+ for(EventBag bag : events) {
+ for(PreferenceChangeEvent event : bag.getEvents()) {
+ for(PreferenceChangeListener l : bag.getListeners()) {
+ try {
+ l.preferenceChange(event);
+ } catch (Throwable t) {
+ LOG.log(Level.WARNING, null, t);
+ }
+ }
+ }
+ }
+ }
+
+ private void fireNodeEvents(List> events) {
+ if (noEvents) {
+ return;
+ }
+
+ for(EventBag bag : events) {
+ for(NodeChangeEvent event : bag.getEvents()) {
+ for(NodeChangeListener l : bag.getListeners()) {
+ try {
+ if ((event instanceof NodeChangeEventExt) && ((NodeChangeEventExt) event).isRemovalEvent()) {
+ l.childRemoved(event);
+ } else {
+ l.childAdded(event);
+ }
+ } catch (Throwable t) {
+ LOG.log(Level.WARNING, null, t);
+ }
+ }
+ }
+ }
+ }
+
+ /* test */ static final class Tree {
+
+ public static Tree getTree(Object token, Preferences prefs) {
+ synchronized (trees) {
+ // find all trees for the token
+ Map forest = trees.get(token);
+ if (forest == null) {
+ forest = new HashMap();
+ trees.put(token, forest);
+ }
+
+ // find the tree for the prefs' root
+ Preferences root = prefs.node("/"); //NOI18N
+ Tree tree = forest.get(root);
+ if (tree == null) {
+ tree = new Tree(token, root);
+ forest.put(root, tree);
+ }
+
+ return tree;
+ }
+ }
+
+ /* test */ static final Map> trees = new WeakHashMap>();
+
+ private final Preferences root;
+ private final Reference> tokenRef;
+ private final Map nodes = new HashMap();
+
+ private Tree(Object token, Preferences root) {
+ this.root = root;
+ this.tokenRef = new WeakReference(token);
+ }
+
+ public Object treeLock() {
+ return this;
+ }
+
+ public ProxyPreferencesImpl get(ProxyPreferencesImpl parent, String name, Preferences delegate) {
+ if (delegate != null) {
+ assert name.equals(delegate.name());
+
+ if (parent == null) {
+ Preferences parentDelegate = delegate.parent();
+ if (parentDelegate != null) {
+ parent = get(null, parentDelegate.name(), parentDelegate);
+ } // else delegate is the root
+ } else {
+ // sanity check
+ assert parent.delegate == delegate.parent();
+ }
+ }
+
+ String absolutePath;
+ if (parent == null) {
+ absolutePath = "/"; //NOI18N
+ } else if (parent.parent() == null) {
+ absolutePath = "/" + name; //NOI18N
+ } else {
+ absolutePath = parent.absolutePath() + "/" + name; //NOI18N
+ }
+
+ ProxyPreferencesImpl node = nodes.get(absolutePath);
+ if (node == null) {
+ node = new ProxyPreferencesImpl(parent, name, delegate, this);
+ nodes.put(absolutePath, node);
+
+ if (parent != null) {
+ parent.addChild(node);
+ }
+ } else {
+ assert !node.removed;
+ }
+
+ return node;
+ }
+
+ public void removeNode(ProxyPreferencesImpl node) {
+ String path = node.absolutePath();
+ assert nodes.containsKey(path);
+ ProxyPreferencesImpl pp = nodes.remove(path);
+ }
+
+ public void destroy() {
+ synchronized (trees) {
+ Object token = tokenRef.get();
+ if (token != null) {
+ trees.remove(token);
+ } // else the token has been GCed and therefore is not even in the trees map
+ }
+ }
+ } // End of Tree class
+
+ private static final class EventBag {
+ private final Set listeners = new HashSet();
+ private final Set events = new HashSet();
+
+ public EventBag() {
+ }
+
+ public Set extends L> getListeners() {
+ return listeners;
+ }
+
+ public Set extends E> getEvents() {
+ return events;
+ }
+
+ public void addListeners(Collection extends L> l) {
+ listeners.addAll(l);
+ }
+
+ public void addEvent(E event) {
+ events.add(event);
+ }
+ } // End of EventBag class
+
+ private static final class NodeChangeEventExt extends NodeChangeEvent {
+ private final boolean removal;
+ public NodeChangeEventExt(Preferences parent, Preferences child, boolean removal) {
+ super(parent, child);
+ this.removal = removal;
+ }
+
+ public boolean isRemovalEvent() {
+ return removal;
+ }
+ } // End of NodeChangeEventExt class
+}
diff --git a/editor.settings.storage/test/unit/src/org/netbeans/modules/editor/settings/storage/preferences/ProxyPreferencesImplTest.java b/editor.settings.storage/test/unit/src/org/netbeans/modules/editor/settings/storage/preferences/ProxyPreferencesImplTest.java
new file mode 100644
--- /dev/null
+++ b/editor.settings.storage/test/unit/src/org/netbeans/modules/editor/settings/storage/preferences/ProxyPreferencesImplTest.java
@@ -0,0 +1,544 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2008 Sun Microsystems, Inc.
+ */
+
+package org.netbeans.modules.editor.settings.storage.preferences;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.prefs.AbstractPreferences;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+import static junit.framework.Assert.assertEquals;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.modules.editor.settings.storage.api.OverridePreferences;
+import org.netbeans.modules.editor.settings.storage.api.MemoryPreferences;
+
+/**
+ *
+ * @author vita
+ */
+public class ProxyPreferencesImplTest extends NbTestCase {
+
+ public ProxyPreferencesImplTest(String name) {
+ super(name);
+ }
+
+ public void testSimpleRead() {
+ Preferences orig = Preferences.userRoot().node(getName());
+ orig.put("key-1", "value-1");
+
+ Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
+ assertEquals("Wrong value", "value-1", test.get("key-1", null));
+ }
+
+ public void testSimpleWrite() {
+ Preferences orig = Preferences.userRoot().node(getName());
+ assertNull("Original contains value", orig.get("key-1", null));
+
+ Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
+ test.put("key-1", "xyz");
+ assertEquals("Wrong value", "xyz", test.get("key-1", null));
+ }
+
+ public void testBase64() {
+ Preferences orig = Preferences.userRoot().node(getName());
+ assertNull("Original contains value", orig.get("key-1", null));
+ Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
+ test.putByteArray("key-1", "however you like it".getBytes());
+ assertEquals("Wrong value", "however you like it", new String(test.getByteArray("key-1", null)));
+ }
+
+ public void testSimpleSync() throws BackingStoreException {
+ Preferences orig = Preferences.userRoot().node(getName());
+ assertNull("Original contains value", orig.get("key-1", null));
+
+ Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
+ assertNull("Test should not contains pair", orig.get("key-1", null));
+
+ test.put("key-1", "xyz");
+ assertEquals("Test doesn't contain new pair", "xyz", test.get("key-1", null));
+
+ test.sync();
+ assertNull("Test didn't rollback pair", test.get("key-1", null));
+ }
+
+ public void testSimpleFlush() throws BackingStoreException {
+ Preferences orig = Preferences.userRoot().node(getName());
+ assertNull("Original contains value", orig.get("key-1", null));
+
+ Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
+ assertNull("Test should not contains pair", orig.get("key-1", null));
+
+ test.put("key-1", "xyz");
+ assertEquals("Test doesn't contain new pair", "xyz", test.get("key-1", null));
+
+ test.flush();
+ assertEquals("Test should still contain the pair", "xyz", test.get("key-1", null));
+ assertEquals("Test didn't flush the pair", "xyz", orig.get("key-1", null));
+ }
+
+ public void testSyncTree1() throws BackingStoreException {
+ String [] origTree = new String [] {
+ "CodeStyle/profile=GLOBAL",
+ };
+ String [] newTree = new String [] {
+ "CodeStyle/text/x-java/tab-size=2",
+ "CodeStyle/text/x-java/override-global-settings=true",
+ "CodeStyle/text/x-java/expand-tabs=true",
+ "CodeStyle/profile=PROJECT",
+ };
+
+ Preferences orig = Preferences.userRoot().node(getName());
+ write(orig, origTree);
+ checkContains(orig, origTree, "Orig");
+ checkNotContains(orig, newTree, "Orig");
+
+ Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
+ checkEquals("Test should be the same as Orig", orig, test);
+
+ write(test, newTree);
+ checkContains(test, newTree, "Test");
+
+ test.sync();
+ checkContains(orig, origTree, "Orig");
+ checkNotContains(orig, newTree, "Orig");
+ checkContains(test, origTree, "Test");
+ checkNotContains(test, newTree, "Test");
+ }
+
+ public void testFlushTree1() throws BackingStoreException {
+ String [] origTree = new String [] {
+ "CodeStyle/profile=GLOBAL",
+ };
+ String [] newTree = new String [] {
+ "CodeStyle/text/x-java/tab-size=2",
+ "CodeStyle/text/x-java/override-global-settings=true",
+ "CodeStyle/text/x-java/expand-tabs=true",
+ "CodeStyle/profile=PROJECT",
+ };
+
+ Preferences orig = Preferences.userRoot().node(getName());
+ write(orig, origTree);
+ checkContains(orig, origTree, "Orig");
+ checkNotContains(orig, newTree, "Orig");
+
+ Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
+ checkEquals("Test should be the same as Orig", orig, test);
+
+ write(test, newTree);
+ checkContains(test, newTree, "Test");
+
+ test.flush();
+ checkEquals("Test didn't flush to Orig", test, orig);
+ }
+
+ public void testRemoveKey() throws BackingStoreException {
+ Preferences orig = Preferences.userRoot().node(getName());
+ orig.put("key-2", "value-2");
+ assertNull("Original contains value", orig.get("key-1", null));
+ assertEquals("Original doesn't contain value", "value-2", orig.get("key-2", null));
+
+ Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
+ test.put("key-1", "xyz");
+ assertEquals("Wrong value", "xyz", test.get("key-1", null));
+
+ test.remove("key-1");
+ assertNull("Test contains removed key-1", test.get("key-1", null));
+
+ test.remove("key-2");
+ assertNull("Test contains removed key-2", test.get("key-2", null));
+
+ test.flush();
+ assertNull("Test flushed removed key-1", orig.get("key-1", null));
+ assertNull("Test.flush did not remove removed key-2", orig.get("key-2", null));
+ }
+
+ public void testRemoveNode() throws BackingStoreException {
+ Preferences orig = Preferences.userRoot().node(getName());
+ Preferences origChild = orig.node("child");
+
+ Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
+ assertTrue("Test child shoculd exist", test.nodeExists("child"));
+ Preferences testChild = test.node("child");
+
+ testChild.removeNode();
+ assertFalse("Removed test child should not exist", testChild.nodeExists(""));
+ assertFalse("Removed test child should not exist in parent", test.nodeExists("child"));
+
+ test.flush();
+ assertFalse("Test.flush did not remove orig child", origChild.nodeExists(""));
+ assertFalse("Test.flush did not remove orig child from parent", orig.nodeExists("child"));
+ }
+
+ public void testRemoveNodeCreateItAgain() throws BackingStoreException {
+ Preferences orig = Preferences.userRoot().node(getName());
+
+ Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
+ Preferences testChild = test.node("child");
+
+ testChild.removeNode();
+ assertFalse("Removed test child should not exist", testChild.nodeExists(""));
+ assertFalse("Removed test child should not exist in parent", test.nodeExists("child"));
+
+ Preferences testChild2 = test.node("child");
+ assertTrue("Recreated test child should exist", testChild2.nodeExists(""));
+ assertTrue("Recreated test child should exist in parent", test.nodeExists("child"));
+ assertNotSame("Recreated child must not be the same as the removed one", testChild2, testChild);
+ assertEquals("Wrong childrenNames list", Arrays.asList(new String [] { "child" }), Arrays.asList(test.childrenNames()));
+
+ try {
+ testChild.get("key", null);
+ fail("Removed test node should not be accessible");
+ } catch (Exception e) {
+ }
+
+ try {
+ testChild2.get("key", null);
+ } catch (Exception e) {
+ fail("Recreated test node should be accessible");
+ }
+
+ }
+
+ public void testRemoveHierarchy() throws BackingStoreException {
+ String [] origTree = new String [] {
+ "R.CodeStyle.project.expand-tabs=true",
+ "R.CodeStyle.project.indent-shift-width=6",
+ "R.CodeStyle.project.spaces-per-tab=6",
+ "R.CodeStyle.project.tab-size=7",
+ "R.CodeStyle.project.text-limit-width=88",
+ "R.CodeStyle.usedProfile=project",
+ "R.text.x-ruby.CodeStyle.project.indent-shift-width=2",
+ "R.text.x-ruby.CodeStyle.project.spaces-per-tab=2",
+ "R.text.x-ruby.CodeStyle.project.tab-size=2",
+ };
+ String [] newTree = new String [] {
+ "R.CodeStyle.project.expand-tabs=true",
+ "R.CodeStyle.project.indent-shift-width=3",
+ "R.CodeStyle.project.spaces-per-tab=3",
+ "R.CodeStyle.project.tab-size=5",
+ "R.CodeStyle.project.text-limit-width=77",
+ "R.CodeStyle.usedProfile=project",
+ "R.text.x-ruby.CodeStyle.project.indent-shift-width=2",
+ "R.text.x-ruby.CodeStyle.project.spaces-per-tab=2",
+ "R.text.x-ruby.CodeStyle.project.tab-size=2",
+ };
+
+ Preferences orig = Preferences.userRoot().node(getName());
+ write(orig, origTree);
+
+ checkContains(orig, origTree, "Orig");
+
+ Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
+ checkEquals("Test should be the same as Orig", orig, test);
+
+ Preferences testRoot = test.node("R");
+ removeAllKidsAndKeys(testRoot);
+
+ write(test, newTree);
+ checkContains(test, newTree, "Test");
+
+ test.flush();
+ checkEquals("Test didn't flush to Orig", test, orig);
+ }
+
+ public void testTreeGCed() throws BackingStoreException {
+ String [] newTree = new String [] {
+ "R.CodeStyle.project.expand-tabs=true",
+ "R.CodeStyle.project.indent-shift-width=3",
+ "R.CodeStyle.project.spaces-per-tab=3",
+ "R.CodeStyle.project.tab-size=5",
+ "R.CodeStyle.project.text-limit-width=77",
+ "R.CodeStyle.usedProfile=project",
+ "R.text.x-ruby.CodeStyle.project.indent-shift-width=2",
+ "R.text.x-ruby.CodeStyle.project.spaces-per-tab=2",
+ "R.text.x-ruby.CodeStyle.project.tab-size=2",
+ };
+
+ Preferences orig = Preferences.userRoot().node(getName());
+
+ Object treeToken = new Object();
+ Preferences test = ProxyPreferencesImpl.getProxyPreferences(treeToken, orig);
+ write(test, newTree);
+ checkContains(test, newTree, "Test");
+
+ Reference treeTokenRef = new WeakReference(treeToken);
+ Reference testRef = new WeakReference(test);
+ treeToken = null;
+ test = null;
+ assertGC("Tree token was not GCed", treeTokenRef, Collections.singleton(this));
+ // touch the WeakHashMap to expungeStaleEntries
+ Object dummyToken = new Object();
+ ProxyPreferencesImpl dummyPrefs = ProxyPreferencesImpl.getProxyPreferences(dummyToken, orig);
+ assertGC("Test preferences were not GCed", testRef, Collections.singleton(this));
+
+ }
+
+ /**
+ * Checks that a value not defined in delegate can be read from the parent prefs.
+ * Checks that if the parent prefs also do not define the value, the
+ * default from parameter is used.
+ *
+ * @throws Exception
+ */
+ public void testInheritedRead() throws Exception {
+ Preferences stored = new MapPreferences();
+ Preferences inherited = new MapPreferences();
+
+ stored.put("key-1", "value-1");
+ stored.put("key-3", "override");
+ inherited.put("key-2", "value-2");
+ inherited.put("key-3", "base");
+
+ MemoryPreferences mem = MemoryPreferences.getWithInherited(this, inherited, stored);
+ Preferences test = mem.getPreferences();
+
+ assertEquals("Wrong value 1", "value-1", test.get("key-1", null));
+ assertEquals("Wrong value 2", "value-2", test.get("key-2", "a"));
+ assertEquals("Wrong value 3", "override", test.get("key-3", "a"));
+ assertEquals("Wrong value 4", "value-4", test.get("key-4", "value-4"));
+ }
+
+ /**
+ * Asserts that if a value is remove()d during editing, the inherited value
+ * will be seen through. Also checks that the Preferences key is actually
+ * deleted on flush() and the inherited preferences is not altered.
+ */
+ public void testSeeInheritedThroughRemoves() throws Exception {
+ Preferences stored = new MapPreferences();
+ Preferences inherited = new MapPreferences();
+
+ stored.put("key", "value");
+ inherited.put("key", "parentValue");
+
+ MemoryPreferences mem = MemoryPreferences.getWithInherited(this, inherited, stored);
+ Preferences test = mem.getPreferences();
+
+ assertEquals("Does not see local value", "value", test.get("key", null));
+ test.remove("key");
+
+ assertEquals("Stored value changed prematurely", "value", stored.get("key", null));
+ assertEquals("Inherited not seen", "parentValue", test.get("key", null));
+
+ test.flush();
+ assertNull("Stored value not erased", stored.get("key", null));
+ assertEquals("Inherited changed", "parentValue", test.get("key", null));
+ }
+
+ // -----------------------------------------------------------------------
+ // private implementation
+ // -----------------------------------------------------------------------
+
+ private static class MapPreferences extends AbstractPreferences implements OverridePreferences {
+
+ private Map map = new HashMap();
+
+ public MapPreferences() {
+ super(null, ""); // NOI18N
+ }
+
+ @Override
+ public boolean isOverriden(String key) {
+ return map.containsKey(key);
+ }
+
+ protected void putSpi(String key, String value) {
+ map.put(key, value);
+ }
+
+ protected String getSpi(String key) {
+ return (String)map.get(key);
+ }
+
+ protected void removeSpi(String key) {
+ map.remove(key);
+ }
+
+ protected void removeNodeSpi() throws BackingStoreException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ protected String[] keysSpi() throws BackingStoreException {
+ String array[] = new String[map.keySet().size()];
+ return map.keySet().toArray( array );
+ }
+
+ protected String[] childrenNamesSpi() throws BackingStoreException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ protected AbstractPreferences childSpi(String name) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ protected void syncSpi() throws BackingStoreException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ protected void flushSpi() throws BackingStoreException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ private void write(Preferences prefs, String[] tree) {
+ for(String s : tree) {
+ int equalIdx = s.lastIndexOf('=');
+ assertTrue(equalIdx != -1);
+ String value = s.substring(equalIdx + 1);
+
+ String key;
+ String nodePath;
+ int slashIdx = s.lastIndexOf('/', equalIdx);
+ if (slashIdx != -1) {
+ key = s.substring(slashIdx + 1, equalIdx);
+ nodePath = s.substring(0, slashIdx);
+ } else {
+ key = s.substring(0, equalIdx);
+ nodePath = "";
+ }
+
+ Preferences node = prefs.node(nodePath);
+ node.put(key, value);
+ }
+ }
+
+ private void checkContains(Preferences prefs, String[] tree, String prefsId) throws BackingStoreException {
+ for(String s : tree) {
+ int equalIdx = s.lastIndexOf('=');
+ assertTrue(equalIdx != -1);
+ String value = s.substring(equalIdx + 1);
+
+ String key;
+ String nodePath;
+ int slashIdx = s.lastIndexOf('/', equalIdx);
+ if (slashIdx != -1) {
+ key = s.substring(slashIdx + 1, equalIdx);
+ nodePath = s.substring(0, slashIdx);
+ } else {
+ key = s.substring(0, equalIdx);
+ nodePath = "";
+ }
+
+ assertTrue(prefsId + " doesn't contain node '" + nodePath + "'", prefs.nodeExists(nodePath));
+ Preferences node = prefs.node(nodePath);
+
+ String realValue = node.get(key, null);
+ assertNotNull(prefsId + ", '" + nodePath + "' node doesn't contain key '" + key + "'", realValue);
+ assertEquals(prefsId + ", '" + nodePath + "' node, '" + key + "' contains wrong value", value, realValue);
+ }
+ }
+
+ private void checkNotContains(Preferences prefs, String[] tree, String prefsId) throws BackingStoreException {
+ for(String s : tree) {
+ int equalIdx = s.lastIndexOf('=');
+ assertTrue(equalIdx != -1);
+ String value = s.substring(equalIdx + 1);
+
+ String key;
+ String nodePath;
+ int slashIdx = s.lastIndexOf('/', equalIdx);
+ if (slashIdx != -1) {
+ key = s.substring(slashIdx + 1, equalIdx);
+ nodePath = s.substring(0, slashIdx);
+ } else {
+ key = s.substring(0, equalIdx);
+ nodePath = "";
+ }
+
+ if (prefs.nodeExists(nodePath)) {
+ Preferences node = prefs.node(nodePath);
+ String realValue = node.get(key, null);
+ if (realValue != null && realValue.equals(value)) {
+ fail(prefsId + ", '" + nodePath + "' node contains key '" + key + "' = '" + realValue + "'");
+ }
+ }
+ }
+ }
+
+ private void dump(Preferences prefs, String prefsId) throws BackingStoreException {
+ for(String key : prefs.keys()) {
+ System.out.println(prefsId + ", " + prefs.absolutePath() + "/" + key + "=" + prefs.get(key, null));
+ }
+ for(String child : prefs.childrenNames()) {
+ dump(prefs.node(child), prefsId);
+ }
+ }
+
+ private void checkEquals(String msg, Preferences expected, Preferences test) throws BackingStoreException {
+ assertEquals("Won't compare two Preferences with different absolutePath", expected.absolutePath(), test.absolutePath());
+
+ // check the keys and their values
+ for(String key : expected.keys()) {
+ String expectedValue = expected.get(key, null);
+ assertNotNull(msg + "; Expected:" + expected.absolutePath() + " has no '" + key + "'", expectedValue);
+
+ String value = test.get(key, null);
+ assertNotNull(msg + "; Test:" + test.absolutePath() + " has no '" + key + "'", value);
+ assertEquals(msg + "; Test:" + test.absolutePath() + "/" + key + " has wrong value", expectedValue, value);
+ }
+
+ // check the children
+ for(String child : expected.childrenNames()) {
+ assertTrue(msg + "; Expected:" + expected.absolutePath() + " has no '" + child + "' subnode", expected.nodeExists(child));
+ Preferences expectedChild = expected.node(child);
+
+ assertTrue(msg + "; Test:" + test.absolutePath() + " has no '" + child + "' subnode", test.nodeExists(child));
+ Preferences testChild = test.node(child);
+
+ checkEquals(msg, expectedChild, testChild);
+ }
+ }
+
+ private void removeAllKidsAndKeys(Preferences prefs) throws BackingStoreException {
+ for(String kid : prefs.childrenNames()) {
+ prefs.node(kid).removeNode();
+ }
+ for(String key : prefs.keys()) {
+ prefs.remove(key);
+ }
+ }
+
+}
diff --git a/editor/src/org/netbeans/modules/editor/resources/layer.xml b/editor/src/org/netbeans/modules/editor/resources/layer.xml
--- a/editor/src/org/netbeans/modules/editor/resources/layer.xml
+++ b/editor/src/org/netbeans/modules/editor/resources/layer.xml
@@ -251,6 +251,13 @@
+
+
+
+
+
+
+
diff --git a/html.editor/nbproject/project.xml b/html.editor/nbproject/project.xml
--- a/html.editor/nbproject/project.xml
+++ b/html.editor/nbproject/project.xml
@@ -123,6 +123,10 @@
org.netbeans.modules.editor.fold
+
+ 1
+ 1.34
+ org.netbeans.modules.editor.indent
diff --git a/html.editor/src/org/netbeans/modules/html/editor/gsf/HtmlFoldTypeProvider.java b/html.editor/src/org/netbeans/modules/html/editor/gsf/HtmlFoldTypeProvider.java
new file mode 100644
--- /dev/null
+++ b/html.editor/src/org/netbeans/modules/html/editor/gsf/HtmlFoldTypeProvider.java
@@ -0,0 +1,74 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2013 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.html.editor.gsf;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.netbeans.api.editor.fold.FoldType;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.api.editor.mimelookup.MimeRegistrations;
+import org.netbeans.spi.editor.fold.FoldTypeProvider;
+
+/**
+ * @author sdedic
+ */
+@MimeRegistrations({
+ @MimeRegistration(mimeType = "text/html", service = FoldTypeProvider.class),
+ @MimeRegistration(mimeType = "text/xhtml", service = FoldTypeProvider.class)
+})
+public class HtmlFoldTypeProvider implements FoldTypeProvider {
+ private Collection TYPES = Arrays.asList(new FoldType[] {
+ HtmlStructureScanner.TYPE_TAG,
+ HtmlStructureScanner.TYPE_COMMENT
+ });
+
+ @Override
+ public Collection getValues(Class type) {
+ return type == FoldType.class ? TYPES : null;
+ }
+
+ @Override
+ public boolean inheritable() {
+ return false;
+ }
+
+}
diff --git a/html.editor/src/org/netbeans/modules/html/editor/gsf/HtmlStructureScanner.java b/html.editor/src/org/netbeans/modules/html/editor/gsf/HtmlStructureScanner.java
--- a/html.editor/src/org/netbeans/modules/html/editor/gsf/HtmlStructureScanner.java
+++ b/html.editor/src/org/netbeans/modules/html/editor/gsf/HtmlStructureScanner.java
@@ -47,6 +47,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.BadLocationException;
+import org.netbeans.api.editor.fold.FoldType;
import org.netbeans.editor.BaseDocument;
import org.netbeans.editor.Utilities;
import org.netbeans.modules.csl.api.OffsetRange;
@@ -58,12 +59,27 @@
import org.netbeans.modules.parsing.api.Snapshot;
import org.netbeans.modules.web.common.api.Pair;
import org.openide.filesystems.FileObject;
+import org.openide.util.Exceptions;
+import org.openide.util.NbBundle;
+import static org.netbeans.modules.html.editor.gsf.Bundle.*;
/**
*
* @author mfukala@netbeans.org
*/
public class HtmlStructureScanner implements StructureScanner {
+
+ /**
+ * Tag fold type. Overrides the default label.
+ */
+ @NbBundle.Messages("FT_Tag=Tags")
+ public static final FoldType TYPE_TAG = FoldType.TAG.override(
+ FT_Tag(), FoldType.TAG.getTemplate());
+
+ /**
+ * HTML comments
+ */
+ public static final FoldType TYPE_COMMENT = FoldType.COMMENT;
private static final Logger LOGGER = Logger.getLogger(HtmlStructureScanner.class.getName());
private static final boolean LOG = LOGGER.isLoggable(Level.FINE);
@@ -187,8 +203,8 @@
} finally {
doc.readUnlock();
}
- folds.put("tags", tags);
- folds.put("comments", comments);
+ folds.put(TYPE_TAG.code(), tags);
+ folds.put(TYPE_COMMENT.code(), comments);
return folds;
}
diff --git a/html.editor/src/org/netbeans/modules/html/editor/resources/layer.xml b/html.editor/src/org/netbeans/modules/html/editor/resources/layer.xml
--- a/html.editor/src/org/netbeans/modules/html/editor/resources/layer.xml
+++ b/html.editor/src/org/netbeans/modules/html/editor/resources/layer.xml
@@ -133,6 +133,14 @@
+
+
+
+
+
+
+
+
@@ -268,6 +276,14 @@
+
+
+
+
+
+
+
+
diff --git a/java.editor.lib/manifest.mf b/java.editor.lib/manifest.mf
--- a/java.editor.lib/manifest.mf
+++ b/java.editor.lib/manifest.mf
@@ -1,5 +1,5 @@
Manifest-Version: 1.0
OpenIDE-Module: org.netbeans.modules.java.editor.lib/1
OpenIDE-Module-Localizing-Bundle: org/netbeans/lib/java/editor/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.28
+OpenIDE-Module-Specification-Version: 1.29
AutoUpdate-Show-In-Client: false
diff --git a/java.editor.lib/nbproject/project.properties b/java.editor.lib/nbproject/project.properties
--- a/java.editor.lib/nbproject/project.properties
+++ b/java.editor.lib/nbproject/project.properties
@@ -40,6 +40,8 @@
# Version 2 license, then the option applies only if the new code is
# made subject to such option by the copyright holder.
+javac.compilerargs=-Xlint -Xlint:-serial
+javac.source=1.5
#javadoc.arch=${basedir}/arch/arch-java-editor-lib.xml
javadoc.title=Java Editor Library
javadoc.apichanges=${basedir}/apichanges.xml
diff --git a/java.editor.lib/nbproject/project.xml b/java.editor.lib/nbproject/project.xml
--- a/java.editor.lib/nbproject/project.xml
+++ b/java.editor.lib/nbproject/project.xml
@@ -50,11 +50,21 @@
org.netbeans.modules.java.editor.lib
+ org.netbeans.modules.editor.deprecated.pre65formatting
+
+
+
+ 0-1
+ 1.0
+
+
+ org.netbeans.modules.editor.fold1
+ 1.34
@@ -76,6 +86,15 @@
+ org.netbeans.modules.editor.lib2
+
+
+
+ 1
+ 1.71
+
+
+ org.netbeans.modules.editor.mimelookup
@@ -108,15 +127,6 @@
8.0
-
- org.netbeans.modules.editor.deprecated.pre65formatting
-
-
-
- 0-1
- 1.0
-
- org.netbeans.editor.ext.java
diff --git a/java.editor.lib/src/org/netbeans/editor/ext/java/JavaFoldManager.java b/java.editor.lib/src/org/netbeans/editor/ext/java/JavaFoldManager.java
--- a/java.editor.lib/src/org/netbeans/editor/ext/java/JavaFoldManager.java
+++ b/java.editor.lib/src/org/netbeans/editor/ext/java/JavaFoldManager.java
@@ -44,11 +44,12 @@
package org.netbeans.editor.ext.java;
-import org.netbeans.api.editor.fold.Fold;
-import org.netbeans.api.editor.fold.FoldHierarchy;
import org.netbeans.api.editor.fold.FoldType;
import org.netbeans.spi.editor.fold.FoldManager;
+import static org.netbeans.editor.ext.java.Bundle.*;
+import org.openide.util.NbBundle;
+
/**
* Java fold maintainer creates and updates folds for java sources.
*
@@ -58,13 +59,23 @@
public abstract class JavaFoldManager implements FoldManager {
- public static final FoldType INITIAL_COMMENT_FOLD_TYPE = new FoldType("initial-comment"); // NOI18N
+ public static final FoldType INITIAL_COMMENT_FOLD_TYPE = FoldType.INITIAL_COMMENT; // NOI18N
- public static final FoldType IMPORTS_FOLD_TYPE = new FoldType("imports"); // NOI18N
+ @NbBundle.Messages("FoldType_Imports=Imports")
+ public static final FoldType IMPORTS_FOLD_TYPE = FoldType.create("import", FoldType_Imports(),
+ new org.netbeans.api.editor.fold.FoldTemplate(0, 0, "...")); // NOI18N
- public static final FoldType JAVADOC_FOLD_TYPE = new FoldType("javadoc"); // NOI18N
+ @NbBundle.Messages("FoldType_Javadoc=Javadoc Comments")
+ public static final FoldType JAVADOC_FOLD_TYPE = FoldType.DOCUMENTATION.derive("javadoc", FoldType_Javadoc(),
+ new org.netbeans.api.editor.fold.FoldTemplate(3, 2, "/**...*/")); // NOI18N
- public static final FoldType CODE_BLOCK_FOLD_TYPE = new FoldType("code-block"); // NOI18N
+ @NbBundle.Messages("FoldType_Methods=Methods")
+ public static final FoldType CODE_BLOCK_FOLD_TYPE = FoldType.MEMBER.derive("method", FoldType_Methods(),
+ new org.netbeans.api.editor.fold.FoldTemplate(1, 1, "{...}")); // NOI18N
+
+ @NbBundle.Messages("FoldType_InnerClasses=Inner Classes")
+ public static final FoldType INNERCLASS_TYPE = FoldType.NESTED.derive("innerclass", "Inner Classes",
+ new org.netbeans.api.editor.fold.FoldTemplate(1, 1, "{...}")); // NOI18N
private static final String IMPORTS_FOLD_DESCRIPTION = "..."; // NOI18N
@@ -73,19 +84,27 @@
private static final String JAVADOC_FOLD_DESCRIPTION = "/**...*/"; // NOI18N
private static final String CODE_BLOCK_FOLD_DESCRIPTION = "{...}"; // NOI18N
-
+
+ @Deprecated
public static final FoldTemplate INITIAL_COMMENT_FOLD_TEMPLATE
= new FoldTemplate(INITIAL_COMMENT_FOLD_TYPE, COMMENT_FOLD_DESCRIPTION, 2, 2);
+ @Deprecated
public static final FoldTemplate IMPORTS_FOLD_TEMPLATE
= new FoldTemplate(IMPORTS_FOLD_TYPE, IMPORTS_FOLD_DESCRIPTION, 0, 0);
+ @Deprecated
public static final FoldTemplate JAVADOC_FOLD_TEMPLATE
= new FoldTemplate(JAVADOC_FOLD_TYPE, JAVADOC_FOLD_DESCRIPTION, 3, 2);
+ @Deprecated
public static final FoldTemplate CODE_BLOCK_FOLD_TEMPLATE
= new FoldTemplate(CODE_BLOCK_FOLD_TYPE, CODE_BLOCK_FOLD_DESCRIPTION, 1, 1);
+ @Deprecated
+ public static final FoldTemplate INNER_CLASS_FOLD_TEMPLATE
+ = new FoldTemplate(INNERCLASS_TYPE, CODE_BLOCK_FOLD_DESCRIPTION, 1, 1);
+
protected static final class FoldTemplate {
diff --git a/java.editor/nbproject/project.properties b/java.editor/nbproject/project.properties
--- a/java.editor/nbproject/project.properties
+++ b/java.editor/nbproject/project.properties
@@ -42,7 +42,7 @@
javadoc.title=Java Editor
-spec.version.base=2.51.0
+spec.version.base=2.52.0
test.qa-functional.cp.extra=${editor.dir}/modules/org-netbeans-modules-editor-fold.jar
javac.source=1.6
#test.unit.cp.extra=
diff --git a/java.editor/nbproject/project.xml b/java.editor/nbproject/project.xml
--- a/java.editor/nbproject/project.xml
+++ b/java.editor/nbproject/project.xml
@@ -143,6 +143,7 @@
1
+ 1.34
@@ -205,7 +206,7 @@
1
- 1.9
+ 1.29
diff --git a/java.editor/src/org/netbeans/modules/editor/java/NbJavaCodeFoldingSideBarFactory.java b/java.editor/src/org/netbeans/modules/editor/java/NbJavaCodeFoldingSideBarFactory.java
--- a/java.editor/src/org/netbeans/modules/editor/java/NbJavaCodeFoldingSideBarFactory.java
+++ b/java.editor/src/org/netbeans/modules/editor/java/NbJavaCodeFoldingSideBarFactory.java
@@ -50,8 +50,12 @@
/**
* Java Code Folding Side Bar Factory, responsible for creating CodeFoldingSideBar
* Plugged via layer.xml
+ *
+ * As this factory does not provide any extra functionality over the standard factory,
+ * it should be probably removed.
*
* @author Martin Roskanin
+ * @Deprecated
*/
public class NbJavaCodeFoldingSideBarFactory implements SideBarFactory{
diff --git a/java.editor/src/org/netbeans/modules/java/editor/fold/JavaElementFoldManager.java b/java.editor/src/org/netbeans/modules/java/editor/fold/JavaElementFoldManager.java
--- a/java.editor/src/org/netbeans/modules/java/editor/fold/JavaElementFoldManager.java
+++ b/java.editor/src/org/netbeans/modules/java/editor/fold/JavaElementFoldManager.java
@@ -71,6 +71,7 @@
import javax.swing.text.Document;
import javax.swing.text.Position;
import org.netbeans.api.editor.fold.Fold;
+import org.netbeans.spi.editor.fold.FoldInfo;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.settings.SimpleValueNames;
import org.netbeans.api.java.lexer.JavaTokenId;
@@ -101,45 +102,8 @@
private FileObject file;
private JavaElementFoldTask task;
- /**
- * Default folding of individual fold types. New instance is created
- * when a new FoldManager opens (= editor appears).
- */
- private static class Presets {
- // Folding presets
- private boolean foldImportsPreset = false;
- private boolean foldInnerClassesPreset = false;
- private boolean foldJavadocsPreset = false;
- private boolean foldCodeBlocksPreset = false;
- private boolean foldInitialCommentsPreset = false;
-
- public Presets() {
- Preferences prefs = MimeLookup.getLookup(JavaKit.JAVA_MIME_TYPE).lookup(Preferences.class);
- foldInitialCommentsPreset = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_COLLAPSE_INITIAL_COMMENT, foldInitialCommentsPreset);
- foldImportsPreset = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_COLLAPSE_IMPORT, foldImportsPreset);
- foldCodeBlocksPreset = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_COLLAPSE_METHOD, foldCodeBlocksPreset);
- foldInnerClassesPreset = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_COLLAPSE_INNERCLASS, foldInnerClassesPreset);
- foldJavadocsPreset = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_COLLAPSE_JAVADOC, foldJavadocsPreset);
- }
-
- private static volatile Presets CURRENT;
-
- static void refresh() {
- CURRENT = null;
- }
-
- static Presets get() {
- Presets p = CURRENT;
- if (p != null) {
- return p;
- }
- return CURRENT = new Presets();
- }
- }
-
public void init(FoldOperation operation) {
this.operation = operation;
- Presets.refresh();
}
public synchronized void initFolds(FoldHierarchyTransaction transaction) {
@@ -149,7 +113,6 @@
if (od instanceof DataObject) {
FileObject file = ((DataObject)od).getPrimaryFile();
- currentFolds = new ArrayList();
task = JavaElementFoldTask.getTask(file);
task.setJavaElementFoldManager(JavaElementFoldManager.this, file);
}
@@ -169,7 +132,6 @@
}
public void removeDamagedNotify(Fold damagedFold) {
- currentFolds.remove(operation.getExtraInfo(damagedFold));
if (importsFold == damagedFold) {
importsFold = null;//not sure if this is correct...
}
@@ -187,7 +149,6 @@
task = null;
file = null;
- currentFolds = null;
importsFold = null;
initialCommentFold = null;
}
@@ -278,7 +239,7 @@
}
final JavaElementFoldVisitor v = new JavaElementFoldVisitor(info,
- cu, info.getTrees().getSourcePositions(), doc, Presets.get());
+ cu, info.getTrees().getSourcePositions(), doc);
scan(v, cu, null);
@@ -291,15 +252,14 @@
if (v.stopped || isCancelled())
return ;
- Collections.sort(v.folds);
if (mgrs instanceof JavaElementFoldManager) {
- SwingUtilities.invokeLater(((JavaElementFoldManager)mgrs).new CommitFolds(doc, v.folds));
+ SwingUtilities.invokeLater(((JavaElementFoldManager)mgrs).createCommit(doc, v));
} else {
SwingUtilities.invokeLater(new Runnable() {
Collection jefms = (Collection)mgrs;
public void run() {
for (JavaElementFoldManager jefm : jefms) {
- jefm.new CommitFolds(doc, v.folds).run();
+ jefm.createCommit(doc, v).run();
}
}});
}
@@ -312,16 +272,24 @@
}
+ private CommitFolds createCommit(Document doc, JavaElementFoldVisitor v) {
+ return new CommitFolds(doc, v.folds, v.initialCommentInfo, v.importsInfo);
+ }
+
private class CommitFolds implements Runnable {
private boolean insideRender;
private Document doc;
private List infos;
private long startTime;
+ private FoldInfo initComment;
+ private FoldInfo imports;
- public CommitFolds(Document doc, List infos) {
+ public CommitFolds(Document doc, List infos, FoldInfo initComment, FoldInfo imports) {
this.doc = doc;
this.infos = infos;
+ this.imports = imports;
+ this.initComment = initComment;
}
/**
@@ -329,26 +297,11 @@
* ignores the default state, and takes it from the actual state of
* existing fold.
*/
- private boolean mergeSpecialFoldState(FoldInfo fi) {
- if (fi.template == IMPORTS_FOLD_TEMPLATE) {
- if (importsFold != null) {
- return importsFold.isCollapsed();
- }
- } else if (fi.template == INITIAL_COMMENT_FOLD_TEMPLATE) {
- if (initialCommentFold != null) {
- return initialCommentFold.isCollapsed();
- }
- }
- return fi.collapseByDefault;
- }
-
- private int flip(int order) {
- if (order > 0) {
- return -1;
- } else if (order < 0) {
- return 1;
+ private void setSpecialFoldState(FoldInfo info, Fold f) {
+ if (info == null || f == null) {
+ return;
} else {
- return 0;
+ info.collapsed(f.isCollapsed());
}
}
@@ -356,6 +309,10 @@
if (!insideRender) {
startTime = System.currentTimeMillis();
insideRender = true;
+
+ // retain import & initial comment states
+ setSpecialFoldState(imports, importsFold);
+ setSpecialFoldState(initComment, initialCommentFold);
Document d = operation.getHierarchy().getComponent().getDocument();
if (d != doc) {
return;
@@ -366,92 +323,20 @@
}
operation.getHierarchy().lock();
-
try {
- FoldHierarchyTransaction tr = operation.openTransaction();
-
- try {
- if (currentFolds == null)
- return ;
-
- List updatedFolds = new ArrayList(infos.size());
- Iterator itExisting = currentFolds.iterator();
- Iterator itNew = infos.iterator();
- Fold currentExisting = itExisting.hasNext() ? itExisting.next() : null;
- FoldInfo currentNew = itNew.hasNext() ? itNew.next() : null;
-
- while (currentExisting != null || currentNew != null) {
- int order = currentExisting != null && currentNew != null ? currentNew.compareTo(currentExisting) : currentExisting != null ? 1 : -1;
-
- if (order > 0) {
- //fold removed:
- operation.removeFromHierarchy(currentExisting, tr);
-
- if (importsFold == currentExisting) {
- importsFold = null;
- }
-
- if (initialCommentFold == currentExisting) {
- initialCommentFold = null;
- }
-
- currentExisting = itExisting.hasNext() ? itExisting.next() : null;
- } else {
- //added or remains:
- if (order < 0) {
- //added:
- int start = currentNew.start.getOffset();
- int end = currentNew.end.getOffset();
-
- if (end > start &&
- (end - start) >= (currentNew.template.getStartGuardedLength() + currentNew.template.getEndGuardedLength())) {
- Fold f = operation.addToHierarchy(currentNew.template.getType(),
- currentNew.template.getDescription(),
- mergeSpecialFoldState(currentNew),
- start,
- end,
- currentNew.template.getStartGuardedLength(),
- currentNew.template.getEndGuardedLength(),
- currentNew,
- tr);
-
- if (currentNew.template == IMPORTS_FOLD_TEMPLATE) {
- importsFold = f;
- }
-
- if (currentNew.template == INITIAL_COMMENT_FOLD_TEMPLATE) {
- initialCommentFold = f;
- }
-
- updatedFolds.add(f);
- }
- } else {
- updatedFolds.add(currentExisting);
- currentExisting = itExisting.hasNext() ? itExisting.next() : null;
- }
-
- FoldInfo newNew = itNew.hasNext() ? itNew.next() : null;
-
- // XXX: In some situations infos contains duplicate folds and we don't
- // want to add the same multiple times. The situation that I came across
- // was with having an empty enum subclass with javadoc. The javadoc fold
- // was added twice - once from visitClass and second time from visitMethod
- // for the node, which for some reason has the same offset as the
- // the enum inner class.
- while (newNew != null && currentNew.compareTo(newNew) == 0) {
- newNew = itNew.hasNext() ? itNew.next() : null;
- }
-
- currentNew = newNew;
- }
- }
-
- currentFolds = updatedFolds;
- } catch (BadLocationException e) {
- Exceptions.printStackTrace(e);
- } finally {
- tr.commit();
+ Map folds = operation.update(infos, null, null);
+ if (initComment != null) {
+ initialCommentFold = folds.get(initComment);
+ } else {
+ initialCommentFold = null;
}
+ if (imports != null) {
+ importsFold = folds.get(imports);
+ } else {
+ importsFold = null;
+ }
+ } catch (BadLocationException e) {
+ Exceptions.printStackTrace(e);
} finally {
operation.getHierarchy().unlock();
}
@@ -464,27 +349,26 @@
}
//@GuardedBy(FoldOperation.openTransaction())
- private List currentFolds; //in natural order
private Fold initialCommentFold;
private Fold importsFold;
private static final class JavaElementFoldVisitor extends CancellableTreePathScanner {
- private List folds = new ArrayList();
+ private List folds = new ArrayList();
private CompilationInfo info;
private CompilationUnitTree cu;
private SourcePositions sp;
private boolean stopped;
private int initialCommentStopPos = Integer.MAX_VALUE;
private Document doc;
- private Presets presets;
+ private FoldInfo initialCommentInfo;
+ private FoldInfo importsInfo;
- public JavaElementFoldVisitor(CompilationInfo info, CompilationUnitTree cu, SourcePositions sp, Document doc, Presets presets) {
+ public JavaElementFoldVisitor(CompilationInfo info, CompilationUnitTree cu, SourcePositions sp, Document doc) {
this.info = info;
this.cu = cu;
this.sp = sp;
this.doc = doc;
- this.presets = presets;
}
public void checkInitialFold() {
@@ -500,21 +384,10 @@
if (token.id() == JavaTokenId.BLOCK_COMMENT || token.id() == JavaTokenId.JAVADOC_COMMENT) {
int startOffset = ts.offset();
- boolean collapsed = presets.foldInitialCommentsPreset;
-
- /*
- if (initialCommentFold != null) {
- collapsed = initialCommentFold.isCollapsed();
- }
- */
-
- folds.add(new FoldInfo(doc, startOffset, startOffset + token.length(), INITIAL_COMMENT_FOLD_TEMPLATE, collapsed));
+ folds.add(initialCommentInfo = FoldInfo.range(startOffset, startOffset + token.length(), INITIAL_COMMENT_FOLD_TYPE));
break;
}
}
- } catch (BadLocationException e) {
- //the document probably changed, stop
- stopped = true;
} catch (ConcurrentModificationException e) {
//from TokenSequence, document probably changed, stop
stopped = true;
@@ -542,7 +415,7 @@
if (token.id() == JavaTokenId.JAVADOC_COMMENT) {
int startOffset = ts.offset();
- folds.add(new FoldInfo(doc, startOffset, startOffset + token.length(), JAVADOC_FOLD_TEMPLATE, presets.foldJavadocsPreset));
+ folds.add(FoldInfo.range(startOffset, startOffset + token.length(), JAVADOC_FOLD_TYPE));
if (startOffset < initialCommentStopPos)
initialCommentStopPos = startOffset;
}
@@ -559,8 +432,9 @@
int start = (int)sp.getStartPosition(cu, node);
int end = (int)sp.getEndPosition(cu, node);
- if (start != (-1) && end != (-1))
- folds.add(new FoldInfo(doc, start, end, CODE_BLOCK_FOLD_TEMPLATE, presets.foldCodeBlocksPreset));
+ if (start != (-1) && end != (-1)) {
+ folds.add(FoldInfo.range(start, end, CODE_BLOCK_FOLD_TYPE));
+ }
}
handleJavadoc(javadocTree != null ? javadocTree : node);
@@ -588,8 +462,9 @@
int start = Utilities.findBodyStart(node, cu, sp, doc);
int end = (int)sp.getEndPosition(cu, node);
- if (start != (-1) && end != (-1))
- folds.add(new FoldInfo(doc, start, end, CODE_BLOCK_FOLD_TEMPLATE, presets.foldInnerClassesPreset));
+ if (start != (-1) && end != (-1)) {
+ folds.add(FoldInfo.range(start, end, INNERCLASS_TYPE));
+ }
}
handleJavadoc(node);
@@ -642,99 +517,17 @@
}
if (importsEnd != (-1) && importsStart != (-1)) {
- if (importsStart < initialCommentStopPos)
+ if (importsStart < initialCommentStopPos) {
initialCommentStopPos = importsStart;
-
- try {
- boolean collapsed = presets.foldImportsPreset;
-
- /*
- if (importsFold != null) {
- collapsed = importsFold.isCollapsed();
- }
- */
-
- importsStart += 7/*"import ".length()*/;
-
- if (importsStart < importsEnd) {
- folds.add(new FoldInfo(doc, importsStart , importsEnd, IMPORTS_FOLD_TEMPLATE, collapsed));
- }
- } catch (BadLocationException e) {
- //the document probably changed, stop
- stopped = true;
+ }
+ importsStart += 7/*"import ".length()*/;
+
+ if (importsStart < importsEnd) {
+ folds.add(importsInfo = FoldInfo.range(importsStart , importsEnd, IMPORTS_FOLD_TYPE));
}
}
return super.visitCompilationUnit(node, p);
}
-
- }
-
- protected static final class FoldInfo implements Comparable {
-
- private final Position start;
- private final Position end;
- private final FoldTemplate template;
- private final boolean collapseByDefault;
- //@GUardedBy(FoldOperation.openTransaction())
-
- public FoldInfo(Document doc, int start, int end, FoldTemplate template, boolean collapseByDefault) throws BadLocationException {
- this.start = doc.createPosition(start);
- this.end = doc.createPosition(end);
- this.template = template;
- this.collapseByDefault = collapseByDefault;
- }
-
- public int compareTo(Fold remote) {
- if (start.getOffset() < remote.getStartOffset()) {
- return -1;
- }
-
- if (start.getOffset() > remote.getStartOffset()) {
- return 1;
- }
-
- if (end.getOffset() < remote.getEndOffset()) {
- return -1;
- }
-
- if (end.getOffset() > remote.getEndOffset()) {
- return 1;
- }
-
- //XXX: abusing the length of the fold description to implement ordering (the exact order does not matter in this case):
- return template.getDescription().length() - remote.getDescription().length();
- }
-
- public int compareTo(Object o) {
- if (o instanceof Fold) {
- return compareTo((Fold)o);
- }
- FoldInfo remote = (FoldInfo) o;
-
- if (start.getOffset() < remote.start.getOffset()) {
- return -1;
- }
-
- if (start.getOffset() > remote.start.getOffset()) {
- return 1;
- }
-
- if (end.getOffset() < remote.end.getOffset()) {
- return -1;
- }
-
- if (end.getOffset() > remote.end.getOffset()) {
- return 1;
- }
-
- //XXX: abusing the length of the fold description to implement ordering (the exact order does not matter in this case):
- return template.getDescription().length() - remote.template.getDescription().length();
- }
-
- @Override
- public String toString() {
- return "FoldInfo[" + start.getOffset() + ", " + end.getOffset() + ", " + template.getDescription() + "]";
- }
}
}
diff --git a/java.editor/src/org/netbeans/modules/java/editor/fold/JavaFoldTypeProvider.java b/java.editor/src/org/netbeans/modules/java/editor/fold/JavaFoldTypeProvider.java
new file mode 100644
--- /dev/null
+++ b/java.editor/src/org/netbeans/modules/java/editor/fold/JavaFoldTypeProvider.java
@@ -0,0 +1,78 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2013 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.java.editor.fold;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import org.netbeans.api.editor.fold.FoldType;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.editor.ext.java.JavaFoldManager;
+import org.netbeans.spi.editor.fold.FoldTypeProvider;
+
+/**
+ *
+ * @author sdedic
+ */
+@MimeRegistration(mimeType = "text/x-java", service = FoldTypeProvider.class)
+public class JavaFoldTypeProvider implements FoldTypeProvider {
+ private Collection types = new ArrayList(5);
+
+ public JavaFoldTypeProvider() {
+ types.add(JavaFoldManager.CODE_BLOCK_FOLD_TYPE);
+ types.add(JavaFoldManager.INNERCLASS_TYPE);
+ types.add(JavaFoldManager.IMPORTS_FOLD_TYPE);
+ types.add(JavaFoldManager.JAVADOC_FOLD_TYPE);
+ types.add(JavaFoldManager.INITIAL_COMMENT_FOLD_TYPE);
+ }
+
+
+ @Override
+ public Collection getValues(Class type) {
+ return types;
+ }
+
+ @Override
+ public boolean inheritable() {
+ return false;
+ }
+
+}
diff --git a/java.editor/src/org/netbeans/modules/java/editor/fold/JavadocReaderFactory.java b/java.editor/src/org/netbeans/modules/java/editor/fold/JavadocReaderFactory.java
new file mode 100644
--- /dev/null
+++ b/java.editor/src/org/netbeans/modules/java/editor/fold/JavadocReaderFactory.java
@@ -0,0 +1,64 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2013 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.java.editor.fold;
+
+import org.netbeans.api.editor.fold.FoldType;
+import org.netbeans.api.editor.fold.FoldUtilities;
+import org.netbeans.api.editor.fold.FoldingSupport;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.spi.editor.fold.ContentReader;
+
+/**
+ *
+ * @author sdedic
+ */
+@MimeRegistration(mimeType = "text/x-java", service = ContentReader.Factory.class)
+public class JavadocReaderFactory implements ContentReader.Factory {
+ @Override
+ public ContentReader createReader(FoldType ft) {
+ if (ft == JavaElementFoldManager.JAVADOC_FOLD_TYPE) {
+ return FoldingSupport.contentReader("*", "\\.", "@", null);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/java.editor/src/org/netbeans/modules/java/editor/resources/layer.xml b/java.editor/src/org/netbeans/modules/java/editor/resources/layer.xml
--- a/java.editor/src/org/netbeans/modules/java/editor/resources/layer.xml
+++ b/java.editor/src/org/netbeans/modules/java/editor/resources/layer.xml
@@ -141,13 +141,13 @@
-
-
-
-
+
-
+
+
+
+
diff --git a/options.editor/manifest.mf b/options.editor/manifest.mf
--- a/options.editor/manifest.mf
+++ b/options.editor/manifest.mf
@@ -2,6 +2,6 @@
OpenIDE-Module: org.netbeans.modules.options.editor/1
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/options/editor/Bundle.properties
OpenIDE-Module-Layer: org/netbeans/modules/options/editor/mf-layer.xml
-OpenIDE-Module-Specification-Version: 1.39
+OpenIDE-Module-Specification-Version: 1.40
AutoUpdate-Show-In-Client: false
diff --git a/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.form b/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.form
--- a/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.form
+++ b/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.form
@@ -29,24 +29,11 @@
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
@@ -54,10 +41,7 @@
-
-
-
-
+
@@ -72,11 +56,6 @@
-
-
-
-
-
@@ -97,33 +76,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -154,64 +106,12 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.java b/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.java
--- a/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.java
+++ b/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.java
@@ -76,18 +76,6 @@
public GeneralEditorPanel () {
initComponents ();
- loc (lCodeFolding, "Code_Folding");
- loc (lUseCodeFolding, "Code_Folding_Section");
- loc (lCollapseByDefault, "Fold_by_Default");
-
- loc (cbUseCodeFolding, "Use_Folding");
- loc (cbFoldMethods, "Fold_Methods");
- loc (cbFoldInnerClasses, "Fold_Classes");
- loc (cbFoldImports, "Fold_Imports");
- loc (cbFoldJavadocComments, "Fold_JavaDoc");
- loc (cbFoldInitialComments, "Fold_Licence");
- loc (cbFoldTags, "Fold_Tags");
-
loc (lCamelCaseBehavior, "Camel_Case_Behavior");
loc (cbCamelCaseBehavior, "Enable_Camel_Case_In_Java");
loc (lCamelCaseBehaviorExample, "Camel_Case_Behavior_Example");
@@ -99,7 +87,6 @@
loc (cbBraceTooltip, "Brace_First_Tooltip");
loc (cbShowBraceOutline, "Brace_Show_Outline");
- cbUseCodeFolding.setMnemonic(NbBundle.getMessage (GeneralEditorPanel.class, "MNEMONIC_Use_Folding").charAt(0));
cboEditorSearchType.setRenderer(new EditorSearchTypeRenderer(cboEditorSearchType.getRenderer()));
cboEditorSearchType.setModel(new DefaultComboBoxModel(new Object [] { "default", "closing"})); //NOI18N
cboEditorSearchType.addActionListener( new ActionListener() {
@@ -123,17 +110,6 @@
// //GEN-BEGIN:initComponents
private void initComponents() {
- lCodeFolding = new javax.swing.JLabel();
- lUseCodeFolding = new javax.swing.JLabel();
- lCollapseByDefault = new javax.swing.JLabel();
- cbUseCodeFolding = new javax.swing.JCheckBox();
- cbFoldMethods = new javax.swing.JCheckBox();
- cbFoldInnerClasses = new javax.swing.JCheckBox();
- cbFoldImports = new javax.swing.JCheckBox();
- cbFoldJavadocComments = new javax.swing.JCheckBox();
- cbFoldInitialComments = new javax.swing.JCheckBox();
- cbFoldTags = new javax.swing.JCheckBox();
- jSeparator1 = new javax.swing.JSeparator();
lBracesMatching = new javax.swing.JLabel();
cbShowBraceOutline = new javax.swing.JCheckBox();
cbBraceTooltip = new javax.swing.JCheckBox();
@@ -150,25 +126,6 @@
setForeground(new java.awt.Color(99, 130, 191));
- lCodeFolding.setText("Code Folding");
-
- lUseCodeFolding.setLabelFor(cbUseCodeFolding);
- lUseCodeFolding.setText("Use Code Folding:");
-
- lCollapseByDefault.setText("Collapse by Default:");
-
- cbFoldMethods.setText("Methods");
-
- cbFoldInnerClasses.setText("Inner Classes");
-
- cbFoldImports.setText("Imports");
-
- cbFoldJavadocComments.setText("Javadoc Comments");
-
- cbFoldInitialComments.setText("Initial Comments");
-
- cbFoldTags.setText("Tags and Other Code Blocks");
-
lBracesMatching.setText(org.openide.util.NbBundle.getMessage(GeneralEditorPanel.class, "BRACES_MATCHING")); // NOI18N
cbShowBraceOutline.setText("Show outline");
@@ -200,29 +157,16 @@
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
- .addContainerGap()
- .addComponent(lCollapseByDefault)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGap(163, 163, 163)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(cbUseCodeFolding)
.addGroup(layout.createSequentialGroup()
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(cbFoldMethods)
- .addComponent(cbFoldInnerClasses)
- .addComponent(cbFoldImports))
- .addGap(18, 18, 18)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(cbFoldJavadocComments)
- .addComponent(cbFoldInitialComments)
- .addComponent(cbFoldTags)
- .addComponent(cbBraceTooltip)))
+ .addGap(175, 175, 175)
+ .addComponent(cbBraceTooltip))
.addComponent(cbCamelCaseBehavior)
.addGroup(layout.createSequentialGroup()
.addGap(21, 21, 21)
- .addComponent(lCamelCaseBehaviorExample, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))))
- .addGroup(layout.createSequentialGroup()
- .addGap(12, 12, 12)
- .addComponent(lUseCodeFolding))
+ .addComponent(lCamelCaseBehaviorExample, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
+ .addGap(26, 26, 26))
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(lEditorSearchType)
@@ -232,10 +176,6 @@
.addComponent(cboEditorSearchType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))))
.addGap(0, 12, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
- .addComponent(lCodeFolding)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(jSeparator1))
- .addGroup(layout.createSequentialGroup()
.addComponent(lBracesMatching)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jSeparator6))
@@ -248,34 +188,9 @@
.addGap(0, 0, Short.MAX_VALUE))
.addComponent(jSeparator3)))
);
-
- layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cbFoldImports, cbFoldInitialComments, cbFoldInnerClasses, cbFoldJavadocComments, cbFoldMethods});
-
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
- .addContainerGap()
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
- .addComponent(lCodeFolding)
- .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(cbUseCodeFolding)
- .addComponent(lUseCodeFolding))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(lCollapseByDefault)
- .addComponent(cbFoldMethods)
- .addComponent(cbFoldJavadocComments))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(cbFoldInnerClasses)
- .addComponent(cbFoldInitialComments))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
- .addComponent(cbFoldImports)
- .addComponent(cbFoldTags))
- .addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(lBracesMatching)
.addComponent(jSeparator6, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE))
@@ -301,7 +216,7 @@
.addComponent(cboEditorSearchType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lSearchtypeTooltip, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addContainerGap())
+ .addGap(0, 12, Short.MAX_VALUE))
);
}// //GEN-END:initComponents
@@ -309,28 +224,17 @@
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JCheckBox cbBraceTooltip;
private javax.swing.JCheckBox cbCamelCaseBehavior;
- private javax.swing.JCheckBox cbFoldImports;
- private javax.swing.JCheckBox cbFoldInitialComments;
- private javax.swing.JCheckBox cbFoldInnerClasses;
- private javax.swing.JCheckBox cbFoldJavadocComments;
- private javax.swing.JCheckBox cbFoldMethods;
- private javax.swing.JCheckBox cbFoldTags;
private javax.swing.JCheckBox cbShowBraceOutline;
- private javax.swing.JCheckBox cbUseCodeFolding;
private javax.swing.JComboBox cboEditorSearchType;
- private javax.swing.JSeparator jSeparator1;
private javax.swing.JSeparator jSeparator3;
private javax.swing.JSeparator jSeparator5;
private javax.swing.JSeparator jSeparator6;
private javax.swing.JLabel lBracesMatching;
private javax.swing.JLabel lCamelCaseBehavior;
private javax.swing.JLabel lCamelCaseBehaviorExample;
- private javax.swing.JLabel lCodeFolding;
- private javax.swing.JLabel lCollapseByDefault;
private javax.swing.JLabel lEditorSearchType;
private javax.swing.JLabel lSearch;
private javax.swing.JLabel lSearchtypeTooltip;
- private javax.swing.JLabel lUseCodeFolding;
// End of variables declaration//GEN-END:variables
@@ -362,28 +266,12 @@
listen = false;
if (model == null) {
model = new Model ();
- cbUseCodeFolding.addActionListener (this);
- cbFoldMethods.addActionListener (this);
- cbFoldInnerClasses.addActionListener (this);
- cbFoldImports.addActionListener (this);
- cbFoldJavadocComments.addActionListener (this);
- cbFoldInitialComments.addActionListener (this);
cbCamelCaseBehavior.addActionListener (this);
- cbFoldTags.addActionListener (this);
cboEditorSearchType.addActionListener(this);
cbBraceTooltip.addActionListener(this);
cbShowBraceOutline.addActionListener(this);
}
- // init code folding
- cbUseCodeFolding.setSelected (model.isShowCodeFolding ());
- cbFoldImports.setSelected (model.isFoldImports ());
- cbFoldInitialComments.setSelected (model.isFoldInitialComment ());
- cbFoldInnerClasses.setSelected (model.isFoldInnerClasses ());
- cbFoldJavadocComments.setSelected (model.isFoldJavaDocComments ());
- cbFoldMethods.setSelected (model.isFoldMethods ());
- cbFoldTags.setSelected (model.isFoldTag());
-
// Java Camel Case Navigation
Boolean ccJava = model.isCamelCaseJavaNavigation();
if ( ccJava == null ) {
@@ -400,8 +288,6 @@
cbBraceTooltip.setSelected(model.isBraceTooltip());
cbShowBraceOutline.setSelected(model.isBraceOutline());
- updateEnabledState ();
-
listen = true;
}
@@ -409,17 +295,6 @@
if (model == null || !changed) return;
- // code folding options
- model.setFoldingOptions (
- cbUseCodeFolding.isSelected (),
- cbFoldImports.isSelected (),
- cbFoldInitialComments.isSelected (),
- cbFoldInnerClasses.isSelected (),
- cbFoldJavadocComments.isSelected (),
- cbFoldMethods.isSelected (),
- cbFoldTags.isSelected ()
- );
-
// java camel case navigation
model.setCamelCaseNavigation(cbCamelCaseBehavior.isSelected());
@@ -446,25 +321,12 @@
@Override
public void actionPerformed (ActionEvent e) {
if (!listen) return;
- if (e.getSource () == cbUseCodeFolding) {
- updateEnabledState ();
- }
changed = true;
}
// other methods ...........................................................
- private void updateEnabledState () {
- boolean useCodeFolding = cbUseCodeFolding.isSelected ();
- cbFoldImports.setEnabled (useCodeFolding);
- cbFoldInitialComments.setEnabled (useCodeFolding);
- cbFoldInnerClasses.setEnabled (useCodeFolding);
- cbFoldJavadocComments.setEnabled (useCodeFolding);
- cbFoldMethods.setEnabled (useCodeFolding);
- cbFoldTags.setEnabled(useCodeFolding);
- }
-
private static final class EditorSearchTypeRenderer implements ListCellRenderer {
private final ListCellRenderer defaultRenderer;
diff --git a/options.editor/src/org/netbeans/modules/options/indentation/ProxyPreferences.java b/options.editor/src/org/netbeans/modules/options/indentation/ProxyPreferences.java
--- a/options.editor/src/org/netbeans/modules/options/indentation/ProxyPreferences.java
+++ b/options.editor/src/org/netbeans/modules/options/indentation/ProxyPreferences.java
@@ -44,482 +44,229 @@
import java.io.IOException;
import java.io.OutputStream;
-import java.lang.ref.Reference;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EventObject;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.WeakHashMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.NodeChangeEvent;
import java.util.prefs.NodeChangeListener;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
-import javax.xml.bind.DatatypeConverter;
-import org.netbeans.modules.editor.settings.storage.spi.TypedValue;
+import org.netbeans.modules.editor.settings.storage.api.MemoryPreferences;
import org.openide.util.WeakListeners;
/**
- *
+ * This class has been obsoleted by {@link MemoryPreferences}
+ *
* @author vita
+ * @deprecated Please use {@link MemoryPreferences} API instead
*/
public final class ProxyPreferences extends Preferences implements PreferenceChangeListener, NodeChangeListener {
+
+ private final MemoryPreferences delegateRoot;
+ private Preferences delegate;
+ private boolean noEvents;
+
+ public static ProxyPreferences getProxyPreferences(Object token, Preferences delegate) {
+ return new ProxyPreferences(null, MemoryPreferences.get(token, delegate), null);
+ }
- public static ProxyPreferences getProxyPreferences(Object token, Preferences delegate) {
- return Tree.getTree(token, delegate).get(null, delegate.name(), delegate); //NOI18N
+ public void silence() {
+ synchronized (delegate) {
+ this.noEvents = true;
+ }
+ }
+
+ public void destroy() {
+ delegateRoot.destroy();
}
@Override
public void put(String key, String value) {
- _put(key, value, String.class.getName());
+ delegate.put(key, value);
}
@Override
public String get(String key, String def) {
- synchronized (tree.treeLock()) {
- checkNotNull(key, "key"); //NOI18N
- checkRemoved();
-
- if (removedKeys.contains(key)) {
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("Key '" + key + "' removed, using default '" + def + "'"); //NOI18N
- }
- return def;
- } else {
- TypedValue typedValue = data.get(key);
- if (typedValue != null) {
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("Key '" + key + "' modified, local value '" + typedValue.getValue() + "'"); //NOI18N
- }
- return typedValue.getValue();
- } else if (delegate != null) {
- String value = delegate.get(key, def);
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("Key '" + key + "' undefined, original value '" + value + "'"); //NOI18N
- }
- return value;
- } else {
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("Key '" + key + "' undefined, '" + name + "' is a new node, using default '" + def + "'"); //NOI18N
- }
- return def;
- }
+ return delegate.get(key, def);
+ }
+
+ @Override
+ public void remove(String key) {
+ delegate.remove(key);
+ }
+
+ @Override
+ public void clear() throws BackingStoreException {
+ delegate.clear();
+ }
+
+ @Override
+ public void putInt(String key, int value) {
+ delegate.putInt(key, value);
+ }
+
+ @Override
+ public int getInt(String key, int def) {
+ return delegate.getInt(key, def);
+ }
+
+ @Override
+ public void putLong(String key, long value) {
+ delegate.putLong(key, value);
+ }
+
+ @Override
+ public long getLong(String key, long def) {
+ return delegate.getLong(key, def);
+ }
+
+ @Override
+ public void putBoolean(String key, boolean value) {
+ delegate.putBoolean(key, value);
+ }
+
+ @Override
+ public boolean getBoolean(String key, boolean def) {
+ return delegate.getBoolean(key, def);
+ }
+
+ @Override
+ public void putFloat(String key, float value) {
+ delegate.putFloat(key, value);
+ }
+
+ @Override
+ public float getFloat(String key, float def) {
+ return delegate.getFloat(key, def);
+ }
+
+ @Override
+ public void putDouble(String key, double value) {
+ delegate.putDouble(key, value);
+ }
+
+ @Override
+ public double getDouble(String key, double def) {
+ return delegate.getDouble(key, def);
+ }
+
+ @Override
+ public void putByteArray(String key, byte[] value) {
+ delegate.putByteArray(key, value);
+ }
+
+ @Override
+ public byte[] getByteArray(String key, byte[] def) {
+ return delegate.getByteArray(key, def);
+ }
+
+ @Override
+ public String[] keys() throws BackingStoreException {
+ return delegate.keys();
+ }
+
+ @Override
+ public String[] childrenNames() throws BackingStoreException {
+ return delegate.childrenNames();
+ }
+
+ @Override
+ public Preferences parent() {
+ return parent;
+ }
+
+ @Override
+ public Preferences node(String pathName) {
+ synchronized (this) {
+ Preferences pref = children.get(pathName);
+ if (pref != null) {
+ return pref;
}
}
+ Preferences pref = delegate.node(pathName);
+ ProxyPreferences result;
+
+ synchronized (this) {
+ result = children.get(pathName);
+ if (result != null) {
+ return result;
+ }
+ result = new ProxyPreferences(this, delegateRoot, pref);
+ children.put(pathName, result);
+ return result;
+ }
}
@Override
- public void remove(String key) {
- EventBag bag = null;
-
- synchronized (tree.treeLock()) {
- checkNotNull(key, "key"); //NOI18N
- checkRemoved();
-
- if (removedKeys.add(key)) {
- data.remove(key);
- bag = new EventBag();
- bag.addListeners(prefListeners);
- bag.addEvent(new PreferenceChangeEvent(this, key, null));
- }
- }
-
- if (bag != null) {
- firePrefEvents(Collections.singletonList(bag));
- }
- }
-
- @Override
- public void clear() throws BackingStoreException {
- EventBag bag = new EventBag();
-
- synchronized (tree.treeLock()) {
- checkRemoved();
-
- // Determine modified or added keys
- Set keys = new HashSet();
- keys.addAll(data.keySet());
- keys.removeAll(removedKeys);
- if (!keys.isEmpty()) {
- for(String key : keys) {
- String value = delegate == null ? null : delegate.get(key, null);
- bag.addEvent(new PreferenceChangeEvent(this, key, value));
- }
- }
-
- // Determine removed keys
- if (delegate != null) {
- for(String key : removedKeys) {
- String value = delegate.get(key, null);
- if (value != null) {
- bag.addEvent(new PreferenceChangeEvent(this, key, value));
- }
- }
- }
-
- // Initialize bag's listeners
- bag.addListeners(prefListeners);
-
- // Finally, remove the data
- data.clear();
- removedKeys.clear();
- }
-
- firePrefEvents(Collections.singletonList(bag));
- }
-
- @Override
- public void putInt(String key, int value) {
- _put(key, Integer.toString(value), Integer.class.getName());
- }
-
- @Override
- public int getInt(String key, int def) {
- String value = get(key, null);
- if (value != null) {
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException nfe) {
- // ignore
- }
- }
- return def;
- }
-
- @Override
- public void putLong(String key, long value) {
- _put(key, Long.toString(value), Long.class.getName());
- }
-
- @Override
- public long getLong(String key, long def) {
- String value = get(key, null);
- if (value != null) {
- try {
- return Long.parseLong(value);
- } catch (NumberFormatException nfe) {
- // ignore
- }
- }
- return def;
- }
-
- @Override
- public void putBoolean(String key, boolean value) {
- _put(key, Boolean.toString(value), Boolean.class.getName());
- }
-
- @Override
- public boolean getBoolean(String key, boolean def) {
- String value = get(key, null);
- if (value != null) {
- return Boolean.parseBoolean(value);
- } else {
- return def;
- }
- }
-
- @Override
- public void putFloat(String key, float value) {
- _put(key, Float.toString(value), Float.class.getName());
- }
-
- @Override
- public float getFloat(String key, float def) {
- String value = get(key, null);
- if (value != null) {
- try {
- return Float.parseFloat(value);
- } catch (NumberFormatException nfe) {
- // ignore
- }
- }
- return def;
- }
-
- @Override
- public void putDouble(String key, double value) {
- _put(key, Double.toString(value), Double.class.getName());
- }
-
- @Override
- public double getDouble(String key, double def) {
- String value = get(key, null);
- if (value != null) {
- try {
- return Double.parseDouble(value);
- } catch (NumberFormatException nfe) {
- // ignore
- }
- }
- return def;
- }
-
- @Override
- public void putByteArray(String key, byte[] value) {
- _put(key, DatatypeConverter.printBase64Binary(value), value.getClass().getName());
- }
-
- @Override
- public byte[] getByteArray(String key, byte[] def) {
- String value = get(key, null);
- if (value != null) {
- byte [] decoded = DatatypeConverter.parseBase64Binary(value);
- if (decoded != null) {
- return decoded;
- }
- }
- return def;
- }
-
- @Override
- public String[] keys() throws BackingStoreException {
- synchronized (tree.treeLock()) {
- checkRemoved();
- HashSet keys = new HashSet();
- if (delegate != null) {
- keys.addAll(Arrays.asList(delegate.keys()));
- }
- keys.addAll(data.keySet());
- keys.removeAll(removedKeys);
- return keys.toArray(new String [keys.size()]);
- }
- }
-
- @Override
- public String[] childrenNames() throws BackingStoreException {
- synchronized (tree.treeLock()) {
- checkRemoved();
- HashSet names = new HashSet();
- if (delegate != null) {
- names.addAll(Arrays.asList(delegate.childrenNames()));
- }
- names.addAll(children.keySet());
- names.removeAll(removedChildren);
- return names.toArray(new String [names.size()]);
- }
- }
-
- @Override
- public Preferences parent() {
- synchronized (tree.treeLock()) {
- checkRemoved();
- return parent;
- }
- }
-
- @Override
- public Preferences node(String pathName) {
- Preferences node;
- LinkedList> events = new LinkedList>();
-
- synchronized (tree.treeLock()) {
- checkNotNull(pathName, "pathName"); //NOI18N
- checkRemoved();
- node = node(pathName, true, events);
- }
-
- fireNodeEvents(events);
- return node;
- }
-
- @Override
public boolean nodeExists(String pathName) throws BackingStoreException {
- synchronized (tree.treeLock()) {
- if (pathName.length() == 0) {
- return !removed;
- } else {
- checkRemoved();
- return node(pathName, false, null) != null;
- }
- }
+ return delegate.nodeExists(pathName);
}
@Override
public void removeNode() throws BackingStoreException {
- synchronized (tree.treeLock()) {
- checkRemoved();
- ProxyPreferences p = parent;
- if (p != null) {
- p.removeChild(this);
- } else {
- throw new UnsupportedOperationException("Can't remove the root."); //NOI18N
- }
+ delegate.removeNode();
+ if (parent != null) {
+ parent.nodeRemoved(this);
}
}
@Override
public String name() {
- return name;
+ return delegate.name();
}
@Override
public String absolutePath() {
- synchronized (tree.treeLock()) {
- ProxyPreferences pp = parent;
- if (pp != null) {
- if (pp.parent == null) {
- // pp is the root, we don't want two consecutive slashes in the path
- return "/" + name(); //NOI18N
- } else {
- return pp.absolutePath() + "/" + name(); //NOI18N
- }
- } else {
- return "/"; //NOI18N
- }
- }
+ return delegate.absolutePath();
}
@Override
public boolean isUserNode() {
- synchronized (tree.treeLock()) {
- if (delegate != null) {
- return delegate.isUserNode();
- } else {
- ProxyPreferences pp = parent;
- if (pp != null) {
- return pp.isUserNode();
- } else {
- return true;
- }
- }
- }
+ return delegate.isUserNode();
}
@Override
public String toString() {
- return (isUserNode() ? "User" : "System") + " Preference Node: " + absolutePath(); //NOI18N
+ return delegate.toString();
}
@Override
public void flush() throws BackingStoreException {
- synchronized (tree.treeLock()) {
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("Flushing " + absolutePath());
- }
-
- checkRemoved();
- for(ProxyPreferences pp : children.values()) {
- pp.flush();
- }
-
- if (delegate == null) {
- ProxyPreferences proxyRoot = parent.node("/", false, null); //NOI18N
- assert proxyRoot != null : "Root must always exist"; //NOI18N
-
- Preferences delegateRoot = proxyRoot.delegate;
- assert delegateRoot != null : "Root must always have its corresponding delegate"; //NOI18N
-
- Preferences nueDelegate = delegateRoot.node(absolutePath());
- changeDelegate(nueDelegate);
- }
-
- delegate.removeNodeChangeListener(weakNodeListener);
- delegate.removePreferenceChangeListener(weakPrefListener);
- try {
- // remove all removed children
- for(String childName : removedChildren) {
- if (delegate.nodeExists(childName)) {
- delegate.node(childName).removeNode();
- }
- }
-
- // write all valid key-value pairs
- for(String key : data.keySet()) {
- if (!removedKeys.contains(key)) {
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("Writing " + absolutePath() + "/" + key + "=" + data.get(key));
- }
-
- TypedValue typedValue = data.get(key);
- if (String.class.getName().equals(typedValue.getJavaType())) {
- delegate.put(key, typedValue.getValue());
-
- } else if (Integer.class.getName().equals(typedValue.getJavaType())) {
- delegate.putInt(key, Integer.parseInt(typedValue.getValue()));
-
- } else if (Long.class.getName().equals(typedValue.getJavaType())) {
- delegate.putLong(key, Long.parseLong(typedValue.getValue()));
-
- } else if (Boolean.class.getName().equals(typedValue.getJavaType())) {
- delegate.putBoolean(key, Boolean.parseBoolean(typedValue.getValue()));
-
- } else if (Float.class.getName().equals(typedValue.getJavaType())) {
- delegate.putFloat(key, Float.parseFloat(typedValue.getValue()));
-
- } else if (Double.class.getName().equals(typedValue.getJavaType())) {
- delegate.putDouble(key, Double.parseDouble(typedValue.getValue()));
-
- } else {
- delegate.putByteArray(key, DatatypeConverter.parseBase64Binary(typedValue.getValue()));
- }
- }
- }
- data.clear();
-
- // remove all removed keys
- for(String key : removedKeys) {
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("Removing " + absolutePath() + "/" + key);
- }
- delegate.remove(key);
- }
- removedKeys.clear();
- } finally {
- delegate.addNodeChangeListener(weakNodeListener);
- delegate.addPreferenceChangeListener(weakPrefListener);
- }
- }
+ delegate.flush();
}
@Override
public void sync() throws BackingStoreException {
- ArrayList> prefEvents = new ArrayList>();
- ArrayList> nodeEvents = new ArrayList>();
-
- synchronized (tree.treeLock()) {
- _sync(prefEvents, nodeEvents);
- }
-
- fireNodeEvents(nodeEvents);
- firePrefEvents(prefEvents);
+ delegate.sync();
}
-
+
@Override
public void addPreferenceChangeListener(PreferenceChangeListener pcl) {
- synchronized (tree.treeLock()) {
+ synchronized (this) {
prefListeners.add(pcl);
}
}
@Override
public void removePreferenceChangeListener(PreferenceChangeListener pcl) {
- synchronized (tree.treeLock()) {
+ synchronized (this) {
prefListeners.remove(pcl);
}
}
@Override
public void addNodeChangeListener(NodeChangeListener ncl) {
- synchronized (tree.treeLock()) {
+ synchronized (this) {
nodeListeners.add(ncl);
}
}
@Override
public void removeNodeChangeListener(NodeChangeListener ncl) {
- synchronized (tree.treeLock()) {
+ synchronized (this) {
nodeListeners.remove(ncl);
}
}
@@ -540,11 +287,10 @@
public void preferenceChange(PreferenceChangeEvent evt) {
PreferenceChangeListener [] listeners;
- synchronized (tree.treeLock()) {
- if (removed || removedKeys.contains(evt.getKey()) || data.containsKey(evt.getKey())) {
+ synchronized (this) {
+ if (!noEvents) {
return;
}
-
listeners = prefListeners.toArray(new PreferenceChangeListener[prefListeners.size()]);
}
@@ -556,6 +302,14 @@
l.preferenceChange(myEvt);
}
}
+
+ private synchronized void changeDelegate(Preferences del) {
+ this.delegate = del;
+ }
+
+ private synchronized void nodeRemoved(ProxyPreferences child) {
+ children.values().remove(child);
+ }
// ------------------------------------------------------------------------
// NodeChangeListener implementation
@@ -565,12 +319,8 @@
NodeChangeListener [] listeners;
Preferences childNode;
- synchronized (tree.treeLock()) {
+ synchronized (this) {
String childName = evt.getChild().name();
- if (removed || removedChildren.contains(childName)) {
- return;
- }
-
childNode = children.get(childName);
if (childNode != null) {
// swap delegates
@@ -578,7 +328,9 @@
} else {
childNode = node(evt.getChild().name());
}
-
+ if (!noEvents) {
+ return;
+ }
listeners = nodeListeners.toArray(new NodeChangeListener[nodeListeners.size()]);
}
@@ -595,21 +347,18 @@
NodeChangeListener [] listeners;
Preferences childNode;
- synchronized (tree.treeLock()) {
+ synchronized (this) {
String childName = evt.getChild().name();
- if (removed || removedChildren.contains(childName)) {
- return;
- }
childNode = children.get(childName);
- if (childNode != null) {
- // swap delegates
- ((ProxyPreferences) childNode).changeDelegate(null);
- } else {
+ if (childNode == null) {
// nobody has accessed the child yet
return;
}
+ if (!noEvents) {
+ return;
+ }
listeners = nodeListeners.toArray(new NodeChangeListener[nodeListeners.size()]);
}
@@ -622,445 +371,21 @@
}
}
- // ------------------------------------------------------------------------
- // Other public implementation
- // ------------------------------------------------------------------------
+ private final ProxyPreferences parent;
+ private final Map children = new HashMap();
- /**
- * Destroys whole preferences tree as if called on the root.
- */
- public void destroy() {
- synchronized (tree.treeLock()) {
- tree.destroy();
- }
- }
-
- public void silence() {
- synchronized (tree.treeLock()) {
- noEvents = true;
- }
- }
-
- // ------------------------------------------------------------------------
- // private implementation
- // ------------------------------------------------------------------------
-
- private static final Logger LOG = Logger.getLogger(ProxyPreferences.class.getName());
-
- private final ProxyPreferences parent;
- private final String name;
- private Preferences delegate;
- private final Tree tree;
- private boolean removed;
-
- private final Map data = new HashMap();
- private final Set removedKeys = new HashSet();
- private final Map children = new HashMap();
- private final Set removedChildren = new HashSet();
-
- private boolean noEvents = false;
private PreferenceChangeListener weakPrefListener;
private final Set prefListeners = new HashSet();
private NodeChangeListener weakNodeListener;
private final Set nodeListeners = new HashSet();
- private ProxyPreferences(ProxyPreferences parent, String name, Preferences delegate, Tree tree) {
- assert name != null;
-
+ private ProxyPreferences(ProxyPreferences parent, MemoryPreferences memoryPref, Preferences delegate) {
this.parent = parent;
- this.name = name;
- this.delegate = delegate;
- if (delegate != null) {
- assert name.equals(delegate.name());
-
- weakPrefListener = WeakListeners.create(PreferenceChangeListener.class, this, delegate);
- delegate.addPreferenceChangeListener(weakPrefListener);
-
- weakNodeListener = WeakListeners.create(NodeChangeListener.class, this, delegate);
- delegate.addNodeChangeListener(weakNodeListener);
- }
- this.tree = tree;
+ this.delegateRoot = memoryPref;
+ this.delegate = delegate == null ? memoryPref.getPreferences() : delegate;
+ weakPrefListener = WeakListeners.create(PreferenceChangeListener.class, this, delegate);
+ this.delegate.addPreferenceChangeListener(weakPrefListener);
+ weakNodeListener = WeakListeners.create(NodeChangeListener.class, this, delegate);
+ this.delegate.addNodeChangeListener(weakNodeListener);
}
-
- private void _put(String key, String value, String javaType) {
- EventBag bag = null;
-
- synchronized (tree.treeLock()) {
- checkNotNull(key, "key"); //NOI18N
- checkNotNull(value, "value"); //NOI18N
- checkRemoved();
-
- String orig = get(key, null);
- if (orig == null || !orig.equals(value)) {
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("Overwriting '" + key + "' = '" + value + "'"); //NOI18N
- }
-
- data.put(key, new TypedValue(value, javaType));
- removedKeys.remove(key);
-
- bag = new EventBag();
- bag.addListeners(prefListeners);
- bag.addEvent(new PreferenceChangeEvent(this, key, value));
- }
- }
-
- if (bag != null) {
- firePrefEvents(Collections.singletonList(bag));
- }
- }
-
- private ProxyPreferences node(String pathName, boolean create, List> events) {
- if (pathName.length() > 0 && pathName.charAt(0) == '/') { //NOI18N
- // absolute path, if this is not the root then find the root
- // and pass the call to it
- if (parent != null) {
- Preferences root = this;
- while (root.parent() != null) {
- root = root.parent();
- }
- return ((ProxyPreferences) root).node(pathName, create, events);
- } else {
- // this is the root, change the pathName to a relative path and proceed
- pathName = pathName.substring(1);
- }
- }
-
- if (pathName.length() > 0) {
- String childName;
- String pathFromChild;
-
- int idx = pathName.indexOf('/'); //NOI18N
- if (idx != -1) {
- childName = pathName.substring(0, idx);
- pathFromChild = pathName.substring(idx + 1);
- } else {
- childName = pathName;
- pathFromChild = null;
- }
-
- ProxyPreferences child = children.get(childName);
- if (child == null) {
- if (removedChildren.contains(childName) && !create) {
- // this child has been removed
- return null;
- }
-
- Preferences childDelegate = null;
- try {
- if (delegate != null && delegate.nodeExists(childName)) {
- childDelegate = delegate.node(childName);
- }
- } catch (BackingStoreException bse) {
- // ignore
- }
-
- if (childDelegate != null || create) {
- child = tree.get(this, childName, childDelegate);
- children.put(childName, child);
- removedChildren.remove(childName);
-
- // fire event if we really created the new child node
- if (childDelegate == null) {
- EventBag bag = new EventBag();
- bag.addListeners(nodeListeners);
- bag.addEvent(new NodeChangeEventExt(this, child, false));
- events.add(bag);
- }
- } else {
- // childDelegate == null && !create
- return null;
- }
- } else {
- assert !child.removed;
- }
-
- return pathFromChild != null ? child.node(pathFromChild, create, events) : child;
- } else {
- return this;
- }
- }
-
- private void addChild(ProxyPreferences child) {
- ProxyPreferences pp = children.get(child.name());
- if (pp == null) {
- children.put(child.name(), child);
- } else {
- assert pp == child;
- }
- }
-
- private void removeChild(ProxyPreferences child) {
- assert child != null;
- assert children.get(child.name()) == child;
-
- child.nodeRemoved();
- children.remove(child.name());
- removedChildren.add(child.name());
- }
-
- private void nodeRemoved() {
- for(ProxyPreferences pp : children.values()) {
- pp.nodeRemoved();
- }
-
- data.clear();
- removedKeys.clear();
- children.clear();
- removedChildren.clear();
- tree.removeNode(this);
-
- removed = true;
- }
-
- private void checkNotNull(Object paramValue, String paramName) {
- if (paramValue == null) {
- throw new NullPointerException("The " + paramName + " must not be null");
- }
- }
-
- private void checkRemoved() {
- if (removed) {
- throw new IllegalStateException("The node '" + this + " has already been removed."); //NOI18N
- }
- }
-
- private void changeDelegate(Preferences nueDelegate) {
- if (delegate != null) {
- try {
- if (delegate.nodeExists("")) { //NOI18N
- assert weakPrefListener != null;
- assert weakNodeListener != null;
- delegate.removePreferenceChangeListener(weakPrefListener);
- delegate.removeNodeChangeListener(weakNodeListener);
- }
- } catch (BackingStoreException bse) {
- LOG.log(Level.WARNING, null, bse);
- }
- }
-
- delegate = nueDelegate;
- weakPrefListener = null;
- weakNodeListener = null;
-
- if (delegate != null) {
- weakPrefListener = WeakListeners.create(PreferenceChangeListener.class, this, delegate);
- delegate.addPreferenceChangeListener(weakPrefListener);
-
- weakNodeListener = WeakListeners.create(NodeChangeListener.class, this, delegate);
- delegate.addNodeChangeListener(weakNodeListener);
- }
- }
-
- private void _sync(
- List> prefEvents,
- List> nodeEvents
- ) {
- // synchronize all children firts
- for(ProxyPreferences pp : children.values()) {
- pp._sync(prefEvents, nodeEvents);
- }
-
- // report all new children as removed
- EventBag nodeBag = new EventBag();
- nodeBag.addListeners(nodeListeners);
-
- for(ProxyPreferences pp : children.values()) {
- if (pp.delegate == null) {
- // new node that does not have corresponding node in the original hierarchy
- nodeBag.addEvent(new NodeChangeEventExt(this, pp, true));
- }
- }
-
- if (!nodeBag.getEvents().isEmpty()) {
- nodeEvents.add(nodeBag);
- }
-
- // report all modified keys
- if (delegate != null) {
- EventBag prefBag = new EventBag();
- prefBag.addListeners(prefListeners);
- prefEvents.add(prefBag);
-
- for(String key : data.keySet()) {
- prefBag.addEvent(new PreferenceChangeEvent(this, key, delegate.get(key, data.get(key).getValue())));
- }
- } // else there is no corresponding node in the orig hierarchy and this node
- // will be reported as removed
-
- // erase modified data
- for(NodeChangeEvent nce : nodeBag.getEvents()) {
- children.remove(nce.getChild().name());
- }
- data.clear();
- }
-
- private void firePrefEvents(List> events) {
- if (noEvents) {
- return;
- }
-
- for(EventBag bag : events) {
- for(PreferenceChangeEvent event : bag.getEvents()) {
- for(PreferenceChangeListener l : bag.getListeners()) {
- try {
- l.preferenceChange(event);
- } catch (Throwable t) {
- LOG.log(Level.WARNING, null, t);
- }
- }
- }
- }
- }
-
- private void fireNodeEvents(List> events) {
- if (noEvents) {
- return;
- }
-
- for(EventBag bag : events) {
- for(NodeChangeEvent event : bag.getEvents()) {
- for(NodeChangeListener l : bag.getListeners()) {
- try {
- if ((event instanceof NodeChangeEventExt) && ((NodeChangeEventExt) event).isRemovalEvent()) {
- l.childRemoved(event);
- } else {
- l.childAdded(event);
- }
- } catch (Throwable t) {
- LOG.log(Level.WARNING, null, t);
- }
- }
- }
- }
- }
-
- /* test */ static final class Tree {
-
- public static Tree getTree(Object token, Preferences prefs) {
- synchronized (trees) {
- // find all trees for the token
- Map forest = trees.get(token);
- if (forest == null) {
- forest = new HashMap();
- trees.put(token, forest);
- }
-
- // find the tree for the prefs' root
- Preferences root = prefs.node("/"); //NOI18N
- Tree tree = forest.get(root);
- if (tree == null) {
- tree = new Tree(token, root);
- forest.put(root, tree);
- }
-
- return tree;
- }
- }
-
- /* test */ static final Map> trees = new WeakHashMap>();
-
- private final Preferences root;
- private final Reference> tokenRef;
- private final Map nodes = new HashMap();
-
- private Tree(Object token, Preferences root) {
- this.root = root;
- this.tokenRef = new WeakReference(token);
- }
-
- public Object treeLock() {
- return this;
- }
-
- public ProxyPreferences get(ProxyPreferences parent, String name, Preferences delegate) {
- if (delegate != null) {
- assert name.equals(delegate.name());
-
- if (parent == null) {
- Preferences parentDelegate = delegate.parent();
- if (parentDelegate != null) {
- parent = get(null, parentDelegate.name(), parentDelegate);
- } // else delegate is the root
- } else {
- // sanity check
- assert parent.delegate == delegate.parent();
- }
- }
-
- String absolutePath;
- if (parent == null) {
- absolutePath = "/"; //NOI18N
- } else if (parent.parent() == null) {
- absolutePath = "/" + name; //NOI18N
- } else {
- absolutePath = parent.absolutePath() + "/" + name; //NOI18N
- }
-
- ProxyPreferences node = nodes.get(absolutePath);
- if (node == null) {
- node = new ProxyPreferences(parent, name, delegate, this);
- nodes.put(absolutePath, node);
-
- if (parent != null) {
- parent.addChild(node);
- }
- } else {
- assert !node.removed;
- }
-
- return node;
- }
-
- public void removeNode(ProxyPreferences node) {
- String path = node.absolutePath();
- assert nodes.containsKey(path);
- ProxyPreferences pp = nodes.remove(path);
- }
-
- public void destroy() {
- synchronized (trees) {
- Object token = tokenRef.get();
- if (token != null) {
- trees.remove(token);
- } // else the token has been GCed and therefore is not even in the trees map
- }
- }
- } // End of Tree class
-
- private static final class EventBag {
- private final Set listeners = new HashSet();
- private final Set events = new HashSet();
-
- public EventBag() {
- }
-
- public Set extends L> getListeners() {
- return listeners;
- }
-
- public Set extends E> getEvents() {
- return events;
- }
-
- public void addListeners(Collection extends L> l) {
- listeners.addAll(l);
- }
-
- public void addEvent(E event) {
- events.add(event);
- }
- } // End of EventBag class
-
- private static final class NodeChangeEventExt extends NodeChangeEvent {
- private final boolean removal;
- public NodeChangeEventExt(Preferences parent, Preferences child, boolean removal) {
- super(parent, child);
- this.removal = removal;
- }
-
- public boolean isRemovalEvent() {
- return removal;
- }
- } // End of NodeChangeEventExt class
}
diff --git a/options.editor/test/unit/src/org/netbeans/modules/options/indentation/ProxyPreferencesTest.java b/options.editor/test/unit/src/org/netbeans/modules/options/indentation/ProxyPreferencesTest.java
--- a/options.editor/test/unit/src/org/netbeans/modules/options/indentation/ProxyPreferencesTest.java
+++ b/options.editor/test/unit/src/org/netbeans/modules/options/indentation/ProxyPreferencesTest.java
@@ -305,7 +305,8 @@
test = null;
assertGC("Tree token was not GCed", treeTokenRef, Collections.singleton(this));
// touch the WeakHashMap to expungeStaleEntries
- ProxyPreferences.Tree.trees.size();
+ Object dummyToken = new Object();
+ ProxyPreferences dummyPrefs = ProxyPreferences.getProxyPreferences(dummyToken, orig);
assertGC("Test preferences were not GCed", testRef, Collections.singleton(this));
}
diff --git a/php.editor/nbproject/project.xml b/php.editor/nbproject/project.xml
--- a/php.editor/nbproject/project.xml
+++ b/php.editor/nbproject/project.xml
@@ -113,6 +113,15 @@
+ org.netbeans.modules.editor.fold
+
+
+
+ 1
+ 1.34
+
+
+ org.netbeans.modules.editor.indent
diff --git a/php.editor/src/org/netbeans/modules/php/editor/nav/FoldingScanner.java b/php.editor/src/org/netbeans/modules/php/editor/nav/FoldingScanner.java
--- a/php.editor/src/org/netbeans/modules/php/editor/nav/FoldingScanner.java
+++ b/php.editor/src/org/netbeans/modules/php/editor/nav/FoldingScanner.java
@@ -47,6 +47,8 @@
import java.util.List;
import java.util.Map;
import javax.swing.text.Document;
+import org.netbeans.api.editor.fold.FoldTemplate;
+import org.netbeans.api.editor.fold.FoldType;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.csl.spi.ParserResult;
import org.netbeans.modules.parsing.api.Source;
@@ -77,17 +79,44 @@
import org.netbeans.modules.php.editor.parser.astnodes.WhileStatement;
import org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultVisitor;
+import org.openide.util.NbBundle;
+import static org.netbeans.modules.php.editor.nav.Bundle.*;
/**
*
* @author Ondrej Brejla
*/
public final class FoldingScanner {
+
+ public static final FoldType TYPE_CODE_BLOCKS = FoldType.CODE_BLOCK;
+
+ /**
+ * FoldType for the PHP class (either nested, or top-level)
+ */
+ @NbBundle.Messages("FT_Classes=Classes")
+ public static final FoldType TYPE_CLASS = FoldType.NESTED.derive(
+ "class",
+ FT_Classes(), FoldTemplate.DEFAULT_BLOCK);
- private static final String FOLD_CODE_BLOCKS = "codeblocks"; //NOI18N
- private static final String FOLD_CLASS = "inner-classes"; //NOI18N
- private static final String FOLD_PHPDOC = "comments"; //NOI18N
- private static final String FOLD_COMMENT = "initial-comment"; //NOI18N
- private static final String FOLD_OTHER_CODE_BLOCKS = "othercodeblocks"; //NOI18N
+ /**
+ * PHP documentation comments
+ */
+ @NbBundle.Messages("FT_PHPDoc=PHPDoc documentation")
+ public static final FoldType TYPE_PHPDOC = FoldType.DOCUMENTATION.override(
+ FT_PHPDoc(), // NOI18N
+ new FoldTemplate(3, 2, "/**...*/")); // NOI18N
+
+ public static final FoldType TYPE_COMMENT = FoldType.COMMENT.override(
+ FoldType.COMMENT.getLabel(),
+ new FoldTemplate(2, 2, "/*...*/")); // NOI18N
+
+ /**
+ *
+ */
+ @NbBundle.Messages("FT_Functions=Functions and methods")
+ public static final FoldType TYPE_FUNCTION = FoldType.MEMBER.derive("function",
+ FT_Functions(),
+ FoldTemplate.DEFAULT_BLOCK);
+
private static final String LAST_CORRECT_FOLDING_PROPERTY = "LAST_CORRECT_FOLDING_PROPERY"; //NOI18N
public static FoldingScanner create() {
@@ -120,10 +149,10 @@
if (comments != null) {
for (Comment comment : comments) {
if (comment.getCommentType() == Comment.Type.TYPE_PHPDOC) {
- getRanges(folds, FOLD_PHPDOC).add(createOffsetRange(comment));
+ getRanges(folds, TYPE_PHPDOC).add(createOffsetRange(comment, -3));
} else {
if (comment.getCommentType() == Comment.Type.TYPE_MULTILINE) {
- getRanges(folds, FOLD_COMMENT).add(createOffsetRange(comment));
+ getRanges(folds, TYPE_COMMENT).add(createOffsetRange(comment));
}
}
}
@@ -138,10 +167,10 @@
continue;
}
if (scope instanceof TypeScope) {
- getRanges(folds, FOLD_CLASS).add(offsetRange);
+ getRanges(folds, TYPE_CLASS).add(offsetRange);
} else {
if (scope instanceof FunctionScope || scope instanceof MethodScope) {
- getRanges(folds, FOLD_CODE_BLOCKS).add(offsetRange);
+ getRanges(folds, TYPE_FUNCTION).add(offsetRange);
}
}
}
@@ -157,16 +186,20 @@
}
return Collections.emptyMap();
}
+
+ private OffsetRange createOffsetRange(ASTNode node, int startShift) {
+ return new OffsetRange(node.getStartOffset() + startShift, node.getEndOffset());
+ }
private OffsetRange createOffsetRange(ASTNode node) {
- return new OffsetRange(node.getStartOffset(), node.getEndOffset());
+ return createOffsetRange(node, 0);
}
- private List getRanges(Map> folds, String kind) {
- List ranges = folds.get(kind);
+ private List getRanges(Map> folds, FoldType kind) {
+ List ranges = folds.get(kind.code());
if (ranges == null) {
ranges = new ArrayList();
- folds.put(kind, ranges);
+ folds.put(kind.code(), ranges);
}
return ranges;
}
@@ -290,7 +323,7 @@
}
private void addFold(final OffsetRange offsetRange) {
- getRanges(folds, FOLD_OTHER_CODE_BLOCKS).add(offsetRange);
+ getRanges(folds, TYPE_CODE_BLOCKS).add(offsetRange);
}
}
diff --git a/php.editor/src/org/netbeans/modules/php/editor/nav/PHPFoldingProvider.java b/php.editor/src/org/netbeans/modules/php/editor/nav/PHPFoldingProvider.java
new file mode 100644
--- /dev/null
+++ b/php.editor/src/org/netbeans/modules/php/editor/nav/PHPFoldingProvider.java
@@ -0,0 +1,75 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2013 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.php.editor.nav;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import org.netbeans.api.editor.fold.FoldType;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.spi.editor.fold.FoldTypeProvider;
+
+/**
+ *
+ * @author sdedic
+ */
+@MimeRegistration(mimeType = "text/x-php5", service = FoldTypeProvider.class)
+public class PHPFoldingProvider implements FoldTypeProvider {
+ private static final Collection TYPES = new ArrayList(5);
+
+ static {
+ TYPES.add(FoldingScanner.TYPE_CLASS);
+ TYPES.add(FoldingScanner.TYPE_FUNCTION);
+ TYPES.add(FoldingScanner.TYPE_CODE_BLOCKS);
+ TYPES.add(FoldingScanner.TYPE_COMMENT);
+ TYPES.add(FoldingScanner.TYPE_PHPDOC);
+ }
+
+ @Override
+ public Collection getValues(Class type) {
+ return type == FoldType.class ? TYPES : null;
+ }
+
+ @Override
+ public boolean inheritable() {
+ return false;
+ }
+}
diff --git a/php.editor/src/org/netbeans/modules/php/editor/resources/layer.xml b/php.editor/src/org/netbeans/modules/php/editor/resources/layer.xml
--- a/php.editor/src/org/netbeans/modules/php/editor/resources/layer.xml
+++ b/php.editor/src/org/netbeans/modules/php/editor/resources/layer.xml
@@ -116,7 +116,17 @@
-
+
+
+
+
+
+
+
+
+
+
+