project model: support excluding file from content roots by pattern (IDEA-127753)

Currently only file/directory names, not relative paths, are matched.
This commit is contained in:
nik
2017-05-25 15:02:30 +03:00
parent bc4893474d
commit b90d1eafbb
16 changed files with 339 additions and 32 deletions
@@ -0,0 +1,79 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.roots.impl;
import com.intellij.ide.projectView.actions.MarkRootActionBase;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.Arrays;
/**
* @author nik
*/
public class DirectoryIndexForExcludePatternsTest extends DirectoryIndexTestCase {
private VirtualFile myContentRoot;
@Override
protected void setUp() throws Exception {
super.setUp();
final File root = createTempDirectory();
myContentRoot = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(root);
ModuleRootModificationUtil.addContentRoot(myModule, myContentRoot.getPath());
}
public void testExcludeFileByExtension() {
addExcludePattern("*.txt");
VirtualFile dir = createChildDirectory(myContentRoot, "dir");
VirtualFile txt1 = createChildData(myContentRoot, "a.txt");
VirtualFile txt2 = createChildData(dir, "a.txt");
VirtualFile java1 = createChildData(myContentRoot, "A.java");
VirtualFile java2 = createChildData(dir, "A.java");
assertExcluded(txt1, myModule);
assertExcluded(txt2, myModule);
assertNotExcluded(java1);
assertNotExcluded(java2);
assertIteratedContent(myModule, Arrays.asList(java1, java2), Arrays.asList(txt1, txt2));
}
public void testExcludeDirectoryByName() {
addExcludePattern("exc");
VirtualFile dir = createChildDirectory(myContentRoot, "dir");
VirtualFile exc = createChildDirectory(myContentRoot, "exc");
VirtualFile dirUnderExc = createChildDirectory(exc, "dir2");
VirtualFile excUnderDir = createChildDirectory(dir, "exc");
VirtualFile underExc = createChildData(exc, "a.txt");
VirtualFile underDir = createChildData(dir, "a.txt");
VirtualFile underExcUnderDir = createChildData(excUnderDir, "a.txt");
VirtualFile underDirUnderExc = createChildData(dirUnderExc, "a.txt");
assertExcluded(exc, myModule);
assertExcluded(underExc, myModule);
assertExcluded(dirUnderExc, myModule);
assertExcluded(underDirUnderExc, myModule);
assertExcluded(underExcUnderDir, myModule);
assertNotExcluded(dir);
assertNotExcluded(underDir);
assertIteratedContent(myModule, Arrays.asList(underDir), Arrays.asList(underExc, underDirUnderExc, underExcUnderDir));
}
private void addExcludePattern(@NotNull String pattern) {
ModuleRootModificationUtil.updateModel(myModule,
model -> MarkRootActionBase.findContentEntry(model, myContentRoot).addExcludePattern(pattern));
}
}
@@ -18,13 +18,20 @@ package com.intellij.roots;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.impl.ContentEntryImpl;
import com.intellij.openapi.roots.impl.ModuleRootManagerComponent;
import com.intellij.openapi.roots.impl.ModuleRootManagerImpl;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.IdeaTestCase;
import com.intellij.testFramework.PsiTestUtil;
import org.jdom.Element;
import org.jdom.JDOMException;
import java.io.IOException;
import static com.intellij.testFramework.assertions.Assertions.assertThat;
public class ManagingContentRootsTest extends IdeaTestCase {
private VirtualFile dir;
@@ -90,6 +97,36 @@ public class ManagingContentRootsTest extends IdeaTestCase {
});
}
public void testAddExcludePattern() {
PsiTestUtil.addContentRoot(myModule, dir);
ModuleRootModificationUtil.updateModel(myModule, model -> findContentEntry(dir.getUrl(), model).addExcludePattern("*.txt"));
assertEquals("*.txt", assertOneElement(findContentEntry(dir.getUrl()).getExcludePatterns()));
ModuleRootModificationUtil.updateModel(myModule, model -> findContentEntry(dir.getUrl(), model).removeExcludePattern("*.txt"));
assertEmpty(findContentEntry(dir.getUrl()).getExcludePatterns());
}
public void testExcludePatternSerialization() {
PsiTestUtil.addContentRoot(myModule, dir);
ModuleRootModificationUtil.updateModel(myModule, model -> findContentEntry(dir.getUrl(), model).addExcludePattern("exc"));
Element entry = new Element(ContentEntryImpl.ELEMENT_NAME);
((ContentEntryImpl)findContentEntry(dir.getUrl())).writeExternal(entry);
String elementText = "<content url=\"" + dir.getUrl() + "\">\n" +
" <excludePattern pattern=\"exc\" />\n" +
"</content>";
assertThat(entry).isEqualTo(elementText);
}
public void testExcludePatternDeserialization() throws IOException, JDOMException {
ModuleRootManagerImpl.ModuleRootManagerState state = new ModuleRootManagerImpl.ModuleRootManagerState();
state.readExternal(JDOMUtil.load("<component name=\"NewModuleRootManager\">" +
" <content url=\"" + dir.getUrl() + "\">\n" +
" <excludePattern pattern=\"exc\" />\n" +
" </content>" +
"</component>\n"));
((ModuleRootManagerComponent)getRootManager()).loadState(state);
assertEquals("exc", assertOneElement(findContentEntry(dir.getUrl()).getExcludePatterns()));
}
private ContentEntry findContentEntry(String url) {
return findContentEntry(url, getRootManager());
}
@@ -102,7 +139,7 @@ public class ManagingContentRootsTest extends IdeaTestCase {
}
private void addContentRoot(final String path) {
ApplicationManager.getApplication().runWriteAction(() -> ModuleRootModificationUtil.addContentRoot(getModule(), path));
ModuleRootModificationUtil.addContentRoot(getModule(), path);
}
private ModuleRootManager getRootManager() {
@@ -134,7 +134,7 @@ private class DefaultWebServerRootsProvider : WebServerRootsProvider() {
val directoryIndex = DirectoryIndex.getInstance(project)
val info = directoryIndex.getInfoForFile(file)
// we serve excluded files
if (!info.isExcluded && !info.isInProject) {
if (!info.isExcluded(file) && !info.isInProject(file)) {
// javadoc jars is "not under project", but actually is, so, let's check library or SDK
if (file.fileSystem == JarFileSystem.getInstance()) getInfoForDocJar(file, project) else null
}
@@ -226,7 +226,7 @@ public class ProjectViewDirectoryHelper {
DirectoryInfo info = myIndex.getInfoForFile(parent);
if (!module.equals(info.getModule())) return true;
//show inner content root separately only if it won't be shown under outer content root
return info.isExcluded() && !shouldShowExcludedFiles(settings);
return info.isExcluded(parent) && !shouldShowExcludedFiles(settings);
});
}
@@ -279,7 +279,7 @@ public class ProjectViewDirectoryHelper {
private boolean shouldBeShown(VirtualFile dir, ViewSettings settings) {
DirectoryInfo directoryInfo = myIndex.getInfoForFile(dir);
return directoryInfo.isInProject() || shouldShowExcludedFiles(settings) && directoryInfo.isExcluded();
return directoryInfo.isInProject(dir) || shouldShowExcludedFiles(settings) && directoryInfo.isExcluded(dir);
}
private static boolean shouldShowExcludedFiles(ViewSettings settings) {
@@ -132,7 +132,7 @@ public class DirectoryIndexImpl extends DirectoryIndex {
@Override
public DirectoryInfo getInfoForDirectory(@NotNull VirtualFile dir) {
DirectoryInfo info = getInfoForFile(dir);
return info.isInProject() ? info : null;
return info.isInProject(dir) ? info : null;
}
@NotNull
@@ -193,4 +193,15 @@ public interface ContentEntry extends Synthetic {
boolean removeExcludeFolder(@NotNull String url);
void clearExcludeFolders();
/**
* Returns patterns for names of files which should be excluded from this content root. If name of a file under this content root matches
* any of the patterns it'll be excluded from the module, if name of a directory matches any of the patterns the directory and all of its
* contents will be excluded. '?' and '*' wildcards are supported.
*/
@NotNull
List<String> getExcludePatterns();
void addExcludePattern(@NotNull String pattern);
void removeExcludePattern(@NotNull String pattern);
}
@@ -55,6 +55,7 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE
private final Set<SourceFolder> mySourceFolders = new LinkedHashSet<>();
private final Set<ExcludeFolder> myExcludeFolders = new TreeSet<>(ContentFolderComparator.INSTANCE);
@NonNls public static final String URL_ATTRIBUTE = JpsModuleRootModelSerializer.URL_ATTRIBUTE;
private List<String> myExcludePatterns;
ContentEntryImpl(@NotNull VirtualFile file, @NotNull RootModelImpl m) {
this(file.getUrl(), m);
@@ -69,6 +70,9 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE
this(getUrlFrom(e), m);
initSourceFolders(e);
initExcludeFolders(e);
for (Element element : e.getChildren(JpsModuleRootModelSerializer.EXCLUDE_PATTERN_TAG)) {
addExcludePattern(element.getAttributeValue(JpsModuleRootModelSerializer.EXCLUDE_PATTERN_ATTRIBUTE));
}
}
private static String getUrlFrom(@NotNull Element e) throws InvalidDataException {
@@ -312,6 +316,30 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE
myExcludeFolders.clear();
}
@NotNull
@Override
public List<String> getExcludePatterns() {
return myExcludePatterns != null ? myExcludePatterns : Collections.emptyList();
}
@Override
public void addExcludePattern(@NotNull String pattern) {
if (myExcludePatterns == null) {
myExcludePatterns = new SmartList<>();
}
myExcludePatterns.add(pattern);
}
@Override
public void removeExcludePattern(@NotNull String pattern) {
if (myExcludePatterns != null) {
myExcludePatterns.remove(pattern);
if (myExcludePatterns.isEmpty()) {
myExcludePatterns = null;
}
}
}
private ExcludeFolder addExcludeFolder(ExcludeFolder f) {
Disposer.register(this, (Disposable)f);
myExcludeFolders.add(f);
@@ -355,6 +383,10 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE
}
}
for (String pattern : getExcludePatterns()) {
cloned.addExcludePattern(pattern);
}
return cloned;
}
@@ -375,6 +407,10 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE
element.addContent(subElement);
}
}
for (String pattern : getExcludePatterns()) {
element.addContent(new Element(JpsModuleRootModelSerializer.EXCLUDE_PATTERN_TAG).setAttribute(JpsModuleRootModelSerializer.EXCLUDE_PATTERN_ATTRIBUTE, pattern));
}
}
private static final class ContentFolderComparator implements Comparator<ContentFolder> {
@@ -392,6 +428,8 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE
if (i != 0) return i;
i = ArrayUtil.lexicographicCompare(getSourceFolders(), other.getSourceFolders());
if (i != 0) return i;
return ArrayUtil.lexicographicCompare(getExcludeFolders(), other.getExcludeFolders());
i = ArrayUtil.lexicographicCompare(getExcludeFolders(), other.getExcludeFolders());
if (i != 0) return i;
return ContainerUtil.compareLexicographically(getExcludePatterns(), other.getExcludePatterns());
}
}
@@ -17,25 +17,39 @@
package com.intellij.openapi.roots.impl;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.roots.ContentEntry;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class DirectoryInfo {
/**
* @return {@code true} if located under project content or library roots and not excluded or ignored
* @return {@code true} if the whole directory is located under project content or library roots and not excluded or ignored
* @deprecated use {@link #isInProject(VirtualFile)} instead, this method doesn't take {@link ContentEntry#getExcludePatterns()} into account
*/
public abstract boolean isInProject();
/**
* @return {@code true} if {@code file} is located under project content or library roots and not excluded or ignored
*/
public abstract boolean isInProject(@NotNull VirtualFile file);
/**
* @return {@code true} if located under ignored directory
*/
public abstract boolean isIgnored();
/**
* @return {@code true} if located project content, output or library root but excluded from the project
* @return {@code true} if the whole directory is located in project content, output or library root but excluded from the project
* @deprecated use {@link #isExcluded(VirtualFile)} instead, this method doesn't take {@link ContentEntry#getExcludePatterns()} into account
*/
public abstract boolean isExcluded();
/**
* @return {@code true} if {@code file} located under this directory is excluded from the project.
*/
public abstract boolean isExcluded(@NotNull VirtualFile file);
public abstract boolean isInModuleSource();
public abstract boolean isInLibrarySource();
@@ -25,14 +25,14 @@ import org.jetbrains.annotations.Nullable;
*/
public class DirectoryInfoImpl extends DirectoryInfo {
public static final int MAX_ROOT_TYPE_ID = Byte.MAX_VALUE;
private final VirtualFile myRoot;//original project root for which this information is calculated
protected final VirtualFile myRoot;//original project root for which this information is calculated
private final Module module; // module to which content it belongs or null
private final VirtualFile libraryClassRoot; // class root in library
private final VirtualFile contentRoot;
private final VirtualFile sourceRoot;
private final boolean myInModuleSource;
private final boolean myInLibrarySource;
private final boolean myExcluded;
protected final boolean myExcluded;
private final byte mySourceRootTypeId;
DirectoryInfoImpl(@NotNull VirtualFile root, Module module, VirtualFile contentRoot, VirtualFile sourceRoot, VirtualFile libraryClassRoot,
@@ -72,7 +72,7 @@ public class DirectoryInfoImpl extends DirectoryInfo {
", isInModuleSource=" + isInModuleSource() +
", rootTypeId=" + getSourceRootTypeId() +
", isInLibrarySource=" + isInLibrarySource() +
", isExcludedFromModule=" + isExcluded() +
", isExcludedFromModule=" + myExcluded +
", libraryClassRoot=" + getLibraryClassRoot() +
", contentRoot=" + getContentRoot() +
", sourceRoot=" + getSourceRoot() +
@@ -80,7 +80,12 @@ public class DirectoryInfoImpl extends DirectoryInfo {
}
public boolean isInProject() {
return !isExcluded();
return !myExcluded;
}
@Override
public boolean isInProject(@NotNull VirtualFile file) {
return !isExcluded(file);
}
public boolean isIgnored() {
@@ -113,6 +118,11 @@ public class DirectoryInfoImpl extends DirectoryInfo {
return myExcluded;
}
@Override
public boolean isExcluded(@NotNull VirtualFile file) {
return myExcluded;
}
public Module getModule() {
return module;
}
@@ -0,0 +1,48 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.roots.impl;
import com.intellij.openapi.fileTypes.impl.FileTypeAssocTable;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
/**
* @author nik
*/
public class DirectoryInfoWithExcludePatterns extends DirectoryInfoImpl {
private final FileTypeAssocTable<Boolean> myExcludePatterns;
public DirectoryInfoWithExcludePatterns(@NotNull VirtualFile root, Module module, VirtualFile contentRoot, VirtualFile sourceRoot,
VirtualFile libraryClassRoot, boolean inModuleSource, boolean inLibrarySource, boolean isExcluded,
int sourceRootTypeId, FileTypeAssocTable<Boolean> excludePatterns) {
super(root, module, contentRoot, sourceRoot, libraryClassRoot, inModuleSource, inLibrarySource, isExcluded, sourceRootTypeId);
myExcludePatterns = excludePatterns;
}
@Override
public boolean isExcluded(@NotNull VirtualFile file) {
if (myExcluded) return true;
VirtualFile current = file;
while (!myRoot.equals(current)) {
if (myExcludePatterns.findAssociatedFileType(current.getNameSequence()) != null) {
return true;
}
current = current.getParent();
}
return false;
}
}
@@ -51,12 +51,12 @@ public class ModuleFileIndexImpl extends FileIndexBase implements ModuleFileInde
for (VirtualFile[] roots : allRoots) {
for (VirtualFile root : roots) {
DirectoryInfo info = getInfoForFileOrDirectory(root);
if (!info.isInProject()) continue;
if (!info.isInProject(root)) continue;
VirtualFile parent = root.getParent();
if (parent != null) {
DirectoryInfo parentInfo = myDirectoryIndex.getInfoForFile(parent);
if (parentInfo.isInProject() && myModule.equals(parentInfo.getModule())) continue; // inner content - skip it
if (parentInfo.isInProject(parent) && myModule.equals(parentInfo.getModule())) continue; // inner content - skip it
}
result.add(root);
}
@@ -77,7 +77,7 @@ public class ModuleFileIndexImpl extends FileIndexBase implements ModuleFileInde
@Override
public boolean isInContent(@NotNull VirtualFile fileOrDir) {
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInProject() && myModule.equals(info.getModule());
return info.isInProject(fileOrDir) && myModule.equals(info.getModule());
}
@Override
@@ -17,6 +17,7 @@ package com.intellij.openapi.roots.impl;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -48,6 +49,11 @@ class NonProjectDirectoryInfo extends DirectoryInfo {
return false;
}
@Override
public boolean isInProject(@NotNull VirtualFile file) {
return false;
}
@Override
public String toString() {
return "DirectoryInfo: " + myDebugName;
@@ -93,6 +99,11 @@ class NonProjectDirectoryInfo extends DirectoryInfo {
return false;
}
@Override
public boolean isExcluded(@NotNull VirtualFile file) {
return isExcluded();
}
public Module getModule() {
return null;
}
@@ -88,7 +88,7 @@ public class ProjectFileIndexFacade extends FileIndexFacade {
while (true) {
if (childDir == null) return false;
if (childDir.equals(baseDir)) return true;
if (!myDirectoryIndex.getInfoForFile(childDir).isInProject()) return false;
if (!myDirectoryIndex.getInfoForFile(childDir).isInProject(childDir)) return false;
childDir = childDir.getParent();
}
}
@@ -69,13 +69,13 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
for (VirtualFile[] roots : getModuleContentAndSourceRoots(module)) {
for (VirtualFile root : roots) {
DirectoryInfo info = getInfoForFileOrDirectory(root);
if (!info.isInProject()) continue; // is excluded or ignored
if (!info.isInProject(root)) continue; // is excluded or ignored
if (!module.equals(info.getModule())) continue; // maybe 2 modules have the same content root?
VirtualFile parent = root.getParent();
if (parent != null) {
DirectoryInfo parentInfo = getInfoForFileOrDirectory(parent);
if (parentInfo.isInProject() && parentInfo.getModule() != null) continue;
if (parentInfo.isInProject(parent) && parentInfo.getModule() != null) continue;
}
result.add(root);
}
@@ -88,7 +88,7 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Override
public boolean isExcluded(@NotNull VirtualFile file) {
DirectoryInfo info = getInfoForFileOrDirectory(file);
return info.isIgnored() || info.isExcluded();
return info.isIgnored() || info.isExcluded(file);
}
@Override
@@ -106,7 +106,7 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
public Module getModuleForFile(@NotNull VirtualFile file, boolean honorExclusion) {
if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
DirectoryInfo info = getInfoForFileOrDirectory(file);
if (info.isInProject() || !honorExclusion && info.isExcluded()) {
if (info.isInProject(file) || !honorExclusion && info.isExcluded(file)) {
return info.getModule();
}
return null;
@@ -121,14 +121,14 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Override
public VirtualFile getClassRootForFile(@NotNull VirtualFile file) {
final DirectoryInfo info = getInfoForFileOrDirectory(file);
if (!info.isInProject()) return null;
if (!info.isInProject(file)) return null;
return info.getLibraryClassRoot();
}
@Override
public VirtualFile getSourceRootForFile(@NotNull VirtualFile file) {
final DirectoryInfo info = getInfoForFileOrDirectory(file);
if (!info.isInProject()) return null;
if (!info.isInProject(file)) return null;
return info.getSourceRoot();
}
@@ -140,7 +140,7 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Override
public VirtualFile getContentRootForFile(@NotNull VirtualFile file, final boolean honorExclusion) {
final DirectoryInfo info = getInfoForFileOrDirectory(file);
if (info.isInProject() || !honorExclusion && info.isExcluded()) {
if (info.isInProject(file) || !honorExclusion && info.isExcluded(file)) {
return info.getContentRoot();
}
return null;
@@ -156,7 +156,7 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
public boolean isLibraryClassFile(@NotNull VirtualFile file) {
if (file.isDirectory()) return false;
DirectoryInfo parentInfo = getInfoForFileOrDirectory(file);
return parentInfo.isInProject() && parentInfo.hasLibraryClassRoot();
return parentInfo.isInProject(file) && parentInfo.hasLibraryClassRoot();
}
@Override
@@ -168,19 +168,19 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Override
public boolean isInLibraryClasses(@NotNull VirtualFile fileOrDir) {
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInProject() && info.hasLibraryClassRoot();
return info.isInProject(fileOrDir) && info.hasLibraryClassRoot();
}
@Override
public boolean isInLibrarySource(@NotNull VirtualFile fileOrDir) {
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInProject() && info.isInLibrarySource();
return info.isInProject(fileOrDir) && info.isInLibrarySource();
}
// a slightly faster implementation then the default one
public boolean isInLibrary(@NotNull VirtualFile fileOrDir) {
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInProject() && (info.hasLibraryClassRoot() || info.isInLibrarySource());
return info.isInProject(fileOrDir) && (info.hasLibraryClassRoot() || info.isInLibrarySource());
}
@Override
@@ -191,7 +191,7 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Override
public boolean isInContent(@NotNull VirtualFile fileOrDir) {
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInProject() && info.getModule() != null;
return info.isInProject(fileOrDir) && info.getModule() != null;
}
@Override
@@ -18,6 +18,7 @@ package com.intellij.openapi.roots.impl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
import com.intellij.openapi.fileTypes.impl.FileTypeAssocTable;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
@@ -38,6 +39,7 @@ import com.intellij.util.containers.SLRUMap;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.fileTypes.FileNameMatcherFactory;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import java.util.*;
@@ -81,7 +83,7 @@ public class RootIndex {
myPackageDirectoryCache = new PackageDirectoryCache(rootsByPackagePrefix) {
@Override
protected boolean isPackageDirectory(@NotNull VirtualFile dir, @NotNull String packageName) {
return getInfoForFile(dir).isInProject() && packageName.equals(getPackageName(dir));
return getInfoForFile(dir).isInProject(dir) && packageName.equals(getPackageName(dir));
}
};
}
@@ -109,6 +111,14 @@ public class RootIndex {
info.excludedFromModule.put(excludeRoot, module);
}
List<String> patterns = contentEntry.getExcludePatterns();
if (!patterns.isEmpty()) {
FileTypeAssocTable<Boolean> table = new FileTypeAssocTable<>();
for (String pattern : patterns) {
table.addAssociation(FileNameMatcherFactory.getInstance().createMatcher(pattern), Boolean.TRUE);
}
info.excludeFromContentRootTables.put(contentEntry.getFile(), table);
}
}
// Init module sources
@@ -469,7 +479,7 @@ public class RootIndex {
if (!includeLibrarySources) {
result = ContainerUtil.filter(result, file -> {
DirectoryInfo info = getInfoForFile(file);
return info.isInProject() && (!info.isInLibrarySource() || info.isInModuleSource() || info.hasLibraryClassRoot());
return info.isInProject(file) && (!info.isInLibrarySource() || info.isInModuleSource() || info.hasLibraryClassRoot());
});
}
return new CollectionQuery<>(result);
@@ -547,6 +557,7 @@ public class RootIndex {
@NotNull final MultiMap<VirtualFile, /*Library|SyntheticLibrary*/ Object> sourceOfLibraries = MultiMap.createSmart();
@NotNull final Set<VirtualFile> excludedFromProject = ContainerUtil.newHashSet();
@NotNull final Map<VirtualFile, Module> excludedFromModule = ContainerUtil.newHashMap();
@NotNull final Map<VirtualFile, FileTypeAssocTable<Boolean>> excludeFromContentRootTables = ContainerUtil.newHashMap();
@NotNull final Map<VirtualFile, String> packagePrefix = ContainerUtil.newHashMap();
@NotNull
@@ -571,6 +582,12 @@ public class RootIndex {
for (VirtualFile root : hierarchy) {
Module module = contentRootOf.get(root);
Module excludedFrom = excludedFromModule.get(root);
if (module != null) {
FileTypeAssocTable<Boolean> table = excludeFromContentRootTables.get(root);
if (table != null && isExcludedByPattern(root, hierarchy, table)) {
excludedFrom = module;
}
}
if (module != null && (excludedFrom != module || underExcludedSourceRoot && sourceRootOwners.contains(module))) {
return root;
}
@@ -598,6 +615,18 @@ public class RootIndex {
return null;
}
private static boolean isExcludedByPattern(VirtualFile contentRoot, List<VirtualFile> hierarchy, FileTypeAssocTable<Boolean> table) {
for (VirtualFile file : hierarchy) {
if (table.findAssociatedFileType(file.getNameSequence()) != null) {
return true;
}
if (file.equals(contentRoot)) {
break;
}
}
return false;
}
@Nullable
private VirtualFile findNearestContentRootForExcluded(@NotNull List<VirtualFile> hierarchy) {
for (VirtualFile root : hierarchy) {
@@ -727,8 +756,12 @@ public class RootIndex {
int typeId = moduleSourceRoot != null ? info.rootTypeId.get(moduleSourceRoot) : 0;
Module module = info.contentRootOf.get(nearestContentRoot);
DirectoryInfo directoryInfo =
new DirectoryInfoImpl(root, module, nearestContentRoot, sourceRoot, libraryClassRoot, inModuleSources, inLibrarySource, !inProject, typeId);
FileTypeAssocTable<Boolean> excludePatterns = moduleContentRoot != null ? info.excludeFromContentRootTables.get(moduleContentRoot) : null;
DirectoryInfo directoryInfo = excludePatterns != null
? new DirectoryInfoWithExcludePatterns(root, module, nearestContentRoot, sourceRoot, libraryClassRoot,
inModuleSources, inLibrarySource, !inProject, typeId, excludePatterns)
: new DirectoryInfoImpl(root, module, nearestContentRoot, sourceRoot, libraryClassRoot, inModuleSources,
inLibrarySource, !inProject, typeId);
String packagePrefix = info.calcPackagePrefix(root, hierarchy, moduleContentRoot, libraryClassRoot, librarySourceRoot);
@@ -33,6 +33,7 @@ import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.model.JpsElement;
import org.jetbrains.jps.model.JpsExcludePattern;
import org.jetbrains.jps.model.java.JavaSourceRootProperties;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;
@@ -54,6 +55,7 @@ public class JpsContentEntry implements ContentEntry, Disposable {
private final JpsRootModel myRootModel;
private final List<JpsSourceFolder> mySourceFolders;
private final List<JpsExcludeFolder> myExcludeFolders;
private final List<String> myExcludePatterns;
public JpsContentEntry(JpsModule module, JpsRootModel rootModel, String rootUrl) {
myModule = module;
@@ -72,6 +74,12 @@ public class JpsContentEntry implements ContentEntry, Disposable {
myExcludeFolders.add(new JpsExcludeFolder(excludedUrl, this));
}
}
myExcludePatterns = new SmartList<>();
for (JpsExcludePattern pattern : myModule.getExcludePatterns()) {
if (pattern.getBaseDirUrl().equals(rootUrl)) {
myExcludePatterns.add(pattern.getPattern());
}
}
}
@Override
@@ -286,6 +294,24 @@ public class JpsContentEntry implements ContentEntry, Disposable {
}
}
@NotNull
@Override
public List<String> getExcludePatterns() {
return myExcludePatterns;
}
@Override
public void addExcludePattern(@NotNull String pattern) {
myExcludePatterns.add(pattern);
myModule.addExcludePattern(getUrl(), pattern);
}
@Override
public void removeExcludePattern(@NotNull String pattern) {
myExcludePatterns.remove(pattern);
myModule.removeExcludePattern(getUrl(), pattern);
}
@Override
public boolean isSynthetic() {
return false;