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

org.netbeans.modules.csl.navigation.GsfStructureProvider 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.csl.navigation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.api.editor.document.LineDocument;
import org.netbeans.api.editor.document.LineDocumentUtils;
import org.netbeans.modules.csl.api.StructureItem;
import org.netbeans.modules.csl.api.StructureScanner;
import org.netbeans.modules.csl.core.Language;
import org.netbeans.modules.csl.core.LanguageRegistry;
import org.netbeans.modules.csl.spi.ParserResult;
import org.netbeans.modules.parsing.api.ParserManager;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.api.UserTask;
import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.modules.parsing.spi.Parser;
import org.netbeans.api.lsp.StructureElement;
import org.netbeans.modules.csl.api.ElementKind;
import org.netbeans.modules.csl.api.HtmlFormatter;
import org.netbeans.modules.csl.api.Modifier;
import org.netbeans.spi.lsp.StructureProvider;

/**
 * Implementation of StructureProvider to supply outline view in VSCode
 * @author Petr Pisl
 */
public class GsfStructureProvider implements StructureProvider {

    private static final Logger LOGGER = Logger.getLogger(GsfStructureProvider.class.getName());
    
    private static StructureElement.Kind convertKind(ElementKind elementKind) {
        switch(elementKind) {
            case ATTRIBUTE: return StructureElement.Kind.Property;
            case CALL: return StructureElement.Kind.Event;
            case CLASS: return StructureElement.Kind.Class;
            case CONSTANT: return StructureElement.Kind.Constant;
            case CONSTRUCTOR: return StructureElement.Kind.Constructor;
            case DB: return StructureElement.Kind.File;
            case ERROR: return StructureElement.Kind.Event;
            case METHOD: return StructureElement.Kind.Method;
            case FILE: return StructureElement.Kind.File;
            case FIELD: return StructureElement.Kind.Field;
            case MODULE: return StructureElement.Kind.Module;
            case VARIABLE: return StructureElement.Kind.Variable;
            case GLOBAL: return StructureElement.Kind.Module;
            case INTERFACE: return StructureElement.Kind.Interface;
            case KEYWORD: return StructureElement.Kind.Key;
            case OTHER: return StructureElement.Kind.Object;
            case PACKAGE: return StructureElement.Kind.Package;
            case PARAMETER: return StructureElement.Kind.Variable;
            case PROPERTY: return StructureElement.Kind.Property;
            case RULE: return StructureElement.Kind.Event;
            case TAG: return StructureElement.Kind.Operator;
            case TEST: return StructureElement.Kind.Function;
        }
        return StructureElement.Kind.Object;
    }
    
    private static void  createDetail(StructureItem item, Builder builder) {
        NoHtmlFormatter formatter = new NoHtmlFormatter();
        String s = item.getHtml(formatter);
        s = s.substring(item.getName().length()).trim();
        if (!s.trim().isEmpty()) {
            builder.detail(s);
        }
    }
    
    private static void createTags(StructureItem item, Builder builder) {
        if (item.getModifiers().contains(Modifier.DEPRECATED)) {
            builder.addTag(StructureElement.Tag.Deprecated);
        }
    }
    
    private static void createChildren(Document doc, StructureItem item, Builder builder) {
        if (!item.isLeaf()) {
            List  origChildren = item.getNestedItems();
            if (!origChildren.isEmpty()) {
                List children = new ArrayList<>(origChildren.size());
                convertStructureItems(doc, origChildren, children);
                builder.children(children);
            }
        }
    }
    
    /** A formatter that strips the html elements from the text */
    static private class NoHtmlFormatter extends HtmlFormatter {
        StringBuilder sb = new StringBuilder();
        
        @Override
        public void reset() {
            sb = new StringBuilder();
        }

        static String stripHtml( String htmlText ) {
            if( null == htmlText )
                return null;
            return htmlText.replaceAll( "<[^>]*>", "" ) // NOI18N
                           .replace( " ", " " ) // NOI18N
                           .trim();
        }
        
        @Override
        public void appendHtml(String html) {
            sb.append(stripHtml(html));
        }

