diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ScoreableProposal.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/AbstractScoreableProposal.java similarity index 75% rename from headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ScoreableProposal.java rename to headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/AbstractScoreableProposal.java index 28440c9542..fc884c23ab 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ScoreableProposal.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/AbstractScoreableProposal.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016-2017 Pivotal, Inc. + * Copyright (c) 2016, 2025 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 @@ -15,7 +15,7 @@ import org.springframework.ide.vscode.commons.util.Assert; -public abstract class ScoreableProposal implements ICompletionProposal { +public abstract class AbstractScoreableProposal implements ICompletionProposalWithScore { public static final double DEEMP_EXISTS = 0.1; public static final double DEEMP_DEPRECATION = 0.2; @@ -34,21 +34,21 @@ public abstract class ScoreableProposal implements ICompletionProposal { public static final Comparator COMPARATOR = new Comparator() { @Override public int compare(ICompletionProposal p1, ICompletionProposal p2) { - if (p1 instanceof ScoreableProposal && p2 instanceof ScoreableProposal) { - double s1 = ((ScoreableProposal)p1).getScore(); - double s2 = ((ScoreableProposal)p2).getScore(); + if (p1 instanceof ICompletionProposalWithScore && p2 instanceof ICompletionProposalWithScore) { + double s1 = ((ICompletionProposalWithScore)p1).getScore(); + double s2 = ((ICompletionProposalWithScore)p2).getScore(); if (s1 == s2) { - String name1 = ((ScoreableProposal)p1).getLabel(); - String name2 = ((ScoreableProposal)p2).getLabel(); + String name1 = ((ICompletionProposalWithScore)p1).getLabel(); + String name2 = ((ICompletionProposalWithScore)p2).getLabel(); return name1.compareTo(name2); } else { return Double.compare(s2, s1); } } - if (p1 instanceof ScoreableProposal) { + if (p1 instanceof ICompletionProposalWithScore) { return -1; } - if (p2 instanceof ScoreableProposal) { + if (p2 instanceof ICompletionProposalWithScore) { return +1; } return p1.getLabel().compareTo(p2.getLabel()); @@ -59,7 +59,7 @@ public final double getScore() { return getBaseScore() - deemphasizedBy; } @Override - public ScoreableProposal deemphasize(double howmuch) { + public AbstractScoreableProposal deemphasize(double howmuch) { Assert.isLegal(howmuch>=0.0); deemphasizedBy+= howmuch*DEEMP_VALUE; return this; diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposalWithScore.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposalWithScore.java new file mode 100644 index 0000000000..aceafd1cfd --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/ICompletionProposalWithScore.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2025 Broadcom, 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: + * Broadcom, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.languageserver.completion; + +public interface ICompletionProposalWithScore extends ICompletionProposal { + + double getScore(); + +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/TransformedCompletion.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/TransformedCompletion.java index a1e5eb2cbb..6623ecb76f 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/TransformedCompletion.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/TransformedCompletion.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2023 Pivotal, Inc. + * Copyright (c) 2017, 2025 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 @@ -23,7 +23,7 @@ * * @author Kris De Volder */ -public abstract class TransformedCompletion extends ScoreableProposal { +public abstract class TransformedCompletion extends AbstractScoreableProposal { protected final ICompletionProposal original; @@ -71,8 +71,8 @@ public String getDetail() { @Override public double getBaseScore() { - if (original instanceof ScoreableProposal) { - return ((ScoreableProposal) original).getScore(); + if (original instanceof AbstractScoreableProposal) { + return ((AbstractScoreableProposal) original).getScore(); } return 0; } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java index a04ea03476..382426a806 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2023 VMware Inc. + * Copyright (c) 2016, 2025 VMware 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 @@ -218,7 +218,7 @@ public CompletionList getCompletions(CancelChecker cancelToken, TextDocumentPosi cancelToken.checkCanceled(); List completions = filter(rawCompletionList.completionItems()); - Collections.sort(completions, ScoreableProposal.COMPARATOR); + Collections.sort(completions, AbstractScoreableProposal.COMPARATOR); cancelToken.checkCanceled(); diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/DefaultCompletionFactory.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/DefaultCompletionFactory.java index 82c478d857..94d2f3fd5f 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/DefaultCompletionFactory.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/DefaultCompletionFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016-2017 Pivotal, Inc. + * Copyright (c) 2016, 2025 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 @@ -14,7 +14,7 @@ import org.eclipse.lsp4j.CompletionItemKind; 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.completion.ScoreableProposal; +import org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal; import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.util.text.IDocument; @@ -27,7 +27,7 @@ public class DefaultCompletionFactory implements CompletionFactory { private static final int ERROR_COMPLETION_SCORE = -10000000; - public static class BeanPropertyProposal extends ScoreableProposal { + public static class BeanPropertyProposal extends AbstractScoreableProposal { // private IDocument doc; private String contextProperty; @@ -81,7 +81,7 @@ public Renderable getDocumentation() { } } - public class ValueProposal extends ScoreableProposal { + public class ValueProposal extends AbstractScoreableProposal { private String value; private String label; @@ -137,7 +137,7 @@ public Renderable getDocumentation() { } } - public static final class ErrorProposal extends ScoreableProposal { + public static final class ErrorProposal extends AbstractScoreableProposal { private final String longMessage; private String shortMessage; private String filterText; diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java index 3d6ec49123..aa02c75187 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2019 Pivotal, Inc. + * Copyright (c) 2016, 2025 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 @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.yaml.completion; -import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_DASH_PROPOSAL; -import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_DEPRECATION; +import static org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal.DEEMP_DASH_PROPOSAL; +import static org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal.DEEMP_DEPRECATION; import java.util.ArrayList; import java.util.Collection; @@ -25,7 +25,7 @@ import org.slf4j.LoggerFactory; 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.completion.ScoreableProposal; +import org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal; import org.springframework.ide.vscode.commons.languageserver.completion.TransformedCompletion; import org.springframework.ide.vscode.commons.languageserver.util.PlaceHolderString; import org.springframework.ide.vscode.commons.util.CollectionUtil; @@ -206,7 +206,7 @@ public List getKeyCompletions(YamlDocument doc, SNode node, ICompletionProposal completion = completionFactory().beanProperty(doc.getDocument(), contextPath.toPropString(), getType(), query, p, score, edits, typeUtil); - if (p.isDeprecated() && completion instanceof ScoreableProposal) { + if (p.isDeprecated() && completion instanceof AbstractScoreableProposal) { completion.deemphasize(DEEMP_DEPRECATION); } proposals.add(completion); diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlCompletionEngine.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlCompletionEngine.java index fd5e4b8085..8d43e58a70 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlCompletionEngine.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlCompletionEngine.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2024 Pivotal, Inc. + * Copyright (c) 2016, 2025 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 @@ -10,8 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.yaml.completion; -import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_DEDENTED_PROPOSAL; -import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_INDENTED_PROPOSAL; +import static org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal.DEEMP_DEDENTED_PROPOSAL; +import static org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal.DEEMP_INDENTED_PROPOSAL; import java.util.ArrayList; import java.util.Collection; @@ -26,7 +26,7 @@ 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.completion.InternalCompletionList; -import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal; +import org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal; import org.springframework.ide.vscode.commons.languageserver.completion.TransformedCompletion; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.Unicodes; @@ -97,7 +97,7 @@ public InternalCompletionList getCompletions(TextDocument _doc, int offset) thro double deempasizeBy = 0.0; for (SNode contextNode : contextNodes) { completions.addAll(getRelaxedCompletions(offset, doc, current, contextNode, baseIndent, deempasizeBy)); - deempasizeBy += ScoreableProposal.DEEMP_NEXT_CONTEXT; + deempasizeBy += AbstractScoreableProposal.DEEMP_NEXT_CONTEXT; } return new InternalCompletionList(completions, false); } else { @@ -129,7 +129,7 @@ protected Collection fixIndentations(Collection transformed = new ArrayList<>(); for (ICompletionProposal p : completions) { int targetIndent = p.getLabel().startsWith("- ") ? dashyIndent : plainIndent; - ScoreableProposal p_fixed = indentFix((ScoreableProposal)p, targetIndent - baseIndent, currentNode, contextNode, doc); + AbstractScoreableProposal p_fixed = indentFix((AbstractScoreableProposal)p, targetIndent - baseIndent, currentNode, contextNode, doc); if (p_fixed!=null) { p_fixed.deemphasize(deempasizeBy); transformed.add(p_fixed); @@ -140,7 +140,7 @@ protected Collection fixIndentations(Collection0) { @@ -190,7 +190,7 @@ private int getTargetIndent(SNode contextNode, SNode currentNode, boolean dashy) : contextNode.getIndent() + YamlIndentUtil.INDENT_BY; } - public ScoreableProposal dedented(ICompletionProposal proposal, int numSpacesToRemove, IDocument doc) { + public AbstractScoreableProposal dedented(ICompletionProposal proposal, int numSpacesToRemove, IDocument doc) { Assert.isLegal(numSpacesToRemove>0); int spacesEnd = proposal.getTextEdit().getFirstEditStart(); int spacesStart = spacesEnd-numSpacesToRemove; @@ -198,7 +198,7 @@ public ScoreableProposal dedented(ICompletionProposal proposal, int numSpacesToR String spaces = new DocumentRegion(doc, spacesStart, spacesEnd).toString(); YamlIndentUtil indenter = new YamlIndentUtil(doc); if (spaces.length()==numSpacesToRemove && SPACES.matcher(spaces).matches()) { - ScoreableProposal transformed = new TransformedCompletion(proposal) { + AbstractScoreableProposal transformed = new TransformedCompletion(proposal) { @Override public String tranformLabel(String originalLabel) { return Strings.repeat(Unicodes.LEFT_ARROW+" ", numArrows) + originalLabel; } @@ -228,9 +228,9 @@ public String getFilterText() { return null; } - public ScoreableProposal indented(ICompletionProposal proposal, String indentStr, YamlDocument doc) { + public AbstractScoreableProposal indented(ICompletionProposal proposal, String indentStr, YamlDocument doc) { int numArrows = (indentStr.length()+1)/2; - ScoreableProposal transformed = new TransformedCompletion(proposal) { + AbstractScoreableProposal transformed = new TransformedCompletion(proposal) { @Override public String tranformLabel(String originalLabel) { return Strings.repeat(Unicodes.RIGHT_ARROW+" ", numArrows) + originalLabel; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/AbstractPropertyProposal.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/AbstractPropertyProposal.java index 2ccded7731..da63a672e5 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/AbstractPropertyProposal.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/AbstractPropertyProposal.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015, 2023 Pivotal, Inc. + * Copyright (c) 2015, 2025 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 @@ -12,11 +12,11 @@ import org.eclipse.lsp4j.CompletionItemKind; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; -import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal; +import org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal; import org.springframework.ide.vscode.commons.util.text.IDocument; import org.springframework.ide.vscode.commons.yaml.schema.YType; -public abstract class AbstractPropertyProposal extends ScoreableProposal { +public abstract class AbstractPropertyProposal extends AbstractScoreableProposal { @Override public String getDetail() { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/PropertyCompletionFactory.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/PropertyCompletionFactory.java index a53fc256f0..473974e2f6 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/PropertyCompletionFactory.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/PropertyCompletionFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2014, 2023 Pivotal, Inc. + * Copyright (c) 2014, 2025 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 @@ -18,7 +18,7 @@ import org.springframework.ide.vscode.boot.metadata.types.TypedProperty; 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.completion.ScoreableProposal; +import org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal; import org.springframework.ide.vscode.commons.util.FuzzyMap.Match; import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.text.IDocument; @@ -28,7 +28,7 @@ public class PropertyCompletionFactory { public ICompletionProposal valueProposal(String value, String query, String niceTypeName, double score, DocumentEdits edits, Renderable info) { - return new ScoreableProposal() { + return new AbstractScoreableProposal() { @Override public DocumentEdits getTextEdit() { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProposal.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProposal.java index b8ca3ebf2f..30a5e522be 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProposal.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProposal.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Broadcom + * Copyright (c) 2024, 2025 Broadcom * 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 @@ -12,13 +12,13 @@ import org.eclipse.lsp4j.CompletionItemKind; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; -import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal; +import org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal; import org.springframework.ide.vscode.commons.util.Renderable; /** * @author Martin Lippert */ -public class AnnotationAttributeCompletionProposal extends ScoreableProposal { +public class AnnotationAttributeCompletionProposal extends AbstractScoreableProposal { private final AnnotationAttributeProposal coreProposal; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProposal.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProposal.java index 704c6757ed..335556c772 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProposal.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProposal.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2024 Broadcom, Inc. + * Copyright (c) 2017, 2025 Broadcom, 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 @@ -18,7 +18,7 @@ import org.eclipse.lsp4j.CompletionItemKind; import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings; 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.completion.ICompletionProposalWithScore; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor; import org.springframework.ide.vscode.commons.rewrite.java.InjectBeanCompletionRecipe; @@ -30,7 +30,7 @@ * @author Udayani V * @author Alex Boyko */ -public class BeanCompletionProposal implements ICompletionProposal { +public class BeanCompletionProposal implements ICompletionProposalWithScore { private DocumentEdits edits; private IDocument doc; @@ -38,14 +38,17 @@ public class BeanCompletionProposal implements ICompletionProposal { private String beanType; private String className; private RewriteRefactorings rewriteRefactorings; + private double score; public BeanCompletionProposal(DocumentEdits edits, IDocument doc, String beanId, String beanType, String className, + double score, RewriteRefactorings rewriteRefactorings) { this.edits = edits; this.doc = doc; this.beanId = beanId; this.beanType = beanType; this.className = className; + this.score = score; this.rewriteRefactorings = rewriteRefactorings; } @@ -82,6 +85,10 @@ public Optional getCommand() { .withRecipeScope(RecipeScope.NODE); return Optional.of(rewriteRefactorings.createFixCommand("Inject bean '%s'".formatted(beanId), f)); } - + + @Override + public double getScore() { + return score; + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProvider.java index 659e7e522a..fe24fa5074 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProvider.java @@ -10,13 +10,19 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans; +import java.util.Arrays; import java.util.Collection; +import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclaration; @@ -30,6 +36,7 @@ import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.util.FuzzyMatcher; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** @@ -53,7 +60,7 @@ public BeanCompletionProvider(JavaProjectFinder javaProjectFinder, SpringMetamod @Override public void provideCompletions(ASTNode node, int offset, TextDocument doc, Collection completions) { - if (node instanceof SimpleName) { + if (node instanceof SimpleName || node instanceof Block) { try { // Don't look at anything inside Annotation or VariableDelcaration node for (ASTNode n = node; n != null; n = n.getParent()) { @@ -77,14 +84,36 @@ public void provideCompletions(ASTNode node, int offset, TextDocument doc, if (isSpringComponent(topLevelClass)) { String className = getFullyQualifiedName(topLevelClass); Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); + ITypeBinding topLevelBeanType = topLevelClass.resolveBinding(); + Set declaredFiledsTypes = Arrays.stream(topLevelBeanType.getDeclaredFields()) + .map(vd -> vd.getType()) + .filter(Objects::nonNull) + .map(t -> t.getQualifiedName()) + .collect(Collectors.toSet()); + final String prefix = node instanceof Block ? "" : node.toString(); for (Bean bean : beans) { - DocumentEdits edits = new DocumentEdits(doc, false); - edits.replace(offset - node.toString().length(), offset, bean.getName()); + // If current class is a bean - ignore it + if (className.equals(bean.getType())) { + continue; + } + // Filter out beans already injected into this class + if (declaredFiledsTypes.contains(bean.getType())) { + continue; + } + double score = FuzzyMatcher.matchScore(prefix, bean.getName()); + if (score > 0) { + DocumentEdits edits = new DocumentEdits(doc, false); + if (node instanceof Block) { + edits.insert(offset, bean.getName()); + } else { + edits.replace(offset - prefix.length(), offset, bean.getName()); + } - BeanCompletionProposal proposal = new BeanCompletionProposal(edits, doc, bean.getName(), - bean.getType(), className, rewriteRefactorings); + BeanCompletionProposal proposal = new BeanCompletionProposal(edits, doc, bean.getName(), + bean.getType(), className, score, rewriteRefactorings); - completions.add(proposal); + completions.add(proposal); + } } } } catch (Exception e) { @@ -127,9 +156,10 @@ private static TypeDeclaration findParentClass(ASTNode node) { } private static String getFullyQualifiedName(TypeDeclaration typeDecl) { - if (typeDecl.resolveBinding() != null) { - String qualifiedName = typeDecl.resolveBinding().getQualifiedName(); - return qualifiedName.replaceAll("\\.(?=[^\\.]+$)", "\\$"); + ITypeBinding binding = typeDecl.resolveBinding(); + if (binding != null) { + String qualifiedName = binding.getQualifiedName(); + return qualifiedName/*.replaceAll("\\.(?=[^\\.]+$)", "\\$")*/; } CompilationUnit cu = (CompilationUnit) typeDecl.getRoot(); String packageName = cu.getPackage() != null ? cu.getPackage().getName().getFullyQualifiedName() : ""; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyKeyProposal.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyKeyProposal.java index bcb742cb91..fdd7ddd514 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyKeyProposal.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyKeyProposal.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2020 Pivotal, Inc. + * Copyright (c) 2017, 2025 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 @@ -14,14 +14,14 @@ import org.springframework.ide.vscode.boot.common.InformationTemplates; import org.springframework.ide.vscode.boot.metadata.PropertyInfo; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; -import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal; +import org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal; import org.springframework.ide.vscode.commons.util.FuzzyMap.Match; import org.springframework.ide.vscode.commons.util.Renderable; /** * @author Martin Lippert */ -public class ValuePropertyKeyProposal extends ScoreableProposal { +public class ValuePropertyKeyProposal extends AbstractScoreableProposal { private static final String EMPTY_DETAIL = ""; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/GenericXMLCompletionProposal.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/GenericXMLCompletionProposal.java index b6dd750c00..de82c5d7b9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/GenericXMLCompletionProposal.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/GenericXMLCompletionProposal.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2024 Pivotal, Inc. + * Copyright (c) 2019, 2025 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 @@ -15,13 +15,13 @@ import org.eclipse.lsp4j.CompletionItemKind; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; -import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal; +import org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal; import org.springframework.ide.vscode.commons.util.Renderable; /** * @author Martin Lippert */ -public class GenericXMLCompletionProposal extends ScoreableProposal { +public class GenericXMLCompletionProposal extends AbstractScoreableProposal { private final String label; private final CompletionItemKind kind; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java index 4cf3813874..ec47eea286 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015, 2023 Pivotal, Inc. + * Copyright (c) 2015, 2025 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 @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.yaml.completions; -import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_EXISTS; +import static org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal.DEEMP_EXISTS; import java.util.ArrayList; import java.util.Collection; @@ -53,7 +53,7 @@ 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.completion.LazyProposalApplier; -import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal; +import org.springframework.ide.vscode.commons.languageserver.completion.AbstractScoreableProposal; import org.springframework.ide.vscode.commons.util.CollectionUtil; import org.springframework.ide.vscode.commons.util.FuzzyMap.Match; import org.springframework.ide.vscode.commons.util.FuzzyMatcher; @@ -466,7 +466,7 @@ public Collection getCompletions(YamlDocument doc, SNode no matchingProps.stream().forEach(match -> { try { DocumentEdits edits = createEdits(doc, node, offset, query, match); - ScoreableProposal completion = completionFactory.property( + AbstractScoreableProposal completion = completionFactory.property( doc.getDocument(), edits, match, typeUtil ); String prefix = indexNav.getPrefix(); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/BeanCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/BeanCompletionProviderTest.java index b946f2709f..9d89eaa3af 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/BeanCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/BeanCompletionProviderTest.java @@ -65,6 +65,7 @@ public class BeanCompletionProviderTest { private Bean bean3; private Bean bean4; private Bean bean5; + private Bean bean6; @BeforeEach public void setup() throws Exception { @@ -86,8 +87,9 @@ public void setup() throws Exception { bean3 = new Bean("visitRepository", "org.springframework.samples.petclinic.owner.VisitRepository", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false, "symbolLabel"); bean4 = new Bean("visitService", "org.springframework.samples.petclinic.owner.VisitService", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false, "symbolLabel"); bean5 = new Bean("petService", "org.springframework.samples.petclinic.pet.Inner.PetService", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false, "symbolLabel"); + bean6 = new Bean("testBeanCompletionClass", "org.sample.test.TestBeanCompletionClass", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false, "symbolLabel"); - springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2, bean3, bean4, bean5}); + springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2, bean3, bean4, bean5, bean6}); } @AfterEach @@ -97,7 +99,7 @@ public void restoreIndexState() { @Test public void testBeanCompletion_firstCompletion() throws Exception { - assertCompletions(getCompletion("owner<*>"), new String[] {"ownerRepository", "ownerService", "petService", "visitRepository", "visitService"}, 0, + assertCompletions(getCompletion("owner<*>"), new String[] {"ownerRepository", "ownerService"}, 0, """ package org.sample.test; @@ -122,7 +124,32 @@ public void test() { @Test public void testBeanCompletion_secondCompletion() throws Exception { - assertCompletions(getCompletion("owner<*>"), new String[] {"ownerRepository", "ownerService", "petService", "visitRepository", "visitService"}, 1, + assertCompletions(getCompletion("owner<*>"), new String[] {"ownerRepository", "ownerService"}, 1, + """ +package org.sample.test; + +import org.springframework.samples.petclinic.owner.OwnerService; +import org.springframework.stereotype.Controller; + +@Controller +public class TestBeanCompletionClass { + + private final OwnerService ownerService; + + TestBeanCompletionClass(OwnerService ownerService) { + this.ownerService = ownerService; + } + + public void test() { +ownerService<*> + } +} + """); + } + + @Test + public void noPrefix_secondCompletion() throws Exception { + assertCompletions(getCompletion("<*>"), new String[] {"ownerRepository", "ownerService", "petService", "visitRepository", "visitService"}, 1, """ package org.sample.test; @@ -147,7 +174,7 @@ public void test() { @Test public void testBeanCompletion_injectInnerClass() throws Exception { - assertCompletions(getCompletion("owner<*>"), new String[] {"ownerRepository", "ownerService", "petService", "visitRepository", "visitService"}, 2, + assertCompletions(getCompletion("<*>"), new String[] {"ownerRepository", "ownerService", "petService", "visitRepository", "visitService"}, 2, """ package org.sample.test; @@ -199,7 +226,72 @@ public void test() { } """; - assertCompletions(content, new String[] {"ownerRepository", "ownerService", "petService", "visitRepository", "visitService"}, 1, + assertCompletions(content, new String[] {"ownerRepository", "ownerService"}, 1, + """ +package org.sample.test; + +import org.springframework.samples.petclinic.owner.OwnerRepository; +import org.springframework.samples.petclinic.owner.OwnerService; +import org.springframework.stereotype.Controller; + +@Controller +public class TestBeanCompletionClass { + private final OwnerRepository ownerRepository; + + TestBeanCompletionClass(OwnerRepository ownerRepository) { + this.ownerRepository = ownerRepository; + } + + public void test() { + } +} + +@Controller +public class TestBeanCompletionSecondClass { + + private final OwnerService ownerService; + + TestBeanCompletionSecondClass(OwnerService ownerService) { + this.ownerService = ownerService; + } + + public void test() { + ownerService<*> + } +} + """); + } + + @Test + public void noPrefix_multipleClasses() throws Exception { + String content = """ + package org.sample.test; + + import org.springframework.samples.petclinic.owner.OwnerRepository; + import org.springframework.stereotype.Controller; + + @Controller + public class TestBeanCompletionClass { + private final OwnerRepository ownerRepository; + + TestBeanCompletionClass(OwnerRepository ownerRepository) { + this.ownerRepository = ownerRepository; + } + + public void test() { + } + } + + @Controller + public class TestBeanCompletionSecondClass { + + public void test() { + <*> + } + } + """; + + assertCompletions(content, new String[] {"ownerRepository", "ownerService", "petService", "testBeanCompletionClass", "visitRepository", "visitService"}, 1, """ package org.sample.test; @@ -279,13 +371,13 @@ public class TestBeanCompletionClass { public class Inner { public void test() { - ownerRe<*> + owner<*> } } } """; - assertCompletions(content, new String[] {"ownerRepository", "ownerService", "petService", "visitRepository", "visitService"}, 0, + assertCompletions(content, new String[] {"ownerRepository", "ownerService"}, 0, """ package org.sample.test; @@ -330,6 +422,68 @@ public void test() { return content; } + @Test + public void beansPresent() throws Exception { + String content = """ + package org.sample.test; + + import org.springframework.stereotype.Controller; + + @Controller + public class TestBeanCompletionSecondClass { + private final TestBeanCompletionClass testBeanCompletionClass; + + TestBeanCompletionSecondClass(TestBeanCompletionClass testBeanCompletionClass) { + this.testBeanCompletionClass = testBeanCompletionClass; + } + + public void test() { + owner<*> + } + } + + @Controller + public class TestBeanCompletionClass { + + public void test() { + } + } + """; + + assertCompletions(content, new String[] {"ownerRepository", "ownerService"}, 0, + """ +package org.sample.test; + +import org.springframework.samples.petclinic.owner.OwnerRepository; +import org.springframework.stereotype.Controller; + +@Controller +public class TestBeanCompletionSecondClass { + + private final OwnerRepository ownerRepository; + private final TestBeanCompletionClass testBeanCompletionClass; + + TestBeanCompletionSecondClass(TestBeanCompletionClass testBeanCompletionClass, OwnerRepository ownerRepository) { + this.testBeanCompletionClass = testBeanCompletionClass; + this.ownerRepository = ownerRepository; + } + + public void test() { + ownerRepository<*> + } +} + +@Controller +public class TestBeanCompletionClass { + + public void test() { + } +} + """); + } + + + private void assertCompletions(String completionLine, String[] expectedCompletions, int chosenCompletion, String expectedResult) throws Exception { assertCompletions(completionLine, expectedCompletions.length, expectedCompletions, chosenCompletion, expectedResult); }