diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java index ef3ad9629..3041500e0 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java @@ -127,7 +127,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { projectFinder = serverParams.projectFinder; projectObserver = serverParams.projectObserver; - liveHoverWatchdog = new SpringLiveHoverWatchdog(this, hoverInfoProvider, serverParams.runningAppProvider, projectFinder, projectObserver); + liveHoverWatchdog = new SpringLiveHoverWatchdog(this, hoverInfoProvider, serverParams.runningAppProvider, projectFinder, projectObserver, serverParams.watchDogInterval); documents.onDidChangeContent(params -> { TextDocument doc = params.getDocument(); if (testHightlighter != null) { diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerParams.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerParams.java index f045b1e93..ca7a8afba 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerParams.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerParams.java @@ -10,10 +10,12 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java; +import java.time.Duration; import java.util.Arrays; import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider; import org.springframework.ide.vscode.boot.java.utils.BootProjectUtil; +import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog; import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider; import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; import org.springframework.ide.vscode.commons.gradle.GradleCore; @@ -36,17 +38,21 @@ public class BootJavaLanguageServerParams { public final ProjectObserver projectObserver; public final SpringPropertyIndexProvider indexProvider; public final RunningAppProvider runningAppProvider; + public final Duration watchDogInterval; public BootJavaLanguageServerParams( JavaProjectFinder projectFinder, ProjectObserver projectObserver, SpringPropertyIndexProvider indexProvider, - RunningAppProvider runningAppProvider) { + RunningAppProvider runningAppProvider, + Duration watchDogInterval + ) { super(); this.projectFinder = projectFinder; this.projectObserver = projectObserver; this.indexProvider = indexProvider; this.runningAppProvider = runningAppProvider; + this.watchDogInterval = watchDogInterval; } public static LSFactory createDefault() { @@ -66,7 +72,8 @@ public class BootJavaLanguageServerParams { javaProjectFinder.filter(BootProjectUtil::isBootProject), projectObserver, new DefaultSpringPropertyIndexProvider(javaProjectFinder, projectObserver), - RunningAppProvider.DEFAULT + RunningAppProvider.DEFAULT, + SpringLiveHoverWatchdog.DEFAULT_INTERVAL ); }; } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java index 8124bc127..bdf8f050a 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.utils; +import java.time.Duration; import java.util.Arrays; import java.util.Set; import java.util.Timer; @@ -35,8 +36,10 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; */ public class SpringLiveHoverWatchdog { - private static final int POLLING_INTERVAL_MILLISECONDS = 5000; + public static final Duration DEFAULT_INTERVAL = Duration.ofMillis(5000); + + private final long POLLING_INTERVAL_MILLISECONDS; private final Set watchedDocs; private final SimpleLanguageServer server; private final BootJavaHoverProvider hoverProvider; @@ -61,7 +64,15 @@ public class SpringLiveHoverWatchdog { return uris.anyMatch(uri -> projectFinder.find(new TextDocumentIdentifier(uri)).isPresent()); } - public SpringLiveHoverWatchdog(SimpleLanguageServer server, BootJavaHoverProvider hoverProvider, RunningAppProvider runningAppProvider, JavaProjectFinder projectFinder, ProjectObserver projectChanges) { + public SpringLiveHoverWatchdog( + SimpleLanguageServer server, + BootJavaHoverProvider hoverProvider, + RunningAppProvider runningAppProvider, + JavaProjectFinder projectFinder, + ProjectObserver projectChanges, + Duration pollingInterval + ) { + this.POLLING_INTERVAL_MILLISECONDS = pollingInterval == null ? DEFAULT_INTERVAL.toMillis() : pollingInterval.toMillis(); this.server = server; this.hoverProvider = hoverProvider; this.runningAppProvider = runningAppProvider; diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/profile/ActiveProfilesHoverTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/profile/ActiveProfilesHoverTest.java index bdad0d330..0638ae002 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/profile/ActiveProfilesHoverTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/profile/ActiveProfilesHoverTest.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.profile; +import java.time.Duration; + import org.junit.Before; import org.junit.Test; import org.springframework.ide.vscode.commons.util.text.LanguageId; @@ -31,6 +33,7 @@ public class ActiveProfilesHoverTest { harness = BootLanguageServerHarness.builder() .mockDefaults() .runningAppProvider(mockAppProvider.provider) + .watchDogInterval(Duration.ofMillis(100)) .build(); harness.useProject(projects.mavenProject("empty-boot-15-web-app")); harness.intialize(null); @@ -60,6 +63,10 @@ public class ActiveProfilesHoverTest { editor.assertHoverContains("@Profile", "local-profile"); editor.assertHoverContains("@Profile", "foo.bar.RunningApp"); editor.assertHoverContains("@Profile", "22022"); + + editor.assertHighlights( + "@Profile(\"local\")" + ); } diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/project/harness/BootLanguageServerHarness.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/project/harness/BootLanguageServerHarness.java index 4314d04e9..659e87dff 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/project/harness/BootLanguageServerHarness.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/project/harness/BootLanguageServerHarness.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.project.harness; +import java.time.Duration; + import org.junit.Assert; import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer; import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerParams; @@ -46,6 +48,7 @@ public class BootLanguageServerHarness extends LanguageServerHarness RANGE_COMPARATOR = new Comparator() { + @Override + public int compare(Range o1, Range o2) { + int diff = compare(o1.getStart(), o2.getStart()); + if (diff!=0) return diff; + return compare(o1.getEnd(), o2.getEnd()); + } + private int compare(Position p1, Position p2) { + int d = p1.getLine() - p2.getLine(); + if (d!=0) return d; + return p1.getCharacter() - p2.getCharacter(); + } + }; - private LanguageServerHarness harness; + private LanguageServerHarness harness; private TextDocumentInfo doc; private int selectionEnd; @@ -190,6 +211,19 @@ public class Editor { return buf.toString(); } + + public List assertHighlights(String... expectedHighlights) throws Exception { + HighlightParams highlights = harness.getHighlights(doc); + List ranges = new ArrayList<>(highlights.getRanges()); + Collections.sort(ranges, RANGE_COMPARATOR); + List actualHighlights = ranges.stream() + .map(this::getText) + .collect(Collectors.toList()); + assertEquals(ImmutableList.copyOf(expectedHighlights), actualHighlights); + return ranges; + } + + /** * Get the editor text, with cursor markers inserted (for easy textual comparison * after applying a proposal) @@ -806,5 +840,4 @@ public class Editor { public void setCursor(Position position) { this.selectionStart = this.selectionEnd = doc.toOffset(position); } - } 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 201d2b0d0..16310b6a8 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 @@ -24,8 +24,10 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -34,7 +36,10 @@ import java.util.Map.Entry; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import org.assertj.core.api.Condition; @@ -99,6 +104,8 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; import reactor.core.publisher.Mono; @@ -116,6 +123,7 @@ public class LanguageServerHarness { private InitializeResult initResult; private Map documents = new HashMap<>(); + private Multimap> highlights = MultimapBuilder.hashKeys().linkedListValues().build(); private Map diagnostics = new HashMap<>(); private List activeEditors = new ArrayList<>(); @@ -125,6 +133,8 @@ public class LanguageServerHarness { this.defaultLanguageId = defaultLanguageId; } + public static final Duration HIGHLIGHTS_TIMEOUT = Duration.ofMinutes(1000); + public LanguageServerHarness(Callable factory) throws Exception { this(factory, LanguageId.PLAINTEXT); } @@ -176,6 +186,21 @@ public class LanguageServerHarness { this.diagnostics.put(diags.getUri(), diags); } + private void receiveHighlights(HighlightParams highlights) { + Collection>requestors = ImmutableList.of(); + synchronized (this) { + String uri = highlights.getDoc().getUri(); + if (uri!=null) { + requestors = ImmutableList.copyOf(this.highlights.get(uri)); + //Carefull!! Must make a copy above. Because the returned collection is cleared when we call removeAll below. + this.highlights.removeAll(uri); //futures can only be completed once, so no point holding any longer + } + } + for (CompletableFuture future : requestors) { + future.complete(highlights); + } + } + public InitializeResult intialize(File workspaceRoot) throws Exception { server = factory.call(); int parentPid = random.nextInt(40000)+1000; @@ -220,6 +245,11 @@ public class LanguageServerHarness { receiveDiagnostics(diagnostics); } + @Override + public void highlight(HighlightParams highlights) { + receiveHighlights(highlights); + } + @Override public void logMessage(MessageParams message) { // TODO Auto-generated method stub @@ -254,11 +284,6 @@ public class LanguageServerHarness { } return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false)); } - - @Override - public void highlight(HighlightParams highlights) { - // TODO Auto-generated method stub - } }); } @@ -372,6 +397,16 @@ public class LanguageServerHarness { return diagnostics.get(doc.getUri()); } + public synchronized Future getHighlightsFuture(TextDocumentInfo doc) { + CompletableFuture future = new CompletableFuture(); + highlights.put(doc.getUri(), future); + return future; + } + + public HighlightParams getHighlights(TextDocumentInfo doc) throws Exception { + return getHighlightsFuture(doc).get(HIGHLIGHTS_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS); + } + public static Condition isDiagnosticWithSeverity(DiagnosticSeverity severity) { return new Condition<>( (d) -> d.getSeverity()==severity,