        @Override
        public void appendText(String text, int fromInclusive, int toExclusive) {
            int l = toExclusive - fromInclusive;
            if (sb.length() + l < maxLength) {
                sb.append(text.subSequence(fromInclusive, toExclusive));
            } else {
                sb.append(text.subSequence(fromInclusive, toExclusive - (l - maxLength)));
                sb.append("...");   //NOI18N
            }
        }

        @Override
        public void emphasis(boolean start) {
        }

        @Override
        public void name(ElementKind kind, boolean start) {
        }

        @Override
        public void parameters(boolean start) {
        }

        @Override
        public void active(boolean start) {
        }

        @Override
        public void type(boolean start) {
        }

        @Override
        public void deprecated(boolean start) {
        }

        @Override
        public String getText() {
            return sb.toString();
        }
    }
    
    @Override
    public List getStructure(Document doc) {
        final List sElements = new ArrayList<>();
        try {
            ParserManager.parse(Collections.singletonList(Source.create(doc)), new UserTask() {
                @Override
                public void run(ResultIterator resultIterator) throws Exception {
                    Parser.Result result = resultIterator.getParserResult(-1);
                    if(result instanceof ParserResult) {
                        ParserResult parserResult = (ParserResult) result;
                        Language language = LanguageRegistry.getInstance().getLanguageByMimeType(resultIterator.getSnapshot().getMimeType());
                        if (language != null) {
                            StructureScanner scanner = language.getStructure();
                            if (scanner != null) {
                                List items = scanner.scan(parserResult);
                                convertStructureItems(doc, items, sElements);
                            }
                        }
                    }
                }

            });
            return sElements;
        } catch (ParseException ex) {
            LOGGER.log(Level.FINE, null, ex);
            return Collections.EMPTY_LIST;
        }
    }
    
    private static void convertStructureItems(Document doc, List items, List sElements) {
        StructureElement lastElement = null;
        if (doc instanceof LineDocument) {
            //  if it's line document, we can set the enclosing range for whole line
            LineDocument ldoc = (LineDocument)doc;
            for (StructureItem item : items) {
                int startOffset = (int)item.getPosition();
                int lineStart = startOffset;
                int lineEnd = (int)item.getEndPosition();
                Builder builder = StructureProvider.newBuilder(item.getName(), convertKind(item.getKind()));
                builder.selectionStartOffset(startOffset).selectionEndOffset(lineEnd);
                createDetail(item, builder);
                createTags(item, builder);

                try {
                    String prefix = doc.getText(lineStart, startOffset);
                    lineStart = LineDocumentUtils.getLineStart(ldoc, lineStart);
                    if (prefix.trim().isEmpty()) {
                        lineEnd = LineDocumentUtils.getLineEnd(ldoc, lineEnd);
                    }
                } catch (BadLocationException ex) {
                    lineStart = startOffset;
                    lineEnd = (int)item.getEndPosition();
                }
                if (lastElement == null) {
                    builder.expandedStartOffset(lineStart);
                    builder.expandedEndOffset(lineEnd);
                } else {
                    if (lastElement.getExpandedEndOffset() < lineStart) {
                        builder.expandedStartOffset(lineStart);
                        builder.expandedEndOffset(lineEnd);
                    } else if (lastElement.getExpandedStartOffset() <= lineStart && lineEnd == lastElement.getExpandedEndOffset()) {
                        // The same line
                        sElements.remove(lastElement);
                        Builder leBuilder = StructureProvider.copy(lastElement);
                        leBuilder.expandedEndOffset(startOffset - 1);
                        sElements.add(leBuilder.build());
                    }
                }
                builder.expandedStartOffset(lineStart).expandedEndOffset(lineEnd);
                createChildren(doc, item, builder);
                lastElement = builder.build();
                sElements.add(lastElement);
            }
        } else {
            for (StructureItem item : items) {
                int selectionStart = (int)item.getPosition();
                int selectionEnd = (int)item.getEndPosition();
                Builder builder = StructureProvider.newBuilder(item.getName(), convertKind(item.getKind()));
                builder.selectionStartOffset(selectionStart).selectionEndOffset(selectionEnd);
                builder.expandedStartOffset(selectionStart).expandedEndOffset(selectionEnd);
                createDetail(item, builder);
                createTags(item, builder);
                createChildren(doc, item, builder);
                sElements.add(builder.build());
            }
        }
    }
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy