From 7e3e127d658f183bb763a25e0723aa99c53babf9 Mon Sep 17 00:00:00 2001 From: azerr Date: Fri, 29 Sep 2023 16:07:38 +0200 Subject: [PATCH] refactor: Storing VirtualFile instead of Document for opened document Fixes #1082 Signed-off-by: azerr --- ...umentToLanguageServerSetupParticipant.java | 28 ++-- .../devtools/intellij/lsp4ij/LSPIJUtils.java | 13 +- .../intellij/lsp4ij/LSPVirtualFileData2.java | 18 ++ .../lsp4ij/LanguageServerWrapper.java | 154 +++++++----------- .../lsp4ij/LanguageServiceAccessor.java | 61 ++++--- .../lsp4ij/commands/CommandExecutor.java | 6 +- .../codelens/LSPCodelensInlayProvider.java | 10 +- .../completion/LSPCompletionContributor.java | 7 +- .../LSPDocumentLinkAnnotator.java | 15 +- .../LSPDocumentationProvider.java | 24 +-- .../documentation/LSPTextHoverForFile.java | 8 +- .../LSPHighlightUsagesHandlerFactory.java | 6 +- .../inlayhint/LSPInlayHintInlayProvider.java | 9 +- .../navigation/LSPGotoDeclarationHandler.java | 3 +- 14 files changed, 168 insertions(+), 194 deletions(-) create mode 100644 src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPVirtualFileData2.java diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/ConnectDocumentToLanguageServerSetupParticipant.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/ConnectDocumentToLanguageServerSetupParticipant.java index 60dbcb528..fdee5613e 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/ConnectDocumentToLanguageServerSetupParticipant.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/ConnectDocumentToLanguageServerSetupParticipant.java @@ -10,11 +10,8 @@ ******************************************************************************/ package com.redhat.devtools.intellij.lsp4ij; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManagerListener; -import com.intellij.openapi.module.Module; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManagerListener; @@ -40,27 +37,24 @@ public void projectClosing(@NotNull Project project) { @Override public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) { - Document document = FileDocumentManager.getInstance().getDocument(file); - if (document != null) { - Project project = source.getProject(); - if (DumbService.isDumb(project)) { - // Force the start of all languages servers mapped with the given file when indexation is finished - DumbService.getInstance(project).runWhenSmart(() -> { - startLanguageServer(source, document); - }); - } else { - // Force the start of all languages servers mapped with the given file immediately - startLanguageServer(source, document); - } + Project project = source.getProject(); + if (DumbService.isDumb(project)) { + // Force the start of all languages servers mapped with the given file when indexation is finished + DumbService.getInstance(project).runWhenSmart(() -> { + startLanguageServer(source, file); + }); + } else { + // Force the start of all languages servers mapped with the given file immediately + startLanguageServer(source, file); } } - private static void startLanguageServer(@NotNull FileEditorManager source, Document document) { + private static void startLanguageServer(@NotNull FileEditorManager source, @NotNull VirtualFile file) { // Force the start of all languages servers mapped with the given file // Server capabilities filter is set to null to avoid waiting // for the start of the server when server capabilities are checked LanguageServiceAccessor.getInstance(source.getProject()) - .getLanguageServers(document, null); + .getLanguageServers(file, null); } } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPIJUtils.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPIJUtils.java index 5adb53889..7c1d1f1df 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPIJUtils.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPIJUtils.java @@ -27,6 +27,7 @@ import org.apache.commons.lang.StringUtils; import org.eclipse.lsp4j.*; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -140,10 +141,15 @@ public static URI toUri(Document document) { return file != null ? toUri(file) : null; } - public static VirtualFile getFile(Document document) { + public static @Nullable VirtualFile getFile(Document document) { return FileDocumentManager.getInstance().getFile(document); } + public static @Nullable VirtualFile getFile(@NotNull PsiElement element) { + PsiFile psFile = element.getContainingFile(); + return psFile != null ? psFile.getVirtualFile() : null; + } + public static Document getDocument(VirtualFile docFile) { return FileDocumentManager.getInstance().getDocument(docFile); } @@ -405,11 +411,11 @@ public static Language getDocumentLanguage(Document document, Project project) { return getFileLanguage(file, project); } - public static VirtualFile findResourceFor(URI uri) { + public static @Nullable VirtualFile findResourceFor(URI uri) { return LocalFileSystem.getInstance().findFileByIoFile(Paths.get(uri).toFile()); } - public static VirtualFile findResourceFor(String uri) { + public static @Nullable VirtualFile findResourceFor(String uri) { if (uri.startsWith(JAR_SCHEME) || uri.startsWith(JRT_SCHEME)) { // ex : jar:file:///C:/Users/azerr/.m2/repository/io/quarkus/quarkus-core/3.0.1.Final/quarkus-core-3.0.1.Final.jar!/io/quarkus/runtime/ApplicationConfig.class try { @@ -518,4 +524,5 @@ public static String getProjectUri(Project project) { } return project.getName(); } + } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPVirtualFileData2.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPVirtualFileData2.java new file mode 100644 index 000000000..eaa2d7450 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPVirtualFileData2.java @@ -0,0 +1,18 @@ +package com.redhat.devtools.intellij.lsp4ij; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.vfs.VirtualFile; +import org.eclipse.lsp4j.TextDocumentSyncKind; + +public class LSPVirtualFileData2 { + + private final DocumentContentSynchronizer synchronizer; + + public LSPVirtualFileData2(VirtualFile file, DocumentContentSynchronizer synchronizer) { + this.synchronizer = synchronizer; + } + + public DocumentContentSynchronizer getSynchronizer() { + return synchronizer; + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/LanguageServerWrapper.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/LanguageServerWrapper.java index e82c696f6..26d4b1f0e 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/LanguageServerWrapper.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/LanguageServerWrapper.java @@ -17,6 +17,7 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationInfo; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManagerListener; @@ -40,8 +41,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.net.URI; @@ -101,10 +101,10 @@ public void propertyChanged(@NotNull VirtualFilePropertyEvent event) { public void contentsChanged(@NotNull VirtualFileEvent event) { URI uri = LSPIJUtils.toUri(event.getFile()); if (uri != null) { - DocumentContentSynchronizer documentListener = connectedDocuments.get(uri); + LSPVirtualFileData2 documentListener = connectedDocuments.get(uri); if (documentListener != null) { // 1. Send a textDocument/didSave for the saved file - documentListener.documentSaved(); + documentListener.getSynchronizer().documentSaved(); } // 2. Send a workspace/didChangeWatchedFiles didChangeWatchedFiles(fe(uri, FileChangeType.Changed)); @@ -148,8 +148,7 @@ private FileEvent fe(URI uri, FileChangeType type) { private @NotNull URI didClose(VirtualFile virtualParentFile, String fileName) { File parent = VfsUtilCore.virtualToIoFile(virtualParentFile); URI uri = LSPIJUtils.toUri(new File(parent, fileName)); - DocumentContentSynchronizer documentListener = connectedDocuments.get(uri); - if (documentListener != null) { + if (isConnectedTo(uri)) { disconnect(uri, false); } return uri; @@ -167,12 +166,12 @@ private void didChangeWatchedFiles(FileEvent... changes) { private Listener fileBufferListener = new Listener(); private MessageBusConnection messageBusConnection; - @Nonnull + @NotNull public final LanguageServersRegistry.LanguageServerDefinition serverDefinition; @Nullable protected final Project initialProject; - @Nonnull - protected Map connectedDocuments; + @NotNull + protected Map connectedDocuments; @Nullable protected final URI initialPath; protected final InitializeParams initParams = new InitializeParams(); @@ -205,23 +204,23 @@ private void didChangeWatchedFiles(FileEvent... changes) { /** * Map containing unregistration handlers for dynamic capability registrations. */ - private @Nonnull + private @NotNull Map dynamicRegistrations = new HashMap<>(); private boolean initiallySupportsWorkspaceFolders = false; /* Backwards compatible constructor */ - public LanguageServerWrapper(@Nonnull Project project, @Nonnull LanguageServersRegistry.LanguageServerDefinition serverDefinition) { + public LanguageServerWrapper(@NotNull Project project, @NotNull LanguageServersRegistry.LanguageServerDefinition serverDefinition) { this(project, serverDefinition, null); } - public LanguageServerWrapper(@Nonnull LanguageServersRegistry.LanguageServerDefinition serverDefinition, @Nullable URI initialPath) { + public LanguageServerWrapper(@NotNull LanguageServersRegistry.LanguageServerDefinition serverDefinition, @Nullable URI initialPath) { this(null, serverDefinition, initialPath); } /** * Unified private constructor to set sensible defaults in all cases */ - private LanguageServerWrapper(@Nullable Project project, @Nonnull LanguageServersRegistry.LanguageServerDefinition serverDefinition, + private LanguageServerWrapper(@Nullable Project project, @NotNull LanguageServersRegistry.LanguageServerDefinition serverDefinition, @Nullable URI initialPath) { this.initialProject = project; this.initialPath = initialPath; @@ -301,16 +300,13 @@ public synchronized void start() throws LanguageServerException { numberOfRestartAttempts++; } } - final var filesToReconnect = new HashMap(); + final var filesToReconnect = new ArrayList(); if (this.languageServer != null) { if (isActive()) { return; } else { - for (Map.Entry entry : this.connectedDocuments.entrySet()) { - // Get the Document from the VirtualFile to use the proper Document Instance - // (not an old instanceof the synchronizer which could be out of dated). - Document document = getDocument(entry.getKey(), entry.getValue()); - filesToReconnect.put(entry.getKey(), document); + for (Map.Entry entry : this.connectedDocuments.entrySet()) { + filesToReconnect.add(entry.getKey()); } stop(); } @@ -389,11 +385,11 @@ public synchronized void start() throws LanguageServerException { }).thenRun(() -> { this.languageServer.initialized(new InitializedParams()); }).thenRun(() -> { - final Map toReconnect = filesToReconnect; + final List toReconnect = filesToReconnect; initializeFuture.thenRunAsync(() -> { - for (Map.Entry fileToReconnect : toReconnect.entrySet()) { + for (URI fileToReconnect : toReconnect) { try { - connect(fileToReconnect.getKey(), fileToReconnect.getValue()); + connect(fileToReconnect); } catch (IOException e) { throw new RuntimeException(e); } @@ -423,25 +419,6 @@ public synchronized void start() throws LanguageServerException { } } - /** - * Returns the document instance from the virtual file which matches the given uri and the document from teh synchronizer otherwise. - * - * @param uri the document Uri - * @param synchronizer the document synchronizer. - * @return the document instance from the virtual file which matches the given uri and the document from teh synchronizer otherwise. - */ - private static Document getDocument(URI uri, DocumentContentSynchronizer synchronizer) { - Document document = synchronizer.getDocument(); - VirtualFile file = LSPIJUtils.findResourceFor(uri); - if (file != null) { - Document documentFromFile = LSPIJUtils.getDocument(file); - if (documentFromFile != null) { - document = documentFromFile; - } - } - return document; - } - private CompletableFuture initServer(final URI rootURI) { final var workspaceClientCapabilities = SupportedFeatures.getWorkspaceClientCapabilities(); @@ -676,34 +653,17 @@ private void exitLanguageServerInstance(LanguageServer languageServerInstance) t } /** - * @param file - * @param document - * @return null if not connection has happened, a future tracking the connection state otherwise - * @throws IOException - */ - public @Nullable - CompletableFuture connect(@Nonnull VirtualFile file, Document document) throws IOException { - return connect(LSPIJUtils.toUri(file), document); - } - - /** - * @param document + * Connect the given file to the language server. + * + * @param file the file to connect to the language server * @return null if not connection has happened, a future tracking the connection state otherwise * @throws IOException */ - public @Nullable - CompletableFuture connect(Document document) throws IOException { - VirtualFile file = LSPIJUtils.getFile(document); - + public CompletableFuture<@Nullable LanguageServer> connect(VirtualFile file) throws IOException { if (file != null && file.exists()) { - return connect(file, document); - } else { - URI uri = LSPIJUtils.toUri(document); - if (uri != null) { - return connect(uri, document); - } + return connect(LSPIJUtils.toUri(file)); } - return null; + return CompletableFuture.completedFuture(null); } /** @@ -727,28 +687,23 @@ public boolean canOperate(Project project) { * @return null if not connection has happened, a future that completes when file is initialized otherwise * @noreference internal so far */ - private CompletableFuture connect(@Nonnull URI absolutePath, Document document) throws IOException { + private CompletableFuture connect(@NotNull URI fileUri) throws IOException { removeStopTimer(false); - final URI thePath = absolutePath; // should be useless - if (this.connectedDocuments.containsKey(thePath)) { + if (this.connectedDocuments.containsKey(fileUri)) { return CompletableFuture.completedFuture(languageServer); } start(); if (this.initializeFuture == null) { - return null; - } - if (document == null) { - VirtualFile docFile = LSPIJUtils.findResourceFor(thePath); - document = LSPIJUtils.getDocument(docFile); + return CompletableFuture.completedFuture(null); } - if (document == null) { - return null; + VirtualFile file = LSPIJUtils.findResourceFor(fileUri); + if (file == null) { + return CompletableFuture.completedFuture(null); } - final Document theDocument = document; return initializeFuture.thenComposeAsync(theVoid -> { synchronized (connectedDocuments) { - if (this.connectedDocuments.containsKey(thePath)) { + if (this.connectedDocuments.containsKey(fileUri)) { return CompletableFuture.completedFuture(null); } Either syncOptions = initializeFuture == null ? null @@ -761,10 +716,15 @@ private CompletableFuture connect(@Nonnull URI absolutePath, Doc syncKind = syncOptions.getLeft(); } } - DocumentContentSynchronizer listener = new DocumentContentSynchronizer(this, theDocument, syncKind); - theDocument.addDocumentListener(listener); - LanguageServerWrapper.this.connectedDocuments.put(thePath, listener); - return listener.didOpenFuture; + + Document document = ReadAction.compute(() -> LSPIJUtils.getDocument(file)); + DocumentContentSynchronizer synchronizer = new DocumentContentSynchronizer(this, document, syncKind); + document.addDocumentListener(synchronizer); + + LSPVirtualFileData2 data = new LSPVirtualFileData2(file, synchronizer); + LanguageServerWrapper.this.connectedDocuments.put(fileUri, data); + + return synchronizer.didOpenFuture; } }).thenApply(theVoid -> languageServer); } @@ -774,11 +734,12 @@ private void disconnect(URI path) { } private void disconnect(URI path, boolean stopIfNoOpenedFiles) { - DocumentContentSynchronizer documentListener = this.connectedDocuments.remove(path); - if (documentListener != null) { + LSPVirtualFileData2 data = this.connectedDocuments.remove(path); + if (data != null) { // Remove the listener from the old document stored in synchronizer - documentListener.getDocument().removeDocumentListener(documentListener); - documentListener.documentClosed(); + DocumentContentSynchronizer synchronizer = data.getSynchronizer(); + synchronizer.getDocument().removeDocumentListener(synchronizer); + synchronizer.documentClosed(); } if (stopIfNoOpenedFiles && this.connectedDocuments.isEmpty()) { if (this.serverDefinition.lastDocumentDisconnectedTimeout != 0 && !ApplicationManager.getApplication().isUnitTestMode()) { @@ -821,7 +782,7 @@ public LanguageServer getServer() { * server to be initialized. If done in the UI stream, a job will be created * displaying that the server is being initialized */ - @Nonnull + @NotNull public CompletableFuture getInitializedServer() { try { start(); @@ -854,7 +815,7 @@ protected Void compute(@NotNull ProgressIndicator indicator) throws Exception { * * @param fn LS notification to send */ - public void sendNotification(@Nonnull Consumer fn) { + public void sendNotification(@NotNull Consumer fn) { // Enqueues a notification on the dispatch thread associated with the wrapped language server. This // ensures the interleaving of document updates and other requests in the UI is mirrored in the // order in which they get dispatched to the server @@ -953,7 +914,7 @@ public void registerCapability(RegistrationParams params) { }); } - private void addRegistration(@Nonnull Registration reg, @Nonnull Runnable unregistrationHandler) { + private void addRegistration(@NotNull Registration reg, @NotNull Runnable unregistrationHandler) { String regId = reg.getId(); synchronized (dynamicRegistrations) { assert !dynamicRegistrations.containsKey(regId) : "Registration id is not unique"; //$NON-NLS-1$ @@ -1022,23 +983,28 @@ void unregisterCommands(List cmds) { } int getVersion(VirtualFile file) { - if (file != null && LSPIJUtils.toUri(file) != null) { - DocumentContentSynchronizer documentContentSynchronizer = connectedDocuments.get(LSPIJUtils.toUri(file)); - if (documentContentSynchronizer != null) { - return documentContentSynchronizer.getVersion(); + if (file != null) { + URI uri = LSPIJUtils.toUri(file); + if (uri != null) { + LSPVirtualFileData2 data = connectedDocuments.get(LSPIJUtils.toUri(file)); + if (data != null) { + var synchronizer = data.getSynchronizer(); + if (synchronizer != null) { + return synchronizer.getVersion(); + } + } } } return -1; } - public boolean canOperate(@Nonnull Document document) { - if (this.isConnectedTo(LSPIJUtils.toUri(document))) { + public boolean canOperate(@NotNull VirtualFile file) { + if (this.isConnectedTo(LSPIJUtils.toUri(file))) { return true; } if (this.initialProject == null && this.connectedDocuments.isEmpty()) { return true; } - VirtualFile file = LSPIJUtils.getFile(document); if (file != null && file.exists() && canOperate(LSPIJUtils.getProject(file))) { return true; } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/LanguageServiceAccessor.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/LanguageServiceAccessor.java index fd822afb3..3f2720ecf 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/LanguageServiceAccessor.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/LanguageServiceAccessor.java @@ -12,19 +12,17 @@ import com.intellij.lang.Language; import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.module.Module; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.redhat.devtools.intellij.lsp4ij.server.StreamConnectionProvider; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.services.LanguageServer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.io.IOException; import java.net.URI; import java.util.*; @@ -51,21 +49,21 @@ private LanguageServiceAccessor(Project project) { private final Set startedServers = new HashSet<>(); private Map providersToLSDefinitions = new HashMap<>(); - @Nonnull - public CompletableFuture> getLanguageServers(@Nonnull Document document, + @NotNull + public CompletableFuture> getLanguageServers(@NotNull VirtualFile file, Predicate filter) { - URI uri = LSPIJUtils.toUri(document); + URI uri = LSPIJUtils.toUri(file); if (uri == null) { return CompletableFuture.completedFuture(Collections.emptyList()); } final List servers = Collections.synchronizedList(new ArrayList<>()); try { - return CompletableFuture.allOf(getLSWrappers(document).stream().map(wrapper -> + return CompletableFuture.allOf(getLSWrappers(file).stream().map(wrapper -> wrapper.getInitializedServer() .thenComposeAsync(server -> { if (server != null && wrapper.isEnabled() && (filter == null || filter.test(wrapper.getServerCapabilities()))) { try { - return wrapper.connect(document); + return wrapper.connect(file); } catch (IOException ex) { LOGGER.warn(ex.getLocalizedMessage(), ex); } @@ -104,24 +102,24 @@ public void projectClosing(Project project) { } /** - * Get the requested language server instance for the given document. Starts the + * Get the requested language server instance for the given file. Starts the * language server if not already started. * - * @param document the document for which the initialized LanguageServer shall be returned - * @param serverId the ID of the LanguageServer to be returned - * @param capabilitesPredicate a predicate to check capabilities + * @param file the file for which the initialized LanguageServer shall be returned + * @param lsDefinition the language server definition + * @param capabilitiesPredicate a predicate to check capabilities * @return a LanguageServer for the given file, which is defined with provided * server ID and conforms to specified request. If * {@code capabilitesPredicate} does not test positive for the server's * capabilities, {@code null} is returned. */ - public CompletableFuture getInitializedLanguageServer(Document document, + public CompletableFuture getInitializedLanguageServer(VirtualFile file, LanguageServersRegistry.LanguageServerDefinition lsDefinition, Predicate capabilitiesPredicate) throws IOException { - URI initialPath = LSPIJUtils.toUri(document); - LanguageServerWrapper wrapper = getLSWrapperForConnection(document, lsDefinition, initialPath); + URI initialPath = LSPIJUtils.toUri(file); + LanguageServerWrapper wrapper = getLSWrapperForConnection(file, lsDefinition, initialPath); if (wrapper != null && capabilitiesComply(wrapper, capabilitiesPredicate)) { - wrapper.connect(document); + wrapper.connect(file); return wrapper.getInitializedServer(); } return null; @@ -146,11 +144,10 @@ private static boolean capabilitiesComply(LanguageServerWrapper wrapper, || capabilitiesPredicate.test(wrapper.getServerCapabilities()); } - @Nonnull - private Collection getLSWrappers(@Nonnull Document document) { + @NotNull + private Collection getLSWrappers(@NotNull VirtualFile file) { LinkedHashSet res = new LinkedHashSet<>(); - VirtualFile file = LSPIJUtils.getFile(document); - URI uri = LSPIJUtils.toUri(document); + URI uri = LSPIJUtils.toUri(file); if (uri == null) { return Collections.emptyList(); } @@ -159,14 +156,14 @@ private Collection getLSWrappers(@Nonnull Document docume // look for running language servers via content-type Queue contentTypes = new LinkedList<>(); Set processedContentTypes = new HashSet<>(); - contentTypes.add(LSPIJUtils.getDocumentLanguage(document, project)); + contentTypes.add(LSPIJUtils.getFileLanguage(file, project)); synchronized (startedServers) { // already started compatible servers that fit request res.addAll(startedServers.stream() .filter(wrapper -> { try { - return wrapper.isEnabled() && (wrapper.isConnectedTo(path) || LanguageServersRegistry.getInstance().matches(document, wrapper.serverDefinition, project)); + return wrapper.isEnabled() && (wrapper.isConnectedTo(path) || LanguageServersRegistry.getInstance().matches(file, wrapper.serverDefinition, project)); } catch (ProcessCanceledException cancellation) { throw cancellation; } catch (Exception e) { @@ -174,7 +171,7 @@ private Collection getLSWrappers(@Nonnull Document docume return false; } }) - .filter(wrapper -> wrapper.canOperate(document)) + .filter(wrapper -> wrapper.canOperate(file)) .collect(Collectors.toList())); while (!contentTypes.isEmpty()) { @@ -192,7 +189,7 @@ private Collection getLSWrappers(@Nonnull Document docume continue; } if (startedServers.stream().anyMatch(wrapper -> wrapper.serverDefinition.equals(serverDefinition) - && wrapper.canOperate(document))) { + && wrapper.canOperate(file))) { // we already checked a compatible LS with this definition continue; } @@ -209,7 +206,7 @@ private Collection getLSWrappers(@Nonnull Document docume } } - private LanguageServerWrapper getLSWrapperForConnection(Document document, + private LanguageServerWrapper getLSWrapperForConnection(VirtualFile file, LanguageServersRegistry.LanguageServerDefinition serverDefinition, URI initialPath) throws IOException { if (!serverDefinition.isEnabled()) { // don't return a language server wrapper for the given server definition @@ -218,7 +215,7 @@ private LanguageServerWrapper getLSWrapperForConnection(Document document, LanguageServerWrapper wrapper = null; synchronized (startedServers) { - for (LanguageServerWrapper startedWrapper : getStartedLSWrappers(document)) { + for (LanguageServerWrapper startedWrapper : getStartedLSWrappers(file)) { if (startedWrapper.serverDefinition.equals(serverDefinition)) { wrapper = startedWrapper; break; @@ -235,8 +232,8 @@ private LanguageServerWrapper getLSWrapperForConnection(Document document, } private List getStartedLSWrappers( - Document document) { - return getStartedLSWrappers(wrapper -> wrapper.canOperate(document)); + VirtualFile file) { + return getStartedLSWrappers(wrapper -> wrapper.canOperate(file)); } private List getStartedLSWrappers(Predicate predicate) { @@ -246,7 +243,7 @@ private List getStartedLSWrappers(Predicate getMatchingStartedWrappers(@Nonnull VirtualFile file, + private Collection getMatchingStartedWrappers(@NotNull VirtualFile file, @Nullable Predicate request) { synchronized (startedServers) { return startedServers.stream().filter(wrapper -> wrapper.isConnectedTo(LSPIJUtils.toUri(file)) @@ -264,7 +261,7 @@ private Collection getMatchingStartedWrappers(@Nonnull Vi * @param request * @return list of Language Servers */ - @Nonnull + @NotNull public List getActiveLanguageServers(Predicate request) { return getLanguageServers(null, request, true); } @@ -277,7 +274,7 @@ public List getActiveLanguageServers(Predicate getLanguageServers(@Nullable Project project, Predicate request, boolean onlyActiveLS) { List serverInfos = new ArrayList<>(); diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/commands/CommandExecutor.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/commands/CommandExecutor.java index bbb044bbe..2de873fa3 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/commands/CommandExecutor.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/commands/CommandExecutor.java @@ -130,13 +130,13 @@ private static boolean executeCommandServerSide(Project project, Command command private static CompletableFuture getLanguageServerForCommand(Project project, Command command, URI documentUri, LanguageServersRegistry.LanguageServerDefinition languageServerDefinition) throws IOException { - Document document = LSPIJUtils.getDocument(documentUri); - if (document == null) { + VirtualFile file = LSPIJUtils.findResourceFor(documentUri); + if (file == null) { return null; } return LanguageServiceAccessor.getInstance(project) //TODO pass documentUri instead of document, but looks like that implies non-trivial refactoring - .getInitializedLanguageServer(document, languageServerDefinition, serverCapabilities -> { + .getInitializedLanguageServer(file, languageServerDefinition, serverCapabilities -> { ExecuteCommandOptions provider = serverCapabilities.getExecuteCommandProvider(); return provider != null && provider.getCommands().contains(command.getCommand()); }); diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/codelens/LSPCodelensInlayProvider.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/codelens/LSPCodelensInlayProvider.java index f8dbb5d2f..384e3100c 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/codelens/LSPCodelensInlayProvider.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/codelens/LSPCodelensInlayProvider.java @@ -23,6 +23,7 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.redhat.devtools.intellij.lsp4ij.AbstractLSPInlayProvider; @@ -74,12 +75,13 @@ public boolean collect(@NotNull PsiElement psiElement, @NotNull Editor editor, @ Document document = editor.getDocument(); final CancellationSupport cancellationSupport = new CancellationSupport(); try { - URI docURI = LSPIJUtils.toUri(document); + VirtualFile file = LSPIJUtils.getFile(psiElement); + URI docURI = LSPIJUtils.toUri(file); if (docURI != null) { CodeLensParams param = new CodeLensParams(new TextDocumentIdentifier(docURI.toString())); BlockingDeque> pairs = new LinkedBlockingDeque<>(); - CompletableFuture future = collect(document, project, param, pairs, cancellationSupport); + CompletableFuture future = collect(file, project, param, pairs, cancellationSupport); List>> codeLenses = createCodeLenses(document, pairs, future, cancellationSupport); codeLenses.stream() .collect(Collectors.groupingBy(p -> p.first)) @@ -122,9 +124,9 @@ private List>> createCodeLenses(Doc return codelenses; } - private CompletableFuture collect(Document document, Project project, CodeLensParams param, BlockingDeque> pairs, CancellationSupport cancellationSupport) { + private CompletableFuture collect(VirtualFile file, Project project, CodeLensParams param, BlockingDeque> pairs, CancellationSupport cancellationSupport) { return LanguageServiceAccessor.getInstance(project) - .getLanguageServers(document, capabilities -> capabilities.getCodeLensProvider() != null) + .getLanguageServers(file, capabilities -> capabilities.getCodeLensProvider() != null) .thenComposeAsync(languageServers -> cancellationSupport.execute(CompletableFuture.allOf(languageServers.stream() .map(languageServer -> diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionContributor.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionContributor.java index 11eeb7bd8..8d56cbf79 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionContributor.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionContributor.java @@ -22,6 +22,7 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; import com.redhat.devtools.intellij.lsp4ij.LanguageServerItem; @@ -59,7 +60,7 @@ public void fillCompletionVariants(@NotNull CompletionParameters parameters, @No final CancellationSupport cancellationSupport = new CancellationSupport(); try { - CompletableFuture> completionLanguageServersFuture = initiateLanguageServers(project, document); + CompletableFuture> completionLanguageServersFuture = initiateLanguageServers(project, file.getVirtualFile()); cancellationSupport.execute(completionLanguageServersFuture); ProgressManager.checkCanceled(); @@ -169,8 +170,8 @@ private static LookupElement createErrorProposal(int offset, Exception ex) { return LookupElementBuilder.create("Error while computing completion", ""); } - private static CompletableFuture> initiateLanguageServers(Project project, Document document) { - return LanguageServiceAccessor.getInstance(project).getLanguageServers(document, + private static CompletableFuture> initiateLanguageServers(Project project, VirtualFile file) { + return LanguageServiceAccessor.getInstance(project).getLanguageServers(file, capabilities -> { CompletionOptions provider = capabilities.getCompletionProvider(); if (provider != null) { diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentLink/LSPDocumentLinkAnnotator.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentLink/LSPDocumentLinkAnnotator.java index e7f40db9a..27ae5458e 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentLink/LSPDocumentLinkAnnotator.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentLink/LSPDocumentLinkAnnotator.java @@ -23,6 +23,7 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.redhat.devtools.intellij.lsp4ij.*; import com.redhat.devtools.intellij.lsp4ij.internal.CancellationSupport; @@ -46,19 +47,23 @@ public class LSPDocumentLinkAnnotator extends ExternalAnnotator, LanguageServerWrapper>> documentLinks = new LinkedBlockingDeque<>(); - CompletableFuture future = LanguageServiceAccessor.getInstance(editor.getProject()).getLanguageServers(document, + CompletableFuture future = LanguageServiceAccessor.getInstance(editor.getProject()).getLanguageServers(file, capabilities -> capabilities.getDocumentLinkProvider() != null) .thenAcceptAsync(languageServers -> cancellationSupport.execute(CompletableFuture.allOf(languageServers.stream() @@ -74,7 +79,7 @@ public LSPVirtualFileWrapper collectInformation(@NotNull PsiFile file, @NotNull ProgressManager.checkCanceled(); Pair, LanguageServerWrapper> links = documentLinks.poll(25, TimeUnit.MILLISECONDS); if (links != null) { - LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file.getVirtualFile()) + LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file) .updateDocumentLink(links.getFirst(), links.getSecond()); } } @@ -85,7 +90,7 @@ public LSPVirtualFileWrapper collectInformation(@NotNull PsiFile file, @NotNull LOGGER.warn(e.getLocalizedMessage(), e); Thread.currentThread().interrupt(); } - return LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file.getVirtualFile()); + return LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file); } @Override diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPDocumentationProvider.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPDocumentationProvider.java index 1aba08130..832b8b047 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPDocumentationProvider.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPDocumentationProvider.java @@ -85,7 +85,7 @@ public String generateDoc(@NotNull PsiElement element, @Nullable PsiElement orig return null; } editor = LSPIJUtils.editorForElement(originalElement); - VirtualFile file = originalElement.getContainingFile().getVirtualFile(); + VirtualFile file = LSPIJUtils.getFile(originalElement); if (LSPVirtualFileWrapper.hasWrapper(file)) { int targetOffset = getTargetOffset(originalElement); markupContent = LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file).getHoverContent(originalElement, targetOffset, editor); @@ -107,28 +107,6 @@ public String generateDoc(@NotNull PsiElement element, @Nullable PsiElement orig } } - @Nullable - public List getMarkupContents(PsiElement element, @Nullable PsiElement originalElement) { - if (element instanceof LSPPsiElementForLookupItem) { - // Show documentation for a given completion item in the "documentation popup" (see IJ Completion setting) - // (LSP textDocument/completion request) - return ((LSPPsiElementForLookupItem) element).getDocumentation(); - } - - Editor editor = LSPIJUtils.editorForElement(originalElement); - if (editor == null) { - return null; - } - - // Show documentation for a hovered element (LSP textDocument/hover request). - VirtualFile file = originalElement.getContainingFile().getVirtualFile(); - if (LSPVirtualFileWrapper.hasWrapper(file)) { - int targetOffset = getTargetOffset(originalElement); - return LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file).getHoverContent(originalElement, targetOffset, editor); - } - return null; - } - private static int getTargetOffset(PsiElement originalElement) { Integer targetOffset = originalElement.getUserData(TARGET_OFFSET_KEY); if (targetOffset != null) { diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPTextHoverForFile.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPTextHoverForFile.java index e8d271d51..00137af40 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPTextHoverForFile.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPTextHoverForFile.java @@ -12,8 +12,10 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; import com.redhat.devtools.intellij.lsp4ij.LanguageServiceAccessor; import com.redhat.devtools.intellij.lsp4ij.internal.CancellationSupport; @@ -81,13 +83,15 @@ private void initiateHoverRequest(PsiElement element, int offset) { this.previousCancellationSupport.cancel(); } PsiDocumentManager manager = PsiDocumentManager.getInstance(element.getProject()); - final Document document = manager.getDocument(element.getContainingFile()); + PsiFile psiFile = element.getContainingFile(); + VirtualFile file = LSPIJUtils.getFile(element); + final Document document = manager.getDocument(psiFile); if (offset != -1 && (this.lspRequest == null || !element.equals(this.lastElement) || offset != this.lastOffset)) { this.lastElement = element; this.lastOffset = offset; CancellationSupport cancellationSupport = new CancellationSupport(); this.lspRequest = LanguageServiceAccessor.getInstance(element.getProject()) - .getLanguageServers(document, capabilities -> isHoverCapable(capabilities)) + .getLanguageServers(file, capabilities -> isHoverCapable(capabilities)) .thenApplyAsync(languageServers -> // Async is very important here, otherwise the LS Client thread is in // deadlock and doesn't read bytes from LS languageServers.stream() diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandlerFactory.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandlerFactory.java index db28edcb8..829a29869 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandlerFactory.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandlerFactory.java @@ -49,11 +49,11 @@ public class LSPHighlightUsagesHandlerFactory implements HighlightUsagesHandlerF return targets.isEmpty() ? null : new LSPHighlightUsagesHandler(editor, file, targets); } - private List getTargets(Editor editor, PsiFile file) { + private List getTargets(Editor editor, PsiFile psiFile) { List elements = new ArrayList<>(); final CancellationSupport cancellationSupport = new CancellationSupport(); try { - int offset = TargetElementUtil.adjustOffset(file, editor.getDocument(), editor.getCaretModel().getOffset()); + int offset = TargetElementUtil.adjustOffset(psiFile, editor.getDocument(), editor.getCaretModel().getOffset()); Document document = editor.getDocument(); Position position; position = LSPIJUtils.toPosition(offset, document); @@ -67,7 +67,7 @@ private List getTargets(Editor editor, PsiFile file) { BlockingDeque highlights = new LinkedBlockingDeque<>(); CompletableFuture future = LanguageServiceAccessor.getInstance(editor.getProject()) - .getLanguageServers(document, capabilities -> LSPIJUtils.hasCapability(capabilities.getDocumentHighlightProvider())) + .getLanguageServers(psiFile.getVirtualFile(), capabilities -> LSPIJUtils.hasCapability(capabilities.getDocumentHighlightProvider())) .thenAcceptAsync(languageServers -> cancellationSupport.execute(CompletableFuture.allOf(languageServers.stream() .map(languageServer -> cancellationSupport.execute(languageServer.getServer().getTextDocumentService().documentHighlight(params))) diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/inlayhint/LSPInlayHintInlayProvider.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/inlayhint/LSPInlayHintInlayProvider.java index e06283d8c..da8f373ae 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/inlayhint/LSPInlayHintInlayProvider.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/inlayhint/LSPInlayHintInlayProvider.java @@ -23,6 +23,7 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.redhat.devtools.intellij.lsp4ij.AbstractLSPInlayProvider; @@ -41,7 +42,6 @@ import java.net.URI; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.BlockingDeque; import java.util.concurrent.CompletableFuture; @@ -76,11 +76,12 @@ public boolean collect(@NotNull PsiElement psiElement, @NotNull Editor editor, @ if (docURI == null) { return false; } + VirtualFile file = LSPIJUtils.getFile(psiFile); Range viewPortRange = getViewPortRange(editor); InlayHintParams param = new InlayHintParams(new TextDocumentIdentifier(docURI.toString()), viewPortRange); BlockingDeque> pairs = new LinkedBlockingDeque<>(); - CompletableFuture future = collect(psiElement.getProject(), document, param, pairs, cancellationSupport); + CompletableFuture future = collect(psiElement.getProject(), file, param, pairs, cancellationSupport); List>> inlayHints = createInlayHints(document, pairs, future); inlayHints.stream() .collect(Collectors.groupingBy(p -> p.first)) @@ -115,9 +116,9 @@ private List>> createInlayHints( return inlayHints; } - private CompletableFuture collect(@NotNull Project project, @NotNull Document document, InlayHintParams param, BlockingDeque> pairs, CancellationSupport cancellationSupport) { + private CompletableFuture collect(@NotNull Project project, @NotNull VirtualFile file, InlayHintParams param, BlockingDeque> pairs, CancellationSupport cancellationSupport) { return LanguageServiceAccessor.getInstance(project) - .getLanguageServers(document, capabilities -> capabilities.getInlayHintProvider() != null) + .getLanguageServers(file, capabilities -> capabilities.getInlayHintProvider() != null) .thenComposeAsync(languageServers -> cancellationSupport.execute(CompletableFuture.allOf(languageServers.stream() .map(languageServer -> cancellationSupport.execute(languageServer.getServer().getTextDocumentService().inlayHint(param)) diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/navigation/LSPGotoDeclarationHandler.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/navigation/LSPGotoDeclarationHandler.java index 1fef0ca9c..4d1c40614 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/navigation/LSPGotoDeclarationHandler.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/navigation/LSPGotoDeclarationHandler.java @@ -53,12 +53,13 @@ public class LSPGotoDeclarationHandler implements GotoDeclarationHandler { public PsiElement[] getGotoDeclarationTargets(@Nullable PsiElement sourceElement, int offset, Editor editor) { URI uri = LSPIJUtils.toUri(editor.getDocument()); if (uri != null) { + VirtualFile file = LSPIJUtils.getFile(sourceElement); DefinitionParams params = new DefinitionParams(LSPIJUtils.toTextDocumentIdentifier(uri), LSPIJUtils.toPosition(offset, editor.getDocument())); Set targets = new HashSet<>(); final CancellationSupport cancellationSupport = new CancellationSupport(); try { LanguageServiceAccessor.getInstance(editor.getProject()) - .getLanguageServers(editor.getDocument(), capabilities -> LSPIJUtils.hasCapability(capabilities.getDefinitionProvider())) + .getLanguageServers(file, capabilities -> LSPIJUtils.hasCapability(capabilities.getDefinitionProvider())) .thenComposeAsync(languageServers -> cancellationSupport.execute( CompletableFuture.allOf(