Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-1323, GH-1324: Cron Expressions completion proposals and inlay hints #1357

Merged
merged 2 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.InlayHintParams;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.MarkupContent;
Expand Down Expand Up @@ -681,6 +683,12 @@ public List<? extends CodeLens> getCodeLenses(TextDocumentInfo document) throws
params.setTextDocument(document.getId());
return getServer().getTextDocumentService().codeLens(params).get();
}

public List<InlayHint> getInlayHints(TextDocumentInfo document) throws Exception {
InlayHintParams params = new InlayHintParams();
params.setTextDocument(document.getId());
return getServer().getTextDocumentService().inlayHint(params).get();
}

public List<? extends DocumentHighlight> getDocumentHighlights(TextDocumentIdentifier docId, Position cursor) throws InterruptedException, ExecutionException {
return getServer().getTextDocumentService().documentHighlight(new DocumentHighlightParams(docId, cursor)).get();
Expand Down
7 changes: 7 additions & 0 deletions headless-services/spring-boot-language-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@
<artifactId>commons-util</artifactId>
<version>1.58.0-SNAPSHOT</version>
</dependency>

<!-- Cron expression descriptor library -->
<dependency>
<groupId>com.cronutils</groupId>
<artifactId>cron-utils</artifactId>
<version>9.2.0</version>
</dependency>
</dependencies>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
import org.springframework.ide.vscode.boot.java.beans.ProfileCompletionProvider;
import org.springframework.ide.vscode.boot.java.beans.QualifierCompletionProvider;
import org.springframework.ide.vscode.boot.java.beans.ResourceCompletionProvider;
import org.springframework.ide.vscode.boot.java.conditionalonresource.ConditionalOnResourceCompletionProcessor;
import org.springframework.ide.vscode.boot.java.contextconfiguration.ContextConfigurationProcessor;
import org.springframework.ide.vscode.boot.java.cron.CronExpressionCompletionProvider;
import org.springframework.ide.vscode.boot.java.data.DataRepositoryCompletionProcessor;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine;
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
Expand All @@ -40,8 +43,6 @@
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.java.value.ValueCompletionProcessor;
import org.springframework.ide.vscode.boot.java.contextconfiguration.ContextConfigurationProcessor;
import org.springframework.ide.vscode.boot.java.conditionalonresource.ConditionalOnResourceCompletionProcessor;
import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
Expand Down Expand Up @@ -132,6 +133,8 @@ BootJavaCompletionEngine javaCompletionEngine(

providers.put(Annotations.NAMED_JAKARTA, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new NamedCompletionProvider(springIndex))));
providers.put(Annotations.NAMED_JAVAX, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new NamedCompletionProvider(springIndex))));

providers.put(Annotations.SCHEDULED, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("cron", new CronExpressionCompletionProvider())));

return new BootJavaCompletionEngine(cuCache, providers, snippetManager);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ide.vscode.boot.java.cron.CronExpressionsInlayHintsProvider;
import org.springframework.ide.vscode.boot.java.cron.CronReconciler;
import org.springframework.ide.vscode.boot.java.cron.CronSemanticTokens;
import org.springframework.ide.vscode.boot.java.cron.JdtCronReconciler;
Expand Down Expand Up @@ -136,6 +137,10 @@ public class JdtConfig {
return new JdtDataQueriesInlayHintsProvider(semanticTokensProvider);
}

@Bean CronExpressionsInlayHintsProvider cronExpressionsInlayHintsProvider() {
return new CronExpressionsInlayHintsProvider();
}

