Skip to content

Commit

Permalink
feat: Check if project is a MicroProfile, Qute, etc project to map file
Browse files Browse the repository at this point in the history
with a language server

Fixes #1185

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Oct 2, 2023
1 parent 2be23ca commit 69bc64e
Show file tree
Hide file tree
Showing 16 changed files with 434 additions and 110 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.redhat.devtools.intellij.lsp4ij;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.NonBlockingReadAction;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.concurrency.AppExecutorUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.concurrency.CancellablePromise;

import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;

public abstract class AbstractDocumentMatcher implements DocumentMatcher {

private class CompletableFutureWrapper<R> extends CompletableFuture<R> {

public CompletableFutureWrapper(CancellablePromise<R> promise) {
// On error
promise.onError(ex -> {
if (ex instanceof ProcessCanceledException || ex instanceof CancellationException) {
// Case 2: cancel the completable future
this.cancel(true);
} else {
// Other case..., mark the completable future as error
this.completeExceptionally(ex);
}
});
// On sucess
promise.onSuccess(value -> {
this.complete(value);
});
}
}

@Override
public @NotNull CompletableFuture<Boolean> matchAsync(@NotNull VirtualFile file, @NotNull Project project) {
var action = ReadAction.nonBlocking(() -> {
return match(file, project);
});
if (DumbService.getInstance(project).isDumb()) {
action = action.inSmartMode(project);
}
action = action.coalesceBy(AbstractDocumentMatcher.class, file.getUrl());
var promise = action
.submit(AppExecutorUtil.getAppExecutorService());
return new CompletableFutureWrapper<Boolean>(promise);
}

@Override
public boolean shouldBeMatchedAsynchronously(@NotNull Project project) {
if (!ApplicationManager.getApplication().isReadAccessAllowed()) {
return true;
}
return DumbService.getInstance(project).isDumb();
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,37 @@
package com.redhat.devtools.intellij.lsp4ij;

import com.intellij.lang.Language;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;

import javax.annotation.Nonnull;
import java.util.AbstractMap;
import java.util.concurrent.CompletableFuture;

public class ContentTypeToLanguageServerDefinition extends AbstractMap.SimpleEntry<Language, LanguageServersRegistry.LanguageServerDefinition> {
public ContentTypeToLanguageServerDefinition(@Nonnull Language language,
@Nonnull LanguageServersRegistry.LanguageServerDefinition provider) {

private final DocumentMatcher documentMatcher;

public ContentTypeToLanguageServerDefinition(@NotNull Language language,
@NotNull LanguageServersRegistry.LanguageServerDefinition provider,
@NotNull DocumentMatcher documentMatcher) {
super(language, provider);
this.documentMatcher = documentMatcher;
}

public boolean match(VirtualFile file, Project project) {
return documentMatcher.match(file, project);
}

public boolean shouldBeMatchedAsynchronously(Project project) {
return documentMatcher.shouldBeMatchedAsynchronously(project);
}

public boolean isEnabled() {
return true;
return getValue().isEnabled();
}

public @NotNull <R> CompletableFuture<Boolean> matchAsync(VirtualFile file, Project project) {
return documentMatcher.matchAsync(file, project);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.redhat.devtools.intellij.lsp4ij;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.concurrency.CancellablePromise;

import java.util.concurrent.CompletableFuture;

public interface DocumentMatcher {

boolean match(@NotNull VirtualFile file, @NotNull Project project);

default @NotNull CompletableFuture<Boolean> matchAsync(@NotNull VirtualFile file, @NotNull Project project) {
return CompletableFuture.completedFuture(match(file,project));
}

default boolean shouldBeMatchedAsynchronously(@NotNull Project project) {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

import com.intellij.openapi.extensions.AbstractExtensionPointBean;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.serviceContainer.BaseKeyedLazyInstance;
import com.intellij.util.xmlb.annotations.Attribute;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class LanguageMappingExtensionPointBean extends BaseKeyedLazyInstance<DocumentMatcher> {

private static final DocumentMatcher DEFAULT_DOCUMENT_MATCHER = (file,project) -> true;

public class LanguageMappingExtensionPointBean extends AbstractExtensionPointBean {
public static final ExtensionPointName<LanguageMappingExtensionPointBean> EP_NAME = ExtensionPointName.create("com.redhat.devtools.intellij.quarkus.languageMapping");

@Attribute("id")
Expand All @@ -15,4 +21,21 @@ public class LanguageMappingExtensionPointBean extends AbstractExtensionPointBea

@Attribute("serverId")
public String serverId;

@Attribute("documentMatcher")
public String documentMatcher;

public @NotNull DocumentMatcher getDocumentMatcher() {
try {
return super.getInstance();
}
catch(Exception e) {
return DEFAULT_DOCUMENT_MATCHER;
}
}

@Override
protected @Nullable String getImplementationClassName() {
return documentMatcher;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
import org.eclipse.lsp4j.services.LanguageServer;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -194,7 +195,7 @@ private void initialize() {
for (LanguageMappingExtensionPointBean extension : LanguageMappingExtensionPointBean.EP_NAME.getExtensions()) {
Language language = Language.findLanguageByID(extension.language);
if (language != null) {
languageMappings.add(new LanguageMapping(language, extension.id, extension.serverId));
languageMappings.add(new LanguageMapping(language, extension.id, extension.serverId, extension.getDocumentMatcher()));
}
}

Expand All @@ -205,7 +206,7 @@ private void initialize() {
for (LanguageMapping mapping : languageMappings) {
LanguageServerDefinition lsDefinition = servers.get(mapping.languageId);
if (lsDefinition != null) {
registerAssociation(mapping.language, lsDefinition, mapping.languageId);
registerAssociation(lsDefinition, mapping);
} else {
LOGGER.warn("server '" + mapping.id + "' not available"); //$NON-NLS-1$ //$NON-NLS-2$
}
Expand All @@ -223,20 +224,21 @@ public Icon getServerIcon(String serverId) {
* @return the {@link LanguageServerDefinition}s <strong>directly</strong> associated to the given content-type.
* This does <strong>not</strong> include the one that match transitively as per content-type hierarchy
*/
List<ContentTypeToLanguageServerDefinition> findProviderFor(final @NonNull Language contentType) {
List<ContentTypeToLanguageServerDefinition> findProviderFor(final @NotNull Language contentType) {
return connections.stream()
.filter(entry -> contentType.isKindOf(entry.getKey()))
.collect(Collectors.toList());
}


public void registerAssociation(@Nonnull Language language,
@Nonnull LanguageServerDefinition serverDefinition, @Nullable String languageId) {
public void registerAssociation(@NotNull LanguageServerDefinition serverDefinition, @Nullable LanguageMapping mapping) {
@NotNull Language language = mapping.language;
@Nullable String languageId = mapping.languageId;
if (languageId != null) {
serverDefinition.registerAssociation(language, languageId);
}

connections.add(new ContentTypeToLanguageServerDefinition(language, serverDefinition));
connections.add(new ContentTypeToLanguageServerDefinition(language, serverDefinition, mapping.getDocumentMatcher()));
}

public List<ContentTypeToLanguageServerDefinition> getContentTypeToLSPExtensions() {
Expand All @@ -258,39 +260,46 @@ LanguageServerDefinition getDefinition(@NonNull String languageServerId) {
*/
private static class LanguageMapping {

@Nonnull
@NotNull
public final String id;
@Nonnull
@NotNull
public final Language language;
@Nullable
public final String languageId;

public LanguageMapping(@Nonnull Language language, @Nonnull String id, @Nullable String languageId) {
private final DocumentMatcher documentMatcher;

public LanguageMapping(@NotNull Language language, @Nullable String id, @Nullable String languageId, @NotNull DocumentMatcher documentMatcher) {
this.language = language;
this.id = id;
this.languageId = languageId;
this.documentMatcher = documentMatcher;
}

public DocumentMatcher getDocumentMatcher() {
return documentMatcher;
}
}

/**
* @param file
* @param serverDefinition
* @return whether the given serverDefinition is suitable for the file
*/
public boolean matches(@Nonnull VirtualFile file, @NonNull LanguageServerDefinition serverDefinition,
Project project) {
return getAvailableLSFor(LSPIJUtils.getFileLanguage(file, project)).contains(serverDefinition);
}

/**
* @param document
* @param serverDefinition
* @return whether the given serverDefinition is suitable for the file
*/
public boolean matches(@Nonnull Document document, @Nonnull LanguageServerDefinition serverDefinition,
Project project) {
return getAvailableLSFor(LSPIJUtils.getDocumentLanguage(document, project)).contains(serverDefinition);
public boolean matches(VirtualFile file,
Project project, @Nonnull LanguageServerDefinition serverDefinition) {
Language language = LSPIJUtils.getFileLanguage(file, project);
if (language == null) {
return false;
}
for (ContentTypeToLanguageServerDefinition mapping : this.connections) {
if (language.isKindOf(mapping.getKey()) && serverDefinition.equals(mapping.getValue())) {
if (mapping.match(file, project)) {
return true;
}
}
}
return false;
}


Expand Down
Loading

0 comments on commit 69bc64e

Please sign in to comment.