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 991739ba1..1a1e18f95 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 @@ -33,7 +33,9 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; +import java.util.Set; 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; @@ -98,12 +100,14 @@ import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceClientCapabilities; import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.WorkspaceEditCapabilities; +import org.eclipse.lsp4j.WorkspaceSymbolParams; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.services.LanguageClientAware; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; import org.springframework.ide.vscode.commons.languageserver.util.LanguageServerTestListener; import org.springframework.ide.vscode.commons.languageserver.util.Settings; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.languageserver.util.WorkspaceSymbolHandler; import org.springframework.ide.vscode.commons.protocol.CursorMovement; import org.springframework.ide.vscode.commons.protocol.HighlightParams; import org.springframework.ide.vscode.commons.protocol.ProgressParams; @@ -121,6 +125,7 @@ import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import com.google.gson.Gson; @@ -942,4 +947,15 @@ public class LanguageServerHarness { public void enableHierarchicalDocumentSymbols(boolean b) { this.enableHierarchicalDocumentSymbols = b; } + + public Collection getWorkspaceSymbols(String query) throws Exception { + WorkspaceSymbolParams params = new WorkspaceSymbolParams(query); + List r = server.getWorkspaceService().symbol(params).get(); + return ImmutableList.copyOf(r); + } + + public void assertWorkspaceSymbols(String query, String... expectedSymbols) throws Exception { + Set actualSymbols = getWorkspaceSymbols(query).stream().map(sym -> sym.getName()).collect(Collectors.toSet()); + assertEquals(ImmutableSet.copyOf(expectedSymbols), actualSymbols); + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/LiveAppURLSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/LiveAppURLSymbolProvider.java index 7417fe6de..b59b67dc6 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/LiveAppURLSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/LiveAppURLSymbolProvider.java @@ -11,9 +11,7 @@ package org.springframework.ide.vscode.boot.java.requestmapping; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.stream.Stream; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.Position; @@ -24,6 +22,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; +import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping; /** * @author Martin Lippert @@ -49,10 +48,20 @@ public class LiveAppURLSymbolProvider { String host = app.getHost(); String port = app.getPort(); String contextPath = app.getContextPath(); - Stream urls = app.getRequestMappings().stream() - .flatMap(rm -> Arrays.stream(rm.getSplitPath())) - .map(path -> UrlUtil.createUrl(urlScheme, host, port, path, contextPath)); - urls.forEach(url -> result.add(new SymbolInformation(url, SymbolKind.Method, new Location(url, new Range(new Position(0, 0), new Position(0, 1)))))); + for (RequestMapping rm : app.getRequestMappings()) { + String[] paths = rm.getSplitPath(); + if (paths==null || paths.length==0) { + //Technically, this means the path 'predicate' is unconstrained, meaning any path matches. + //So this is not quite the same as the case where path=""... but... + //It is better for us to show one link where any path is allowed, versus showing no links where any link is allowed. + //So we'll pretend this is the same as path="" as that gives a working link. + paths = new String[] {""}; + } + for (String path : paths) { + String url = UrlUtil.createUrl(urlScheme, host, port, path, contextPath); + result.add(new SymbolInformation(url, SymbolKind.Method, new Location(url, new Range(new Position(0, 0), new Position(0, 1))))); + } + } } catch (Exception e) { log.error("", e); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/DynamicRequestMappingSymbolTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/DynamicRequestMappingSymbolTest.java new file mode 100644 index 000000000..d83c1df2a --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/DynamicRequestMappingSymbolTest.java @@ -0,0 +1,91 @@ +package org.springframework.ide.vscode.boot.test; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.bootiful.HoverTestConf; +import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; +import org.springframework.ide.vscode.project.harness.MockRequestMapping; +import org.springframework.ide.vscode.project.harness.MockRunningAppProvider; +import org.springframework.test.context.junit4.SpringRunner; + +import com.google.common.collect.ImmutableList; + +@RunWith(SpringRunner.class) +@BootLanguageServerTest +@Import(HoverTestConf.class) +public class DynamicRequestMappingSymbolTest { + + @Autowired LanguageServerHarness harness; + @Autowired MockRunningAppProvider mockApps; + + @Test + public void runningAppProviderRequestMappingSymbol() throws Exception { + mockApps.builder() + .isSpringBootApp(true) + .port("1111") + .processId("22022") + .host("cfapps.io") + .urlScheme("https") + .processName("test-request-mapping-live-hover") + .requestMappings(ImmutableList.of( + new MockRequestMapping() + .className("example.HelloWorldController") + .methodName("sayHello") + .methodParams("java.lang.String") + .paths("/blah") + )) + .build(); + harness.assertWorkspaceSymbols("//", + "https://cfapps.io:1111/blah" + ); + } + + @Test + public void noPaths() throws Exception { + mockApps.builder() + .isSpringBootApp(true) + .port("1111") + .processId("22022") + .host("cfapps.io") + .urlScheme("https") + .processName("test-request-mapping-live-hover") + .requestMappings(ImmutableList.of( + new MockRequestMapping() + .className("example.HelloWorldController") + .methodName("sayHello") + .methodParams("java.lang.String") + .paths() + )) + .build(); + harness.assertWorkspaceSymbols("//", + "https://cfapps.io:1111/" + ); + } + + @Test + public void multiplePaths() throws Exception { + mockApps.builder() + .isSpringBootApp(true) + .port("80") + .processId("22022") + .host("localhost") + .urlScheme("http") + .processName("test-request-mapping-live-hover") + .requestMappings(ImmutableList.of( + new MockRequestMapping() + .className("example.HelloWorldController") + .methodName("sayHello") + .methodParams("java.lang.String") + .paths("foo", "/bar") + )) + .build(); + harness.assertWorkspaceSymbols("//", + "http://localhost/foo", + "http://localhost/bar" + ); + } + +}