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

org.netbeans.modules.languages.features.BraceHighlighting 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.ArrayList;
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.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.api.languages.LanguageDefinitionNotFoundException;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.languages.Feature;
import org.netbeans.modules.languages.Language;
import org.netbeans.modules.languages.LanguagesManager;
import org.netbeans.spi.editor.bracesmatching.BracesMatcher;
import org.netbeans.spi.editor.bracesmatching.BracesMatcherFactory;
import org.netbeans.spi.editor.bracesmatching.MatcherContext;
import org.netbeans.spi.editor.bracesmatching.support.BracesMatcherSupport;

/**
 *
 * @author Dan Prusa, Vita Stejskal
 */
public class BraceHighlighting implements BracesMatcher, BracesMatcherFactory {

    public BraceHighlighting(String topLevelMimeType) {
        this(topLevelMimeType, null);
    }

    public BraceHighlighting(String topLevelMimeType, MatcherContext context) {
        this.topLevelMimeType = topLevelMimeType;
        this.context = context;
    }

    // --------------------------------------------
    // BracesMatcher implementation
    // --------------------------------------------

    public int[] findOrigin() throws InterruptedException, BadLocationException {
        ((AbstractDocument) context.getDocument()).readLock();
        try {
            Language language = null;
            try {
                language = LanguagesManager.getDefault().getLanguage(topLevelMimeType);
            } catch (LanguageDefinitionNotFoundException e) {
                // ignore, handled later
            }
            TokenHierarchy th = TokenHierarchy.get(context.getDocument());

            if (language == null || th == null) {
                // ?? no lexer for the language, all Schliemann languages should have
                // a lexer
                return defaultFindOrigin(context);
            }

            int caretOffset = context.getSearchOffset();
            boolean searchBack = context.isSearchingBackward();
            List> sequences = th.embeddedTokenSequences(caretOffset, searchBack);

            for(int i = sequences.size() - 1; i >= 0; i--) {
                TokenSequence ts = sequences.get(i);
                /** @todo can ts.language() equals language ?*/
                //if (ts.language().equals(language)) {
                if (ts.language().mimeType().equals(language.getMimeType())) {
                    seq = ts;
                    if (i > 0) {
                        TokenSequence outerSeq = sequences.get(i - 1);
                        seqStart = outerSeq.offset();
                        seqEnd = outerSeq.offset() + outerSeq.token().length();
                    } else {
                        // seq is the top level sequence, ie the whole document is just javadoc
                        seqStart = 0;
                        seqEnd = context.getDocument().getLength();
                    }
                    break;
                }
            }

            if (seq == null) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("TokenSequence with wrong language " + language); //NOI18N
                }
                return null;
            }

            Map>[] pairsMaps = getPairsMap(language);
            if (pairsMaps == null) {
                return defaultFindOrigin(context);
            }

            seq.move(caretOffset);
            if (seq.moveNext()) {
                boolean [] bckwrd = new boolean[1];

                String tokenText = seq.token().text().toString();
                String trimedTokenText = tokenText.trim();
                if (isOrigin(pairsMaps, trimedTokenText, bckwrd)) {
                    if (seq.offset() < caretOffset || !searchBack) {
                        originText = trimedTokenText;
                        backwards = bckwrd[0];
                        pairsMap = backwards ? pairsMaps[1] : pairsMaps[0];
                        int offset = seq.offset() + tokenText.indexOf(trimedTokenText);
                        int length = trimedTokenText.length();
                        return new int [] { offset, offset + length };
                    }
                }

                while(moveTheSequence(seq, searchBack, context.getLimitOffset())) {
                    tokenText = seq.token().text().toString();
                    trimedTokenText = tokenText.trim();
                    if (isOrigin(pairsMaps, trimedTokenText, bckwrd)) {
                        originText = trimedTokenText;
                        backwards = bckwrd[0];
                        pairsMap = backwards ? pairsMaps[1] : pairsMaps[0];
                        int offset = seq.offset() + tokenText.indexOf(trimedTokenText);
                        int length = trimedTokenText.length();
                        return new int [] { offset, offset + length };
                    }
                }
            }