@Bean JdtQueryDocHighlightsProvider jdtDocHighlightsProvider(JdtDataQuerySemanticTokensProvider semanticTokensProvider) {
return new JdtQueryDocHighlightsProvider(semanticTokensProvider);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,34 +114,34 @@ else if (node instanceof ArrayInitializer && node.getParent() instanceof Annotat
/**
* create the concrete completion proposal
*/
private void createCompletionProposals(IJavaProject project, TextDocument doc, ASTNode node, String attributeName, Collection<ICompletionProposal> completions, int startOffset, int endOffset,
String filterPrefix, Function<String, String> createReplacementText) {
private void createCompletionProposals(IJavaProject project, TextDocument doc, ASTNode node, String attributeName,
Collection<ICompletionProposal> completions, int startOffset, int endOffset, String filterPrefix,
Function<String, String> createReplacementText) {

Set<String> alreadyMentionedValues = alreadyMentionedValues(node);

AnnotationAttributeCompletionProvider completionProvider = this.completionProviders.get(attributeName);
if (completionProvider != null) {
List<String> candidates = completionProvider.getCompletionCandidates(project);

List<String> filteredCandidates = candidates.stream()
// .filter(candidate -> candidate.toLowerCase().startsWith(filterPrefix.toLowerCase()))
.filter(candidate -> candidate.toLowerCase().contains(filterPrefix.toLowerCase()))
.filter(candidate -> !alreadyMentionedValues.contains(candidate))
.collect(Collectors.toList());

double score = filteredCandidates.size();
for (String candidate : filteredCandidates) {

Map<String, String> proposals = completionProvider.getCompletionCandidates(project);
Map<String, String> filteredProposals = proposals.entrySet().stream()
.filter(candidate -> candidate.getKey().toLowerCase().contains(filterPrefix.toLowerCase()))
.filter(candidate -> !alreadyMentionedValues.contains(candidate.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
double score = filteredProposals.size();
for (Map.Entry<String, String> entry : filteredProposals.entrySet()) {
String candidate = entry.getKey();
DocumentEdits edits = new DocumentEdits(doc, false);
edits.replace(startOffset, endOffset, createReplacementText.apply(candidate));

AnnotationAttributeCompletionProposal proposal = new AnnotationAttributeCompletionProposal(edits, candidate, candidate, null, score--);

AnnotationAttributeCompletionProposal proposal = new AnnotationAttributeCompletionProposal(edits,
candidate, entry.getValue(), null, score--);
completions.add(proposal);

}
}
}


//
// internal computation of the right positions, prefixes, etc.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.annotations;

import java.util.List;
import java.util.Map;

import org.springframework.ide.vscode.commons.java.IJavaProject;

public interface AnnotationAttributeCompletionProvider {

List<String> getCompletionCandidates(IJavaProject project);
Map<String, String> getCompletionCandidates(IJavaProject project);

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
package org.springframework.ide.vscode.boot.java.beans;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
Expand Down Expand Up @@ -235,11 +236,11 @@ public DependsOnCompletionProcessor(SpringMetamodelIndex springIndex) {
// }

@Override
public List<String> getCompletionCandidates(IJavaProject project) {
public Map<String, String> getCompletionCandidates(IJavaProject project) {
return Arrays.stream(this.springIndex.getBeansOfProject(project.getElementName()))
.map(bean -> bean.getName())
.distinct()
.toList();
.collect(Collectors.toMap(key -> key, value -> value));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
package org.springframework.ide.vscode.boot.java.beans;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
Expand All @@ -32,15 +33,15 @@ public NamedCompletionProvider(SpringMetamodelIndex springIndex) {
}

@Override
public List<String> getCompletionCandidates(IJavaProject project) {
public Map<String, String> getCompletionCandidates(IJavaProject project) {

Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());

return Stream.concat(
findAllNamedValues(beans),
Arrays.stream(beans).map(bean -> bean.getName()))
.distinct()
.toList();
.collect(Collectors.toMap(key -> key, value -> value));
}

private Stream<String> findAllNamedValues(Bean[] beans) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
package org.springframework.ide.vscode.boot.java.beans;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
Expand All @@ -32,13 +33,13 @@ public ProfileCompletionProvider(SpringMetamodelIndex springIndex) {
}

@Override
public List<String> getCompletionCandidates(IJavaProject project) {
public Map<String, String> getCompletionCandidates(IJavaProject project) {

Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());

return findAllProfiles(beans)
.distinct()
.toList();
.collect(Collectors.toMap(key -> key, value -> value));
}

private Stream<String> findAllProfiles(Bean[] beans) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
package org.springframework.ide.vscode.boot.java.beans;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
Expand All @@ -32,15 +33,15 @@ public QualifierCompletionProvider(SpringMetamodelIndex springIndex) {
}

@Override
public List<String> getCompletionCandidates(IJavaProject project) {
public Map<String, String> getCompletionCandidates(IJavaProject project) {

Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());

return Stream.concat(
findAllQualifiers(beans),
Arrays.stream(beans).map(bean -> bean.getName()))
.distinct()
.toList();
.collect(Collectors.toMap(key -> key, value -> value));
}

private Stream<String> findAllQualifiers(Bean[] beans) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
package org.springframework.ide.vscode.boot.java.beans;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
Expand All @@ -30,13 +31,13 @@ public ResourceCompletionProvider(SpringMetamodelIndex springIndex) {
}

@Override
public List<String> getCompletionCandidates(IJavaProject project) {
public Map<String, String> getCompletionCandidates(IJavaProject project) {

Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());

return Arrays.stream(beans).map(bean -> bean.getName())
.distinct()
.toList();
.collect(Collectors.toMap(key -> key, value -> value));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

import java.nio.file.Paths;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
Expand All @@ -23,8 +24,8 @@
*/
public class ConditionalOnResourceCompletionProcessor implements AnnotationAttributeCompletionProvider {

private List<String> findResources(IJavaProject project) {
List<String> resources = IClasspathUtil.getClasspathResources(project.getClasspath()).stream()
private Map<String, String> findResources(IJavaProject project) {
Map<String, String> resources = IClasspathUtil.getClasspathResources(project.getClasspath()).stream()
.distinct()
.sorted(new Comparator<String>() {
@Override
Expand All @@ -34,13 +35,13 @@ public int compare(String o1, String o2) {
})
.map(r -> r.replaceAll("\\\\", "/"))
.map(r -> "classpath:" + r)
.toList();
.collect(Collectors.toMap(key -> key, value -> value));

return resources;
}

@Override
public List<String> getCompletionCandidates(IJavaProject project) {
public Map<String, String> getCompletionCandidates(IJavaProject project) {
return findResources(project);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.springframework.ide.vscode.boot.java.cron;

import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
import org.springframework.ide.vscode.commons.java.IJavaProject;

public class CronExpressionCompletionProvider implements AnnotationAttributeCompletionProvider {

private static final Map<String, String> CRON_EXPRESSIONS_MAP = new LinkedHashMap<>();

static {
CRON_EXPRESSIONS_MAP.put("0 0 * * * 1-5", "every hour every day between Monday and Friday");
CRON_EXPRESSIONS_MAP.put("0 */5 * * * *", "every 5 minutes");
CRON_EXPRESSIONS_MAP.put("0 * * * * *", "every minute");
CRON_EXPRESSIONS_MAP.put("0 0 */6 * * *", "every 6 hours at minute 0");
CRON_EXPRESSIONS_MAP.put("0 0 * * * *", "every hour");
CRON_EXPRESSIONS_MAP.put("0 0 * * * SUN", "every hour at Sunday day");
CRON_EXPRESSIONS_MAP.put("0 0 0 * * *", "at 00:00");
CRON_EXPRESSIONS_MAP.put("0 0 0 * * SAT,SUN", "at 00:00 on Saturday and Sunday");
CRON_EXPRESSIONS_MAP.put("0 0 0 * * 6,0", "at 00:00 at Saturday and Sunday days");
CRON_EXPRESSIONS_MAP.put("0 0 0 1-7 * SUN", "at 00:00 every day between 1 and 7 at Sunday day");
CRON_EXPRESSIONS_MAP.put("0 0 0 1 * *", "at 00:00 at 1 day");
CRON_EXPRESSIONS_MAP.put("0 0 0 1 1 *", "at 00:00 at 1 day at January month");
CRON_EXPRESSIONS_MAP.put("0 0 8-18 * * *", "every hour between 8 and 18");
CRON_EXPRESSIONS_MAP.put("0 0 9 * * MON", "at 09:00 at Monday day");
CRON_EXPRESSIONS_MAP.put("0 0 10 * * *", "at 10:00");
CRON_EXPRESSIONS_MAP.put("0 30 9 * JAN MON", "at 09:30 at January month at Monday day");
CRON_EXPRESSIONS_MAP.put("10 * * * * *", "every minute at second 10");
CRON_EXPRESSIONS_MAP.put("0 0 8-10 * * *", "every hour between 8 and 10");
CRON_EXPRESSIONS_MAP.put("0 0/30 8-10 * * *", "every 30 minutes every hour between 8 and 10");
CRON_EXPRESSIONS_MAP.put("0 0 0 L * *", " at 00:00 last day of month");
CRON_EXPRESSIONS_MAP.put("0 0 0 1W * *", "at 00:00 the nearest weekday to the 1 of the month");
CRON_EXPRESSIONS_MAP.put("0 0 0 * * THUL", "at 00:00 last Thursday of every month");
CRON_EXPRESSIONS_MAP.put("0 0 0 ? * 5#2", "at 00:00 Friday 2 of every month");
CRON_EXPRESSIONS_MAP.put("0 0 0 ? * MON#1", "at 00:00 Monday 1 of every month");
}


@Override
public Map<String, String> getCompletionCandidates(IJavaProject project) {
return CRON_EXPRESSIONS_MAP;
}
}
Loading