diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java index 9cbeb5063..acb06e000 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java @@ -14,6 +14,7 @@ import org.springframework.ide.vscode.boot.java.completions.BootJavaCompletionEn import org.springframework.ide.vscode.boot.java.completions.BootJavaReconcileEngine; import org.springframework.ide.vscode.boot.java.hover.BootJavaHoverProvider; import org.springframework.ide.vscode.boot.java.references.BootJavaReferencesHandler; +import org.springframework.ide.vscode.boot.java.symbols.BootJavaDocumentSymbolHandler; import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; import org.springframework.ide.vscode.commons.gradle.GradleCore; import org.springframework.ide.vscode.commons.gradle.GradleProjectFinderStrategy; @@ -68,6 +69,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { ReferencesHandler referencesHandler = new BootJavaReferencesHandler(this, javaProjectFinder); documents.onReferences(referencesHandler); + documents.onDocumentSymbol(new BootJavaDocumentSymbolHandler(this, javaProjectFinder)); } public void setMaxCompletionsNumber(int number) { diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/symbols/BootJavaDocumentSymbolHandler.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/symbols/BootJavaDocumentSymbolHandler.java new file mode 100644 index 000000000..553d26e32 --- /dev/null +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/symbols/BootJavaDocumentSymbolHandler.java @@ -0,0 +1,184 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.symbols; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; +import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.lsp4j.DocumentSymbolParams; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.SymbolKind; +import org.springframework.ide.vscode.commons.java.IClasspath; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbolHandler; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; +import org.springframework.ide.vscode.commons.util.text.IDocument; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +/** + * @author Martin Lippert + */ +public class BootJavaDocumentSymbolHandler implements DocumentSymbolHandler { + + private SimpleLanguageServer server; + private JavaProjectFinder projectFinder; + + public BootJavaDocumentSymbolHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder) { + this.server = server; + this.projectFinder = projectFinder; + } + + @Override + public List handle(DocumentSymbolParams params) { + SimpleTextDocumentService documents = server.getTextDocumentService(); + TextDocument doc = documents.get(params.getTextDocument().getUri()).copy(); + if (doc != null) { + try { + return provideDocumentSymbols(doc); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return SimpleTextDocumentService.NO_SYMBOLS; + } + + private List provideDocumentSymbols(TextDocument document) throws Exception { + ASTParser parser = ASTParser.newParser(AST.JLS8); + Map options = JavaCore.getOptions(); + JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options); + parser.setCompilerOptions(options); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + parser.setStatementsRecovery(true); + parser.setBindingsRecovery(true); + parser.setResolveBindings(true); + + String[] classpathEntries = getClasspathEntries(document); + String[] sourceEntries = new String[] {}; + parser.setEnvironment(classpathEntries, sourceEntries, null, true); + + String docURI = document.getUri(); + String unitName = docURI.substring(docURI.lastIndexOf("/")); + parser.setUnitName(unitName); + parser.setSource(document.get(0, document.getLength()).toCharArray()); + + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + if (cu != null) { + System.out.println("AST node found: " + cu.getClass().getName()); + return provideDocumentSymbolsForAnnotations(cu, document); + } + + return null; + } + + private List provideDocumentSymbolsForAnnotations(ASTNode node, TextDocument doc) { + List result = new ArrayList<>(); + ASTVisitor visitor = new ASTVisitor() { + @Override + public boolean visit(AnnotationTypeDeclaration node) { + // TODO Auto-generated method stub + return super.visit(node); + } + + @Override + public boolean visit(SingleMemberAnnotation node) { + ITypeBinding type = node.resolveTypeBinding(); + if (type != null) { + String qualifiedName = type.getQualifiedName(); + if (qualifiedName != null && qualifiedName.startsWith("org.springframework")) { + provideDocumentSymbolsForSpringAnnotations(node, type, doc, result); + } + } + return super.visit(node); + } + + @Override + public boolean visit(AnnotationTypeMemberDeclaration node) { + // TODO Auto-generated method stub + return super.visit(node); + } + + @Override + public boolean visit(MemberValuePair node) { + // TODO Auto-generated method stub + return super.visit(node); + } + + @Override + public boolean visit(NormalAnnotation node) { + ITypeBinding type = node.resolveTypeBinding(); + if (type != null) { + String qualifiedName = type.getQualifiedName(); + if (qualifiedName != null && qualifiedName.startsWith("org.springframework")) { + provideDocumentSymbolsForSpringAnnotations(node, type, doc, result); + } + } + return super.visit(node); + } + + @Override + public boolean visit(MarkerAnnotation node) { + ITypeBinding type = node.resolveTypeBinding(); + if (type != null) { + String qualifiedName = type.getQualifiedName(); + if (qualifiedName != null && qualifiedName.startsWith("org.springframework")) { + provideDocumentSymbolsForSpringAnnotations(node, type, doc, result); + } + } + return super.visit(node); + } + }; + node.accept(visitor); + + return result; + } + + private void provideDocumentSymbolsForSpringAnnotations(Annotation node, ITypeBinding type, + TextDocument doc, List resultAccumulator) { + try { + resultAccumulator.add(new SymbolInformation(node.toString(), SymbolKind.Interface, new Location(doc.getUri(), + doc.toRange(node.getStartPosition(), node.getLength())))); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + private String[] getClasspathEntries(IDocument doc) throws Exception { + IJavaProject project = this.projectFinder.find(doc); + IClasspath classpath = project.getClasspath(); + Stream classpathEntries = classpath.getClasspathEntries(); + return classpathEntries + .filter(path -> path.toFile().exists()) + .map(path -> path.toAbsolutePath().toString()).toArray(String[]::new); + } + +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java index b3a31ae63..b01e39089 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java @@ -219,6 +219,7 @@ public class SimpleTextDocumentService implements TextDocumentService { public final static CompletionList NO_COMPLETIONS = new CompletionList(false, Collections.emptyList()); public final static CompletableFuture NO_HOVER = CompletableFuture.completedFuture(new Hover(ImmutableList.of(), null)); public final static CompletableFuture> NO_REFERENCES = CompletableFuture.completedFuture(ImmutableList.of()); + public final static List NO_SYMBOLS = ImmutableList.of(); @Override public CompletableFuture, CompletionList>> completion(TextDocumentPositionParams position) { @@ -369,7 +370,11 @@ public class SimpleTextDocumentService implements TextDocumentService { } public synchronized TextDocument get(TextDocumentPositionParams params) { - TrackedDocument td = documents.get(params.getTextDocument().getUri()); + return get(params.getTextDocument().getUri()); + } + + public synchronized TextDocument get(String uri) { + TrackedDocument td = documents.get(uri); return td == null ? null : td.getDocument(); } diff --git a/vscode-extensions/vscode-boot-java/.vscode/launch.json b/vscode-extensions/vscode-boot-java/.vscode/launch.json index 9d73948ec..1c135881c 100644 --- a/vscode-extensions/vscode-boot-java/.vscode/launch.json +++ b/vscode-extensions/vscode-boot-java/.vscode/launch.json @@ -3,14 +3,17 @@ "version": "0.1.0", "configurations": [ { - "name": "Launch Extension", "type": "extensionHost", "request": "launch", + "name": "Launch Extension", "runtimeExecutable": "${execPath}", - "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], - "stopOnEntry": false, + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}" + ], "sourceMaps": true, - "outFiles": ["${workspaceRoot}/out/lib"], + "outFiles": [ + "${workspaceRoot}/out/**/*.js" + ], "preLaunchTask": "npm" }, { diff --git a/vscode-extensions/vscode-boot-java/.vscode/settings.json b/vscode-extensions/vscode-boot-java/.vscode/settings.json index c5592bee9..6ec5515d8 100644 --- a/vscode-extensions/vscode-boot-java/.vscode/settings.json +++ b/vscode-extensions/vscode-boot-java/.vscode/settings.json @@ -1,7 +1,7 @@ // Place your settings in this file to overwrite default and user settings. { "files.exclude": { - "out": true, // set this to true to hide the "out" folder with the compiled JS files + "out": false, // set this to true to hide the "out" folder with the compiled JS files "node_modules": false, "target": true }, diff --git a/vscode-extensions/vscode-boot-java/package.json b/vscode-extensions/vscode-boot-java/package.json index fb9236398..93ce6e1ea 100644 --- a/vscode-extensions/vscode-boot-java/package.json +++ b/vscode-extensions/vscode-boot-java/package.json @@ -22,7 +22,6 @@ "java", "spring-boot" ], "activationEvents": [ - "onLanguage:ini", "onLanguage:java" ], "main": "./out/lib/Main",