From 5abc6f9df00495b033af3ddef15b86ec3058366e Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Thu, 2 May 2019 09:15:10 +0200 Subject: [PATCH] initial implementation towards a compilation-unit-based content-assist for types and packages --- .../commons/STS4LanguageClientImpl.java | 25 +++- .../commons/protocol/STS4LanguageClient.java | 5 + .../protocol/java/JavaCodeCompleteData.java | 67 +++++++++ .../protocol/java/JavaCodeCompleteParams.java | 100 ++++++++++++++ .../testharness/LanguageServerHarness.java | 7 + .../ls/commons/java/JavaCodeCompletion.java | 35 +++++ .../JavaCodeCompletionProposalCollector.java | 83 ++++++++++++ .../commons/java/JavaCodeCompletionUtils.java | 116 ++++++++++++++++ .../jdt/ls/commons/java/JavaFluxSearch.java | 2 + .../boot/xml/SpringXMLCompletionEngine.java | 8 +- .../SpringXMLLanguageServerComponents.java | 2 +- .../TypeCompletionProposalProvider.java | 128 ++++++++++-------- 12 files changed, 512 insertions(+), 66 deletions(-) create mode 100644 headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/JavaCodeCompleteData.java create mode 100644 headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/JavaCodeCompleteParams.java create mode 100644 headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaCodeCompletion.java create mode 100644 headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaCodeCompletionProposalCollector.java create mode 100644 headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaCodeCompletionUtils.java diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java index 22a1a35c0..0256def10 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java @@ -68,6 +68,8 @@ import org.springframework.ide.vscode.commons.protocol.HighlightParams; import org.springframework.ide.vscode.commons.protocol.ProgressParams; import org.springframework.ide.vscode.commons.protocol.STS4LanguageClient; import org.springframework.ide.vscode.commons.protocol.java.ClasspathListenerParams; +import org.springframework.ide.vscode.commons.protocol.java.JavaCodeCompleteData; +import org.springframework.ide.vscode.commons.protocol.java.JavaCodeCompleteParams; import org.springframework.ide.vscode.commons.protocol.java.JavaDataParams; import org.springframework.ide.vscode.commons.protocol.java.JavaSearchParams; import org.springframework.ide.vscode.commons.protocol.java.JavaTypeHierarchyParams; @@ -75,6 +77,7 @@ import org.springframework.ide.vscode.commons.protocol.java.TypeData; import org.springframework.ide.vscode.commons.protocol.java.TypeDescriptorData; import org.springframework.tooling.jdt.ls.commons.Logger; import org.springframework.tooling.jdt.ls.commons.classpath.ReusableClasspathListenerHandler; +import org.springframework.tooling.jdt.ls.commons.java.JavaCodeCompletion; import org.springframework.tooling.jdt.ls.commons.java.JavaData; import org.springframework.tooling.jdt.ls.commons.java.JavaFluxSearch; import org.springframework.tooling.jdt.ls.commons.java.TypeHierarchy; @@ -125,11 +128,10 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La } } - final private JavaData javaData = new JavaData(STS4LanguageClientImpl::label , Logger.forEclipsePlugin(LanguageServerCommonsActivator::getInstance)); - - final private JavaFluxSearch javaFluxSearch = new JavaFluxSearch(Logger.forEclipsePlugin(LanguageServerCommonsActivator::getInstance), javaData); - - final private TypeHierarchy typeHierarchy = new TypeHierarchy(Logger.forEclipsePlugin(LanguageServerCommonsActivator::getInstance), javaData); + private final JavaData javaData = new JavaData(STS4LanguageClientImpl::label , Logger.forEclipsePlugin(LanguageServerCommonsActivator::getInstance)); + private final JavaFluxSearch javaFluxSearch = new JavaFluxSearch(Logger.forEclipsePlugin(LanguageServerCommonsActivator::getInstance), javaData); + private final TypeHierarchy typeHierarchy = new TypeHierarchy(Logger.forEclipsePlugin(LanguageServerCommonsActivator::getInstance), javaData); + private final JavaCodeCompletion codeComplete = new JavaCodeCompletion(); private static final String ANNOTION_TYPE_ID = "org.springframework.tooling.bootinfo"; @@ -482,4 +484,17 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La ); } + @Override + public CompletableFuture> javaCodeComplete(JavaCodeCompleteParams params) { + return CompletableFuture.supplyAsync(() -> { + try { + return codeComplete.codeComplete(params.getProjectUri(), params.getPrefix(), params.isIncludeTypes(), params.isIncludePackages()); + } catch (Exception e) { + LanguageServerCommonsActivator.logError(e, "Failed to do code complete with prefix '" + params.getPrefix() + + "' in project " + params.getProjectUri()); + return Collections.emptyList(); + } + }); + } + } diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/STS4LanguageClient.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/STS4LanguageClient.java index 7c2a1ae80..626590859 100644 --- a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/STS4LanguageClient.java +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/STS4LanguageClient.java @@ -19,6 +19,8 @@ import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; import org.eclipse.lsp4j.services.LanguageClient; import org.springframework.ide.vscode.commons.protocol.java.ClasspathListenerParams; +import org.springframework.ide.vscode.commons.protocol.java.JavaCodeCompleteData; +import org.springframework.ide.vscode.commons.protocol.java.JavaCodeCompleteParams; import org.springframework.ide.vscode.commons.protocol.java.JavaDataParams; import org.springframework.ide.vscode.commons.protocol.java.JavaSearchParams; import org.springframework.ide.vscode.commons.protocol.java.JavaTypeHierarchyParams; @@ -71,4 +73,7 @@ public interface STS4LanguageClient extends LanguageClient { @JsonRequest("sts/javaSuperTypes") CompletableFuture> javaSuperTypes(JavaTypeHierarchyParams params); + @JsonRequest("sts/javaCodeComplete") + CompletableFuture> javaCodeComplete(JavaCodeCompleteParams params); + } diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/JavaCodeCompleteData.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/JavaCodeCompleteData.java new file mode 100644 index 000000000..96c337c46 --- /dev/null +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/JavaCodeCompleteData.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2019 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.protocol.java; + +/** + * @author Martin Lippert + */ +public class JavaCodeCompleteData { + + public static final String PACKAGE_PROPOSAL = "package"; + public static final String CLASS_PROPOSAL = "class"; + public static final String INTERFACE_PROPOSAL = "interface"; + public static final String ENUM_PROPOSAL = "enum"; + + private String kind; + private String fullyQualifiedName; + private int relevance; + + public String getFullyQualifiedName() { + return this.fullyQualifiedName; + } + + public void setFullyQualifiedName(String fullyQualifiedName) { + this.fullyQualifiedName = fullyQualifiedName; + } + + public String getKind() { + return this.kind; + } + + public void setKind(String kind) { + this.kind = kind; + } + + public int getRelevance() { + return this.relevance; + } + + public void setRelevance(int relevance) { + this.relevance = relevance; + } + + public boolean isInterfaceProposal() { + return INTERFACE_PROPOSAL.equals(kind); + } + + public boolean isEnumProposal() { + return ENUM_PROPOSAL.equals(kind); + } + + public boolean isPackageProposal() { + return PACKAGE_PROPOSAL.equals(kind); + } + + public boolean isClassProposal() { + return CLASS_PROPOSAL.equals(kind); + } + +} diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/JavaCodeCompleteParams.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/JavaCodeCompleteParams.java new file mode 100644 index 000000000..9c7268943 --- /dev/null +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/JavaCodeCompleteParams.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2019 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.protocol.java; + +/** + * @author Martin Lippert + */ +public class JavaCodeCompleteParams { + + private String projectUri; + private String prefix; + private boolean includeTypes; + private boolean includePackages; + + public JavaCodeCompleteParams(String projectUri, String prefix, boolean includeTypes, boolean includePackages) { + super(); + this.projectUri = projectUri; + this.prefix = prefix; + this.includeTypes = includeTypes; + this.includePackages = includePackages; + } + + public String getProjectUri() { + return projectUri; + } + + public void setProjectUri(String projectUri) { + this.projectUri = projectUri; + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public boolean isIncludeTypes() { + return includeTypes; + } + + public void setIncludeTypes(boolean includeTypes) { + this.includeTypes = includeTypes; + } + + public boolean isIncludePackages() { + return includePackages; + } + + public void setIncludePackages(boolean includePackages) { + this.includePackages = includePackages; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (includePackages ? 1231 : 1237); + result = prime * result + (includeTypes ? 1231 : 1237); + result = prime * result + ((prefix == null) ? 0 : prefix.hashCode()); + result = prime * result + ((projectUri == null) ? 0 : projectUri.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + JavaCodeCompleteParams other = (JavaCodeCompleteParams) obj; + if (includePackages != other.includePackages) + return false; + if (includeTypes != other.includeTypes) + return false; + if (prefix == null) { + if (other.prefix != null) + return false; + } else if (!prefix.equals(other.prefix)) + return false; + if (projectUri == null) { + if (other.projectUri != null) + return false; + } else if (!projectUri.equals(other.projectUri)) + return false; + return true; + } + +} diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java index 8e71f43cb..eb13c381a 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java @@ -113,6 +113,8 @@ import org.springframework.ide.vscode.commons.protocol.HighlightParams; import org.springframework.ide.vscode.commons.protocol.ProgressParams; import org.springframework.ide.vscode.commons.protocol.STS4LanguageClient; import org.springframework.ide.vscode.commons.protocol.java.ClasspathListenerParams; +import org.springframework.ide.vscode.commons.protocol.java.JavaCodeCompleteData; +import org.springframework.ide.vscode.commons.protocol.java.JavaCodeCompleteParams; import org.springframework.ide.vscode.commons.protocol.java.JavaDataParams; import org.springframework.ide.vscode.commons.protocol.java.JavaSearchParams; import org.springframework.ide.vscode.commons.protocol.java.JavaTypeHierarchyParams; @@ -387,6 +389,11 @@ public class LanguageServerHarness { return CompletableFuture.completedFuture(Collections.emptyList()); } + @Override + public CompletableFuture> javaCodeComplete(JavaCodeCompleteParams params) { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + }); } diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaCodeCompletion.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaCodeCompletion.java new file mode 100644 index 000000000..d73140487 --- /dev/null +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaCodeCompletion.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2019 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.jdt.ls.commons.java; + +import java.net.URI; +import java.util.List; + +import org.eclipse.jdt.core.IJavaProject; +import org.springframework.ide.vscode.commons.protocol.java.JavaCodeCompleteData; +import org.springframework.tooling.jdt.ls.commons.resources.ResourceUtils; + +/** + * @author Martin Lippert + */ +public class JavaCodeCompletion { + + public List codeComplete(String project, String prefix, boolean includeTypes, boolean includePackages) throws Exception { + URI projectUri = project == null ? null : URI.create(project); + IJavaProject javaProject = projectUri == null ? null : ResourceUtils.getJavaProject(projectUri); + + JavaCodeCompletionProposalCollector collector = new JavaCodeCompletionProposalCollector(includeTypes, includePackages); + JavaCodeCompletionUtils.codeComplete(javaProject, prefix, collector); + + return collector.getProposals(); + } + +} diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaCodeCompletionProposalCollector.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaCodeCompletionProposalCollector.java new file mode 100644 index 000000000..47d255819 --- /dev/null +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaCodeCompletionProposalCollector.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2019 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.jdt.ls.commons.java; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.core.CompletionProposal; +import org.eclipse.jdt.core.CompletionRequestor; +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.Signature; +import org.springframework.ide.vscode.commons.protocol.java.JavaCodeCompleteData; + +/** + * @author Martin Lippert + */ +public class JavaCodeCompletionProposalCollector extends CompletionRequestor { + + private final boolean includeTypes; + private final boolean includePackages; + private final List proposals; + + public JavaCodeCompletionProposalCollector(boolean includeTypes, boolean includePackages) { + this.includeTypes = includeTypes; + this.includePackages = includePackages; + this.proposals = new ArrayList<>(); + } + + @Override + public void accept(CompletionProposal proposal) { + if (includeTypes && proposal.getKind() == CompletionProposal.TYPE_REF) { + if (!JavaCodeCompletionUtils.CLASS_NAME.equals(String.valueOf(proposal.getCompletion()))) { + addTypeProposal(proposal); + } + } + else if (includePackages && proposal.getKind() == CompletionProposal.PACKAGE_REF) { + addPackageProposal(proposal); + } + } + + public List getProposals() { + return proposals; + } + + private void addPackageProposal(CompletionProposal proposal) { + JavaCodeCompleteData proposalData = new JavaCodeCompleteData(); + + proposalData.setKind(JavaCodeCompleteData.PACKAGE_PROPOSAL); + proposalData.setRelevance(proposal.getRelevance()); + proposalData.setFullyQualifiedName(String.valueOf(proposal.getDeclarationSignature())); + + this.proposals.add(proposalData); + } + + private void addTypeProposal(CompletionProposal proposal) { + JavaCodeCompleteData proposalData = new JavaCodeCompleteData(); + + if (Flags.isInterface(proposal.getFlags())) { + proposalData.setKind(JavaCodeCompleteData.INTERFACE_PROPOSAL); + } + else if (Flags.isEnum(proposal.getFlags())) { + proposalData.setKind(JavaCodeCompleteData.ENUM_PROPOSAL); + } + else { + proposalData.setKind(JavaCodeCompleteData.CLASS_PROPOSAL); + } + proposalData.setRelevance(proposal.getRelevance()); + + String fullyQualifiedName = String.valueOf(Signature.toCharArray(proposal.getSignature())); + proposalData.setFullyQualifiedName(fullyQualifiedName); + + this.proposals.add(proposalData); + } + +} diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaCodeCompletionUtils.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaCodeCompletionUtils.java new file mode 100644 index 000000000..5b44a4c94 --- /dev/null +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaCodeCompletionUtils.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2019 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.jdt.ls.commons.java; + +import org.eclipse.jdt.core.IBuffer; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner; + +/** + * @author Martin Lippert + */ +@SuppressWarnings("restriction") +public class JavaCodeCompletionUtils { + + public static final String CLASS_NAME = "_xxx"; + + private static final String CLASS_SOURCE_START = "public class " + CLASS_NAME + " {\n" + + " public void main(String[] args) {\n" + " "; + + private static final String CLASS_SOURCE_END = "\n" + " }\n" + "}"; + + + public static void codeComplete(IJavaProject project, String prefix, JavaCodeCompletionProposalCollector proposalCollector) { + if (prefix == null || prefix.length() == 0) { + return; + } + + try { + ICompilationUnit unit = createCompilationUnit(project, prefix); + + prefix = prefix.replace('$', '.'); + + String sourceStart = CLASS_SOURCE_START + prefix; + String packageName = null; + + int dot = prefix.lastIndexOf('.'); + if (dot > -1) { + packageName = prefix.substring(0, dot); + sourceStart = "package " + packageName + ";\n" + sourceStart; + } + + String source = sourceStart + CLASS_SOURCE_END; + setContents(unit, source); + + unit.codeComplete(sourceStart.length(), proposalCollector, DefaultWorkingCopyOwner.PRIMARY); + } + catch (Exception e) { + // do nothing + } + } + + private static ICompilationUnit createCompilationUnit(IJavaProject project, String prefix) throws JavaModelException { + IPackageFragment root = getPackageFragment(project, prefix); + ICompilationUnit unit = root.getCompilationUnit("_xxx.java").getWorkingCopy(null); + return unit; + } + + private static IPackageFragment getPackageFragment(IJavaProject project, String prefix) throws JavaModelException { + int dot = prefix.lastIndexOf('.'); + if (dot > -1) { + String packageName = prefix.substring(0, dot); + for (IPackageFragmentRoot root : project.getPackageFragmentRoots()) { + IPackageFragment p = root.getPackageFragment(packageName); + if (p != null && p.exists()) { + return p; + } + } + IPackageFragment[] packages = project.getPackageFragments(); + for (IPackageFragment p : packages) { + if (p.getElementName().equals(packageName)) + return p; + } + } + else { + for (IPackageFragmentRoot p : project.getAllPackageFragmentRoots()) { + if (p.getKind() == IPackageFragmentRoot.K_SOURCE) { + return p.getPackageFragment(""); + } + } + } + return project.getPackageFragments()[0]; + } + + private static void setContents(ICompilationUnit cu, String source) { + if (cu == null) + return; + + synchronized (cu) { + IBuffer buffer; + try { + + buffer = cu.getBuffer(); + } + catch (JavaModelException e) { + e.printStackTrace(); + buffer = null; + } + + if (buffer != null) + buffer.setContents(source); + } + } + +} diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaFluxSearch.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaFluxSearch.java index 954bdd27c..8a1ce5bac 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaFluxSearch.java +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaFluxSearch.java @@ -41,6 +41,7 @@ public class JavaFluxSearch { public List fuzzySearchPackages(JavaSearchParams params) throws Exception { URI projectUri = params.getProjectUri() == null ? null : URI.create(params.getProjectUri()); IJavaProject javaProject = projectUri == null ? null : ResourceUtils.getJavaProject(projectUri); + PackageFluxSearch fluxPackageSearch = packageSearchCache.get( Tuples.of(params.isIncludeBinaries(), params.isIncludeSystemLibs()), () -> new PackageFluxSearch(logger, params.isIncludeBinaries(), params.isIncludeSystemLibs()) ); @@ -50,6 +51,7 @@ public class JavaFluxSearch { public List fuzzySearchTypes(JavaSearchParams params) throws Exception { URI projectUri = params.getProjectUri() == null ? null : URI.create(params.getProjectUri()); IJavaProject javaProject = projectUri == null ? null : ResourceUtils.getJavaProject(projectUri); + TypeFluxSearch fluxTypeSearch = typeSearchCache.get( Tuples.of(params.isIncludeBinaries(), params.isIncludeSystemLibs()), () -> new TypeFluxSearch(logger, javaData, params.isIncludeBinaries(), params.isIncludeSystemLibs()) ); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLCompletionEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLCompletionEngine.java index efde81710..5899cbe70 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLCompletionEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLCompletionEngine.java @@ -30,7 +30,7 @@ import org.springframework.ide.vscode.boot.xml.completions.TypeCompletionProposa import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** @@ -51,12 +51,12 @@ public class SpringXMLCompletionEngine implements ICompletionEngine { private final BootJavaConfig config; public SpringXMLCompletionEngine(SpringXMLLanguageServerComponents springXMLLanguageServerComponents, - JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex, SimpleTextDocumentService simpleTextDocumentService, BootJavaConfig config) { + SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex, BootJavaConfig config) { this.config = config; - + this.completionProviders = new HashMap<>(); - this.completionProviders.put(new XMLCompletionProviderKey(BEANS_NAMESPACE, null, BEAN_ELEMENT, CLASS_ATTRIBUTE), new TypeCompletionProposalProvider(projectFinder, simpleTextDocumentService, true)); + this.completionProviders.put(new XMLCompletionProviderKey(BEANS_NAMESPACE, null, BEAN_ELEMENT, CLASS_ATTRIBUTE), new TypeCompletionProposalProvider(server, projectFinder, true, true, false, false)); this.completionProviders.put(new XMLCompletionProviderKey(BEANS_NAMESPACE, BEAN_ELEMENT, PROPERTY_ELEMENT, NAME_ATTRIBUTE), new PropertyNameCompletionProposalProvider(projectFinder)); this.completionProviders.put(new XMLCompletionProviderKey(BEANS_NAMESPACE, BEAN_ELEMENT, PROPERTY_ELEMENT, REF_ATTRIBUTE), new BeanRefCompletionProposalProvider(projectFinder, symbolIndex)); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLLanguageServerComponents.java index 42d28fdc5..cf9cd46ec 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLLanguageServerComponents.java @@ -55,7 +55,7 @@ public class SpringXMLLanguageServerComponents implements LanguageServerComponen server.doOnInitialized(this::initialized); server.onShutdown(this::shutdown); - this.completionEngine = new SpringXMLCompletionEngine(this, projectFinder, symbolIndex, server.getTextDocumentService(), config); + this.completionEngine = new SpringXMLCompletionEngine(this, server, projectFinder, symbolIndex, config); } @Override diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/TypeCompletionProposalProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/TypeCompletionProposalProvider.java index c2d82c652..76a386769 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/TypeCompletionProposalProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/TypeCompletionProposalProvider.java @@ -12,9 +12,10 @@ package org.springframework.ide.vscode.boot.xml.completions; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; +import java.util.List; import java.util.Optional; -import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import org.eclipse.lsp4j.CompletionItemKind; import org.eclipse.lsp4xml.dom.DOMAttr; @@ -22,33 +23,35 @@ import org.eclipse.lsp4xml.dom.DOMNode; import org.eclipse.lsp4xml.dom.parser.Scanner; import org.springframework.ide.vscode.boot.xml.XMLCompletionProvider; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.java.IType; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.protocol.java.JavaCodeCompleteData; +import org.springframework.ide.vscode.commons.protocol.java.JavaCodeCompleteParams; import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.text.TextDocument; -import reactor.core.publisher.Flux; -import reactor.util.function.Tuple2; - /** * @author Martin Lippert */ public class TypeCompletionProposalProvider implements XMLCompletionProvider { private final JavaProjectFinder projectFinder; - private final boolean classesOnly; - private final Set typeSearchAlreadyInitializedProjects = Collections.synchronizedSet(new HashSet<>()); + private final SimpleLanguageServer server; + private final boolean packagesAllowed; + private final boolean classesAllowed; + private final boolean interfacesAllowed; + private final boolean enumsAllowed; - public TypeCompletionProposalProvider(JavaProjectFinder projectFinder, SimpleTextDocumentService simpleTextDocumentService, boolean classesOnly) { + public TypeCompletionProposalProvider(SimpleLanguageServer server, JavaProjectFinder projectFinder, + boolean packagesAllowed, boolean classesAllowed, boolean interfacesAllowed, boolean enumsAllowed) { + this.server = server; this.projectFinder = projectFinder; - this.classesOnly = classesOnly; - - simpleTextDocumentService.onDidOpen(doc -> { - initializeTypeSearch(doc); - }); + this.packagesAllowed = packagesAllowed; + this.classesAllowed = classesAllowed; + this.interfacesAllowed = interfacesAllowed; + this.enumsAllowed = enumsAllowed; } @Override @@ -69,68 +72,81 @@ public class TypeCompletionProposalProvider implements XMLCompletionProvider { } // Flux> types = project.getIndex().fuzzySearchTypes(prefix, true, true); - Flux> types = project.getIndex().camelcaseSearchTypes(prefix, true, true); +// Flux> types = project.getIndex().camelcaseSearchTypes(prefix, true, true); + + JavaCodeCompleteParams params = new JavaCodeCompleteParams(project.getLocationUri().toString(), prefix, true, true); + CompletableFuture> completions = server.getClient().javaCodeComplete(params); - final String prefixStr = prefix; - - return types - .filter(result -> result.getT1() != null && result.getT1().getElementName() != null && result.getT1().getElementName().length() > 0) - .filter(result -> classesOnly ? result.getT1().isClass() : true) - .map(t -> createProposal(t, doc, offset, prefixStr)) - .collectList().block(); + final String finalPrefix = prefix; + + try { + return completions.get().stream() + .filter(proposal -> { + if (proposal.isClassProposal()) return classesAllowed; + if (proposal.isInterfaceProposal()) return interfacesAllowed; + if (proposal.isEnumProposal()) return enumsAllowed; + if (proposal.isPackageProposal()) return packagesAllowed; + return false; + }) + .map(proposal -> createProposal(proposal, doc, finalPrefix, offset)) + .filter(proposal -> proposal != null) + .collect(Collectors.toList()); + } + catch (Exception e) { + // TODO: logging + } }; return Collections.emptyList(); } - private ICompletionProposal createProposal(Tuple2 t, TextDocument doc, int offset, String prefix) { - IType type = t.getT1(); + private ICompletionProposal createProposal(JavaCodeCompleteData proposal, TextDocument doc, String prefix, int offset) { + if (proposal.isPackageProposal()) { + return createPackageProposal(proposal, doc, prefix, offset); + } + else if (proposal.isClassProposal() || proposal.isInterfaceProposal() || proposal.isEnumProposal()) { + return createTypeProposal(proposal, doc, prefix, offset); + } + else { + return null; + } + } + + private TypeCompletionProposal createPackageProposal(JavaCodeCompleteData proposal, TextDocument doc, String prefix, int offset) { + String label = proposal.getFullyQualifiedName(); + CompletionItemKind kind = CompletionItemKind.Module; + + DocumentEdits edits = new DocumentEdits(doc, false); + edits.replace(offset - prefix.length(), offset, proposal.getFullyQualifiedName()); + + Renderable renderable = null; + + return new TypeCompletionProposal(label, kind, edits, proposal.getFullyQualifiedName(), renderable, proposal.getRelevance()); + } + + private TypeCompletionProposal createTypeProposal(JavaCodeCompleteData proposal, TextDocument doc, String prefix, int offset) { + String label = proposal.getFullyQualifiedName(); - String label = type.getFullyQualifiedName(); int splitIndex = Math.max(label.lastIndexOf("."), label.lastIndexOf("$")); if (splitIndex > 0) { label = label.substring(splitIndex + 1) + " - " + label.substring(0, splitIndex); } - CompletionItemKind kind; - if (type.isClass()) { - kind = CompletionItemKind.Class; - } - else if (type.isInterface()) { + CompletionItemKind kind = CompletionItemKind.Class; + if (proposal.isInterfaceProposal()) { kind = CompletionItemKind.Interface; } - else if (type.isEnum()) { + else if (proposal.isEnumProposal()) { kind = CompletionItemKind.Enum; } - else { - kind = CompletionItemKind.Property; - } DocumentEdits edits = new DocumentEdits(doc, false); - edits.replace(offset - prefix.length(), offset, type.getFullyQualifiedName()); + edits.replace(offset - prefix.length(), offset, proposal.getFullyQualifiedName()); Renderable renderable = null; - return new TypeCompletionProposal(label, kind, edits, type.getFullyQualifiedName(), renderable, t.getT2()); + return new TypeCompletionProposal(label, kind, edits, proposal.getFullyQualifiedName(), renderable, proposal.getRelevance()); } - - /** - * trigger initial type search as soon as the doc opens to avoid search timeouts when doing type proposals for the first time - */ - private void initializeTypeSearch(TextDocument doc) { - Optional foundProject = this.projectFinder.find(doc.getId()); - if (foundProject.isPresent()) { - IJavaProject project = foundProject.get(); - String projectName = project.getElementName(); - - if (!this.typeSearchAlreadyInitializedProjects.contains(projectName)) { - - this.typeSearchAlreadyInitializedProjects.add(projectName); - Flux> types = project.getIndex().camelcaseSearchTypes("", true, true); - types.count(); - } - } - } - + }