diff --git a/platform/indexing-impl/src/com/intellij/psi/search/FileTypeIndex.java b/platform/indexing-impl/src/com/intellij/psi/search/FileTypeIndex.java index 850fc5a119c7..c4e6b9fcbc83 100644 --- a/platform/indexing-impl/src/com/intellij/psi/search/FileTypeIndex.java +++ b/platform/indexing-impl/src/com/intellij/psi/search/FileTypeIndex.java @@ -78,7 +78,7 @@ public class FileTypeIndex extends ScalarIndexExtension @Override public boolean dependsOnFileContent() { - return false; + return true; } @Override diff --git a/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java index b0814354928c..ce9a004301b6 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java @@ -21,6 +21,7 @@ import com.intellij.ide.plugins.PluginManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.application.impl.TransferToPooledThreadQueue; import com.intellij.openapi.components.ExportableApplicationComponent; import com.intellij.openapi.components.RoamingType; import com.intellij.openapi.diagnostic.Logger; @@ -37,16 +38,17 @@ import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.ByteSequence; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.openapi.vfs.VFileProperty; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileWithId; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.FileAttribute; import com.intellij.openapi.vfs.newvfs.FileSystemInterface; +import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent; +import com.intellij.openapi.vfs.newvfs.events.VFileEvent; import com.intellij.psi.SingleRootFileViewProvider; import com.intellij.testFramework.LightVirtualFile; import com.intellij.util.*; import com.intellij.util.containers.ConcurrentBitSet; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBus; import com.intellij.util.messages.MessageBusConnection; import gnu.trove.THashMap; @@ -112,9 +114,11 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements NamedJDOME private final MessageBus myMessageBus; private final Map myStandardFileTypes = new LinkedHashMap(); - @NonNls private static final String[] FILE_TYPES_WITH_PREDEFINED_EXTENSIONS = {"JSP", "JSPX", "DTD", "HTML", "Properties", "XHTML"}; + @NonNls + private static final String[] FILE_TYPES_WITH_PREDEFINED_EXTENSIONS = {"JSP", "JSPX", "DTD", "HTML", "Properties", "XHTML"}; private final SchemesManager mySchemesManager; - @NonNls private static final String FILE_SPEC = "$ROOT_CONFIG$/filetypes"; + @NonNls + private static final String FILE_SPEC = "$ROOT_CONFIG$/filetypes"; private final ConcurrentBitSet autoDetectWasRun = new ConcurrentBitSet(); private final ConcurrentBitSet autoDetectedAsText = new ConcurrentBitSet(); private final ConcurrentBitSet autoDetectedAsBinary = new ConcurrentBitSet(); @@ -238,6 +242,61 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements NamedJDOME fireFileTypesChanged(); } }, RoamingType.PER_USER); + bus.connect().subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter(){ + @Override + public void after(@NotNull List events) { + Set files = ContainerUtil.map2Set(events, new Function() { + @Override + public VirtualFile fun(VFileEvent event) { + VirtualFile file = event instanceof VFileCreateEvent ? null : event.getFile(); + return file != null && isDetectable(file) && wasAutoDetectedBefore(file) ? file : null; + } + }); + files.remove(null); + reDetectQueue.offer(files); + } + }); + } + private final TransferToPooledThreadQueue> reDetectQueue = new TransferToPooledThreadQueue>("file type re-detect", Condition.FALSE, -1, new Processor>() { + @Override + public boolean process(Collection files) { + ((FileTypeManagerImpl)getInstance()).reDetect(files); + return true; + } + }); + void drainReDetectQueue() { + reDetectQueue.drain(); + } + + private void reDetect(@NotNull Collection files) { + final List changed = new ArrayList(); + for (VirtualFile file : files) { + if (isDetectable(file) && wasAutoDetectedBefore(file)) { + FileType before = file.getFileType(); + FileType after = detectFromContent(file); + if (before != after) { + changed.add(file); + } + } + } + if (!changed.isEmpty()) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + FileContentUtilCore.reparseFiles(changed); + } + }, ApplicationManager.getApplication().getDisposed()); + } + } + + private boolean wasAutoDetectedBefore(@NotNull VirtualFile file) { + if (file.getUserData(DETECTED_FROM_CONTENT_FILE_TYPE_KEY) != null) return true; + if (file instanceof VirtualFileWithId) { + int id = Math.abs(((VirtualFileWithId)file).getId()); + // do not re-detect binary files + return autoDetectWasRun.get(id) && !autoDetectedAsBinary.get(id); + } + return false; } private static void writeImportedExtensionsMap(final Element map, final ImportedFileType type) { @@ -394,6 +453,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements NamedJDOME @NotNull @Override + @Deprecated public FileType detectFileTypeFromContent(@NotNull VirtualFile file) { return file.getFileType(); } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/FileTypesTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java similarity index 81% rename from platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/FileTypesTest.java rename to platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java index cbedea149127..ac6a109b4286 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/FileTypesTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java @@ -13,14 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.openapi.fileTypes; +package com.intellij.openapi.fileTypes.impl; +import com.intellij.ide.highlighter.ModuleFileType; +import com.intellij.ide.highlighter.ProjectFileType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; +import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileTypes.*; import com.intellij.openapi.fileTypes.ex.FileTypeManagerEx; -import com.intellij.openapi.fileTypes.impl.FileTypeAssocTable; +import com.intellij.openapi.util.io.ByteSequence; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtil; @@ -32,6 +37,9 @@ import com.intellij.psi.PsiPlainTextFile; import com.intellij.psi.impl.PsiManagerEx; import com.intellij.testFramework.PlatformTestCase; import com.intellij.util.PatternUtil; +import junit.framework.TestCase; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -39,13 +47,13 @@ import java.util.Arrays; import java.util.regex.Pattern; public class FileTypesTest extends PlatformTestCase { - private FileTypeManagerEx myFileTypeManager; + private FileTypeManagerImpl myFileTypeManager; private String myOldIgnoredFilesList; @Override protected void setUp() throws Exception { super.setUp(); - myFileTypeManager = FileTypeManagerEx.getInstanceEx(); + myFileTypeManager = (FileTypeManagerImpl)FileTypeManagerEx.getInstanceEx(); myOldIgnoredFilesList = myFileTypeManager.getIgnoredFilesList(); } @@ -187,7 +195,7 @@ public class FileTypesTest extends PlatformTestCase { FileUtil.writeToFile(file, "xxx xxx xxx xxx"); VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); assertNotNull(virtualFile); - assertEquals(PlainTextFileType.INSTANCE, virtualFile.getFileType()); + TestCase.assertEquals(PlainTextFileType.INSTANCE, virtualFile.getFileType()); } public void testAutoDetectEmptyFile() throws IOException { @@ -253,4 +261,40 @@ public class FileTypesTest extends PlatformTestCase { assertEquals(PlainTextFileType.INSTANCE, vFile.getFileType()); } + + public void testReDetectOnContentsChange() throws IOException { + FileTypeRegistry.FileTypeDetector detector = new FileTypeRegistry.FileTypeDetector() { + @Nullable + @Override + public FileType detect(@NotNull VirtualFile file, @NotNull ByteSequence firstBytes, @Nullable CharSequence firstCharsIfText) { + String text = firstCharsIfText.toString(); + if (text.startsWith("TYPE:")) return FileTypeRegistry.getInstance().findFileTypeByName(StringUtil.trimStart(text, "TYPE:")); + return null; + } + + @Override + public int getVersion() { + return 0; + } + }; + Extensions.getRootArea().getExtensionPoint(FileTypeRegistry.FileTypeDetector.EP_NAME).registerExtension(detector); + try { + File d = createTempDirectory(); + File f = new File(d, "xx.asfdasdfas"); + FileUtil.writeToFile(f, "akjdhfksdjgf"); + VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f); + assertTrue(vFile.getFileType().toString(), vFile.getFileType() instanceof PlainTextFileType); + + VfsUtil.saveText(vFile, "TYPE:IDEA_MODULE"); + myFileTypeManager.drainReDetectQueue(); + assertTrue(vFile.getFileType().toString(), vFile.getFileType() instanceof ModuleFileType); + + VfsUtil.saveText(vFile, "TYPE:IDEA_PROJECT"); + myFileTypeManager.drainReDetectQueue(); + assertTrue(vFile.getFileType().toString(), vFile.getFileType() instanceof ProjectFileType); + } + finally { + Extensions.getRootArea().getExtensionPoint(FileTypeRegistry.FileTypeDetector.EP_NAME).unregisterExtension(detector); + } + } }