diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTUtils.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTUtils.java index f415004ccb..5e9d970c5c 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTUtils.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTUtils.java @@ -257,10 +257,13 @@ public static ICompilationUnit getFakeCompilationUnit(String uri) { } static ICompilationUnit getFakeCompilationUnit(URI uri, IProgressMonitor monitor) { - if (uri == null || !"file".equals(uri.getScheme()) || !uri.getPath().endsWith(".java")) { + if (uri == null || !"file".equals(uri.getScheme())) { return null; } java.nio.file.Path path = Paths.get(uri); + if (!isJavaFile(path)) { + return null; + } //Only support existing standalone java files if (!java.nio.file.Files.isReadable(path)) { return null; @@ -275,7 +278,7 @@ static ICompilationUnit getFakeCompilationUnit(URI uri, IProgressMonitor monitor IProject project = JavaLanguageServerPlugin.getProjectsManager().getDefaultProject(); if (project == null || !project.isAccessible()) { String fileName = path.getFileName().toString(); - if (fileName.endsWith(".java") || fileName.endsWith(".class")) { + if (JDTUtils.isJavaFile(fileName) || fileName.endsWith(".class")) { fileName = fileName.substring(0, fileName.lastIndexOf('.')); } WorkingCopyOwner owner = new WorkingCopyOwner() { @@ -657,6 +660,23 @@ private boolean find(IJavaElement element, final ASTNode[] nodes, SimpleName nod } } + public static boolean isJavaFile(java.nio.file.Path path) { + try { + return path != null && isJavaFile(path.toFile().getName()); + } catch (Exception e) { + JavaLanguageServerPlugin.logException(e.getMessage(), e); + } + return false; + } + + public static boolean isJavaFile(IPath path) { + return path != null && isJavaFile(path.lastSegment()); + } + + public static boolean isJavaFile(String name) { + return name != null && org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(name); + } + /** * Enumeration for determining the location of a Java element. Either returns * with the name range only, or the extended source range around the name of the diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/framework/protobuf/ProtobufSupport.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/framework/protobuf/ProtobufSupport.java index a1cf572eb3..23afa10427 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/framework/protobuf/ProtobufSupport.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/framework/protobuf/ProtobufSupport.java @@ -39,6 +39,7 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.ls.core.internal.ActionableNotification; +import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.JavaClientConnection.JavaLanguageClient; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.ProgressReport; @@ -94,7 +95,7 @@ public void onDidProjectsImported(IProgressMonitor monitor) { JavaLanguageServerPlugin.getProjectsManager().getConnection().sendActionableNotification(notification); } } - + /** * Find all the Protobuf source output directories of the given project. * @param project project. @@ -133,7 +134,7 @@ private boolean containsJavaFiles(Set generatedDirectories) { try (Stream walkStream = Files.walk(dir.toPath())) { boolean containsJavaFile = walkStream.filter(p -> p.toFile().isFile()).anyMatch(f -> { - return f.toString().endsWith(".java"); + return JDTUtils.isJavaFile(f); }); if (containsJavaFile) { @@ -194,7 +195,7 @@ private static void runGenerateProtobufTasks(String projectName, IProgressMonito try { build.get().withConnection(connection -> { connection.newBuild().forTasks("generateProto", "generateTestProto").run(); - return null; + return null; }, monitor); } catch (Exception e) { JavaLanguageServerPlugin.logException(e); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseDocumentLifeCycleHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseDocumentLifeCycleHandler.java index 083d87b2dc..03e9918d35 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseDocumentLifeCycleHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseDocumentLifeCycleHandler.java @@ -387,7 +387,9 @@ public static void handleFileRenameForTypeDeclaration(String documentUri) { IProblem renameProblem = desiredProblem.get(); String newName = renameProblem.getArguments()[1]; String oldName = cu.getElementName(); - String newUri = documentUri.replace(oldName, newName + ".java"); + int index = oldName.lastIndexOf("."); + String extension = index > 0 ? oldName.substring(index) : ".java"; + String newUri = documentUri.replace(oldName, newName + extension); WorkspaceEdit edit = new WorkspaceEdit(List.of(Either.forRight(new RenameFile(documentUri, newUri)))); edit.setChanges(Collections.emptyMap()); final boolean applyNow = JavaLanguageServerPlugin.getPreferencesManager().getClientPreferences().isWorkspaceApplyEditSupported(); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseInitHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseInitHandler.java index 7e0b803416..678ab4fcf0 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseInitHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseInitHandler.java @@ -19,13 +19,18 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.content.IContentType; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.internal.compiler.util.SuffixConstants; import org.eclipse.jdt.ls.core.internal.IConstants; import org.eclipse.jdt.ls.core.internal.JSONUtility; import org.eclipse.jdt.ls.core.internal.JVMConfigurator; @@ -57,7 +62,6 @@ public BaseInitHandler(ProjectsManager projectsManager, PreferenceManager prefer this.projectsManager = projectsManager; } - @SuppressWarnings("unchecked") public InitializeResult initialize(InitializeParams param) { logInfo("Initializing Java Language Server " + JavaLanguageServerPlugin.getVersion()); InitializeResult result = new InitializeResult(); @@ -70,6 +74,7 @@ public InitializeResult initialize(InitializeParams param) { return result; } + @SuppressWarnings("unchecked") public Map handleInitializationOptions(InitializeParams param) { Map initializationOptions = this.getInitializationOptions(param); Map extendedClientCapabilities = getInitializationOption(initializationOptions, "extendedClientCapabilities", Map.class); @@ -109,7 +114,6 @@ public InitializeResult initialize(InitializeParams param) { rootPaths.add(workspaceLocation); } if (initializationOptions.get(SETTINGS_KEY) instanceof Map settings) { - @SuppressWarnings("unchecked") Preferences prefs = Preferences.createFrom((Map) settings); prefs.setRootPaths(rootPaths); preferenceManager.update(prefs); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/FileEventHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/FileEventHandler.java index 6514deaf98..b58e6940d8 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/FileEventHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/FileEventHandler.java @@ -305,7 +305,7 @@ private static boolean isFileNameRenameEvent(FileRename event) { } return (oldPath.toFile().isFile() || newPath.toFile().isFile()) - && oldPath.lastSegment().endsWith(".java") && newPath.lastSegment().endsWith(".java") + && JDTUtils.isJavaFile(oldPath.lastSegment()) && JDTUtils.isJavaFile(newPath.lastSegment()) && Objects.equals(oldPath.removeLastSegments(1), newPath.removeLastSegments(1)); } @@ -331,7 +331,7 @@ private static boolean isMoveEvent(FileRename event) { return false; } - return oldPath.toFile().isFile() && oldPath.lastSegment().endsWith(".java") + return oldPath.toFile().isFile() && JDTUtils.isJavaFile(oldPath.lastSegment()) && Objects.equals(oldPath.lastSegment(), newPath.lastSegment()); } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceEventsHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceEventsHandler.java index 00791774fa..f0908b4db6 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceEventsHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/WorkspaceEventsHandler.java @@ -174,7 +174,7 @@ private void cleanUpDiagnostics(String uri) { private void discardWorkingCopies(String parentUri) { IPath parentPath = ResourceUtils.filePathFromURI(parentUri); - if (parentPath != null && !parentPath.lastSegment().endsWith(".java")) { + if (parentPath != null && !JDTUtils.isJavaFile(parentPath)) { ICompilationUnit[] workingCopies = JavaCore.getWorkingCopies(null); for (ICompilationUnit workingCopy : workingCopies) { IResource resource = workingCopy.getResource(); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/InvisibleProjectImporter.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/InvisibleProjectImporter.java index 0fe42702a7..e07fe5fc7e 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/InvisibleProjectImporter.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/InvisibleProjectImporter.java @@ -520,7 +520,7 @@ private static File findNearbyNonEmptyFile(File nioFile) throws IOException { try (Stream walk = Files.walk(directory, 1)) { Optional found = walk.filter(Files::isRegularFile).filter(file -> { try { - return file.toString().endsWith(".java") && !Objects.equals(nioFile.getName(), file.toFile().getName()) && Files.size(file) > 0; + return JDTUtils.isJavaFile(file) && !Objects.equals(nioFile.getName(), file.toFile().getName()) && Files.size(file) > 0; } catch (IOException e) { return false; } @@ -672,7 +672,7 @@ public FileVisitResult preVisitDirectory(java.nio.file.Path dirPath, BasicFileAt return FileVisitResult.TERMINATE; } - if (javaFile == null && f.getName().endsWith(".java")) { + if (javaFile == null && JDTUtils.isJavaFile(f.getName())) { javaFile = f; } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/PreferenceManager.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/PreferenceManager.java index edabdd257b..8747a2a549 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/PreferenceManager.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/PreferenceManager.java @@ -12,13 +12,11 @@ *******************************************************************************/ package org.eclipse.jdt.ls.core.internal.preferences; -import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; -import java.net.URI; +import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.List; @@ -28,13 +26,13 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.core.runtime.content.IContentType; import org.eclipse.core.runtime.preferences.DefaultScope; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; @@ -43,6 +41,7 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.manipulation.CodeStyleConfiguration; import org.eclipse.jdt.core.manipulation.JavaManipulation; +import org.eclipse.jdt.internal.compiler.util.SuffixConstants; import org.eclipse.jdt.internal.core.manipulation.CodeTemplateContextType; import org.eclipse.jdt.internal.core.manipulation.CodeTemplateContextType.CodeTemplateVariableResolver; import org.eclipse.jdt.internal.core.manipulation.JavaManipulationMessages; @@ -54,7 +53,6 @@ import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil; import org.eclipse.jdt.ls.core.internal.IConstants; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; -import org.eclipse.jdt.ls.core.internal.ProjectUtils; import org.eclipse.jdt.ls.core.internal.ResourceUtils; import org.eclipse.jdt.ls.core.internal.StatusFactory; import org.eclipse.jdt.ls.core.internal.handlers.BaseDiagnosticsHandler; @@ -258,6 +256,49 @@ public void update(Preferences preferences) { JavaLanguageServerPlugin.getInstance().getClientConnection().publishDiagnostics(diagnostics); } } + if (!oldPreferences.getFilesAssociations().equals(preferences.getFilesAssociations())) { + configureContentTypes(preferences); + } + } + + // only for test purpose + public static void configureContentTypes(Preferences preferences) { + if (preferences != null && preferences.getFilesAssociations() != null) { + IContentType javaSourceContentType = Platform.getContentTypeManager().getContentType(JavaCore.JAVA_SOURCE_CONTENT_TYPE); + if (javaSourceContentType != null) { + List toRemove = new ArrayList<>(); + String[] specs = javaSourceContentType.getFileSpecs(IContentType.FILE_EXTENSION_SPEC); + for (String spec : specs) { + if (!SuffixConstants.EXTENSION_java.equals(spec)) { + toRemove.add(spec); + } + } + List toAdd = new ArrayList<>(); + for (String spec : preferences.getFilesAssociations()) { + if (toRemove.contains(spec)) { + toRemove.remove(spec); + } else { + toAdd.add(spec); + } + } + for (String spec : toRemove) { + try { + javaSourceContentType.removeFileSpec(spec, IContentType.FILE_EXTENSION_SPEC); + } catch (CoreException e) { + JavaLanguageServerPlugin.logException(e); + } + } + for (String spec : toAdd) { + try { + javaSourceContentType.addFileSpec(spec, IContentType.FILE_EXTENSION_SPEC); + } catch (CoreException e) { + JavaLanguageServerPlugin.logException(e); + } + } + } else { + JavaLanguageServerPlugin.logInfo("There is no java source content type."); + } + } } private void preferencesChanged(Preferences oldPreferences, Preferences newPreferences) { diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java index 538c95a0c6..4835a8b82c 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java @@ -120,6 +120,11 @@ public class Preferences { * Tab Size */ public static final String JAVA_CONFIGURATION_TABSIZE = "java.format.tabSize"; + + /** + * Files associations to languages + */ + public static final String JAVA_CONFIGURATION_ASSOCIATIONS = "java.associations"; /** * Specifies Java Execution Environments. */ @@ -589,6 +594,7 @@ public class Preferences { private static Map> nonnullClasspathStorage = new HashMap<>(); private static Map> nullableClasspathStorage = new HashMap<>(); private static Map> nonnullbydefaultClasspathStorage = new HashMap<>(); + private List filesAssociations = new ArrayList<>(); private Map configuration; private Severity incompleteClasspathSeverity; @@ -1330,9 +1336,36 @@ public static Preferences createFrom(Map configuration) { prefs.setChainCompletionEnabled(chainCompletionEnabled); List diagnosticFilter = getList(configuration, JAVA_DIAGNOSTIC_FILER, Collections.emptyList()); prefs.setDiagnosticFilter(diagnosticFilter); + Object object = getValue(configuration, JAVA_CONFIGURATION_ASSOCIATIONS); + Set associations = new HashSet<>(); + if (object instanceof Map map) { + try { + Map element = map; + element.forEach((k, v) -> { + // Java LS only support a small subset of the glob pattern syntax (*.xxx) + if ("java".equals(v) && validateFilePattern(k)) { + associations.add(k.substring(2)); + } + }); + } catch (Exception e) { + JavaLanguageServerPlugin.logException(e); + } + } + prefs.setFilesAssociations(new ArrayList<>(associations)); return prefs; } + private static boolean validateFilePattern(String filename) { + if (filename != null && filename.startsWith("*.") && filename.length() > 2) { + String ext = filename.substring(2); + if (!ext.contains("?") && !ext.contains("*")) { + return true; + } + } + JavaLanguageServerPlugin.logInfo("Pattern '" + filename + "' is not supported."); + return false; + } + /** * Sets the new value of the enabled clean ups. * @@ -2582,4 +2615,12 @@ public List getDiagnosticFilter() { public void setDiagnosticFilter(List diagnosticFilter) { this.diagnosticFilter = diagnosticFilter; } + + public List getFilesAssociations() { + return filesAssociations; + } + + public void setFilesAssociations(List filesAssociations) { + this.filesAssociations = filesAssociations; + } } diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandlerTest.java index d6c06dd0a0..860f5d3d7b 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandlerTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/InitHandlerTest.java @@ -56,6 +56,7 @@ import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.core.util.Util; import org.eclipse.jdt.launching.IVMInstall; import org.eclipse.jdt.launching.IVMInstallChangedListener; import org.eclipse.jdt.launching.JavaRuntime; @@ -68,6 +69,7 @@ import org.eclipse.jdt.ls.core.internal.managers.AbstractProjectsManagerBasedTest; import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager; import org.eclipse.jdt.ls.core.internal.preferences.ClientPreferences; +import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager; import org.eclipse.jdt.ls.core.internal.preferences.Preferences; import org.eclipse.lsp4j.ClientCapabilities; import org.eclipse.lsp4j.DidChangeConfigurationCapabilities; @@ -462,6 +464,24 @@ public void testMissingResourceOperations() throws Exception { assertFalse(preferences.isResourceOperationSupported()); } + // https://github.com/eclipse-jdtls/eclipse.jdt.ls/issues/3222 + @Test + public void testFilesAssociations() throws Exception { + try { + ClientPreferences mockCapabilies = mock(ClientPreferences.class); + when(preferenceManager.getClientPreferences()).thenReturn(mockCapabilies); + List fileAssociations = new ArrayList<>(); + fileAssociations.add("maxj"); + preferences.setFilesAssociations(fileAssociations); + PreferenceManager.configureContentTypes(preferences); + assertTrue(Util.isJavaLikeFileName("Test.maxj")); + } finally { + preferences.getFilesAssociations().clear(); + PreferenceManager.configureContentTypes(preferences); + assertFalse(Util.isJavaLikeFileName("Test.maxj")); + } + } + private void removeExclusionPattern(IJavaProject javaProject) throws JavaModelException { IClasspathEntry[] classpath = javaProject.getRawClasspath(); for (int i = 0; i < classpath.length; i++) {