Skip to content

Commit

Permalink
Get initialization options from document uri
Browse files Browse the repository at this point in the history
For some LS the determination of LS initialization options depends on
the first uri to be opened and not on the first project or path.
Therefor the
getInitializationOptionsFromUri(URI) method has been added to the
StreamConnectionProvider interface. It will be called when
getInitializationOptions(URI) returns null.

fixes #683 #684
  • Loading branch information
ghentschke committed Jun 12, 2023
1 parent 80d2076 commit 9d883a6
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public void testGetLSWrapper() throws IOException {
var serverDefinition = LanguageServersRegistry.getInstance().getDefinition("org.eclipse.lsp4e.test.server");
assertNotNull(serverDefinition);

var lsWrapper = getLSWrapper(project, serverDefinition);
var lsWrapper = getLSWrapper(project, serverDefinition, null);
assertNotNull(lsWrapper);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void testAssistForUnknownButConnectedType() throws CoreException, IOExcep

LanguageServerDefinition serverDefinition = LanguageServersRegistry.getInstance().getDefinition("org.eclipse.lsp4e.test.server");
assertNotNull(serverDefinition);
LanguageServerWrapper lsWrapper = LanguageServiceAccessor.getLSWrapper(testFile.getProject(), serverDefinition);
LanguageServerWrapper lsWrapper = LanguageServiceAccessor.getLSWrapper(testFile.getProject(), serverDefinition, null);
URI fileLocation = testFile.getLocationURI();
// force connection (that's what LSP4E should be designed to prevent 3rd party from having to use it).
lsWrapper.connect(null, testFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public void testAssistForUnknownButConnectedType() throws CoreException, IOExcep

LanguageServerDefinition serverDefinition = LanguageServersRegistry.getInstance().getDefinition("org.eclipse.lsp4e.test.server");
assertNotNull(serverDefinition);
LanguageServerWrapper lsWrapper = LanguageServiceAccessor.getLSWrapper(testFile.getProject(), serverDefinition);
LanguageServerWrapper lsWrapper = LanguageServiceAccessor.getLSWrapper(testFile.getProject(), serverDefinition, null);
URI fileLocation = testFile.getLocationURI();
// force connection (that's what LSP4E should be designed to prevent 3rd party from having to use it).
lsWrapper.connect(null, testFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public Object execute(ExecutionEvent event, Command command, IPath context) thro
IDocument document = TestUtils.openTextViewer(file).getDocument();

CodeLensProvider provider = new CodeLensProvider();
LanguageServerWrapper wrapper = LanguageServiceAccessor.getLSWrapper(project, LanguageServersRegistry.getInstance().getDefinition(MOCK_SERVER_ID));
LanguageServerWrapper wrapper = LanguageServiceAccessor.getLSWrapper(project, LanguageServersRegistry.getInstance().getDefinition(MOCK_SERVER_ID), null);

LSPCodeMining sut = new LSPCodeMining(lens, document, wrapper, provider);
MouseEvent mouseEvent = createMouseEvent();
Expand All @@ -120,7 +120,7 @@ public void testLSPCodeMiningActionServerSideHandling()
MockLanguageServer languageServer = MockLanguageServer.INSTANCE;
CodeLensProvider provider = new CodeLensProvider();

LanguageServerWrapper wrapper = LanguageServiceAccessor.getLSWrapper(project, LanguageServersRegistry.getInstance().getDefinition(MOCK_SERVER_ID));
LanguageServerWrapper wrapper = LanguageServiceAccessor.getLSWrapper(project, LanguageServersRegistry.getInstance().getDefinition(MOCK_SERVER_ID), null);

LSPCodeMining sut = new LSPCodeMining(lens, document, wrapper, provider); MouseEvent mouseEvent = createMouseEvent();
sut.getAction().accept(mouseEvent);
Expand Down
45 changes: 36 additions & 9 deletions org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CancellationException;
Expand Down Expand Up @@ -143,11 +144,11 @@ public void dirtyStateChanged(IFileBuffer buffer, boolean isDirty) {
@NonNull
public final LanguageServerDefinition serverDefinition;
@Nullable
protected final IProject initialProject;
protected IProject initialProject;
@NonNull
protected Map<@NonNull URI, @NonNull DocumentContentSynchronizer> connectedDocuments;
@Nullable
protected final IPath initialPath;
protected IPath initialPath;
protected final InitializeParams initParams = new InitializeParams();

protected StreamConnectionProvider lspStreamProvider;
Expand Down Expand Up @@ -233,13 +234,18 @@ private List<WorkspaceFolder> getRelevantWorkspaceFolders() {
return folders;
}

// for backward compatibility
public synchronized void start() throws IOException {
start(null);
}

/**
* Starts a language server and triggers initialization. If language server is
* started and active, does nothing. If language server is inactive, restart it.
*
* @throws IOException
*/
public synchronized void start() throws IOException {
public synchronized void start(@Nullable URI initialUri) throws IOException {
final var filesToReconnect = new HashMap<URI, IDocument>();
if (this.languageServer != null) {
if (isActive()) {
Expand All @@ -252,7 +258,7 @@ public synchronized void start() throws IOException {
}
}
if (this.initializeFuture == null) {
final URI rootURI = getRootURI();
final URI rootURI = getRootURI(initialUri);
this.launcherFuture = new CompletableFuture<>();
this.initializeFuture = CompletableFuture.supplyAsync(() -> {
if (LoggingStreamConnectionProviderProxy.shouldLog(serverDefinition.id)) {
Expand All @@ -261,7 +267,8 @@ public synchronized void start() throws IOException {
} else {
this.lspStreamProvider = serverDefinition.createConnectionProvider();
}
initParams.setInitializationOptions(this.lspStreamProvider.getInitializationOptions(rootURI));
var options = Optional.ofNullable(this.lspStreamProvider.getInitializationOptions(rootURI)).orElse(this.lspStreamProvider.getInitializationOptionsFromUri(initialUri));
initParams.setInitializationOptions(options);
try {
lspStreamProvider.start();
} catch (IOException e) {
Expand Down Expand Up @@ -353,7 +360,22 @@ private ClientInfo getClientInfo(String name) {
}

@Nullable
private URI getRootURI() {
private URI getRootURI(URI initialUri) {
// after stop() the LS client has been terminated and the initialPath and initialProject are
// outdated. In this case use the initialUri to determine the root URI:
if (initialUri != null) {
var file = LSPEclipseUtils.getFileHandle(initialUri);
if (file != null) {
var newInitialProject = file.getProject();
if (newInitialProject != null && newInitialProject.exists()) {
//update initial project and path:
this.initialProject = newInitialProject;
this.initialPath = file.getLocation();
return LSPEclipseUtils.toUri(newInitialProject);
}
}
}

final IProject project = this.initialProject;
if (project != null && project.exists()) {
return LSPEclipseUtils.toUri(project);
Expand Down Expand Up @@ -569,7 +591,7 @@ private boolean supportsWorkspaceFolderCapability() {
if (this.connectedDocuments.containsKey(uri)) {
return CompletableFuture.completedFuture(this);
}
start();
start(uri);
if (this.initializeFuture == null) {
return null;
}
Expand Down Expand Up @@ -655,6 +677,11 @@ protected LanguageServer getServer() {
}
}

//To ensure backward compatibility.
protected CompletableFuture<LanguageServer> getInitializedServer(){
return getInitializedServer(null);
}

/**
* Starts the language server, ensure it's and returns a CompletableFuture waiting for the
* server to be initialized and up-to-date (all related pending document changes
Expand All @@ -664,9 +691,9 @@ protected LanguageServer getServer() {
*
*/
@NonNull
protected CompletableFuture<LanguageServer> getInitializedServer() {
protected CompletableFuture<LanguageServer> getInitializedServer(@Nullable URI documentUri) {
try {
start();
start(documentUri);
} catch (IOException ex) {
LanguageServerPlugin.logError(ex);
}
Expand Down
4 changes: 2 additions & 2 deletions org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServers.java
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ protected LanguageServerDocumentExecutor(final @NonNull IDocument document) {
* Test whether this server supports the requested <code>ServerCapabilities</code>.
*/
private @NonNull CompletableFuture<@Nullable LanguageServerWrapper> filter(@NonNull LanguageServerWrapper wrapper) {
return wrapper.getInitializedServer()
return wrapper.getInitializedServer(LSPEclipseUtils.toUri(document))
.thenCompose(server -> CompletableFuture
.completedFuture(server != null && getFilter().test(wrapper.getServerCapabilities())))
.thenApply(matches -> matches ? wrapper: null);
Expand Down Expand Up @@ -316,7 +316,7 @@ public static class LanguageServerProjectExecutor extends LanguageServers<Langua
Collection<@NonNull LanguageServerWrapper> startedWrappers = order(LanguageServiceAccessor.getStartedWrappers(project, getFilter(), !restartStopped));
List<@NonNull CompletableFuture<LanguageServerWrapper>> wrappers = new ArrayList<>(startedWrappers.size());
for (LanguageServerWrapper wrapper : startedWrappers) {
wrappers.add(wrapper.getInitializedServer().thenApply(ls -> wrapper));
wrappers.add(wrapper.getInitializedServer(null).thenApply(ls -> wrapper));
}
return wrappers;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public static void enableLanguageServerContentType(
private static CompletableFuture<LanguageServer> getInitializedLanguageServer(@NonNull IResource resource,
@NonNull LanguageServerDefinition lsDefinition, Predicate<ServerCapabilities> capabilitiesPredicate)
throws IOException {
LanguageServerWrapper wrapper = getLSWrapper(resource.getProject(), lsDefinition, resource.getFullPath());
LanguageServerWrapper wrapper = getLSWrapper(resource.getProject(), lsDefinition, resource.getFullPath(), resource.getLocationURI());
if (capabilitiesComply(wrapper, capabilitiesPredicate)) {
return wrapper.getInitializedServer();
}
Expand Down Expand Up @@ -250,7 +250,7 @@ public static List<LanguageServerWrapper> getLSWrappers(@NonNull final IFile fil
continue;
}

final var wrapper = getLSWrapper(project, serverDefinition, file.getFullPath());
final var wrapper = getLSWrapper(project, serverDefinition, file.getFullPath(), file.getLocationURI());
if (!wrappers.contains(wrapper) && capabilitiesComply(wrapper, request)) {
wrappers.add(wrapper);
}
Expand Down Expand Up @@ -352,12 +352,28 @@ protected static Collection<LanguageServerWrapper> getLSWrappers(@NonNull final
@NonNull
public static LanguageServerWrapper getLSWrapper(@Nullable IProject project,
@NonNull LanguageServerDefinition serverDefinition) throws IOException {
return getLSWrapper(project, serverDefinition, null);
return getLSWrapper(project, serverDefinition, null, null);
}

/**
* Return existing {@link LanguageServerWrapper} for the given definition. If
* not found, create a new one with the given definition.
*
* @param project
* @param serverDefinition
* @param initialUri
* @return a new or existing {@link LanguageServerWrapper} for the given definition.
* @throws IOException
*/
@NonNull
public static LanguageServerWrapper getLSWrapper(@Nullable IProject project,
@NonNull LanguageServerDefinition serverDefinition, @Nullable URI initialUri) throws IOException {
return getLSWrapper(project, serverDefinition, null, initialUri);
}

@NonNull
private static LanguageServerWrapper getLSWrapper(@Nullable IProject project,
@NonNull LanguageServerDefinition serverDefinition, @Nullable IPath initialPath) throws IOException {
@NonNull LanguageServerDefinition serverDefinition, @Nullable IPath initialPath, @Nullable URI initialUri) throws IOException {

final Predicate<LanguageServerWrapper> serverSelector = wrapper -> project != null && wrapper.canOperate(project)
&& wrapper.serverDefinition.equals(serverDefinition);
Expand All @@ -377,7 +393,7 @@ private static LanguageServerWrapper getLSWrapper(@Nullable IProject project,
final var wrapper = project != null //
? new LanguageServerWrapper(project, serverDefinition)
: new LanguageServerWrapper(serverDefinition, initialPath);
wrapper.start();
wrapper.start(initialUri);

startedServers.add(wrapper);
return wrapper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ public Object getInitializationOptions(@Nullable URI rootUri) {
return provider.getInitializationOptions(rootUri);
}

@Override
public Object getInitializationOptionsFromUri(@Nullable URI initialUri) {
return provider.getInitializationOptionsFromUri(initialUri);
}

@Override
public String getTrace(@Nullable URI rootUri) {
return provider.getTrace(rootUri);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public void run(IMarker marker) {
if (definition != null) {
IResource resource = marker.getResource();
if (resource != null) {
wrapper = LanguageServiceAccessor.getLSWrapper(resource.getProject(), definition);
wrapper = LanguageServiceAccessor.getLSWrapper(resource.getProject(), definition, resource.getLocationURI());
}
}
if (wrapper != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void run(IMarker marker) {
}

try {
LanguageServerWrapper wrapper = LanguageServiceAccessor.getLSWrapper(resource.getProject(), definition);
LanguageServerWrapper wrapper = LanguageServiceAccessor.getLSWrapper(resource.getProject(), definition, resource.getLocationURI());
if (wrapper != null) {
ExecuteCommandOptions provider = wrapper.getServerCapabilities().getExecuteCommandProvider();
if (provider != null && provider.getCommands().contains(command.getCommand())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ public default Object getInitializationOptions(@Nullable URI rootUri){
return null;
}

/**
* User provided initialization options from initial uri.
* Will be called when the {@link #getInitializationOptions(URI)} returns null.
*/
public default Object getInitializationOptionsFromUri(@Nullable URI initialUri){
return null;
}

/**
* Returns an object that describes the experimental features supported
* by the client.
Expand Down

0 comments on commit 9d883a6

Please sign in to comment.