All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.languages.features.LanguagesFoldManager Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.modules.languages.features;

import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.SwingUtilities;
import javax.swing.text.StyledDocument;
import javax.swing.text.Document;
import javax.swing.text.BadLocationException;
import javax.swing.event.DocumentEvent;

import org.netbeans.api.editor.fold.FoldUtilities;
import org.netbeans.editor.Utilities;
import org.openide.text.NbDocument;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.languages.ASTEvaluator;
import org.netbeans.api.languages.ASTToken;
import org.netbeans.api.languages.ASTPath;
import org.netbeans.api.languages.ParserManager.State;
import org.netbeans.api.languages.SyntaxContext;
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.languages.SyntaxContext;
import org.netbeans.api.languages.ASTNode;
import org.netbeans.api.languages.ASTItem;
import org.netbeans.api.lexer.Token;
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.netbeans.modules.languages.Feature;
import org.netbeans.modules.editor.NbEditorDocument;
import org.netbeans.modules.languages.Language;
import org.netbeans.modules.languages.ParserManagerImpl;


/**
 *
 * @author Jan Jancura
 */
public class LanguagesFoldManager extends ASTEvaluator implements FoldManager {
    
    static final String         FOLD = "FOLD";
    private static final int    EVALUATING = 0;
    private static final int    STOPPED = 1;
    
    private FoldOperation       operation;
    private Document            doc;
    private ParserManagerImpl   parserManager;
    private int                 evalState = STOPPED;
    
    
    /** Creates a new instance of JavaFoldManager */
    public LanguagesFoldManager () {
    }
    
    /**
     * Initialize this manager.
     *
     * @param operation fold hierarchy operation dedicated to the fold manager.
     */
    public void init (FoldOperation operation) {
        Document d = operation.getHierarchy ().getComponent ().getDocument ();
        if (d instanceof NbEditorDocument) {
            this.doc = d;
            this.operation = operation;
            parserManager = ParserManagerImpl.getImpl (doc);
            parserManager.addASTEvaluator (this);
            parserManager.fire (
                parserManager.getState (), 
                null, 
                Collections.>singletonMap (FOLD, Collections.singleton (this)),
                parserManager.getAST ()
            );
        }
    }
    
    /**
     * Initialize the folds provided by this manager.
     * 
* The fold manager should create initial set of folds here * if it does not require too much resource consumption. *
* As this method is by default called at the file opening time * then it may be better to schedule the initial fold computations * for later time and do nothing here. * *

* Any listeners necessary for the maintenance of the folds * can be attached here. *
* Generally there should be just weak listeners used * to not prevent the GC of the text component. * * @param transaction transaction in terms of which the intial * fold changes can be performed. */ public void initFolds (FoldHierarchyTransaction transaction) { } /** * Called by hierarchy upon the insertion to the underlying document. *
* If there would be any fold modifications required they may be added * to the given transaction. * * @param evt document event describing the document modification. * @param transaction open transaction to which the manager can add * the fold changes. */ public void insertUpdate (DocumentEvent evt, FoldHierarchyTransaction transaction) { } /** * Called by hierarchy upon the removal in the underlying document. *
* If there would be any fold modifications required they may be added * to the given transaction. * * @param evt document event describing the document modification. * @param transaction open transaction to which the manager can add * the fold changes. */ public void removeUpdate (DocumentEvent evt, FoldHierarchyTransaction transaction) { } /** * Called by hierarchy upon the change in the underlying document. *
* If there would be any fold modifications required they may be added * to the given transaction. * * @param evt document event describing the document change. * @param transaction open transaction to which the manager can add * the fold changes. */ public void changedUpdate (DocumentEvent evt, FoldHierarchyTransaction transaction) { } /** * Notify that the fold was removed from hierarchy automatically * by fold hierarchy infrastructure processing * because it became empty (by a document modification). */ public void removeEmptyNotify (Fold epmtyFold) { } /** * Notify that the fold was removed from hierarchy automatically * by fold hierarchy infrastructure processing * because it was damaged by a document modification. */ public void removeDamagedNotify (Fold damagedFold) { } /** * Notify that the fold was expanded automatically * by fold hierarchy infrastructure processing * because its isExpandNecessary() * return true. */ public void expandNotify (Fold expandedFold) { } /** * Notification that this manager will no longer be used by the hierarchy. *
* The folds that it maintains are still valid but after this method * finishes they will be removed from the hierarchy. * *

* This method is not guaranteed to be called. Therefore the manager * must only listen weekly on the related information providers * so that it does not block the hierarchy from being garbage collected. */ public void release () { //S ystem.out.println("release " + mimeType + " : " + operation + " : " + this); if (doc != null) { parserManager.removeASTEvaluator (this); } parserManager = null; } // ASTEvaluator methods .................................................... private static FoldType defaultFoldType = new FoldType ("default"); private List folds; public void beforeEvaluation (State state, ASTNode root) { evalState = EVALUATING; folds = null; } public void afterEvaluation (State state, ASTNode root) { SwingUtilities.invokeLater (new Runnable () { public void run () { if (operation == null) { evalState = STOPPED; return; } FoldHierarchy fh = operation.getHierarchy (); try { //get existing folds Fold fold = operation.getHierarchy ().getRootFold (); List existingFolds = new ArrayList (); collectFolds(fold, existingFolds); List generated = folds != null ? folds : new ArrayList(); //...and generate a list of new folds and a list of folds to be removed final HashSet newborns = new HashSet(generated.size() / 2); final HashSet zombies = new HashSet(generated.size() / 2); //go through all the parsed elements and compare it with the list of existing folds Iterator genItr = generated.iterator(); Hashtable newbornsLinesCache = new Hashtable(); HashSet duplicateNewborns = new HashSet(); while(genItr.hasNext()) { FoldItem fi = (FoldItem)genItr.next(); //do not add more newborns with the same lineoffset int fiLineOffset = Utilities.getLineOffset((BaseDocument)doc, fi.start); FoldItem found = (FoldItem)newbornsLinesCache.get(Integer.valueOf(fiLineOffset)); if(found != null) { //figure out whether the new element is a descendant of the already added one if(found.end < fi.end) { //remove the descendant and add the current duplicateNewborns.add(found); } } newbornsLinesCache.put(Integer.valueOf(fiLineOffset), fi); //add line mapping of the current element //try to find a fold for the fold info Fold fs = FoldUtilities.findNearestFold(fh, fi.start); //hacky fix - we need to find a better solution //how to check if the fold was created by me if (fs != null) { try { operation.getExtraInfo(fs); //no ISE thrown - my fold } catch (IllegalStateException e) { //not my fold fs = null; } } if(fs != null && fs.getStartOffset() == fi.start && fs.getEndOffset() == fi.end) { //there is a fold with the same boundaries as the FoldInfo if(fi.type != fs.getType() || !(fi.foldName.equals(fs.getDescription()))) { //the fold has different type or/and description => recreate zombies.add(fs); newborns.add(fi); } } else { //create a new fold newborns.add(fi); } } newborns.removeAll(duplicateNewborns); existingFolds.removeAll(zombies); Hashtable linesToFoldsCache = new Hashtable(); //needed by *** //remove not existing folds Iterator extItr = existingFolds.iterator(); while(extItr.hasNext()) { Fold f = (Fold)extItr.next(); // if(!zombies.contains(f)) { //check if not alread scheduled to remove Iterator genItr2 = generated.iterator(); boolean found = false; while(genItr2.hasNext()) { FoldItem fi = (FoldItem)genItr2.next(); if(f.getStartOffset() == fi.start && f.getEndOffset() == fi.end) { found = true; break; } } if(!found) { zombies.add(f); } else { //store the fold lineoffset 2 fold mapping int lineoffset = Utilities.getLineOffset((BaseDocument)doc, f.getStartOffset()); linesToFoldsCache.put(Integer.valueOf(lineoffset), f); } // } } //*** check for all newborns if there isn't any existing fold //starting on the same line which is a descendant of this new fold //if so remove it. Iterator newbornsItr = newborns.iterator(); HashSet newbornsToRemove = new HashSet(); while(newbornsItr.hasNext()) { FoldItem fi = (FoldItem)newbornsItr.next(); Fold existing = (Fold)linesToFoldsCache.get(Integer.valueOf(Utilities.getLineOffset((BaseDocument)doc, fi.start))); if(existing != null) { //test if the fold is my descendant if(existing.getEndOffset() < fi.end) { //descendant - remove it zombies.add(existing); } else { //remove the newborn newbornsToRemove.add(fi); } } } newborns.removeAll(newbornsToRemove); ((BaseDocument)doc).readLock(); try { //lock the hierarchy fh.lock(); try { //open new transaction FoldHierarchyTransaction transaction = operation.openTransaction (); try { //remove outdated folds Iterator iter = zombies.iterator(); while(iter.hasNext()) { Fold f = (Fold)iter.next(); //test whether the size of the document is greater than zero, //if it is then this means that the document has been closed in editor. if(doc.getLength() == 0) break; operation.removeFromHierarchy(f, transaction); } //add new folds Iterator newFolds = newborns.iterator(); while(newFolds.hasNext()) { FoldItem f = (FoldItem)newFolds.next(); //test whether the size of the document is greater than zero, //if it is then this means that the document has been closed in editor. if(doc.getLength() == 0) break; if(f.start >= 0 && f.end >= 0 && f.start < f.end && f.end <= doc.getLength()) { operation.addToHierarchy(f.type, f.foldName, false, f.start , f.end , 0, 0, fh, transaction); } } }catch(BadLocationException ble) { ble.printStackTrace(); }finally { transaction.commit(); } } finally { fh.unlock(); } } finally { ((BaseDocument)doc).readUnlock(); } } catch (BadLocationException ex) { ex.printStackTrace (); } finally { evalState = STOPPED; } } }); } private void collectFolds(Fold fold, List existingFolds) { int i, k = fold.getFoldCount(); for (i = 0; i < k; i++) { Fold f = fold.getFold (i); //hacky fix - we need to find a better solution //how to check if the fold was created by me try { operation.getExtraInfo(f); //no ISE thrown - my fold existingFolds.add(f); collectFolds(f, existingFolds); } catch (IllegalStateException e) { //not my fold } } } public String getFeatureName () { return "FOLD"; } public void evaluate (State state, List path, Feature fold) { ASTItem item = path.get (path.size () - 1); int s = item.getOffset (), e = item.getEndOffset (); int sln = NbDocument.findLineNumber ((StyledDocument)doc, s), eln = NbDocument.findLineNumber ((StyledDocument)doc, e); if (sln == eln) return; String mimeType = item.getMimeType (); Language language = (Language) item.getLanguage (); boolean isTokenFold = ((item instanceof ASTToken) && fold == language.getFeatureList ().getFeature (FOLD, ((ASTToken) item).getTypeName ())); if (!isTokenFold) { TokenHierarchy th = TokenHierarchy.get (doc); if (doc instanceof NbEditorDocument) ((NbEditorDocument) doc).readLock (); try { TokenSequence ts = th.tokenSequence (); ts.move (e - 1); if (!ts.moveNext ()) return; while (!ts.language ().mimeType ().equals (mimeType)) { ts = ts.embedded (); if (ts == null) return; ts.move (e - 1); if (!ts.moveNext ()) return; } Token t = ts.token (); Set skip = language.getAnalyser ().getSkipTokenTypes (); while (skip.contains (t.id ().ordinal ())) { if (!ts.movePrevious ()) break; t = ts.token (); } e = ts.offset () + t.length (); String tokenText = t.text ().toString (); if (tokenText.endsWith ("\n")) e--; sln = NbDocument.findLineNumber ((StyledDocument)doc, s); eln = NbDocument.findLineNumber ((StyledDocument)doc, e); if (eln - sln < 1) return; } finally { if (doc instanceof NbEditorDocument) ((NbEditorDocument) doc).readUnlock (); } } if (fold.hasSingleValue ()) { String foldName = LocalizationSupport.localize (language, (String) fold.getValue (SyntaxContext.create (doc, ASTPath.create (path)))); if (foldName == null) return; addFold (new FoldItem(foldName, s, e, defaultFoldType)); return; } String foldName = LocalizationSupport.localize (language, (String) fold.getValue ("fold_display_name", SyntaxContext.create (doc, ASTPath.create (path)))); if (foldName == null) { foldName = "..."; // NOI18N } String foldType = LocalizationSupport.localize (language, (String) fold.getValue ("collapse_type_action_name")); addFold (new FoldItem (foldName, s, e, Folds.getFoldType (foldType))); } private void addFold (FoldItem foldItem) { if (folds == null) folds = new CopyOnWriteArrayList (); folds.add (foldItem); } // package private methods for unit tests................................... void init (Document doc) { this.doc = doc; this.operation = null; parserManager = ParserManagerImpl.getImpl (doc); parserManager.addASTEvaluator(this); } List getFolds() { return folds; } boolean isEvaluating() { return evalState == EVALUATING; } // innerclasses ............................................................ static final class FoldItem { String foldName; int start; int end; FoldType type; FoldItem (String foldName, int start, int end, FoldType type) { this.foldName = foldName; this.start = start; this.end = end; this.type = type; } } public static final class Factory implements FoldManagerFactory { public FoldManager createFoldManager () { return new LanguagesFoldManager (); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy