Skip to content

Commit

Permalink
fix: Bind language servers to Project instead of Module
Browse files Browse the repository at this point in the history
Fixes #891

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Sep 7, 2023
1 parent 5c22a08 commit bfd1be5
Show file tree
Hide file tree
Showing 12 changed files with 37 additions and 191 deletions.
16 changes: 13 additions & 3 deletions src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPIJUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public static Document getDocument(VirtualFile docFile) {
return getDocument(documentFile);
}

public static @Nullable Module getProject(@Nullable VirtualFile file) {
public static @Nullable Module getModule(@Nullable VirtualFile file) {
if (file == null) {
return null;
}
Expand All @@ -176,6 +176,11 @@ public static Document getDocument(VirtualFile docFile) {
return null;
}

public static @Nullable Project getProject(@Nullable VirtualFile file) {
Module module = getModule(file);
return module != null ? module.getProject() : null;
}

public static int toOffset(Position start, Document document) throws IndexOutOfBoundsException {
int lineStartOffset = document.getLineStartOffset(start.getLine());
return lineStartOffset + start.getCharacter();
Expand All @@ -202,6 +207,11 @@ public static URI toUri(Module project) {
return file.toURI();
}

public static URI toUri(Project project) {
File file = new File(project.getProjectFilePath()).getParentFile();
return file.toURI();
}

public static Range toRange(TextRange range, Document document) {
return new Range(LSPIJUtils.toPosition(range.getStartOffset(), document), LSPIJUtils.toPosition(range.getEndOffset(), document));
}
Expand Down Expand Up @@ -409,8 +419,8 @@ public static Editor[] editorsForFile(VirtualFile file) {
}

public static Editor[] editorsForFile(VirtualFile file, Document document) {
Module module = LSPIJUtils.getProject(file);
return module != null ? EditorFactory.getInstance().getEditors(document, module.getProject()) : new Editor[0];
Project project = LSPIJUtils.getProject(file);
return project != null ? EditorFactory.getInstance().getEditors(document, project) : new Editor[0];
}

public static Editor editorForFile(VirtualFile file) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class LSPVirtualFileWrapper implements Disposable {
this.file = file;
this.dataPerServer = new HashMap<>();
this.hover = new LSPTextHoverForFile();
Module project = LSPIJUtils.getProject(file);
Module project = LSPIJUtils.getModule(file);
if (project != null) {
Disposer.register(project, this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.intellij.AppTopics;
import com.intellij.ProjectTopics;
import com.intellij.lang.Language;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationInfo;
Expand All @@ -23,12 +22,10 @@
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileDocumentManagerListener;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.ModuleListener;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
Expand Down Expand Up @@ -112,7 +109,7 @@ public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile f
@Nonnull
public final LanguageServersRegistry.LanguageServerDefinition serverDefinition;
@Nullable
protected final Module initialProject;
protected final Project initialProject;
@Nonnull
protected final Set<Module> allWatchedProjects;
@Nonnull
Expand Down Expand Up @@ -154,7 +151,7 @@ public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile f
private boolean initiallySupportsWorkspaceFolders = false;

/* Backwards compatible constructor */
public LanguageServerWrapper(@Nonnull Module project, @Nonnull LanguageServersRegistry.LanguageServerDefinition serverDefinition) {
public LanguageServerWrapper(@Nonnull Project project, @Nonnull LanguageServersRegistry.LanguageServerDefinition serverDefinition) {
this(project, serverDefinition, null);
}

Expand All @@ -165,7 +162,7 @@ public LanguageServerWrapper(@Nonnull LanguageServersRegistry.LanguageServerDefi
/**
* Unified private constructor to set sensible defaults in all cases
*/
private LanguageServerWrapper(@Nullable Module project, @Nonnull LanguageServersRegistry.LanguageServerDefinition serverDefinition,
private LanguageServerWrapper(@Nullable Project project, @Nonnull LanguageServersRegistry.LanguageServerDefinition serverDefinition,
@Nullable URI initialPath) {
this.initialProject = project;
this.initialPath = initialPath;
Expand Down Expand Up @@ -198,7 +195,7 @@ public Set<Module> getAllWatchedProjects() {
}

public Project getProject() {
return initialProject.getProject();
return initialProject;
}

void stopDispatcher() {
Expand All @@ -212,29 +209,6 @@ void stopDispatcher() {
this.listener.shutdownNow();
}

/**
* @return the workspace folder to be announced to the language server
*/
private List<WorkspaceFolder> getRelevantWorkspaceFolders() {
final var languageClient = this.languageClient;
List<WorkspaceFolder> folders = null;
if (languageClient != null) {
try {
folders = languageClient.workspaceFolders().get(5, TimeUnit.SECONDS);
} catch (final ExecutionException | TimeoutException ex) {
LOGGER.error("Error while getting workspace folders with language server '" + serverDefinition.id + "'", ex);
} catch (final InterruptedException ex) {
LOGGER.error("Error while getting workspace folders with language server '" + serverDefinition.id + "'", ex);
Thread.currentThread().interrupt();
}
}
if (folders == null) {
// FIXME
// folders = LSPIJUtils.getWorkspaceFolders();
}
return folders;
}

public synchronized void stopAndDisable() {
setEnabled(false);
stop();
Expand Down Expand Up @@ -293,7 +267,7 @@ public synchronized void start() throws LanguageServerException {
final URI rootURI = getRootURI();
this.launcherFuture = new CompletableFuture<>();
this.initializeFuture = CompletableFuture.supplyAsync(() -> {
this.lspStreamProvider = serverDefinition.createConnectionProvider(initialProject.getProject());
this.lspStreamProvider = serverDefinition.createConnectionProvider(initialProject);
initParams.setInitializationOptions(this.lspStreamProvider.getInitializationOptions(rootURI));

// Starting process...
Expand All @@ -317,7 +291,7 @@ public synchronized void start() throws LanguageServerException {
lspStreamProvider.ensureIsAlive();
return null;
}).thenRun(() -> {
languageClient = serverDefinition.createLanguageClient(initialProject.getProject());
languageClient = serverDefinition.createLanguageClient(initialProject);
initParams.setProcessId(getParentProcessId());

if (rootURI != null) {
Expand Down Expand Up @@ -365,9 +339,6 @@ public synchronized void start() throws LanguageServerException {
}).thenRun(() -> {
final Map<URI, Document> toReconnect = filesToReconnect;
initializeFuture.thenRunAsync(() -> {
if (this.initialProject != null) {
watchProject(this.initialProject, true);
}
for (Map.Entry<URI, Document> fileToReconnect : toReconnect.entrySet()) {
try {
connect(fileToReconnect.getKey(), fileToReconnect.getValue());
Expand Down Expand Up @@ -447,7 +418,7 @@ private ClientInfo getClientInfo() {

@Nullable
private URI getRootURI() {
final Module project = this.initialProject;
final Project project = this.initialProject;
if (project != null && !project.isDisposed()) {
return LSPIJUtils.toUri(project);
}
Expand Down Expand Up @@ -678,107 +649,19 @@ CompletableFuture<LanguageServer> connect(Document document) throws IOException
return null;
}

protected synchronized void watchProject(Module project, boolean isInitializationRootProject) {
if (this.allWatchedProjects.contains(project)) {
return;
}
if (isInitializationRootProject && !this.allWatchedProjects.isEmpty()) {
return; // there can be only one root project
}
if (!isInitializationRootProject && !supportsWorkspaceFolderCapability()) {
// multi project and WorkspaceFolder notifications not supported by this server
// instance
return;
}
this.allWatchedProjects.add(project);
project.getProject().getMessageBus().connect(project.getProject()).subscribe(ProjectTopics.MODULES, new ModuleListener() {
@Override
public void moduleRemoved(@NotNull Project project, @NotNull Module module) {
unwatchProject(module);
}
//TODO: should we handle module rename
});
/*project.getWorkspace().addResourceChangeListener(event -> {
if (project.equals(event.getResource()) && (event.getDelta().getKind() == IResourceDelta.MOVED_FROM
|| event.getDelta().getKind() == IResourceDelta.REMOVED)) {
unwatchProject(project);
}
}, IResourceChangeEvent.POST_CHANGE);*/
if (supportsWorkspaceFolderCapability()) {
WorkspaceFoldersChangeEvent event = new WorkspaceFoldersChangeEvent();
event.getAdded().add(LSPIJUtils.toWorkspaceFolder(project));
DidChangeWorkspaceFoldersParams params = new DidChangeWorkspaceFoldersParams();
params.setEvent(event);
this.languageServer.getWorkspaceService().didChangeWorkspaceFolders(params);
}
}

private synchronized void unwatchProject(@Nonnull Module project) {
this.allWatchedProjects.remove(project);
// TODO? disconnect resources?
if (supportsWorkspaceFolderCapability()) {
WorkspaceFoldersChangeEvent event = new WorkspaceFoldersChangeEvent();
event.getRemoved().add(LSPIJUtils.toWorkspaceFolder(project));
DidChangeWorkspaceFoldersParams params = new DidChangeWorkspaceFoldersParams();
params.setEvent(event);
this.languageServer.getWorkspaceService().didChangeWorkspaceFolders(params);
}
}

/* private void watchProjects() {
if (!supportsWorkspaceFolderCapability()) {
return;
}
final LanguageServer currentLS = this.languageServer;
/*new WorkspaceJob("Setting watch projects on server " + serverDefinition.label) { //$NON-NLS-1$
@Override
public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
WorkspaceFoldersChangeEvent wsFolderEvent = new WorkspaceFoldersChangeEvent();
wsFolderEvent.getAdded().addAll(getRelevantWorkspaceFolders());
if (currentLS != null && currentLS == LanguageServerWrapper.this.languageServer) {
currentLS.getWorkspaceService()
.didChangeWorkspaceFolders(new DidChangeWorkspaceFoldersParams(wsFolderEvent));
}
ResourcesPlugin.getWorkspace().addResourceChangeListener(workspaceFolderUpdater,
IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_DELETE);
return Status.OK_STATUS;
}
}.schedule();*/
/* }
*/

/**
* Check whether this LS is suitable for provided project. Starts the LS if not
* already started.
*
* @return whether this language server can operate on the given project
* @since 0.5
*/
public boolean canOperate(Module project) {
public boolean canOperate(Project project) {
if (project != null && (project.equals(this.initialProject) || this.allWatchedProjects.contains(project))) {
return true;
}

return serverDefinition.isSingleton || supportsWorkspaceFolderCapability();
}

/**
* @return true, if the server supports multi-root workspaces via workspace
* folders
* @since 0.6
*/
private boolean supportsWorkspaceFolderCapability() {
if (this.initializeFuture != null) {
try {
this.initializeFuture.get(1, TimeUnit.SECONDS);
} catch (ExecutionException | TimeoutException e) {
LOGGER.warn(e.getLocalizedMessage(), e);
} catch (InterruptedException e) {
LOGGER.warn(e.getLocalizedMessage(), e);
Thread.currentThread().interrupt();
}
}
return initiallySupportsWorkspaceFolders || supportsWorkspaceFolders(serverCapabilities);
return serverDefinition.isSingleton;
}

/**
Expand All @@ -791,11 +674,6 @@ private CompletableFuture<LanguageServer> connect(@Nonnull URI absolutePath, Doc
removeStopTimer(false);
final URI thePath = absolutePath; // should be useless

VirtualFile file = FileDocumentManager.getInstance().getFile(document);
if (file != null && file.exists()) {
watchProject(LSPIJUtils.getProject(file), false);
}

if (this.connectedDocuments.containsKey(thePath)) {
return CompletableFuture.completedFuture(languageServer);
}
Expand Down Expand Up @@ -860,22 +738,6 @@ private void disconnect(URI path, boolean stopping) {
}
}

public void disconnectContentType(@Nonnull Language language) {
List<URI> pathsToDisconnect = new ArrayList<>();
for (URI path : connectedDocuments.keySet()) {
VirtualFile foundFiles = LSPIJUtils.findResourceFor(path);
if (foundFiles != null) {
Language fileLanguage = LSPIJUtils.getFileLanguage(foundFiles, initialProject.getProject());
if (fileLanguage.isKindOf(language)) {
pathsToDisconnect.add(path);
}
}
}
for (URI path : pathsToDisconnect) {
disconnect(path);
}
}

/**
* checks if the wrapper is already connected to the document at the given path
*
Expand Down Expand Up @@ -1128,12 +990,12 @@ public boolean canOperate(@Nonnull Document document) {
if (file != null && file.exists() && canOperate(LSPIJUtils.getProject(file))) {
return true;
}
return serverDefinition.isSingleton || supportsWorkspaceFolderCapability();
return serverDefinition.isSingleton;
}

private LanguageServerLifecycleManager getLanguageServerLifecycleManager() {
Project project = initialProject.getProject();
if (project.isDisposed()) {
Project project = initialProject;
if (project == null || project.isDisposed()) {
return NullLanguageServerLifecycleManager.INSTANCE;
}
return LanguageServerLifecycleManager.getInstance(project);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ private Collection<LanguageServerWrapper> getLSWrappers(@Nonnull Document docume
// we already checked a compatible LS with this definition
continue;
}
final Module fileProject = file != null ? LSPIJUtils.getProject(file) : null;
final Project fileProject = file != null ? LSPIJUtils.getProject(file) : null;
if (fileProject != null) {
LanguageServerWrapper wrapper = new LanguageServerWrapper(fileProject, serverDefinition);
startedServers.add(wrapper);
Expand Down Expand Up @@ -234,14 +234,6 @@ private LanguageServerWrapper getLSWrapperForConnection(Document document,
return wrapper;
}

private @Nonnull
List<LanguageServerWrapper> getStartedLSWrappers(
@Nonnull Module project) {
return startedServers.stream().filter(wrapper -> wrapper.canOperate(project))
.collect(Collectors.toList());
// TODO multi-root: also return servers which support multi-root?
}

private List<LanguageServerWrapper> getStartedLSWrappers(
Document document) {
return getStartedLSWrappers(wrapper -> wrapper.canOperate(document));
Expand Down Expand Up @@ -286,7 +278,7 @@ public List<LanguageServer> getActiveLanguageServers(Predicate<ServerCapabilitie
* @return list of Language Servers
*/
@Nonnull
public List<LanguageServer> getLanguageServers(@Nullable Module project,
public List<LanguageServer> getLanguageServers(@Nullable Project project,
Predicate<ServerCapabilities> request, boolean onlyActiveLS) {
List<LanguageServer> serverInfos = new ArrayList<>();
for (LanguageServerWrapper wrapper : startedServers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ protected <R> CompletableFuture<R> runAsBackground(String progressTitle, Functio
protected String getFilePath(String fileUri) {
VirtualFile file = LSPIJUtils.findResourceFor(fileUri);
if (file != null) {
Module module = LSPIJUtils.getProject(file);
Module module = LSPIJUtils.getModule(file);
if (module != null) {
ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
VirtualFile[] contentRoots = rootManager.getContentRoots();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public class SupportedFeatures {
workspaceClientCapabilities.setExecuteCommand(new ExecuteCommandCapabilities(Boolean.TRUE));
// TODO
// workspaceClientCapabilities.setSymbol(new SymbolCapabilities(Boolean.TRUE));
workspaceClientCapabilities.setWorkspaceFolders(Boolean.TRUE);
workspaceClientCapabilities.setWorkspaceFolders(Boolean.FALSE);
WorkspaceEditCapabilities editCapabilities = new WorkspaceEditCapabilities();
editCapabilities.setDocumentChanges(Boolean.TRUE);
// TODO
Expand Down
Loading

0 comments on commit bfd1be5

Please sign in to comment.