diff --git a/src/main/java/com/redhat/devtools/lsp4ij/DocumentContentSynchronizer.java b/src/main/java/com/redhat/devtools/lsp4ij/DocumentContentSynchronizer.java
index 89a110ac0..e235d9f78 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/DocumentContentSynchronizer.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/DocumentContentSynchronizer.java
@@ -22,7 +22,6 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -46,12 +45,12 @@ public class DocumentContentSynchronizer implements DocumentListener {
@NotNull final CompletableFuture didOpenFuture;
public DocumentContentSynchronizer(@NotNull LanguageServerWrapper languageServerWrapper,
- @NotNull URI fileUri,
+ @NotNull String fileUri,
@NotNull VirtualFile file,
@NotNull Document document,
@Nullable TextDocumentSyncKind syncKind) {
this.languageServerWrapper = languageServerWrapper;
- this.fileUri = fileUri.toASCIIString();
+ this.fileUri = fileUri;
this.syncKind = syncKind != null ? syncKind : TextDocumentSyncKind.Full;
this.document = document;
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LSPFileListener.java b/src/main/java/com/redhat/devtools/lsp4ij/LSPFileListener.java
index 7eb7a9e09..d7a8ea8c1 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/LSPFileListener.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/LSPFileListener.java
@@ -20,7 +20,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
@@ -55,14 +54,11 @@ public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile f
return;
}
// Manage textDocument/didClose
- URI uri = LSPIJUtils.toUri(file);
- if (uri != null) {
- try {
- // Disconnect the given file from the current language servers
- languageServerWrapper.disconnect(uri, !languageServerWrapper.isDisposed());
- } catch (Exception e) {
- LOGGER.warn("Error while disconnecting the file '" + uri + "' from all language servers", e);
- }
+ try {
+ // Disconnect the given file from the current language servers
+ languageServerWrapper.disconnect(file, !languageServerWrapper.isDisposed());
+ } catch (Exception e) {
+ LOGGER.warn("Error while disconnecting the file '" + file.getUrl() + "' from language server '" + languageServerWrapper.getServerDefinition().getDisplayName() + "'.", e);
}
}
@@ -96,7 +92,7 @@ private void moveFile(URI oldFileUri, VirtualFile newFile) {
if (isMatchFilePatterns(oldFileUri, WatchKind.Delete)) {
changes.add(fe(oldFileUri, FileChangeType.Deleted));
}
- URI newFileUri = LSPIJUtils.toUri(newFile);
+ URI newFileUri = languageServerWrapper.toUri(newFile);
if (isMatchFilePatterns(newFileUri, WatchKind.Create)) {
changes.add(fe(newFileUri, FileChangeType.Created));
}
@@ -109,7 +105,7 @@ private void moveFile(URI oldFileUri, VirtualFile newFile) {
@Override
public void contentsChanged(@NotNull VirtualFileEvent event) {
VirtualFile file = event.getFile();
- URI uri = LSPIJUtils.toUri(file);
+ URI uri = languageServerWrapper.toUri(file);
if (uri != null) {
LSPVirtualFileData documentListener = languageServerWrapper.connectedDocuments.get(uri);
if (documentListener != null) {
@@ -126,7 +122,7 @@ public void contentsChanged(@NotNull VirtualFileEvent event) {
@Override
public void fileCreated(@NotNull VirtualFileEvent event) {
VirtualFile file = event.getFile();
- URI uri = LSPIJUtils.toUri(file);
+ URI uri = languageServerWrapper.toUri(file);
if (isMatchFilePatterns(uri, WatchKind.Create)) {
// 2. Send a workspace/didChangeWatchedFiles with 'Created' file change type.
didChangeWatchedFiles(fe(uri, FileChangeType.Created));
@@ -136,7 +132,7 @@ public void fileCreated(@NotNull VirtualFileEvent event) {
@Override
public void fileDeleted(@NotNull VirtualFileEvent event) {
VirtualFile file = event.getFile();
- URI uri = LSPIJUtils.toUri(file);
+ URI uri = languageServerWrapper.toUri(file);
if (isMatchFilePatterns(uri, WatchKind.Delete)) {
// Send a workspace/didChangeWatchedFiles with 'Deleted' file change type.
didChangeWatchedFiles(fe(uri, FileChangeType.Deleted));
@@ -166,20 +162,21 @@ private FileEvent fe(URI uri, FileChangeType type) {
* to a different directory.
*
* @param virtualParentFile directory of the file after renaming
- * @param oldFileName file name before renaming
- * @param virtualNewFile virtual file after renaming
+ * @param oldFileName file name before renaming
+ * @param virtualNewFile virtual file after renaming
* @return URI of the file before renaming
*/
- private @NotNull URI didRename(VirtualFile virtualParentFile, String oldFileName, VirtualFile virtualNewFile) {
- File parentFile = VfsUtilCore.virtualToIoFile(virtualParentFile);
- URI oldUri = LSPIJUtils.toUri(new File(parentFile, oldFileName));
+ private @NotNull URI didRename(@NotNull VirtualFile virtualParentFile,
+ @NotNull String oldFileName,
+ @NotNull VirtualFile virtualNewFile) {
+ URI parentFileUri = languageServerWrapper.toUri(virtualParentFile);
+ URI oldUri = parentFileUri.resolve(oldFileName);
boolean docIsConnected = languageServerWrapper.isConnectedTo(oldUri);
if (docIsConnected) {
// 1. Send a textDocument/didClose for the old file name
languageServerWrapper.disconnect(oldUri, false);
// 2. Send a textDocument/didOpen for the new file name
- File newFile = VfsUtilCore.virtualToIoFile(virtualNewFile);
- URI newUri = LSPIJUtils.toUri(newFile);
+ URI newUri = languageServerWrapper.toUri(virtualNewFile);
if (!languageServerWrapper.isConnectedTo(newUri)) {
languageServerWrapper.connect(virtualNewFile, null);
}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LSPIJUtils.java b/src/main/java/com/redhat/devtools/lsp4ij/LSPIJUtils.java
index bb992d064..2a25552d8 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/LSPIJUtils.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/LSPIJUtils.java
@@ -29,13 +29,16 @@
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.*;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
+import com.redhat.devtools.lsp4ij.client.features.FileUriSupport;
import com.redhat.devtools.lsp4ij.internal.SimpleLanguageUtils;
import com.redhat.devtools.lsp4ij.internal.StringUtils;
+import com.redhat.devtools.lsp4ij.usages.LocationData;
import org.apache.commons.io.FileUtils;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
@@ -56,8 +59,11 @@
* Utilities class for LSP.
*/
public class LSPIJUtils {
+
private static final Logger LOGGER = LoggerFactory.getLogger(LSPIJUtils.class);
+ private static final Key DEFAULT_LSP_FILE_URI_KEY = Key.create("lsp.file.uri");
+
private static final String JAR_PROTOCOL = "jar";
private static final String JRT_PROTOCOL = "jrt";
@@ -88,6 +94,9 @@ public class LSPIJUtils {
/**
* Open the LSP location in an editor.
+ *
+ * Not used but declared to support backward compatibility.
+ *
*
* @param location the LSP location.
* @param project the project.
@@ -95,14 +104,31 @@ public class LSPIJUtils {
*/
public static boolean openInEditor(@Nullable Location location,
@NotNull Project project) {
+ return openInEditor(location, null, project);
+ }
+
+ /**
+ * Open the LSP location in an editor.
+ *
+ * @param location the LSP location.
+ * @param fileUriSupport the file Uri support.
+ * @param project the project.
+ * @return true if the file was opened and false otherwise.
+ */
+ public static boolean openInEditor(@Nullable Location location,
+ @Nullable FileUriSupport fileUriSupport,
+ @NotNull Project project) {
if (location == null) {
return false;
}
- return openInEditor(location.getUri(), location.getRange() != null ? location.getRange().getStart() : null, project);
+ return openInEditor(location.getUri(), location.getRange() != null ? location.getRange().getStart() : null, fileUriSupport, project);
}
/**
* Open the given fileUri with the given position in an editor.
+ *
+ * Not used but declared to support backward compatibility.
+ *
*
* @param fileUri the file Uri.
* @param position the position.
@@ -112,12 +138,31 @@ public static boolean openInEditor(@Nullable Location location,
public static boolean openInEditor(@NotNull String fileUri,
@Nullable Position position,
@NotNull Project project) {
- return openInEditor(fileUri, position, true, project);
+ return openInEditor(fileUri, position, null, project);
}
/**
* Open the given fileUri with the given position in an editor.
*
+ * @param fileUri the file Uri.
+ * @param position the position.
+ * @param fileUriSupport the file Uri support.
+ * @param project the project.
+ * @return true if the file was opened and false otherwise.
+ */
+ public static boolean openInEditor(@NotNull String fileUri,
+ @Nullable Position position,
+ @Nullable FileUriSupport fileUriSupport,
+ @NotNull Project project) {
+ return openInEditor(fileUri, position, true, fileUriSupport, project);
+ }
+
+ /**
+ * Open the given fileUri with the given position in an editor.
+ *
+ * Not used but declared to support backward compatibility.
+ *
+ *
* @param fileUri the file Uri.
* @param position the position.
* @param focusEditor true if editor will take the focus and false otherwise.
@@ -128,7 +173,55 @@ public static boolean openInEditor(@NotNull String fileUri,
@Nullable Position position,
boolean focusEditor,
@NotNull Project project) {
- return openInEditor(fileUri, position, focusEditor, false, project);
+ return openInEditor(fileUri, position, focusEditor, null, project);
+ }
+
+ /**
+ * Open the given fileUri with the given position in an editor.
+ *
+ * @param fileUri the file Uri.
+ * @param position the position.
+ * @param focusEditor true if editor will take the focus and false otherwise.
+ * @param fileUriSupport the file Uri support.
+ * @param project the project.
+ * @return true if the file was opened and false otherwise.
+ */
+ public static boolean openInEditor(@NotNull String fileUri,
+ @Nullable Position position,
+ boolean focusEditor,
+ @Nullable FileUriSupport fileUriSupport,
+ @NotNull Project project) {
+ return openInEditor(fileUri, position, focusEditor, false, fileUriSupport, project);
+ }
+
+ /**
+ * Open the given fileUrl in an editor.
+ *
+ * Not used but declared to support backward compatibility.
+ *
+ *
+ *
+ * the following syntax is supported for fileUrl:
+ *
+ *
file:///C:/Users/username/foo.txt
+ *
C:/Users/username/foo.txt
+ *
file:///C:/Users/username/foo.txt#L1:5
+ *
+ *
+ *
+ * @param fileUri the file Uri to open.
+ * @param position the position.
+ * @param focusEditor true if editor will take the focus and false otherwise.
+ * @param createFileIfNeeded true if file must be created if doesn't exist and false otherwise.
+ * @param project the project.
+ * @return true if file Url can be opened and false otherwise.
+ */
+ public static boolean openInEditor(@NotNull String fileUri,
+ @Nullable Position position,
+ boolean focusEditor,
+ boolean createFileIfNeeded,
+ @NotNull Project project) {
+ return openInEditor(fileUri, position, focusEditor, createFileIfNeeded, null, project);
}
/**
@@ -147,6 +240,7 @@ public static boolean openInEditor(@NotNull String fileUri,
* @param position the position.
* @param focusEditor true if editor will take the focus and false otherwise.
* @param createFileIfNeeded true if file must be created if doesn't exist and false otherwise.
+ * @param fileUriSupport the file Uri support.
* @param project the project.
* @return true if file Url can be opened and false otherwise.
*/
@@ -154,6 +248,7 @@ public static boolean openInEditor(@NotNull String fileUri,
@Nullable Position position,
boolean focusEditor,
boolean createFileIfNeeded,
+ @Nullable FileUriSupport fileUriSupport,
@NotNull Project project) {
if (position == null) {
// Try to get position information from the fileUri
@@ -173,7 +268,7 @@ public static boolean openInEditor(@NotNull String fileUri,
fileUri = fileUri.substring(0, hashIndex);
}
}
- VirtualFile file = findResourceFor(fileUri);
+ VirtualFile file = FileUriSupport.findFileByUri(fileUri, fileUriSupport);
if (file == null && createFileIfNeeded) {
// The file doesn't exist,
// open a dialog to confirm the creation of the file.
@@ -348,7 +443,13 @@ private static Language doGetFileLanguage(@NotNull VirtualFile file, @NotNull Pr
}
public static @NotNull URI toUri(@NotNull VirtualFile file) {
- return toUri(VfsUtilCore.virtualToIoFile(file));
+ URI fileUri = file.getUserData(DEFAULT_LSP_FILE_URI_KEY);
+ if (fileUri == null) {
+ // Cache the file Uri to avoid recomputing again
+ fileUri = toUri(VfsUtilCore.virtualToIoFile(file));
+ file.putUserData(DEFAULT_LSP_FILE_URI_KEY, fileUri);
+ }
+ return fileUri;
}
public static @Nullable String toUriAsString(@NotNull PsiFile psFile) {
@@ -969,11 +1070,7 @@ private static void applyRenameFile(RenameFile renameFile) {
}
public static TextDocumentIdentifier toTextDocumentIdentifier(VirtualFile file) {
- return toTextDocumentIdentifier(toUri(file));
- }
-
- public static TextDocumentIdentifier toTextDocumentIdentifier(final URI uri) {
- return new TextDocumentIdentifier(uri.toASCIIString());
+ return new TextDocumentIdentifier(toUriAsString(file));
}
/**
@@ -1222,7 +1319,8 @@ public static String getProjectUri(Project project) {
* @param locations the locations/links
* @return the most specific location information that was found based on the provided locations/links
*/
- public static @NotNull List getLocations(@Nullable Either, List extends LocationLink>> locations) {
+ public static @NotNull List getLocations(@Nullable Either, List extends LocationLink>> locations,
+ @Nullable LanguageServerItem languageServer) {
if (locations == null) {
// textDocument/definition may return null
return Collections.emptyList();
@@ -1230,13 +1328,13 @@ public static String getProjectUri(Project project) {
if (locations.isLeft()) {
return locations.getLeft()
.stream()
- .map(l -> new Location(l.getUri(), l.getRange()))
+ .map(l -> new LocationData(new Location(l.getUri(), l.getRange()), languageServer))
.toList();
}
return locations.getRight()
.stream()
- .map(l -> new Location(l.getTargetUri(), l.getTargetSelectionRange() != null ? l.getTargetSelectionRange() : l.getTargetRange()))
+ .map(l -> new LocationData(new Location(l.getTargetUri(), l.getTargetSelectionRange() != null ? l.getTargetSelectionRange() : l.getTargetRange()), languageServer))
.toList();
}
}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerWrapper.java b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerWrapper.java
index 8a073afa3..1cd9b935d 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerWrapper.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/LanguageServerWrapper.java
@@ -24,6 +24,7 @@
import com.intellij.psi.PsiFile;
import com.intellij.util.messages.MessageBusConnection;
import com.redhat.devtools.lsp4ij.client.LanguageClientImpl;
+import com.redhat.devtools.lsp4ij.client.features.FileUriSupport;
import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
import com.redhat.devtools.lsp4ij.features.files.operations.FileOperationsManager;
import com.redhat.devtools.lsp4ij.internal.ClientCapabilitiesFactory;
@@ -71,7 +72,7 @@ public class LanguageServerWrapper implements Disposable {
@NotNull
protected final Project initialProject;
@NotNull
- protected Map connectedDocuments;
+ protected Map connectedDocuments;
@Nullable
protected final URI initialPath;
protected final InitializeParams initParams = new InitializeParams();
@@ -259,7 +260,7 @@ public synchronized void start() throws LanguageServerException {
if (isActive()) {
return;
} else {
- for (Map.Entry entry : this.connectedDocuments.entrySet()) {
+ for (Map.Entry entry : this.connectedDocuments.entrySet()) {
filesToReconnect.add(entry.getValue().getFile());
}
stop();
@@ -348,7 +349,7 @@ public synchronized void start() throws LanguageServerException {
}).thenRun(() -> this.languageServer.initialized(new InitializedParams())).thenRun(() -> {
initializeFuture.thenRunAsync(() -> {
for (VirtualFile fileToReconnect : filesToReconnect) {
- connect(LSPIJUtils.toUri(fileToReconnect), fileToReconnect, null);
+ connect(fileToReconnect, null);
}
});
@@ -618,7 +619,7 @@ public boolean canOperate(Project project) {
}
/**
- * Connect the given file Uri to the language server and returns the language server instance when:
+ * Connect the given file to the language server and returns the language server instance when:
*
*
*
language server is initialized
@@ -630,43 +631,21 @@ public boolean canOperate(Project project) {
* the method return a CompletableFuture which returns null.
*
*
- * @param file the file to connect to the language server
- * @param document the document of the file and null otherwise. In the null case, the document will be retrieved from the file
- * by using a blocking read action.
- * @return the completable future with the language server instance or null.
- */
- CompletableFuture<@Nullable LanguageServer> connect(@NotNull VirtualFile file,
- @Nullable Document document) {
- if (file != null && file.exists()) {
- return connect(LSPIJUtils.toUri(file), file, document);
- }
- return CompletableFuture.completedFuture(null);
- }
-
- /**
- * Connect the given file Uri to the language server and returns the language server instance when:
- *
- *
- *
language server is initialized
- *
didOpen for the given file Uri happens
- *
- *
- *
- * In other case (ex : language server initialize future is null, file cannot be retrieved by the given Uri),
- * the method return a CompletableFuture which returns null.
- *
- *
- * @param fileUri the file Uri to connect to the language server
- * @param optionalFile the file which matches the non-nullable file uri and null otherwise. In the null case, the file will be retrieved by the file Uri.
+ * @param file the file to connect to the language server
* @param optionalDocument the document of the file and null otherwise. In the null case, the document will be retrieved from the file
* by using a blocking read action.
* @return the completable future with the language server instance or null.
*/
- private CompletableFuture connect(@NotNull URI fileUri,
- @Nullable VirtualFile optionalFile,
- @Nullable Document optionalDocument) {
+ CompletableFuture<@Nullable LanguageServer> connect(@NotNull VirtualFile file,
+ @Nullable Document optionalDocument) {
removeStopTimer(false);
+ URI fileUri = toUri(file);
+ if (fileUri == null) {
+ // Invalid file uri
+ return CompletableFuture.completedFuture(null);
+ }
+
var ls = getLanguageServerWhenDidOpen(fileUri);
if (ls != null) {
return ls;
@@ -680,15 +659,6 @@ private CompletableFuture connect(@NotNull URI fileUri,
return CompletableFuture.completedFuture(null);
}
- if (optionalFile == null) {
- optionalFile = LSPIJUtils.findResourceFor(fileUri);
- }
- if (optionalFile == null) {
- // The file cannot be retrieved by the given uri, return null as language server.
- return CompletableFuture.completedFuture(null);
- }
-
- final @NotNull VirtualFile fileToConnect = optionalFile;
return initializeFuture.thenComposeAsync(theVoid -> {
// Here, the "initialize" future is initialized
@@ -700,7 +670,7 @@ private CompletableFuture connect(@NotNull URI fileUri,
// Here the file is not already connected
// To connect the file, we need the document instance to add LSP document listener to manage didOpen, didChange, etc.
- Document document = optionalDocument != null ? optionalDocument : LSPIJUtils.getDocument(fileToConnect);
+ Document document = optionalDocument != null ? optionalDocument : LSPIJUtils.getDocument(file);
synchronized (connectedDocuments) {
// Check again if file is already opened (within synchronized block)
@@ -709,9 +679,9 @@ private CompletableFuture connect(@NotNull URI fileUri,
return ls2;
}
- DocumentContentSynchronizer synchronizer = createDocumentContentSynchronizer(fileUri, fileToConnect, document);
+ DocumentContentSynchronizer synchronizer = createDocumentContentSynchronizer(fileUri.toASCIIString(), file, document);
document.addDocumentListener(synchronizer);
- LSPVirtualFileData data = new LSPVirtualFileData(new LanguageServerItem(languageServer, this), fileToConnect, synchronizer);
+ LSPVirtualFileData data = new LSPVirtualFileData(new LanguageServerItem(languageServer, this), file, synchronizer);
LanguageServerWrapper.this.connectedDocuments.put(fileUri, data);
return getLanguageServerWhenDidOpen(synchronizer.didOpenFuture);
@@ -720,7 +690,10 @@ private CompletableFuture connect(@NotNull URI fileUri,
}
@Nullable
- private CompletableFuture getLanguageServerWhenDidOpen(@NotNull URI fileUri) {
+ private CompletableFuture getLanguageServerWhenDidOpen(@Nullable URI fileUri) {
+ if (fileUri == null) {
+ return null;
+ }
var existingData = connectedDocuments.get(fileUri);
if (existingData != null) {
// The file is already connected.
@@ -744,7 +717,7 @@ private CompletableFuture getLanguageServerWhenDidOpen(Completab
}
@NotNull
- private DocumentContentSynchronizer createDocumentContentSynchronizer(@NotNull URI fileUri,
+ private DocumentContentSynchronizer createDocumentContentSynchronizer(@NotNull String fileUri,
@NotNull VirtualFile file,
@NotNull Document document) {
Either syncOptions = initializeFuture == null ? null
@@ -760,8 +733,16 @@ private DocumentContentSynchronizer createDocumentContentSynchronizer(@NotNull U
return new DocumentContentSynchronizer(this, fileUri, file, document, syncKind);
}
- public void disconnect(URI path, boolean stopIfNoOpenedFiles) {
- LSPVirtualFileData data = this.connectedDocuments.remove(path);
+ void disconnect(@NotNull VirtualFile file, boolean stopIfNoOpenedFiles) {
+ URI fileUri = toUri(file);
+ disconnect(fileUri, stopIfNoOpenedFiles);
+ }
+
+ void disconnect(@Nullable URI fileUri, boolean stopIfNoOpenedFiles) {
+ if (fileUri == null) {
+ return;
+ }
+ LSPVirtualFileData data = this.connectedDocuments.remove(fileUri);
if (data != null) {
// Remove the listener from the old document stored in synchronizer
DocumentContentSynchronizer synchronizer = data.getSynchronizer();
@@ -799,11 +780,9 @@ void decrementKeepAlive() {
/**
* checks if the wrapper is already connected to the document at the given path
- *
- * @noreference test only
*/
- public boolean isConnectedTo(URI location) {
- return connectedDocuments.containsKey(location);
+ public boolean isConnectedTo(URI fileUri) {
+ return connectedDocuments.containsKey(fileUri);
}
/**
@@ -1065,7 +1044,7 @@ int getVersion(VirtualFile file) {
}
public boolean canOperate(@NotNull VirtualFile file) {
- if (this.isConnectedTo(LSPIJUtils.toUri(file))) {
+ if (this.isConnectedTo(toUri(file))) {
return true;
}
if (this.connectedDocuments.isEmpty()) {
@@ -1187,4 +1166,8 @@ private synchronized LSPClientFeatures getOrCreateClientFeatures() {
return clientFeatures;
}
+ URI toUri(@NotNull VirtualFile file) {
+ return FileUriSupport.getFileUri(file, getClientFeatures());
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/ServerMessageHandler.java b/src/main/java/com/redhat/devtools/lsp4ij/ServerMessageHandler.java
index 34d4f6ad1..5a1294908 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/ServerMessageHandler.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/ServerMessageHandler.java
@@ -26,12 +26,14 @@
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupListener;
import com.intellij.openapi.ui.popup.LightweightWindowEvent;
+import com.redhat.devtools.lsp4ij.client.features.FileUriSupport;
import com.redhat.devtools.lsp4ij.console.LSPConsoleToolWindowPanel;
import com.redhat.devtools.lsp4ij.features.documentation.MarkdownConverter;
import com.redhat.devtools.lsp4ij.internal.StringUtils;
import com.redhat.devtools.lsp4ij.server.definition.LanguageServerDefinition;
import org.eclipse.lsp4j.*;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.concurrent.CompletableFuture;
@@ -157,6 +159,7 @@ public void onClosed(@NotNull LightweightWindowEvent event) {
}
public static CompletableFuture showDocument(@NotNull ShowDocumentParams params,
+ @Nullable FileUriSupport fileUriSupport,
@NotNull Project project) {
String uri = params.getUri();
if (StringUtils.isEmpty(uri)) {
@@ -180,7 +183,7 @@ public static CompletableFuture showDocument(@NotNull ShowDo
CompletableFuture future = new CompletableFuture<>();
ApplicationManager.getApplication()
.executeOnPooledThread(() -> {
- if (LSPIJUtils.openInEditor(uri, position, focusEditor, project)) {
+ if (LSPIJUtils.openInEditor(uri, position, focusEditor, false, fileUriSupport, project)) {
future.complete(SHOW_DOCUMENT_RESULT_WITH_SUCCESS);
}
future.complete(SHOW_DOCUMENT_RESULT_WITH_FAILURE);
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/IndexAwareLanguageClient.java b/src/main/java/com/redhat/devtools/lsp4ij/client/IndexAwareLanguageClient.java
index b1976d11c..1491d42f4 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/client/IndexAwareLanguageClient.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/IndexAwareLanguageClient.java
@@ -17,16 +17,14 @@
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.redhat.devtools.lsp4ij.LSPIJUtils;
+import com.redhat.devtools.lsp4ij.client.features.FileUriSupport;
import com.redhat.devtools.lsp4ij.client.indexing.ProjectIndexingManager;
import com.redhat.devtools.lsp4ij.internal.CancellationSupport;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
public class IndexAwareLanguageClient extends LanguageClientImpl {
- private static final Logger LOGGER = LoggerFactory.getLogger(IndexAwareLanguageClient.class);
public IndexAwareLanguageClient(Project project) {
super(project);
@@ -68,7 +66,7 @@ protected CompletableFuture runAsBackground(String progressTitle, Functio
* @return the file path to display in the progress bar.
*/
protected String getFilePath(String fileUri) {
- VirtualFile file = LSPIJUtils.findResourceFor(fileUri);
+ VirtualFile file = FileUriSupport.findFileByUri(fileUri, getClientFeatures());
if (file != null) {
Module module = LSPIJUtils.getModule(file, getProject());
if (module != null) {
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/LanguageClientImpl.java b/src/main/java/com/redhat/devtools/lsp4ij/client/LanguageClientImpl.java
index df61774cb..f591449ef 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/client/LanguageClientImpl.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/LanguageClientImpl.java
@@ -10,7 +10,6 @@
******************************************************************************/
package com.redhat.devtools.lsp4ij.client;
-import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.openapi.Disposable;
@@ -21,6 +20,7 @@
import com.intellij.psi.PsiFile;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.redhat.devtools.lsp4ij.*;
+import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
import com.redhat.devtools.lsp4ij.features.diagnostics.LSPDiagnosticHandler;
import com.redhat.devtools.lsp4ij.features.progress.LSPProgressManager;
import com.redhat.devtools.lsp4ij.internal.editor.EditorFeatureManager;
@@ -79,6 +79,10 @@ public LanguageServerDefinition getServerDefinition() {
return wrapper.getServerDefinition();
}
+ public LSPClientFeatures getClientFeatures() {
+ return wrapper.getClientFeatures();
+ }
+
@Override
public void telemetryEvent(Object object) {
// TODO
@@ -96,7 +100,7 @@ public void showMessage(MessageParams messageParams) {
@Override
public CompletableFuture showDocument(ShowDocumentParams params) {
- return ServerMessageHandler.showDocument(params, getProject());
+ return ServerMessageHandler.showDocument(params, getClientFeatures(), getProject());
}
@Override
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/FileUriSupport.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/FileUriSupport.java
new file mode 100644
index 000000000..4fad5ec78
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/FileUriSupport.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Red Hat, Inc.
+ * Distributed under license by Red Hat, Inc. All rights reserved.
+ * This program is made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and declaration
+ ******************************************************************************/
+package com.redhat.devtools.lsp4ij.client.features;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.redhat.devtools.lsp4ij.LSPIJUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.net.URI;
+
+/**
+ * File Uri support.
+ */
+public interface FileUriSupport {
+
+ public static final FileUriSupport DEFAULT_FILE_URI_SUPPORT = new FileUriSupport() {
+
+ @Override
+ public @Nullable URI getFileUri(@NotNull VirtualFile file) {
+ return LSPIJUtils.toUri(file);
+ }
+
+ @Override
+ public @Nullable VirtualFile findFileByUri(@NotNull String fileUri) {
+ return LSPIJUtils.findResourceFor(fileUri);
+ }
+ };
+
+ /**
+ * Returns the file Uri from the given virtual file and null otherwise.
+ * @param file the virtual file.
+ * @return the file Uri from the given virtual file and null otherwise.
+ */
+ @Nullable
+ URI getFileUri(@NotNull VirtualFile file);
+
+ /**
+ * Returns the virtual file found by the given file Uri and null otherwise.
+ * @param fileUri the file Uri.
+ * @return the virtual file found by the given file Uri and null otherwise.
+ */
+ @Nullable
+ VirtualFile findFileByUri(@NotNull String fileUri);
+
+ @Nullable
+ public static URI getFileUri(@NotNull VirtualFile file,
+ @Nullable FileUriSupport fileUriSupport) {
+ URI fileUri = fileUriSupport != null ? fileUriSupport.getFileUri(file) : null;
+ if (fileUri != null) {
+ return fileUri;
+ }
+ return DEFAULT_FILE_URI_SUPPORT.getFileUri(file);
+ }
+
+ @Nullable
+ public static VirtualFile findFileByUri(@NotNull String fileUri,
+ @Nullable FileUriSupport fileUriSupport) {
+ VirtualFile file = fileUriSupport != null ? fileUriSupport.findFileByUri(fileUri) : null;
+ if (file != null) {
+ return file;
+ }
+ return DEFAULT_FILE_URI_SUPPORT.findFileByUri(fileUri);
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPClientFeatures.java b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPClientFeatures.java
index 9b2822435..51fbfdf5b 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPClientFeatures.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/client/features/LSPClientFeatures.java
@@ -26,11 +26,13 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.net.URI;
+
/**
* LSP client features.
*/
@ApiStatus.Experimental
-public class LSPClientFeatures implements Disposable {
+public class LSPClientFeatures implements Disposable, FileUriSupport {
private LanguageServerWrapper serverWrapper;
@@ -84,6 +86,50 @@ public class LSPClientFeatures implements Disposable {
private LSPWorkspaceSymbolFeature workspaceSymbolFeature;
+ /**
+ * Returns true if the language server is enabled for the given file and false otherwise. Default to true
+ *
+ * @param file the file for test
+ * @return true if the language server is enabled for the input and false otherwise. Default to true
+ */
+ public boolean isEnabled(@NotNull VirtualFile file) {
+ return true;
+ }
+
+ /**
+ * Determines whether or not the language grammar for the file is case-sensitive.
+ *
+ * @param file the file
+ * @return true if the file's language grammar is case-sensitive; otherwise false
+ */
+ public boolean isCaseSensitive(@NotNull PsiFile file) {
+ // Default to case-insensitive
+ return false;
+ }
+
+ /**
+ * Overrides this method if you need to generate custom URI.
+ *
+ * @param file the virtual file.
+ *
+ * @return the file Uri and null otherwise.
+ */
+ @Override
+ public @Nullable URI getFileUri(@NotNull VirtualFile file) {
+ return null;
+ }
+
+ /**
+ * Overrides this method if you need to support custom file uri.
+ *
+ * @param fileUri the file Uri.
+ * @return the virtual file and null otherwise.
+ */
+ @Override
+ public @Nullable VirtualFile findFileByUri(@NotNull String fileUri) {
+ return null;
+ }
+
/**
* Returns the project.
*
@@ -955,27 +1001,6 @@ public final LSPClientFeatures setWorkspaceSymbolFeature(@NotNull LSPWorkspaceSy
return this;
}
- /**
- * Returns true if the language server is enabled for the given file and false otherwise. Default to true
- *
- * @param file the file for test
- * @return true if the language server is enabled for the input and false otherwise. Default to true
- */
- public boolean isEnabled(@NotNull VirtualFile file) {
- return true;
- }
-
- /**
- * Determines whether or not the language grammar for the file is case-sensitive.
- *
- * @param file the file
- * @return true if the file's language grammar is case-sensitive; otherwise false
- */
- public boolean isCaseSensitive(@NotNull PsiFile file) {
- // Default to case-insensitive
- return false;
- }
-
/**
* Set the language server wrapper.
*
diff --git a/src/main/java/com/redhat/devtools/lsp4ij/commands/CommandExecutor.java b/src/main/java/com/redhat/devtools/lsp4ij/commands/CommandExecutor.java
index e60cf0592..417fcb4cb 100644
--- a/src/main/java/com/redhat/devtools/lsp4ij/commands/CommandExecutor.java
+++ b/src/main/java/com/redhat/devtools/lsp4ij/commands/CommandExecutor.java
@@ -27,13 +27,13 @@
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
-import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.redhat.devtools.lsp4ij.*;
+import com.redhat.devtools.lsp4ij.client.features.FileUriSupport;
import com.redhat.devtools.lsp4ij.features.documentation.MarkdownConverter;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.services.LanguageServer;
@@ -110,9 +110,9 @@ public static LSPCommandResponse executeCommand(LSPCommandContext context) {
// 3. tentative fallback
if (file != null && command.getArguments() != null) {
- Document document = LSPIJUtils.getDocument(file);
- if (document != null) {
- WorkspaceEdit edit = createWorkspaceEdit(command.getArguments(), document);
+ LanguageServerItem languageServer = context.getPreferredLanguageServer();
+ FileUriSupport fileUriSupport = languageServer != null ? languageServer.getClientFeatures() : null;
+ WorkspaceEdit edit = createWorkspaceEdit(command.getArguments(), file, fileUriSupport);
if (edit != null) {
if (ApplicationManager.getApplication().isWriteAccessAllowed()) {
LSPIJUtils.applyWorkspaceEdit(edit);
@@ -123,7 +123,6 @@ public static LSPCommandResponse executeCommand(LSPCommandContext context) {
});
}
return LSPCommandResponse.FOUND;
- }
}
}
if (context.isShowNotificationError()) {
@@ -152,9 +151,9 @@ public static LSPCommandResponse executeCommand(LSPCommandContext context) {
* do nothing.
* @param languageServer the language server for which the {@code command} is
* applicable.
- * @param preferredLanguageServerId
- * @param project
- * @return true if the LSP command on server side has been executed successfully and false otherwise.
+ * @param preferredLanguageServerId the preferred language server Id.
+ * @param project the project.
+ * @return the LSP command response.
*/
private static LSPCommandResponse executeCommandServerSide(@NotNull Command command,
@Nullable LanguageServerItem languageServer,
@@ -297,9 +296,11 @@ private static final class Pair {
* workspace edit...
*/
@Nullable
- private static WorkspaceEdit createWorkspaceEdit(List