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 a686ad8b2..ffb9804c1 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 @@ -99,10 +99,8 @@ public List getMarkupContents(PsiElement element, @Nullable PsiEl // (LSP textDocument/completion request) return ((LSPPsiElementForLookupItem) element).getDocumentation(); } - if (originalElement == null || !Objects.equals(element.getContainingFile(), originalElement.getContainingFile())) { - return null; - } - Editor editor = LSPIJUtils.editorForElement(element); + + Editor editor = LSPIJUtils.editorForElement(originalElement); if (editor == null) { return null; } @@ -111,7 +109,7 @@ public List getMarkupContents(PsiElement element, @Nullable PsiEl VirtualFile file = originalElement.getContainingFile().getVirtualFile(); if (LSPVirtualFileWrapper.hasWrapper(file)) { int targetOffset = getTargetOffset(originalElement); - return LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file).getHoverContent(element, targetOffset, editor); + return LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file).getHoverContent(originalElement, targetOffset, editor); } return null; } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/AbstractConfigSource.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/AbstractConfigSource.java index d89e1d8c2..cf8610d1e 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/AbstractConfigSource.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/AbstractConfigSource.java @@ -1,12 +1,12 @@ /******************************************************************************* -* Copyright (c) 2020 Red Hat Inc. and others. -* All rights reserved. This program and the accompanying materials -* which accompanies this distribution, and is available at -* https://www.eclipse.org/legal/epl-v20.html -* -* Contributors: -* Red Hat Inc. - initial API and implementation -*******************************************************************************/ + * Copyright (c) 2020 Red Hat Inc. and others. + * All rights reserved. This program and the accompanying materials + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ package com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project; import com.intellij.openapi.compiler.CompilerPaths; @@ -14,6 +14,8 @@ import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,200 +26,217 @@ /** * Abstract class for config file. - * - * @author Angelo ZERR * * @param the config model (ex: Properties for *.properties file) + * @author Angelo ZERR * @see https://github.com/redhat-developer/quarkus-ls/blob/master/microprofile.jdt/com.redhat.microprofile.jdt.core/src/main/java/com/redhat/microprofile/jdt/internal/core/project/AbstractConfigSource.java */ public abstract class AbstractConfigSource implements IConfigSource { - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractConfigSource.class); - - private static final int DEFAULT_ORDINAL = 100; - - private final String configFileName; - - private final String profile; - - private final int ordinal; - - private final Module javaProject; - private VirtualFile outputConfigFile; - private VirtualFile sourceConfigFile; - private long lastModified = -1L; - private T config; - - private Map> propertyInformations; - - public AbstractConfigSource(String configFileName, int ordinal, Module javaProject) { - this(configFileName, null, ordinal, javaProject); - } - - public AbstractConfigSource(String configFileName, Module javaProject) { - this(configFileName, null, javaProject); - } - - public AbstractConfigSource(String configFileName, String profile, Module javaProject) { - this(configFileName, profile, DEFAULT_ORDINAL, javaProject); - } - - public AbstractConfigSource(String configFileName, String profile, int ordinal, Module javaProject) { - this.configFileName = configFileName; - this.profile = profile; - this.ordinal = ordinal; - this.javaProject = javaProject; - // load config file to udpate some fields like lastModified, config instance - // which must be updated when the config source is created. It's important that - // those fields are initialized here (and not in lazy mode) to prevent from - // multi - // thread - // context. - init(); - } - - private void init() { - T config = getConfig(); - if (config != null && propertyInformations == null) { - propertyInformations = loadPropertyInformations(); - } - } - - /** - * Returns the target/classes/$configFile and null otherwise. - * - *

- * Using this file instead of using src/main/resources/$configFile gives the - * capability to get the filtered value. - *

- * - * @return the target/classes/$configFile and null otherwise. - */ - private VirtualFile getOutputConfigFile() { - if (outputConfigFile != null && outputConfigFile.exists()) { - return outputConfigFile; - } - sourceConfigFile = null; - outputConfigFile = null; - if (javaProject.isLoaded()) { - VirtualFile[] sourceRoots = ModuleRootManager.getInstance(javaProject).getSourceRoots(false); - for (VirtualFile sourceRoot : sourceRoots) { - VirtualFile file = sourceRoot.findFileByRelativePath(configFileName); - if (file != null && file.exists()) { - sourceConfigFile = file; - outputConfigFile = file; - } - } - VirtualFile output = CompilerPaths.getModuleOutputDirectory(javaProject, false); - if (output != null) { - output = output.findFileByRelativePath(configFileName); - if (output != null) { - if (sourceConfigFile == null || output.getModificationStamp() >= sourceConfigFile.getModificationStamp()) { - outputConfigFile = output; - } - } - } - return outputConfigFile; - } - return null; - } - - @Override - public String getConfigFileName() { - return configFileName; - } - - @Override - public String getProfile() { - return profile; - } - - @Override - public int getOrdinal() { - return ordinal; - } - - @Override - public String getSourceConfigFileURI() { - getOutputConfigFile(); - if (sourceConfigFile != null) { - String uri = sourceConfigFile.getUrl(); - return fixURI(uri); - } - return null; - } - - private static String fixURI(String uri) { - return VfsUtil.toUri(uri).toString(); - } - - /** - * Returns the loaded config and null otherwise. - * - * @return the loaded config and null otherwise - */ - protected final T getConfig() { - VirtualFile configFile = getOutputConfigFile(); - if (configFile == null) { - reset(); - return null; - } - try { - long currentLastModified = configFile.getModificationStamp(); - if (currentLastModified != lastModified) { - reset(); - try (InputStream input = configFile.getInputStream()) { - config = loadConfig(input); - lastModified = configFile.getModificationStamp(); - } catch (IOException e) { - reset(); - LOGGER.error("Error while loading properties from '" + configFile + "'.", e); - } - } - } catch (RuntimeException e1) { - LOGGER.error("Error while getting last modified time for '" + configFile + "'.", e1); - } - return config; - } - - @Override - public Integer getPropertyAsInt(String key) { - String property = getProperty(key); - if (property != null && !property.trim().isEmpty()) { - try { - return Integer.parseInt(property.trim()); - } catch (NumberFormatException e) { - LOGGER.error("Error while converting '" + property.trim() + "' as Integer for key '" + key + "'", e); - return null; - } - } - return null; - } - - private void reset() { - config = null; - propertyInformations = null; - } - - @Override - public List getPropertyInformations(String propertyKey) { - init(); - return propertyInformations != null ? propertyInformations.get(propertyKey) : null; - } - - /** - * Load the config model from the given input stream input. - * - * @param input the input stream - * @return he config model from the given input stream input. - * @throws IOException - */ - protected abstract T loadConfig(InputStream input) throws IOException; - - /** - * Load the property informations. - * - * @return the property information. - */ - protected abstract Map> loadPropertyInformations(); + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractConfigSource.class); + + private static final int DEFAULT_ORDINAL = 100; + + private final String configFileName; + + private final String profile; + + private final int ordinal; + + private final Module javaProject; + private VirtualFile outputConfigFile; + private VirtualFile sourceConfigFile; + private long lastModified = -1L; + private T config; + + private Map> propertyInformations; + + public AbstractConfigSource(String configFileName, int ordinal, Module javaProject) { + this(configFileName, null, ordinal, javaProject); + } + + public AbstractConfigSource(String configFileName, Module javaProject) { + this(configFileName, null, javaProject); + } + + public AbstractConfigSource(String configFileName, String profile, Module javaProject) { + this(configFileName, profile, DEFAULT_ORDINAL, javaProject); + } + + public AbstractConfigSource(String configFileName, String profile, int ordinal, Module javaProject) { + this.configFileName = configFileName; + this.profile = profile; + this.ordinal = ordinal; + this.javaProject = javaProject; + // load config file to udpate some fields like lastModified, config instance + // which must be updated when the config source is created. It's important that + // those fields are initialized here (and not in lazy mode) to prevent from + // multi + // thread + // context. + init(); + } + + private void init() { + T config = getConfig(); + if (config != null && propertyInformations == null) { + propertyInformations = loadPropertyInformations(); + } + } + + /** + * Returns the target/classes/$configFile and null otherwise. + * + *

+ * Using this file instead of using src/main/resources/$configFile gives the + * capability to get the filtered value. + *

+ * + * @return the target/classes/$configFile and null otherwise. + */ + private VirtualFile getOutputConfigFile() { + if (outputConfigFile != null && outputConfigFile.exists()) { + return outputConfigFile; + } + sourceConfigFile = null; + outputConfigFile = null; + if (javaProject.isLoaded()) { + VirtualFile[] sourceRoots = ModuleRootManager.getInstance(javaProject).getSourceRoots(false); + for (VirtualFile sourceRoot : sourceRoots) { + VirtualFile file = sourceRoot.findFileByRelativePath(configFileName); + if (file != null && file.exists()) { + sourceConfigFile = file; + outputConfigFile = file; + } + } + VirtualFile output = CompilerPaths.getModuleOutputDirectory(javaProject, false); + if (output != null) { + output = output.findFileByRelativePath(configFileName); + if (output != null) { + if (sourceConfigFile == null || output.getModificationStamp() >= sourceConfigFile.getModificationStamp()) { + outputConfigFile = output; + } + } + } + return outputConfigFile; + } + return null; + } + + @Override + public String getConfigFileName() { + return configFileName; + } + + @Override + public String getProfile() { + return profile; + } + + @Override + public int getOrdinal() { + return ordinal; + } + + @Override + public String getSourceConfigFileURI() { + getOutputConfigFile(); + if (sourceConfigFile != null) { + String uri = sourceConfigFile.getUrl(); + return fixURI(uri); + } + return null; + } + + @Override + public boolean isSourceConfigFile(VirtualFile file) { + return file.equals(sourceConfigFile); + } + + private static String fixURI(String uri) { + return VfsUtil.toUri(uri).toString(); + } + + /** + * Returns the loaded config and null otherwise. + * + * @return the loaded config and null otherwise + */ + protected final T getConfig() { + VirtualFile configFile = getOutputConfigFile(); + if (configFile == null) { + reset(); + return null; + } + try { + long currentLastModified = configFile.getModificationStamp(); + if (currentLastModified > lastModified) { + reset(); + try (InputStream input = configFile.getInputStream()) { + config = loadConfig(input); + lastModified = configFile.getModificationStamp(); + } catch (IOException e) { + reset(); + LOGGER.error("Error while loading properties from '" + configFile + "'.", e); + } + } + } catch (RuntimeException e1) { + LOGGER.error("Error while getting last modified time for '" + configFile + "'.", e1); + } + return config; + } + + @Override + public void reload(PsiFile file) { + reset(); + String content = file.getText(); + try (InputStream input = IOUtils.toInputStream(content)) { + config = loadConfig(input); + lastModified = System.currentTimeMillis(); + } catch (IOException e) { + reset(); + LOGGER.error("Error while loading properties from '" + sourceConfigFile + "'.", e); + } + } + + @Override + public Integer getPropertyAsInt(String key) { + String property = getProperty(key); + if (property != null && !property.trim().isEmpty()) { + try { + return Integer.parseInt(property.trim()); + } catch (NumberFormatException e) { + LOGGER.error("Error while converting '" + property.trim() + "' as Integer for key '" + key + "'", e); + return null; + } + } + return null; + } + + private void reset() { + config = null; + propertyInformations = null; + } + + @Override + public List getPropertyInformations(String propertyKey) { + init(); + return propertyInformations != null ? propertyInformations.get(propertyKey) : null; + } + + /** + * Load the config model from the given input stream input. + * + * @param input the input stream + * @return he config model from the given input stream input. + * @throws IOException + */ + protected abstract T loadConfig(InputStream input) throws IOException; + + /** + * Load the property informations. + * + * @return the property information. + */ + protected abstract Map> loadPropertyInformations(); } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/IConfigSource.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/IConfigSource.java index c09c70497..96cd5edd1 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/IConfigSource.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/IConfigSource.java @@ -10,6 +10,7 @@ package com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; import java.util.List; import java.util.Map; @@ -60,6 +61,8 @@ public interface IConfigSource { */ String getSourceConfigFileURI(); + boolean isSourceConfigFile(VirtualFile file); + /** * Returns a list of all values for properties and different profiles that are * defined in this config source. @@ -100,4 +103,5 @@ public interface IConfigSource { */ Set getAllKeys(); + void reload(PsiFile file); } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProject.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProject.java index de88dd155..1fd13b0e9 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProject.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProject.java @@ -9,9 +9,13 @@ *******************************************************************************/ package com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.compiler.CompilerPaths; import com.intellij.openapi.module.Module; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; import com.redhat.devtools.intellij.lsp4mp4ij.psi.internal.core.project.ConfigSourcePropertiesProvider; import org.eclipse.lsp4mp.commons.utils.ConfigSourcePropertiesProviderUtils; import org.eclipse.lsp4mp.commons.utils.IConfigSourcePropertiesProvider; @@ -190,8 +194,19 @@ public List getConfigSources() { * Evict the config sources cache and related cached information as soon as one * of properties, yaml file is saved. */ - public void evictConfigSourcesCache() { - configSources = null; + public void evictConfigSourcesCache(VirtualFile file) { + List configSources = getConfigSources(); + for (IConfigSource configSource: configSources) { + if (configSource.isSourceConfigFile(file)) { + ReadAction.compute(() -> { + PsiFile psiFile = PsiManager.getInstance(javaProject.getProject()).findFile(file); + if (psiFile != null) { + configSource.reload(psiFile); + } + return null; + }); + } + } propertyValueExpander = null; aggregatedPropertiesProvider = null; } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java index 117a82920..b4e2f8af7 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java @@ -63,7 +63,7 @@ public void sourceFilesChanged(Set> sources) { Module javaProject = pair.getSecond(); PsiMicroProfileProject mpProject = getJDTMicroProfileProject(javaProject); if (mpProject != null) { - mpProject.evictConfigSourcesCache(); + mpProject.evictConfigSourcesCache(file); } } } diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java index 58997c435..e4719bace 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java @@ -14,12 +14,20 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupActivity; import com.redhat.devtools.intellij.lsp4mp4ij.classpath.ClasspathResourceChangedManager; +import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project.PsiMicroProfileProjectManager; import org.jetbrains.annotations.NotNull; public class QuarkusPostStartupActivity implements StartupActivity, DumbAware { @Override public void runActivity(@NotNull Project project) { ClasspathResourceChangedManager.getInstance(project); + // Force the instantiation of the manager to be sure that classpath listener + // are registered before QuarkusLanguageClient classpath listener + // When an application.properties changed + // - the manager need to update the properties cache + // - and after the QuarkusLanguageClient throws an event to trigger Java validation. + // As java validation requires the properties cache, it needs that cache must be updated before. + PsiMicroProfileProjectManager.getInstance(project); QuarkusProjectService.getInstance(project); } }