ppisl commented on a change in pull request #3525: URL: https://github.com/apache/netbeans/pull/3525#discussion_r805836410
########## File path: ide/csl.api/src/org/netbeans/modules/csl/navigation/GsfStructureProvider.java ########## @@ -0,0 +1,268 @@ +/* + * 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.TypeParameter; + 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 <? extends StructureItem> origChildren = item.getNestedItems(); + if (!origChildren.isEmpty()) { + List<StructureElement> 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; + String res = htmlText.replaceAll( "<[^>]*>", "" ); // NOI18N // NOI18N + res = res.replaceAll( " ", " " ); // NOI18N // NOI18N + res = res.trim(); + return res; + } + + @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 CompletableFuture<List<StructureElement>> getStructure(Document doc) { + final List<StructureElement> 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<? extends StructureItem> items = scanner.scan(parserResult); + convertStructureItems(doc, items, sElements); + } + } + } + } + + }); + return CompletableFuture.completedFuture(sElements); + } catch (ParseException ex) { + LOGGER.log(Level.FINE, null, ex); + return CompletableFuture.completedFuture(null); + } + } + + private static void convertStructureItems(Document doc, List<? extends StructureItem> items, List<StructureElement> 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); Review comment: The code i a little bit misleading. I have change it little bit. I will try to explain the logic: ``` class A { | def methodC {} { } } ``` The `|` marks text cursor, in this case the logic counts the expanded offset from the beginning of the line, becase on the line starts `methodC` definition. So in this case the methodC element is selected. But when you have something like: ``` class A{ int field1; |int field2 } ``` the start of the expanded offset will be the same as selected offset of the element `field2` and not from the begining of the line. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected] For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists
