Trigger characters per language

This commit is contained in:
Kris De Volder
2019-05-22 10:36:48 -07:00
parent 7fe67c90c6
commit 5c5d535556
8 changed files with 163 additions and 30 deletions

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -1,3 +0,0 @@
spring.main.banner-mode=off
languageserver.extension-id=vscode-spring-boot
languageserver.completion-trigger-characters=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.

View File

@@ -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'