Trigger characters per language
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package org.springframework.ide.vscode.commons.languageserver.config;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties("languageserver")
|
||||
@@ -31,7 +33,7 @@ public class LanguageServerProperties {
|
||||
* being ignored when the client is vscode to avoid breaking Java completion
|
||||
* triggers.
|
||||
*/
|
||||
private String completionTriggerCharacters;
|
||||
private Map<String, String> completionTriggerCharacters;
|
||||
|
||||
public boolean isStandalone() {
|
||||
return standalone;
|
||||
@@ -57,11 +59,11 @@ public class LanguageServerProperties {
|
||||
this.extensionId = extensionId;
|
||||
}
|
||||
|
||||
public String getCompletionTriggerCharacters() {
|
||||
public Map<String,String> getCompletionTriggerCharacters() {
|
||||
return completionTriggerCharacters;
|
||||
}
|
||||
|
||||
public void setCompletionTriggerCharacters(String completionTriggerCharacters) {
|
||||
public void setCompletionTriggerCharacters(Map<String, String> completionTriggerCharacters) {
|
||||
this.completionTriggerCharacters = completionTriggerCharacters;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package org.springframework.ide.vscode.commons.languageserver.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionOptions;
|
||||
import org.eclipse.lsp4j.CompletionRegistrationOptions;
|
||||
import org.eclipse.lsp4j.DocumentFilter;
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.eclipse.lsp4j.Registration;
|
||||
import org.eclipse.lsp4j.RegistrationParams;
|
||||
import org.eclipse.lsp4j.ServerCapabilities;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerProperties;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class CompletionServerCapabilityRegistration implements ServerCapabilityInitializer {
|
||||
|
||||
private static Logger log = LoggerFactory.getLogger(CompletionServerCapabilityRegistration.class);
|
||||
|
||||
private final SimpleLanguageServer server;
|
||||
private final LanguageServerProperties props;
|
||||
|
||||
private boolean hasDynamicCompletionRegistration;
|
||||
|
||||
public CompletionServerCapabilityRegistration(SimpleLanguageServer server, LanguageServerProperties props) {
|
||||
this.server = server;
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
private List<String> getMergedTriggerCharacters() {
|
||||
Set<String> allTriggerChars = new TreeSet<>();
|
||||
Map<String, String> triggersByLanguage = props.getCompletionTriggerCharacters();
|
||||
if (triggersByLanguage!=null) {
|
||||
for (String triggers : triggersByLanguage.values()) {
|
||||
for (int i = 0; i < triggers.length(); i++) {
|
||||
String c = triggers.substring(i, i+1);
|
||||
allTriggerChars.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!allTriggerChars.isEmpty()) {
|
||||
return ImmutableList.copyOf(allTriggerChars);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(InitializeParams params, ServerCapabilities cap) {
|
||||
this.hasDynamicCompletionRegistration = SimpleLanguageServer.safeGet(false, () -> params.getCapabilities().getTextDocument().getCompletion().getDynamicRegistration());
|
||||
log.info("hasDynamicCompletionRegistration = "+hasDynamicCompletionRegistration);
|
||||
List<String> allTiggerChars = getMergedTriggerCharacters();
|
||||
if (!hasDynamicCompletionRegistration || allTiggerChars==null) {
|
||||
//Register completion provider and triggerCharacters statically
|
||||
CompletionOptions completionProvider = new CompletionOptions();
|
||||
completionProvider.setResolveProvider(server.hasLazyCompletionResolver());
|
||||
completionProvider.setTriggerCharacters(getMergedTriggerCharacters());
|
||||
cap.setCompletionProvider(completionProvider);
|
||||
log.info("Registering Completion Capability Statically {}", completionProvider);
|
||||
} else {
|
||||
//Register completion provider and triggerCharacters dynamically, one registration per
|
||||
//language
|
||||
server.onInitialized(Mono.fromCallable(() -> {
|
||||
List<Registration> registrations = new ArrayList<>();
|
||||
for (Entry<String, String> entry : props.getCompletionTriggerCharacters().entrySet()) {
|
||||
String languageId = entry.getKey();
|
||||
String triggerCharsString = entry.getValue();
|
||||
Registration r = new Registration(UUID.randomUUID().toString(), "textDocument/completion");
|
||||
CompletionRegistrationOptions registerOptions = new CompletionRegistrationOptions();
|
||||
registerOptions.setResolveProvider(server.hasLazyCompletionResolver());
|
||||
registerOptions.setTriggerCharacters(toTriggerChars(triggerCharsString));
|
||||
DocumentFilter df = new DocumentFilter();
|
||||
df.setLanguage(languageId);
|
||||
registerOptions.setDocumentSelector(ImmutableList.of(df));
|
||||
r.setRegisterOptions(registerOptions);
|
||||
registrations.add(r);
|
||||
}
|
||||
RegistrationParams regParams = new RegistrationParams(registrations);
|
||||
log.info("Registering Dynamic Completion Capabality {}", regParams);
|
||||
return Mono.fromFuture(server.getClient().registerCapability(regParams));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> toTriggerChars(String triggerCharsString) {
|
||||
ImmutableList.Builder<String> builder = ImmutableList.builder();
|
||||
for (int i = 0; i < triggerCharsString.length(); i++) {
|
||||
builder.add(triggerCharsString.substring(i, i+1));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*******************************************************************************
|
||||
* 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.languageserver.util;
|
||||
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.eclipse.lsp4j.ServerCapabilities;
|
||||
|
||||
/**
|
||||
* {@link SimpleLanguageServer} will automatically find any {@link ServerCapabilityInitializer} beans
|
||||
* in the application context and call on them during language server initialization to allow
|
||||
* it to participate in initializing the ServerCapability the server returns to the client.
|
||||
*/
|
||||
public interface ServerCapabilityInitializer {
|
||||
void initialize(InitializeParams params, ServerCapabilities cap);
|
||||
}
|
||||
@@ -55,6 +55,7 @@ import org.eclipse.lsp4j.services.LanguageClient;
|
||||
import org.eclipse.lsp4j.services.LanguageClientAware;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.ide.vscode.commons.languageserver.DiagnosticService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.ProgressService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.Sts4LanguageServer;
|
||||
@@ -111,9 +112,7 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
|
||||
public final LazyCompletionResolver completionResolver = createCompletionResolver();
|
||||
|
||||
private SimpleTextDocumentService tds;
|
||||
|
||||
private SimpleWorkspaceService workspace;
|
||||
|
||||
private STS4LanguageClient client;
|
||||
|
||||
private ProgressService progressService = (String taskId, String statusMsg) -> {
|
||||
@@ -155,6 +154,7 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
|
||||
|
||||
private String completionTriggerCharacters = null;
|
||||
|
||||
final private ApplicationContext appContext;
|
||||
|
||||
@Override
|
||||
public void connect(LanguageClient _client) {
|
||||
@@ -187,7 +187,8 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
|
||||
}
|
||||
}
|
||||
|
||||
public SimpleLanguageServer(String extensionId) {
|
||||
public SimpleLanguageServer(String extensionId, ApplicationContext appContext) {
|
||||
this.appContext = appContext;
|
||||
Assert.isNotNull(extensionId);
|
||||
this.EXTENSION_ID = extensionId;
|
||||
this.CODE_ACTION_COMMAND_ID = "sts."+EXTENSION_ID+".codeAction";
|
||||
@@ -270,6 +271,12 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
|
||||
getWorkspaceService().onExecuteCommand(this::executeCommand);
|
||||
}
|
||||
ServerCapabilities cap = getServerCapabilities();
|
||||
if (appContext!=null) {
|
||||
Map<String, ServerCapabilityInitializer> extraCaps = appContext.getBeansOfType(ServerCapabilityInitializer.class);
|
||||
for (ServerCapabilityInitializer capIniter : extraCaps.values()) {
|
||||
capIniter.initialize(params, cap);
|
||||
}
|
||||
}
|
||||
result.setCapabilities(cap);
|
||||
Consumer<InitializeParams> ih = this.initializeHandler;
|
||||
if (ih!=null){
|
||||
@@ -324,7 +331,7 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
|
||||
* Get some info safely. If there's any kind of exception, ignore it
|
||||
* and retutn default value instead.
|
||||
*/
|
||||
private static <T> T safeGet(T deflt, Callable<T> getter) {
|
||||
public static <T> T safeGet(T deflt, Callable<T> getter) {
|
||||
try {
|
||||
T x = getter.call();
|
||||
if (x!=null) {
|
||||
@@ -370,17 +377,6 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
|
||||
c.setTextDocumentSync(TextDocumentSyncKind.Incremental);
|
||||
c.setHoverProvider(true);
|
||||
|
||||
CompletionOptions completionProvider = new CompletionOptions();
|
||||
completionProvider.setResolveProvider(hasLazyCompletionResolver());
|
||||
if (this.completionTriggerCharacters!=null) {
|
||||
String[] chars = new String[this.completionTriggerCharacters.length()];
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
chars[i] = this.completionTriggerCharacters.substring(i, i+1);
|
||||
}
|
||||
completionProvider.setTriggerCharacters(ImmutableList.copyOf(chars));
|
||||
}
|
||||
c.setCompletionProvider(completionProvider);
|
||||
|
||||
if (hasQuickFixes()) {
|
||||
c.setCodeActionProvider(true);
|
||||
}
|
||||
|
||||
@@ -19,17 +19,17 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter.CompletionFilter;
|
||||
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerInitializer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerProperties;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.DiagnosticSeverityProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.CompletionServerCapabilityRegistration;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DefinitionHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbolHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LanguageSpecific;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient.Client;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
@@ -46,13 +46,10 @@ public class LanguageServerAutoConf {
|
||||
@Bean public SimpleLanguageServer languageServer(
|
||||
LanguageServerProperties props,
|
||||
Optional<DiagnosticSeverityProvider> severities,
|
||||
Optional<CompletionFilter> completionFilter
|
||||
Optional<CompletionFilter> completionFilter,
|
||||
ApplicationContext appContext
|
||||
) throws Exception {
|
||||
SimpleLanguageServer server = new SimpleLanguageServer(props.getExtensionId());
|
||||
if (LspClient.currentClient()!=Client.VSCODE) {
|
||||
//Vscode excluded. See https://github.com/spring-projects/sts4/issues/193
|
||||
server.setCompletionTriggerCharacters(props.getCompletionTriggerCharacters());
|
||||
}
|
||||
SimpleLanguageServer server = new SimpleLanguageServer(props.getExtensionId(), appContext);
|
||||
server.setCompletionFilter(completionFilter);
|
||||
severities.ifPresent(server::setDiagnosticSeverityProvider);
|
||||
return server;
|
||||
@@ -65,6 +62,10 @@ public class LanguageServerAutoConf {
|
||||
serverInit.initialize(server);
|
||||
};
|
||||
}
|
||||
|
||||
@Bean CompletionServerCapabilityRegistration completionCapabilities(SimpleLanguageServer server, LanguageServerProperties props) {
|
||||
return new CompletionServerCapabilityRegistration(server, props);
|
||||
}
|
||||
|
||||
@Bean SimpleTextDocumentService documents(SimpleLanguageServer ls) {
|
||||
return ls.getTextDocumentService();
|
||||
|
||||
@@ -36,7 +36,7 @@ public class DocumentEditsTest {
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
SimpleLanguageServer server = new SimpleLanguageServer("dont-care");
|
||||
SimpleLanguageServer server = new SimpleLanguageServer("dont-care", null);
|
||||
harness = new LanguageServerHarness(server, LanguageId.PLAINTEXT);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
spring.main.banner-mode=off
|
||||
languageserver.extension-id=vscode-spring-boot
|
||||
languageserver.completion-trigger-characters=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.
|
||||
@@ -0,0 +1,9 @@
|
||||
languageserver:
|
||||
extension-id: vscode-spring-boot
|
||||
completion-trigger-characters:
|
||||
java: ""
|
||||
spring-boot-properties-yaml: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.
|
||||
spring-boot-properties: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.
|
||||
spring:
|
||||
main:
|
||||
banner-mode: 'off'
|
||||
Reference in New Issue
Block a user