Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement file type defnition mapping #253

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 58 additions & 11 deletions src/main/java/org/wso2/lsp4intellij/IntellijLanguageClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.Disposer;
Expand Down Expand Up @@ -57,11 +58,12 @@

public class IntellijLanguageClient implements ApplicationComponent, Disposable {

private static Logger LOG = Logger.getInstance(IntellijLanguageClient.class);
private static final Logger LOG = Logger.getInstance(IntellijLanguageClient.class);
private static final Map<Pair<String, String>, LanguageServerWrapper> extToLanguageWrapper = new ConcurrentHashMap<>();
private static Map<String, Set<LanguageServerWrapper>> projectToLanguageWrappers = new ConcurrentHashMap<>();
private static Map<Pair<String, String>, LanguageServerDefinition> extToServerDefinition = new ConcurrentHashMap<>();
private static Map<String, LSPExtensionManager> extToExtManager = new ConcurrentHashMap<>();
private static final Map<String, Set<LanguageServerWrapper>> projectToLanguageWrappers = new ConcurrentHashMap<>();
private static final Map<Pair<String, String>, LanguageServerDefinition> extToServerDefinition = new ConcurrentHashMap<>();
private static final Map<Pair<Class<? extends FileType>, String>, LanguageServerDefinition> fileTypeToServerDefinition = new ConcurrentHashMap<>();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit new to this but it might be a safe assumption to assume that all file types are singletons and have this be

private static final Map<Pair<FileType, String>, LanguageServerDefinition> fileTypeToServerDefinition = new ConcurrentHashMap<>();

private static final Map<String, LSPExtensionManager> extToExtManager = new ConcurrentHashMap<>();
private static final Predicate<LanguageServerWrapper> RUNNING = (s) -> s.getStatus() != ServerStatus.STOPPED;

@Override
Expand Down Expand Up @@ -91,13 +93,15 @@ public void initComponent() {
/**
* Use it to initialize the server connection for the given project (useful if no editor is launched)
*/
@SuppressWarnings("unused")
public void initProjectConnections(@NotNull Project project) {
String projectStr = FileUtils.projectToUri(project);
// find serverdefinition keys for this project and try to start a wrapper
extToServerDefinition.entrySet().stream().filter(e -> e.getKey().getRight().equals(projectStr)).forEach(entry -> {
updateLanguageWrapperContainers(project, entry.getKey(), entry.getValue()).start();
});
extToServerDefinition.entrySet().stream().filter(e -> e.getKey().getRight().equals(projectStr)).forEach(entry ->
updateLanguageWrapperContainers(project, entry.getKey(), entry.getValue()).start());

fileTypeToServerDefinition.entrySet().stream().filter(e -> e.getKey().getRight().equals(projectStr)).forEach(entry ->
updateLanguageWrapperContainers(project, new ImmutablePair<>(entry.getValue().ext, projectStr), entry.getValue()).start());
}

/**
Expand All @@ -118,7 +122,6 @@ public static void addServerDefinition(@NotNull LanguageServerDefinition definit
*
* @param definition The server definition
*/
@SuppressWarnings("unused")
public static void addServerDefinition(@NotNull LanguageServerDefinition definition, @Nullable Project project) {
if (project != null) {
processDefinition(definition, FileUtils.projectToUri(project));
Expand All @@ -130,6 +133,23 @@ public static void addServerDefinition(@NotNull LanguageServerDefinition definit
LOG.info("Added definition for " + definition);
}

@SuppressWarnings("unused")
public static void addServerDefinition(@NotNull FileType type, @NotNull LanguageServerDefinition definition) {
addServerDefinition(type, definition, null);
}

public static void addServerDefinition(@NotNull FileType type, @NotNull LanguageServerDefinition definition, @Nullable Project project) {
String projectUri = project == null ? "" : FileUtils.projectToUri(project);
ImmutablePair<Class<? extends FileType>, String> key = ImmutablePair.of(type.getClass(), projectUri);
fileTypeToServerDefinition.put(key, definition);

if(project != null) {
reloadEditors(project);
} else {
reloadAllEditors();
}
}

/**
* Adds a new LSP extension manager, attached to the given file extension.
* Plugin developers should register their custom language server extensions using this API.
Expand Down Expand Up @@ -162,10 +182,10 @@ public static Set<LanguageServerWrapper> getAllServerWrappersFor(String projectU
* @return All registered LSP protocol extension managers.
*/
public static LSPExtensionManager getExtensionManagerFor(String fileExt) {
if (extToExtManager.containsKey(fileExt)) {
return extToExtManager.get(fileExt);
if (fileExt == null || !extToExtManager.containsKey(fileExt)) {
return null;
}
return null;
return extToExtManager.get(fileExt);
}

/**
Expand All @@ -177,6 +197,15 @@ public static boolean isExtensionSupported(VirtualFile virtualFile) {
keyMap.getLeft().equals(virtualFile.getExtension()) || (virtualFile.getName().matches(keyMap.getLeft())));
}

/**
* @param virtualFile The virtual file instance to be validated
* @return True if there is a LanguageServer supporting this extension, false otherwise
*/
public static boolean isFileTypeSupported(VirtualFile virtualFile) {
return fileTypeToServerDefinition.keySet().stream().anyMatch(keyMap ->
keyMap.getLeft().equals(virtualFile.getFileType().getClass()));
}

/**
* Called when an editor is opened. Instantiates a LanguageServerWrapper if necessary, and adds the Editor to the Wrapper
*
Expand Down Expand Up @@ -238,11 +267,29 @@ public static void editorOpened(Editor editor) {
}
}

FileType type = file.getFileType();
if (serverDefinition == null) {
ImmutablePair<Class<? extends FileType>, String> key = new ImmutablePair<>(type.getClass(), projectUri);
serverDefinition = fileTypeToServerDefinition.get(key);
if(serverDefinition != null) {
ext = serverDefinition.ext;
}
}

if (serverDefinition == null) {
ImmutablePair<Class<? extends FileType>, String> key = new ImmutablePair<>(type.getClass(), "");
serverDefinition = fileTypeToServerDefinition.get(key);
if(serverDefinition != null) {
ext = serverDefinition.ext;
}
}

if (serverDefinition == null) {
LOG.warn("Could not find a server definition for " + ext);
return;
}
// Update project mapping for language servers.
// TODO: would make a lot more sense if this was a mapping of language wrapper to defnitions (and projects)
LanguageServerWrapper wrapper = updateLanguageWrapperContainers(project, new ImmutablePair<>(ext, projectUri), serverDefinition);

LOG.info("Adding file " + fileName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import org.wso2.lsp4intellij.IntellijLanguageClient;
import org.wso2.lsp4intellij.requests.ReformatHandler;
import org.wso2.lsp4intellij.utils.FileUtils;

/**
* Action overriding the default reformat action
Expand All @@ -43,8 +43,8 @@ public void actionPerformed(AnActionEvent e) {
return;
}
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (LanguageFormatting.INSTANCE.allForLanguage(file.getLanguage()).isEmpty() && IntellijLanguageClient
.isExtensionSupported(file.getVirtualFile())) {
if (LanguageFormatting.INSTANCE.allForLanguage(file.getLanguage()).isEmpty() && FileUtils
.isFileSupported(file.getVirtualFile())) {
// if editor hasSelection, only reformat selection, not reformat the whole file
if (editor.getSelectionModel().hasSelection()) {
ReformatHandler.reformatSelection(editor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import org.wso2.lsp4intellij.IntellijLanguageClient;
import org.wso2.lsp4intellij.editor.EditorEventManager;
import org.wso2.lsp4intellij.editor.EditorEventManagerBase;
import org.wso2.lsp4intellij.utils.FileUtils;

/**
* Class overriding the default action handling the Reformat dialog event (CTRL+ALT+SHIFT+L by default)
Expand All @@ -51,7 +51,7 @@ public void actionPerformed(AnActionEvent e) {
PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
VirtualFile virFile = FileDocumentManager.getInstance().getFile(editor.getDocument());
boolean alreadySupported = !LanguageFormatting.INSTANCE.allForLanguage(psiFile.getLanguage()).isEmpty();
if (!alreadySupported && IntellijLanguageClient.isExtensionSupported(virFile)) {
if (!alreadySupported && FileUtils.isFileSupported(virFile)) {
boolean hasSelection = editor.getSelectionModel().hasSelection();
LayoutCodeDialog dialog = new LayoutCodeDialog(project, psiFile, hasSelection, HELP_ID);
dialog.show();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
package org.wso2.lsp4intellij.client.languageserver.serverdefinition;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.vfs.VirtualFile;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.wso2.lsp4intellij.client.connection.StreamConnectionProvider;

import java.io.IOException;
Expand Down Expand Up @@ -100,8 +102,17 @@ public ServerListener getServerListener() {
/**
* Return language id for the given extension. if there is no langauge ids registered then the
* return value will be the value of <code>extension</code>.
* @param file is the file to get the language id for
*/
public String languageIdFor(String extension) {
return languageIds.getOrDefault(extension, extension);
public String languageIdFor(@NotNull VirtualFile file) {
// TODO: probably makes sense to map file types to lang ids
String id = languageIds.get(file.getExtension());
if (id == null) {
id = file.getExtension();
}
if(id == null) {
id = ext;
}
return id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package org.wso2.lsp4intellij.client.languageserver.serverdefinition;

import org.wso2.lsp4intellij.client.connection.StreamConnectionProvider;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Collections;
import java.util.Map;

/**
* TcpServerDefinition is a {@link LanguageServerDefinition} that connects to an LSP implementation over a TCP socket.
*/
@SuppressWarnings("unused")
public class TcpServerDefinition extends LanguageServerDefinition {
private final TcpStreamConnectionProvider connectionProvider;

/**
* Creates a new TcpServerDefinition with no language IDs
*
* @param ext the extension this lsp definition is for
* @param host the host of the language server
* @param port the port of the language server
*/
public TcpServerDefinition(String ext, String host, int port) {
this(ext, host, port, Collections.emptyMap());
}

/**
* Create a new TcpServerDefinition
*
* @param ext the extension this lsp definition is for
* @param host the host of the language server
* @param port the port of the language server
* @param langIds The language server ids mapping to extension(s).
*/
public TcpServerDefinition(String ext, String host, int port, Map<String, String> langIds) {
this.languageIds = langIds;
this.ext = ext;

// We can do this upfront as we don't care about the working directory
connectionProvider = new TcpStreamConnectionProvider(host, port);
}

@Override
public StreamConnectionProvider createConnectionProvider(String workingDir) {
return connectionProvider;
}

private static class TcpStreamConnectionProvider implements StreamConnectionProvider {
private final String host;
private final int port;

private InputStream is;
private OutputStream os;

private Socket socket;

private TcpStreamConnectionProvider(String host, int port) {
this.host = host;
this.port = port;
}


@Override
public void start() throws IOException {
socket = new Socket(host, port);

// Do this here as these methods can throw IO exception, which we don't want to handle that in the getters
is = socket.getInputStream();
os = socket.getOutputStream();
}

@Override
public InputStream getInputStream() {
return is;
}

@Override
public OutputStream getOutputStream() {
return os;
}

@Override
public void stop() {
try {
socket.close();
} catch (IOException e) {
// ignore... nothing we (anybody?) can do about this
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import org.eclipse.lsp4j.DiagnosticTag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.wso2.lsp4intellij.IntellijLanguageClient;
import org.wso2.lsp4intellij.client.languageserver.ServerStatus;
import org.wso2.lsp4intellij.client.languageserver.wrapper.LanguageServerWrapper;
import org.wso2.lsp4intellij.editor.EditorEventManager;
Expand Down Expand Up @@ -67,7 +66,7 @@ public Object collectInformation(@NotNull PsiFile file, @NotNull Editor editor,
VirtualFile virtualFile = file.getVirtualFile();

// If the file is not supported, we skips the annotation by returning null.
if (!FileUtils.isFileSupported(virtualFile) || !IntellijLanguageClient.isExtensionSupported(virtualFile)) {
if (!FileUtils.isFileSupported(virtualFile) || !FileUtils.isFileSupported(virtualFile)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant checks

return null;
}
EditorEventManager eventManager = EditorEventManagerBase.forEditor(editor);
Expand Down Expand Up @@ -100,7 +99,7 @@ public void apply(@NotNull PsiFile file, Object annotationResult, @NotNull Annot
}

VirtualFile virtualFile = file.getVirtualFile();
if (FileUtils.isFileSupported(virtualFile) && IntellijLanguageClient.isExtensionSupported(virtualFile)) {
if (FileUtils.isFileSupported(virtualFile) && FileUtils.isFileSupported(virtualFile)) {
String uri = FileUtils.VFSToURI(virtualFile);
// TODO annotations are applied to a file / document not to an editor. so store them by file and not by editor..
EditorEventManager eventManager = EditorEventManagerBase.forUri(uri);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@
import com.intellij.refactoring.rename.inplace.MemberInplaceRenameHandler;
import com.intellij.refactoring.rename.inplace.MemberInplaceRenamer;
import org.jetbrains.annotations.NotNull;
import org.wso2.lsp4intellij.IntellijLanguageClient;
import org.wso2.lsp4intellij.contributors.psi.LSPPsiElement;
import org.wso2.lsp4intellij.editor.EditorEventManager;
import org.wso2.lsp4intellij.editor.EditorEventManagerBase;
import org.wso2.lsp4intellij.utils.FileUtils;

import java.util.List;

Expand Down Expand Up @@ -127,7 +127,7 @@ private boolean isAvailable(PsiElement psiElement, Editor editor, PsiFile psiFil
if (psiElement instanceof PsiFile || psiElement instanceof LSPPsiElement) {
return true;
} else {
return IntellijLanguageClient.isExtensionSupported(psiFile.getVirtualFile());
return FileUtils.isFileSupported(psiFile.getVirtualFile());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,14 @@
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.text.StringUtil;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.wso2.lsp4intellij.client.languageserver.requestmanager.RequestManager;
import com.intellij.openapi.vfs.VirtualFile;
import org.eclipse.lsp4j.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets avoid wildcard imports.

import org.wso2.lsp4intellij.client.languageserver.wrapper.LanguageServerWrapper;
import org.wso2.lsp4intellij.utils.ApplicationUtils;
import org.wso2.lsp4intellij.utils.DocumentUtils;
import org.wso2.lsp4intellij.utils.FileUtils;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets avoid wildcard imports.


public class DocumentEventManager {
private final Document document;
Expand Down Expand Up @@ -152,9 +139,10 @@ public void documentOpened() {
LOG.warn("trying to send open notification for document which was already opened!");
} else {
openDocuments.add(document);
final String extension = FileDocumentManager.getInstance().getFile(document).getExtension();
VirtualFile file = FileDocumentManager.getInstance().getFile(document);
String languageId = wrapper.serverDefinition.languageIdFor(file);
wrapper.getRequestManager().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(identifier.getUri(),
wrapper.serverDefinition.languageIdFor(extension),
languageId,
++version,
document.getText())));
}
Expand Down
Loading