From bf54d00a879cf1fcd56e61aab2a1be8a79654a9e Mon Sep 17 00:00:00 2001 From: jpoole Date: Wed, 5 May 2021 14:41:46 +0100 Subject: [PATCH 1/4] Implement file type defnition mapping --- .../lsp4intellij/IntellijLanguageClient.java | 67 ++++++++++++++++--- .../actions/LSPReformatAction.java | 6 +- .../actions/LSPShowReformatDialogAction.java | 4 +- .../contributors/annotator/LSPAnnotator.java | 5 +- .../contributors/rename/LSPRenameHandler.java | 4 +- .../wso2/lsp4intellij/utils/FileUtils.java | 2 +- 6 files changed, 66 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/wso2/lsp4intellij/IntellijLanguageClient.java b/src/main/java/org/wso2/lsp4intellij/IntellijLanguageClient.java index bae0f35c..5377ad79 100644 --- a/src/main/java/org/wso2/lsp4intellij/IntellijLanguageClient.java +++ b/src/main/java/org/wso2/lsp4intellij/IntellijLanguageClient.java @@ -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; @@ -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, LanguageServerWrapper> extToLanguageWrapper = new ConcurrentHashMap<>(); - private static Map> projectToLanguageWrappers = new ConcurrentHashMap<>(); - private static Map, LanguageServerDefinition> extToServerDefinition = new ConcurrentHashMap<>(); - private static Map extToExtManager = new ConcurrentHashMap<>(); + private static final Map> projectToLanguageWrappers = new ConcurrentHashMap<>(); + private static final Map, LanguageServerDefinition> extToServerDefinition = new ConcurrentHashMap<>(); + private static final Map, String>, LanguageServerDefinition> fileTypeToServerDefinition = new ConcurrentHashMap<>(); + private static final Map extToExtManager = new ConcurrentHashMap<>(); private static final Predicate RUNNING = (s) -> s.getStatus() != ServerStatus.STOPPED; @Override @@ -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()); } /** @@ -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)); @@ -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, 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. @@ -162,10 +182,10 @@ public static Set 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); } /** @@ -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 * @@ -238,11 +267,27 @@ public static void editorOpened(Editor editor) { } } + FileType type = file.getFileType(); + if (serverDefinition == null) { + ImmutablePair, String> key = new ImmutablePair<>(type.getClass(), projectUri); + serverDefinition = fileTypeToServerDefinition.get(key); + } + + if (serverDefinition == null) { + ImmutablePair, String> key = new ImmutablePair<>(type.getClass(), ""); + serverDefinition = fileTypeToServerDefinition.get(key); + } + + if(serverDefinition != null && (ext == null || ext.equals(""))) { + 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); diff --git a/src/main/java/org/wso2/lsp4intellij/actions/LSPReformatAction.java b/src/main/java/org/wso2/lsp4intellij/actions/LSPReformatAction.java index 2fe96d20..b9154fb9 100644 --- a/src/main/java/org/wso2/lsp4intellij/actions/LSPReformatAction.java +++ b/src/main/java/org/wso2/lsp4intellij/actions/LSPReformatAction.java @@ -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 @@ -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); diff --git a/src/main/java/org/wso2/lsp4intellij/actions/LSPShowReformatDialogAction.java b/src/main/java/org/wso2/lsp4intellij/actions/LSPShowReformatDialogAction.java index bc32f0c7..ddca9a29 100644 --- a/src/main/java/org/wso2/lsp4intellij/actions/LSPShowReformatDialogAction.java +++ b/src/main/java/org/wso2/lsp4intellij/actions/LSPShowReformatDialogAction.java @@ -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) @@ -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(); diff --git a/src/main/java/org/wso2/lsp4intellij/contributors/annotator/LSPAnnotator.java b/src/main/java/org/wso2/lsp4intellij/contributors/annotator/LSPAnnotator.java index b8b92e40..6b98eff5 100644 --- a/src/main/java/org/wso2/lsp4intellij/contributors/annotator/LSPAnnotator.java +++ b/src/main/java/org/wso2/lsp4intellij/contributors/annotator/LSPAnnotator.java @@ -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; @@ -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)) { return null; } EditorEventManager eventManager = EditorEventManagerBase.forEditor(editor); @@ -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); diff --git a/src/main/java/org/wso2/lsp4intellij/contributors/rename/LSPRenameHandler.java b/src/main/java/org/wso2/lsp4intellij/contributors/rename/LSPRenameHandler.java index 1a0b5eb2..5aa40991 100644 --- a/src/main/java/org/wso2/lsp4intellij/contributors/rename/LSPRenameHandler.java +++ b/src/main/java/org/wso2/lsp4intellij/contributors/rename/LSPRenameHandler.java @@ -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; @@ -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()); } } diff --git a/src/main/java/org/wso2/lsp4intellij/utils/FileUtils.java b/src/main/java/org/wso2/lsp4intellij/utils/FileUtils.java index 5d5f10cc..05eaff7d 100644 --- a/src/main/java/org/wso2/lsp4intellij/utils/FileUtils.java +++ b/src/main/java/org/wso2/lsp4intellij/utils/FileUtils.java @@ -322,7 +322,7 @@ public static boolean isFileSupported(@Nullable VirtualFile file) { return false; } - return IntellijLanguageClient.isExtensionSupported(file); + return IntellijLanguageClient.isExtensionSupported(file) || IntellijLanguageClient.isFileTypeSupported(file); } /** From c3d37087982d6670910808fc53d734c3266f3ae5 Mon Sep 17 00:00:00 2001 From: jpoole Date: Thu, 6 May 2021 15:25:52 +0100 Subject: [PATCH 2/4] Fix up openDocument() --- .../LanguageServerDefinition.java | 15 ++++++++++-- .../editor/DocumentEventManager.java | 24 +++++-------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/wso2/lsp4intellij/client/languageserver/serverdefinition/LanguageServerDefinition.java b/src/main/java/org/wso2/lsp4intellij/client/languageserver/serverdefinition/LanguageServerDefinition.java index b1c7fc40..b3578578 100644 --- a/src/main/java/org/wso2/lsp4intellij/client/languageserver/serverdefinition/LanguageServerDefinition.java +++ b/src/main/java/org/wso2/lsp4intellij/client/languageserver/serverdefinition/LanguageServerDefinition.java @@ -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; @@ -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 extension. + * @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; } } diff --git a/src/main/java/org/wso2/lsp4intellij/editor/DocumentEventManager.java b/src/main/java/org/wso2/lsp4intellij/editor/DocumentEventManager.java index 3f580439..aa5c9728 100644 --- a/src/main/java/org/wso2/lsp4intellij/editor/DocumentEventManager.java +++ b/src/main/java/org/wso2/lsp4intellij/editor/DocumentEventManager.java @@ -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.*; 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.*; public class DocumentEventManager { private final Document document; @@ -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()))); } From cf8bf763426a5089b64075f6da36322ce5c4a4cb Mon Sep 17 00:00:00 2001 From: jpoole Date: Fri, 7 May 2021 15:38:42 +0100 Subject: [PATCH 3/4] Fix ext --- .../org/wso2/lsp4intellij/IntellijLanguageClient.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/wso2/lsp4intellij/IntellijLanguageClient.java b/src/main/java/org/wso2/lsp4intellij/IntellijLanguageClient.java index 5377ad79..527dd8c6 100644 --- a/src/main/java/org/wso2/lsp4intellij/IntellijLanguageClient.java +++ b/src/main/java/org/wso2/lsp4intellij/IntellijLanguageClient.java @@ -271,15 +271,17 @@ public static void editorOpened(Editor editor) { if (serverDefinition == null) { ImmutablePair, String> key = new ImmutablePair<>(type.getClass(), projectUri); serverDefinition = fileTypeToServerDefinition.get(key); + if(serverDefinition != null) { + ext = serverDefinition.ext; + } } if (serverDefinition == null) { ImmutablePair, String> key = new ImmutablePair<>(type.getClass(), ""); serverDefinition = fileTypeToServerDefinition.get(key); - } - - if(serverDefinition != null && (ext == null || ext.equals(""))) { - ext = serverDefinition.ext; + if(serverDefinition != null) { + ext = serverDefinition.ext; + } } if (serverDefinition == null) { From 66e34cbe5cb48dac095fb815fb7b2131e32c53ac Mon Sep 17 00:00:00 2001 From: jpoole Date: Tue, 11 May 2021 10:29:06 +0100 Subject: [PATCH 4/4] Add tcp server definition --- .../serverdefinition/TcpServerDefinition.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/main/java/org/wso2/lsp4intellij/client/languageserver/serverdefinition/TcpServerDefinition.java diff --git a/src/main/java/org/wso2/lsp4intellij/client/languageserver/serverdefinition/TcpServerDefinition.java b/src/main/java/org/wso2/lsp4intellij/client/languageserver/serverdefinition/TcpServerDefinition.java new file mode 100644 index 00000000..556aa965 --- /dev/null +++ b/src/main/java/org/wso2/lsp4intellij/client/languageserver/serverdefinition/TcpServerDefinition.java @@ -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 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 + } + } + } +}