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

org.netbeans.modules.textmate.lexer.TextmateTokenId Maven / Gradle / Ivy

/*
 * 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.textmate.lexer;

import java.io.IOException;
import java.io.InputStream;
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.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.tm4e.core.grammar.IGrammar;
import org.eclipse.tm4e.core.registry.IRegistryOptions;
import org.eclipse.tm4e.core.registry.Registry;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.api.lexer.TokenId;
import org.netbeans.spi.editor.mimelookup.MimeDataProvider;
import org.netbeans.spi.lexer.LanguageHierarchy;
import org.netbeans.spi.lexer.Lexer;
import org.netbeans.spi.lexer.LexerRestartInfo;
import org.openide.filesystems.FileAttributeEvent;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileRenameEvent;
import org.openide.filesystems.FileUtil;
import org.openide.util.Lookup;
import org.openide.util.RequestProcessor;
import org.openide.util.RequestProcessor.Task;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
import org.openide.util.lookup.ServiceProvider;

public enum TextmateTokenId implements TokenId {

    TEXTMATE,
    UNTOKENIZED;

    @Override
    public String primaryCategory() {
        return "textmate";
    }

    public static class LanguageHierarchyImpl extends LanguageHierarchy {

        public static final String GRAMMAR_MARK = "textmate-grammar";
        public static final String INJECTION_MARK = "inject-to";
        private static final Task REFRESH = new RequestProcessor(TextmateTokenId.class.getName(), 1, false, false).create(() -> {
            refreshGrammars();
        });
        private static final int REFRESH_DELAY = 500;
        private static Map scope2File;
        private static Map> scope2Injections;
        private static Map mimeType2Scope;

        static {
            FileObject editors = FileUtil.getSystemConfigRoot().getFileObject("Editors");
            if (editors != null) {
                editors.addRecursiveListener(new FileChangeListener() {
                    @Override
                    public void fileFolderCreated(FileEvent fe) {
                        if (fe.getFile().getParent() == editors || fe.getFile().getParent().getParent() == editors) {
                            REFRESH.schedule(REFRESH_DELAY);
                        }
                    }
                    @Override
                    public void fileDataCreated(FileEvent fe) {
                        if (fe.getFile().getAttribute(GRAMMAR_MARK) != null) {
                            REFRESH.schedule(REFRESH_DELAY);
                        }
                    }

                    @Override
                    public void fileChanged(FileEvent fe) {
                        if (fe.getFile().getAttribute(GRAMMAR_MARK) != null) {
                            REFRESH.schedule(REFRESH_DELAY);
                        }
                    }

                    @Override
                    public void fileDeleted(FileEvent fe) {
                    }

                    @Override
                    public void fileRenamed(FileRenameEvent fe) {
                    }

                    @Override
                    public void fileAttributeChanged(FileAttributeEvent fe) {
                    }
                });
            }
            refreshGrammars();
        }

        public static void refreshGrammars() {
            Map scope2File = new HashMap<>();
            Map> scope2Injections = new HashMap<>();
            Map mimeType2Scope = new HashMap<>();
            FileObject editors = FileUtil.getSystemConfigRoot().getFileObject("Editors");
            if (editors != null) {
                Enumeration en = editors.getChildren(true);
                while (en.hasMoreElements()) {
                    FileObject candidate = en.nextElement();
                    Object attr = candidate.getAttribute(GRAMMAR_MARK);
                    if (attr instanceof String) {
                        String scope = (String) attr;
                        scope2File.put(scope, candidate);
                        attr = candidate.getAttribute(INJECTION_MARK);
                        if (attr instanceof String) {
                            for (String s : ((String)attr).split(",")) {
                                scope2Injections.computeIfAbsent(s, str -> new ArrayList<>()).add(scope);
                            }
                        } else {
                            mimeType2Scope.put(FileUtil.getRelativePath(editors, candidate.getParent()), scope);
                        }
                    }
                }
            }

            synchronized (LanguageHierarchyImpl.class) {
                if (!Objects.equals(LanguageHierarchyImpl.scope2File, scope2File) ||
                    !Objects.equals(LanguageHierarchyImpl.scope2Injections, scope2Injections) ||
                    !Objects.equals(LanguageHierarchyImpl.mimeType2Scope, mimeType2Scope)) {
                    LanguageHierarchyImpl.scope2File = scope2File;
                    LanguageHierarchyImpl.scope2Injections = scope2Injections;
                    LanguageHierarchyImpl.mimeType2Scope = mimeType2Scope;
                    MimeDataProviderImpl.updateAllMimeTypes();
                }
            }
        }

        private final String mimeType;
        private final IGrammar grammar;

        public LanguageHierarchyImpl(String mimeType, String scope) throws Exception {
            this.mimeType = mimeType;
            IRegistryOptions opts = new IRegistryOptions() {
                @Override
                public String getFilePath(String scopeName) {
                    synchronized (LanguageHierarchyImpl.class) {
                        FileObject file = scope2File.get(scopeName);
                        return file != null ? file.getNameExt() : null;
                    }
                }
                @Override
                public InputStream getInputStream(String scopeName) throws IOException {
                    synchronized (LanguageHierarchyImpl.class) {
                        FileObject file = scope2File.get(scopeName);
                        return file != null ? file.getInputStream(): null;
                    }
                }
                @Override
                public Collection getInjections(String scopeName) {
                    synchronized (LanguageHierarchyImpl.class) {
                        return scope2Injections.get(scopeName);
                    }
                }
            };
            this.grammar = new Registry(opts).loadGrammar(scope);
        }

        @Override
        protected Collection createTokenIds() {
            return Arrays.asList(TextmateTokenId.values());
        }

        @Override
        protected Lexer createLexer(LexerRestartInfo lri) {
            return new TextmateLexer(lri.input(), lri.state(), lri.tokenFactory(), grammar);
        }

        @Override
        protected String mimeType() {
            return mimeType;
        }

    }
    
    @ServiceProvider(service=MimeDataProvider.class, position=Integer.MAX_VALUE)
    public static final class MimeDataProviderImpl implements MimeDataProvider {

        private static final Logger LOG = Logger.getLogger(MimeDataProviderImpl.class.getName());
        private static final Map> placeholders = new HashMap<>();

        @Override
        public Lookup getLookup(MimePath arg0) {
            synchronized (LanguageHierarchyImpl.class) {
                String path = arg0.getPath();
                Reference placeholderRef = placeholders.get(path);
                LookupPlaceholder placeholder = placeholderRef != null ? placeholderRef.get() : null;
                if (placeholder == null) {
                    placeholders.put(path, new WeakReference<>(placeholder = new LookupPlaceholder()));
                }

                placeholder.setDelegate(updateMimeType(path));

                return placeholder;
            }
        }
        
        private static Lookup updateMimeType(String path) {
            String scope;

            scope = LanguageHierarchyImpl.mimeType2Scope.get(path);
            Lookup nested = Lookup.EMPTY;
            if (scope != null) {
                try {
                    nested = Lookups.singleton(new LanguageHierarchyImpl(path, scope).language());
                } catch (Exception ex) {
                    LOG.log(Level.FINE, null, ex);
                }
            }

            return nested;
        }

        public static void updateAllMimeTypes() {
            synchronized (LanguageHierarchyImpl.class) {
                for (Entry> e : placeholders.entrySet()) {
                    LookupPlaceholder lp = e.getValue().get();
                    if (lp != null) {
                        lp.setDelegate(updateMimeType(e.getKey()));
                    }
                }
            }
        }

        public static class LookupPlaceholder extends ProxyLookup {

            private void setDelegate(Lookup nested) {
                setLookups(nested);
            }
            
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy