sdedic commented on a change in pull request #3525: URL: https://github.com/apache/netbeans/pull/3525#discussion_r804709345
########## 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); + lineStart = LineDocumentUtils.getLineStart(ldoc, lineStart); + if (prefix.trim().isEmpty()) { + lineEnd = LineDocumentUtils.getLineEnd(ldoc, lineEnd); + } else { + lineStart = startOffset; + } + + } catch (BadLocationException ex) { + lineStart = (int)item.getPosition(); + 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.newBuilder(lastElement.getName(), lastElement.getKind()); Review comment: What about adding `Builder StructureProvider.copy(StructureElement from)` ? All the settings can be copied, the caller will just call builder methods for changes. ########## 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: `lineStart` was assigned the value of `startOffset` (line 210), but was never changed after that -- maybe swap lines 219 & 220 ? ########## File path: java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java ########## @@ -813,97 +814,61 @@ protected void process(CompilationInfo arg0, Document arg1, SchedulerEvent arg2) @Override public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(DocumentSymbolParams params) { return server.openedProjects().thenCompose(projects -> { - JavaSource js = getJavaSource(params.getTextDocument().getUri()); - if (js == null) { + List<Either<SymbolInformation, DocumentSymbol>> result = new ArrayList<>(); + + String uri = params.getTextDocument().getUri(); + FileObject file = fromURI(uri); + Document doc = server.getOpenedDocuments().getDocument(uri); + if (file == null || !(doc instanceof LineDocument)) { return CompletableFuture.completedFuture(Collections.emptyList()); } - List<Either<SymbolInformation, DocumentSymbol>> result = new ArrayList<>(); - try { - js.runUserActionTask(cc -> { - cc.toPhase(JavaSource.Phase.RESOLVED); - Trees trees = cc.getTrees(); - CompilationUnitTree cu = cc.getCompilationUnit(); - if (cu.getPackage() != null) { - TreePath tp = trees.getPath(cu, cu.getPackage()); - Element el = trees.getElement(tp); - if (el != null && el.getKind() == ElementKind.PACKAGE) { - String name = Utils.label(cc, el, true); - SymbolKind kind = Utils.elementKind2SymbolKind(el.getKind()); - Range range = Utils.treeRange(cc, tp.getLeaf()); - result.add(Either.forRight(new DocumentSymbol(name, kind, range, range))); - } - } - for (Element tel : cc.getTopLevelElements()) { - DocumentSymbol ds = element2DocumentSymbol(cc, tel); - if (ds != null) + StructureProvider structureProvider = MimeLookup.getLookup(DocumentUtilities.getMimeType(doc)).lookup(StructureProvider.class); + if (structureProvider != null) { + return structureProvider.getStructure(doc).thenApply(structureElements -> { Review comment: The implementations (java, GSF) in this PR will actually block, not fork; so this will likely block the LSP message thread until the parsing is done. I was about to suggest in the GSF impl that it should fork the .parse() into a RP, but maybe it would be better to do the fork here, centrally (?) ########## File path: ide/csl.api/src/org/netbeans/modules/csl/navigation/GsfStructureProvider.java ########## @@ -0,0 +1,328 @@ +/* + * 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.Set; +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()); + + /** The structure element implementation. Some properties are counted lazy. + * + */ + private static class GsfStructureElement implements StructureElement { + + private final static Set<StructureElement.Tag> DEPRECATED_TAG = Collections.singleton(StructureElement.Tag.Deprecated); + + private final Document doc; // The children are counted lazily and we need the document for it. + private final StructureItem origItem; + private List<GsfStructureElement> children; + private String signature; + private int expandedStartOffset; + private int expandedEndOffset; + + + public GsfStructureElement(Document doc, StructureItem origItem) { + this.doc = doc; + this.origItem = origItem; + this.signature = null; + this.children = null; + this.expandedStartOffset = (int)origItem.getPosition(); + this.expandedEndOffset = (int)origItem.getEndPosition(); + } + + @Override + public String getName() { + return origItem.getName(); + } + + @Override + public int getSelectionStartOffset() { + return (int)origItem.getPosition(); + } + + @Override + public int getSelectionEndOffset() { + return (int)origItem.getEndPosition(); + } + + @Override + public int getExpandedStartOffset() { + return expandedStartOffset; + } + + protected void setExpandedStartOffset(int enclosedStartOffset) { + this.expandedStartOffset = enclosedStartOffset; + } + + @Override + public int getExpandedEndOffset() { + return expandedEndOffset; + } + + protected void setExpandedEndOffset(int enclosedEndOffset) { + this.expandedEndOffset = enclosedEndOffset; + } + + + @Override + public String getDetail() { + if (signature == null) { + createSignature(); + } + return signature; + } + + private void createSignature() { + NoHtmlFormatter formatter = new NoHtmlFormatter(); + String s = origItem.getHtml(formatter); + signature = s.substring(getName().length()).trim(); + } + + @Override + public StructureElement.Kind getKind() { + switch(origItem.getKind()) { + case ATTRIBUTE: return Kind.Property; + case CALL: return Kind.Event; + case CLASS: return Kind.Class; + case CONSTANT: return Kind.Constant; + case CONSTRUCTOR: return Kind.Constructor; + case DB: return Kind.File; + case ERROR: return Kind.Event; + case METHOD: return Kind.Method; + case FILE: return Kind.File; + case FIELD: return Kind.Field; + case MODULE: return Kind.Module; + case VARIABLE: return Kind.Variable; + case GLOBAL: return Kind.Module; + case INTERFACE: return Kind.Interface; + case KEYWORD: return Kind.Key; + case OTHER: return Kind.Object; + case PACKAGE: return Kind.Package; + case PARAMETER: return Kind.TypeParameter; Review comment: What is `PARAMETER` really in csl langugaes used for ? ########## File path: java/java.editor/src/org/netbeans/modules/editor/java/JavaStructureProvider.java ########## @@ -0,0 +1,314 @@ +/* + * 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.editor.java; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; +import com.sun.source.util.Trees; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.swing.text.Document; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.api.java.source.JavaSource; +import org.netbeans.api.java.source.TreeUtilities; +import org.netbeans.api.lsp.StructureElement; +import org.netbeans.spi.lsp.StructureProvider; + +/** + * Implementation of StructureProvider from LSP API. It's used for displaying + * outline view and GoTo File Symbols in VSCode. + * + * @author Petr Pisl + */ +@MimeRegistration(mimeType = "text/x-java", service = StructureProvider.class) +public class JavaStructureProvider implements StructureProvider { + + private static final Logger LOGGER = Logger.getLogger(JavaStructureProvider.class.getName()); + + @Override + public CompletableFuture<List<StructureElement>> getStructure(Document doc) { + JavaSource js = JavaSource.forDocument(doc); + + if (js != null) { + List<StructureElement> result = new ArrayList<>(); + try { + js.runUserActionTask(cc -> { + cc.toPhase(JavaSource.Phase.RESOLVED); + Trees trees = cc.getTrees(); + CompilationUnitTree cu = cc.getCompilationUnit(); + if (cu.getPackage() != null) { + TreePath tp = trees.getPath(cu, cu.getPackage()); + Element el = trees.getElement(tp); + if (el != null && el.getKind() == ElementKind.PACKAGE) { + StructureElement jse = element2StructureElement(cc, el); + if (jse != null) { + result.add(jse); + } + } + } + for (Element tel : cc.getTopLevelElements()) { + StructureElement jse = element2StructureElement(cc, tel); + if (jse != null) { + result.add(jse); + } + } + }, true); + } catch (IOException ex) { + LOGGER.log(Level.FINE, null, ex); + } + return CompletableFuture.completedFuture(result); + } + return CompletableFuture.completedFuture(null); + } + + private static StructureElement element2StructureElement(CompilationInfo info, Element el) { + TreePath path = info.getTrees().getPath(el); + if (path == null) { + return null; + } + TreeUtilities tu = info.getTreeUtilities(); + if (tu.isSynthetic(path)) { + return null; + } + + Builder builder = StructureProvider.newBuilder(createName(info, el), convertKind(el.getKind())); + builder.detail(createDetail(info, el)); + setOffsets(info, el, builder); + if (info.getElements().isDeprecated(el)) { + builder.addTag(StructureElement.Tag.Deprecated); + } + for (Element child : el.getEnclosedElements()) { + StructureElement jse = element2StructureElement(info, child); + if (jse != null) { + builder.children(jse); + } + } + if (path.getLeaf().getKind() == Tree.Kind.METHOD || path.getLeaf().getKind() == Tree.Kind.VARIABLE) { Review comment: What about initializers ? ########## File path: ide/api.lsp/src/org/netbeans/api/lsp/StructureElement.java ########## @@ -0,0 +1,191 @@ +/* + * 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.api.lsp; + +import java.util.List; +import java.util.Set; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.modules.lsp.StructureElementAccessor; + +/** + * StructureElement is a tree item that shows the structure of the source code. + * + * @author Petr Pisl + * @since 1.8 + */ +public final class StructureElement { + + static { + StructureElementAccessor.setDefault(new StructureElementAccessor() { + @Override + public StructureElement createStructureElement(String name, String detail, int selectionStartOffset, int selectionEndOffset, int expandedStartOffset, int expandedEndOffset, Kind kind, Set<Tag> tags, List<StructureElement> children) { + return new StructureElement(name, detail, selectionStartOffset, selectionEndOffset, expandedStartOffset, expandedEndOffset, kind, tags, children); + } + }); + } + + private final String name; + private final String detail; + private final int selectionStartOffset; Review comment: A weird idea: if `javax.swing.text.Position` is used here, the structure could survive some edits between the time StructureElement is created and transofmed into LSP4J wire format -- and during the translation the document could be readlocked to protect the consistency. @JaroslavTulach ? -- 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