            return null;
        } finally {
            ((AbstractDocument) context.getDocument()).readUnlock();
        }
    }

    public int[] findMatches() throws InterruptedException, BadLocationException {
        ((AbstractDocument) context.getDocument()).readLock();
        try {
            // Use the default matcher if no better was available
            if (defaultMatcher != null) {
                return defaultMatcher.findMatches();
            }

            // Proper matching using the pairs supplied by the language definition
            assert seq != null : "No token sequence"; //NOI18N

            List unresolved = new ArrayList();
            unresolved.add(originText);
            while(moveTheSequence(seq, backwards, -1)) {
                String tokenText = seq.token().text().toString();
                String trimedTokenText = tokenText.trim();
                int depth = unresolved.size() - 1;
                String currentOrigin = unresolved.get(depth);
                Set matchingTexts = pairsMap.get(currentOrigin);
                if (matchingTexts != null && matchingTexts.contains(trimedTokenText)) {
                    unresolved.remove(depth);
                    if (unresolved.size() == 0) {                    
                        int offset = seq.offset() + tokenText.indexOf(trimedTokenText);
                        int length = trimedTokenText.length();
                        return new int [] { offset, offset + length };
                    }
                } else if (pairsMap.containsKey(trimedTokenText)) {
                    unresolved.add(trimedTokenText);
                }
            }

            return null;
        } finally {
            ((AbstractDocument) context.getDocument()).readUnlock();
        }
    }
    
    // --------------------------------------------
    // BracesMatcherFactory implementation
    // --------------------------------------------
    
    public BracesMatcher createMatcher(MatcherContext context) {
        return new BraceHighlighting(topLevelMimeType, context);
    }

    // --------------------------------------------
    // Private implementation
    // --------------------------------------------
    
    private static final Logger LOG = Logger.getLogger(BraceHighlighting.class.getName());
    private static final Map>[]> PAIRS = new WeakHashMap>[]>();

    private static Map>[] getPairsMap(Language l) {
        if (!PAIRS.containsKey(l)) {
            Map> startToEnd = new HashMap>();
            Map> endToStart = new HashMap>();

            List indents = l.getFeatureList().getFeatures("BRACE"); //NOI18N
            Iterator it = indents.iterator();
            while (it.hasNext()) {
                Feature indent = it.next();
                String s = (String) indent.getValue ();
                int i = s.indexOf(':'); //NOI18N
                String start = s.substring(0, i);
                String end = s.substring(i + 1);
                Set matchTextsEnd = startToEnd.get(start);
                if (matchTextsEnd == null) {
                    matchTextsEnd = new HashSet();
                    startToEnd.put(start, matchTextsEnd);

                }
                matchTextsEnd.add(end);
                Set matchTextsStart = endToStart.get(end); 
                if (matchTextsStart == null) {
                    matchTextsStart = new HashSet();
                    endToStart.put(end, matchTextsStart);
                }
                matchTextsStart.add(start);
            }
            @SuppressWarnings("unchecked")
            Map> [] arr = new Map [] { startToEnd, endToStart };
            PAIRS.put(l, arr);
        }
        return PAIRS.get(l);
    }
    
    private static boolean moveTheSequence(TokenSequence seq, boolean backward, int offsetLimit) {
        if (backward) {
            if (seq.movePrevious()) {
                int e = seq.offset() + seq.token().length();
                return offsetLimit == -1 ? true : e > offsetLimit;
            }
        } else {
            if (seq.moveNext()) {
                int s = seq.offset();
                return offsetLimit == -1 ? true : s < offsetLimit;
            }
        }
        return false;
    }

    private static boolean isOrigin(Map>[] pairsMaps, String originText, boolean backwards[]) {
        Set s = pairsMaps[0].get(originText);
        if (s != null && s.size() > 0) {
            backwards[0] = false;
            return true;
        } else {
            s = pairsMaps[1].get(originText);
            if (s != null && s.size() > 0) {
                backwards[0] = true;
                return true;
            }
        }
        return false;
    }
    
    private int [] defaultFindOrigin(MatcherContext context) throws InterruptedException, BadLocationException {
        defaultMatcher = BracesMatcherSupport.defaultMatcher(context, -1, -1);
        return defaultMatcher.findOrigin();
    }
    
    private final MatcherContext context;
    private final String topLevelMimeType;
    
    private TokenSequence seq;
    private int seqStart;
    private int seqEnd;
    private String originText;
    private Map> pairsMap;
    private boolean backwards;
    private BracesMatcher defaultMatcher;
    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy