diff --git a/spellchecker/src/com/intellij/spellchecker/SpellCheckerManager.java b/spellchecker/src/com/intellij/spellchecker/SpellCheckerManager.java index d321485cba14..0881dc7ffb8e 100644 --- a/spellchecker/src/com/intellij/spellchecker/SpellCheckerManager.java +++ b/spellchecker/src/com/intellij/spellchecker/SpellCheckerManager.java @@ -24,10 +24,8 @@ import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.io.FileUtilRt; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFileEvent; -import com.intellij.openapi.vfs.VirtualFileListener; + +import com.intellij.openapi.vfs.*; import com.intellij.psi.PsiManager; import com.intellij.psi.impl.PsiModificationTrackerImpl; import com.intellij.spellchecker.dictionary.AggregatedDictionary; @@ -45,11 +43,14 @@ import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.File; import java.io.InputStream; import java.util.*; import static com.intellij.openapi.util.io.FileUtil.isAncestor; +import static com.intellij.openapi.util.io.FileUtilRt.extensionEquals; import static com.intellij.openapi.util.io.FileUtilRt.toSystemDependentName; +import static com.intellij.openapi.vfs.VfsUtilCore.visitChildrenRecursively; public class SpellCheckerManager implements Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.spellchecker.SpellCheckerManager"); @@ -62,7 +63,7 @@ public class SpellCheckerManager implements Disposable { private AggregatedDictionary userDictionary; private final SuggestionProvider suggestionProvider = new BaseSuggestionProvider(this); private final SpellCheckerSettings settings; - private final VirtualFileListener myVirtualFileListener; + private final VirtualFileListener myCustomDictFileListener; public static SpellCheckerManager getInstance(Project project) { return ServiceManager.getService(project, SpellCheckerManager.class); @@ -74,9 +75,9 @@ public class SpellCheckerManager implements Disposable { fullConfigurationReload(); Disposer.register(project, this); - - myVirtualFileListener = new CustomDictVirtualFileListener(settings); - LocalFileSystem.getInstance().addVirtualFileListener(myVirtualFileListener); + + myCustomDictFileListener = new CustomDictFileListener(settings); + LocalFileSystem.getInstance().addVirtualFileListener(myCustomDictFileListener); } public void fullConfigurationReload() { @@ -240,44 +241,113 @@ public class SpellCheckerManager implements Disposable { @Override public void dispose() { - LocalFileSystem.getInstance().removeVirtualFileListener(myVirtualFileListener); + LocalFileSystem.getInstance().removeVirtualFileListener(myCustomDictFileListener); } - private class CustomDictVirtualFileListener implements VirtualFileListener { + private class CustomDictFileListener implements VirtualFileListener { private final SpellCheckerSettings mySettings; - public CustomDictVirtualFileListener(SpellCheckerSettings settings) {mySettings = settings;} + public CustomDictFileListener(@NotNull SpellCheckerSettings settings) {mySettings = settings;} @Override public void fileDeleted(@NotNull VirtualFileEvent event) { - final String path = toSystemDependentName(event.getFile().getPath()); - if (spellChecker.isDictionaryLoad(path)) { - spellChecker.removeDictionary(path); - restartInspections(); - } + removeCustomDictionaries(event.getFile().getPath()); } @Override public void fileCreated(@NotNull VirtualFileEvent event) { - final String path = toSystemDependentName(event.getFile().getPath()); - boolean customDic = FileUtilRt.extensionEquals(path, "dic") && - mySettings.getDictionaryFoldersPaths().stream().anyMatch(dicFolderPath -> isAncestor(dicFolderPath, path, true)); - if (customDic) { - spellChecker.loadDictionary(new FileLoader(path)); - restartInspections(); + loadCustomDictionaries(event.getFile()); + } + + @Override + public void fileMoved(@NotNull VirtualFileMoveEvent event) { + final String oldPath = event.getOldParent().getPath(); + if (!locatedInDictFolders(oldPath)) { + loadCustomDictionaries(event.getFile()); + } + else { + final String newPath = event.getNewParent().getPath(); + if (!locatedInDictFolders(newPath)) { + removeCustomDictionaries(oldPath + File.separator + event.getFileName()); + } } } @Override public void contentsChanged(@NotNull VirtualFileEvent event) { final String path = toSystemDependentName(event.getFile().getPath()); - if (mySettings.getDisabledDictionariesPaths().contains(path)) return; + if (!spellChecker.isDictionaryLoad(path) || mySettings.getDisabledDictionariesPaths().contains(path)) return; + + spellChecker.removeDictionary(path); + spellChecker.loadDictionary(new FileLoader(path)); + restartInspections(); + } + + @Override + public void propertyChanged(@NotNull VirtualFilePropertyEvent event) { + final VirtualFile file = event.getFile(); + if (file.isDirectory()) return; + + if (VirtualFile.PROP_NAME.equals(event.getPropertyName())) { + final String oldName = (String)event.getOldValue(); + if (!isDic(oldName)) { + loadCustomDictionaries(file); + } + else { + final String newName = (String)event.getNewValue(); + if (!isDic(newName)) { + removeCustomDictionaries(file.getParent().getPath() + File.separator + oldName); + } + } + } + } + + private void removeCustomDictionaries(@NotNull String path) { + path = toSystemDependentName(path); if (spellChecker.isDictionaryLoad(path)) { spellChecker.removeDictionary(path); + restartInspections(); + } + else if (locatedInDictFolders(path)) { + spellChecker.removeDictionariesRecursively(path); + restartInspections(); + } + if (mySettings.getDictionaryFoldersPaths().contains(path)) { + mySettings.getDictionaryFoldersPaths().remove(path); + } + } + + private void loadCustomDictionaries(@NotNull VirtualFile file) { + final String path = toSystemDependentName(file.getPath()); + if (!locatedInDictFolders(path)) return; + + if (file.isDirectory()) { + visitChildrenRecursively(file, new VirtualFileVisitor() { + @Override + public boolean visitFile(@NotNull VirtualFile file) { + final boolean isDirectory = file.isDirectory(); + final String path = file.getPath(); + if (!isDirectory && isDic(path)) { + spellChecker.loadDictionary(new FileLoader(path)); + restartInspections(); + } + return isDirectory; + } + }); + } + else if (isDic(path)) { spellChecker.loadDictionary(new FileLoader(path)); restartInspections(); } } + + private boolean isDic(String path) { + return extensionEquals(path, "dic"); + } + + private boolean locatedInDictFolders(@NotNull String path) { + return mySettings.getDictionaryFoldersPaths().stream().anyMatch(dicFolderPath -> isAncestor(dicFolderPath, path, false)); + } } -} +} \ No newline at end of file diff --git a/spellchecker/src/com/intellij/spellchecker/engine/BaseSpellChecker.java b/spellchecker/src/com/intellij/spellchecker/engine/BaseSpellChecker.java index 6980da40f59c..2f760e80cda3 100644 --- a/spellchecker/src/com/intellij/spellchecker/engine/BaseSpellChecker.java +++ b/spellchecker/src/com/intellij/spellchecker/engine/BaseSpellChecker.java @@ -38,6 +38,8 @@ import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; +import static com.intellij.openapi.util.io.FileUtil.isAncestor; + public class BaseSpellChecker implements SpellCheckerEngine { static final Logger LOG = Logger.getInstance("#com.intellij.spellchecker.engine.BaseSpellChecker"); @@ -236,6 +238,14 @@ public class BaseSpellChecker implements SpellCheckerEngine { } } + @Override + public void removeDictionariesRecursively(@NotNull String directory) { + bundledDictionaries.stream() + .map(Dictionary::getName) + .filter(dict -> isAncestor(directory, dict, true) && isDictionaryLoad(dict)) + .forEach(this::removeDictionary); + } + @Nullable public Dictionary getBundledDictionaryByName(@NotNull String name) { for (Dictionary dictionary : bundledDictionaries) { diff --git a/spellchecker/src/com/intellij/spellchecker/engine/SpellCheckerEngine.java b/spellchecker/src/com/intellij/spellchecker/engine/SpellCheckerEngine.java index 32cc35ba30fc..381c9506cec5 100644 --- a/spellchecker/src/com/intellij/spellchecker/engine/SpellCheckerEngine.java +++ b/spellchecker/src/com/intellij/spellchecker/engine/SpellCheckerEngine.java @@ -43,4 +43,6 @@ public interface SpellCheckerEngine { boolean isDictionaryLoad(@NotNull String name); void removeDictionary(@NotNull String name); + + void removeDictionariesRecursively(@NotNull String directory); } diff --git a/spellchecker/testData/inspection/dictionary/addDictDir/test.after.php b/spellchecker/testData/inspection/dictionary/addDictDir/test.after.php new file mode 100644 index 000000000000..15df5adfcaf3 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/addDictDir/test.after.php @@ -0,0 +1,2 @@ +newword"; \ No newline at end of file diff --git a/spellchecker/testData/inspection/dictionary/addDictDir/test.dic.after b/spellchecker/testData/inspection/dictionary/addDictDir/test.dic.after new file mode 100644 index 000000000000..be652306a932 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/addDictDir/test.dic.after @@ -0,0 +1 @@ +newword \ No newline at end of file diff --git a/spellchecker/testData/inspection/dictionary/moveDict/test.after.php b/spellchecker/testData/inspection/dictionary/moveDict/test.after.php new file mode 100644 index 000000000000..15df5adfcaf3 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/moveDict/test.after.php @@ -0,0 +1,2 @@ +newword"; \ No newline at end of file diff --git a/spellchecker/testData/inspection/dictionary/moveDict/test.dic.after b/spellchecker/testData/inspection/dictionary/moveDict/test.dic.after new file mode 100644 index 000000000000..be652306a932 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/moveDict/test.dic.after @@ -0,0 +1 @@ +newword \ No newline at end of file diff --git a/spellchecker/testData/inspection/dictionary/moveDictOutside/test.after.php b/spellchecker/testData/inspection/dictionary/moveDictOutside/test.after.php new file mode 100644 index 000000000000..a52ac2799c91 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/moveDictOutside/test.after.php @@ -0,0 +1,2 @@ +newword"; diff --git a/spellchecker/testData/inspection/dictionary/moveDictOutside/test.before.php b/spellchecker/testData/inspection/dictionary/moveDictOutside/test.before.php new file mode 100644 index 000000000000..15df5adfcaf3 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/moveDictOutside/test.before.php @@ -0,0 +1,2 @@ +newword"; diff --git a/spellchecker/testData/inspection/dictionary/moveNotInDictFolder/test.before.php b/spellchecker/testData/inspection/dictionary/moveNotInDictFolder/test.before.php new file mode 100644 index 000000000000..a52ac2799c91 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/moveNotInDictFolder/test.before.php @@ -0,0 +1,2 @@ +newword"; diff --git a/spellchecker/testData/inspection/dictionary/moveNotInDictFolder/test.dic.after b/spellchecker/testData/inspection/dictionary/moveNotInDictFolder/test.dic.after new file mode 100644 index 000000000000..be652306a932 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/moveNotInDictFolder/test.dic.after @@ -0,0 +1 @@ +newword \ No newline at end of file diff --git a/spellchecker/testData/inspection/dictionary/removeDictDir/test.after.php b/spellchecker/testData/inspection/dictionary/removeDictDir/test.after.php new file mode 100644 index 000000000000..6d1b2f522512 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/removeDictDir/test.after.php @@ -0,0 +1,2 @@ +newword"; \ No newline at end of file diff --git a/spellchecker/testData/inspection/dictionary/removeDictDir/test.before.php b/spellchecker/testData/inspection/dictionary/removeDictDir/test.before.php new file mode 100644 index 000000000000..15df5adfcaf3 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/removeDictDir/test.before.php @@ -0,0 +1,2 @@ +newword"; diff --git a/spellchecker/testData/inspection/dictionary/renameStillNotDicExtension/test.before.php b/spellchecker/testData/inspection/dictionary/renameStillNotDicExtension/test.before.php new file mode 100644 index 000000000000..6d1b2f522512 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/renameStillNotDicExtension/test.before.php @@ -0,0 +1,2 @@ +newword"; \ No newline at end of file diff --git a/spellchecker/testData/inspection/dictionary/renameStillNotDicExtension/test.dic.after b/spellchecker/testData/inspection/dictionary/renameStillNotDicExtension/test.dic.after new file mode 100644 index 000000000000..be652306a932 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/renameStillNotDicExtension/test.dic.after @@ -0,0 +1 @@ +newword \ No newline at end of file diff --git a/spellchecker/testData/inspection/dictionary/renameToDict/test.after.php b/spellchecker/testData/inspection/dictionary/renameToDict/test.after.php new file mode 100644 index 000000000000..15df5adfcaf3 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/renameToDict/test.after.php @@ -0,0 +1,2 @@ +newword"; \ No newline at end of file diff --git a/spellchecker/testData/inspection/dictionary/renameToDict/test.dic.after b/spellchecker/testData/inspection/dictionary/renameToDict/test.dic.after new file mode 100644 index 000000000000..be652306a932 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/renameToDict/test.dic.after @@ -0,0 +1 @@ +newword \ No newline at end of file diff --git a/spellchecker/testData/inspection/dictionary/renameToTxt/test.after.php b/spellchecker/testData/inspection/dictionary/renameToTxt/test.after.php new file mode 100644 index 000000000000..a52ac2799c91 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/renameToTxt/test.after.php @@ -0,0 +1,2 @@ +newword"; diff --git a/spellchecker/testData/inspection/dictionary/renameToTxt/test.before.php b/spellchecker/testData/inspection/dictionary/renameToTxt/test.before.php new file mode 100644 index 000000000000..15df5adfcaf3 --- /dev/null +++ b/spellchecker/testData/inspection/dictionary/renameToTxt/test.before.php @@ -0,0 +1,2 @@ + oldPaths; SpellCheckerSettings settings; SpellCheckerManager spellCheckerManager; @@ -186,5 +189,173 @@ public class CustomDictionaryTest extends SpellcheckerInspectionTestCase { public void testUtf16LEDict() throws IOException { doNewDictTest(); } - + + public void testMoveDict() throws IOException { + final VirtualFile tempDir = findFileByIoFile(createTempDirectory(TEST_DIC_DIR, TEMP), true); + final VirtualFile testDir = findFileByIoFile(new File(getTestDictDirectory()), true); + final VirtualFile file = testDir.findChild(TEST_DIC_AFTER); + + try { + doBeforeCheck(); + + WriteAction.run(() -> { + final VirtualFile copy = file.copy(this, tempDir, TEST_DIC); + copy.move(this, testDir); + }); + doAfterCheck(); + } + finally { + //back to initial state + WriteAction.run(() -> { + tempDir.delete(this); + testDir.findChild(TEST_DIC).delete(this); + }); + } + } + + public void testMoveDictOutside() throws IOException { + final VirtualFile tempDir = findFileByIoFile(createTempDirectory(TEST_DIC_DIR, TEMP), true); + final VirtualFile testDir = findFileByIoFile(new File(getTestDictDirectory()), true); + final VirtualFile file = testDir.findChild(TEST_DIC); + moveFileToDirAndCheck(file,testDir, tempDir); + } + + public void testMoveNotInDictFolder() throws IOException { + final VirtualFile tempDir1 = findFileByIoFile(createTempDirectory(TEST_DIC_DIR, TEMP + "1"), true); + final VirtualFile tempDir2 = findFileByIoFile(createTempDirectory(TEST_DIC_DIR, TEMP + "2"), true); + + final VirtualFile testDir = findFileByIoFile(new File(getTestDictDirectory()), true); + final VirtualFile file = testDir.findChild(TEST_DIC_AFTER); + WriteAction.run(() -> file.copy(this, tempDir1, TEST_DIC)); + + try { + doBeforeCheck(); + WriteAction.run(() -> tempDir1.findChild(TEST_DIC).move(this, tempDir2)); + doAfterCheck(); + } + finally { + //back to initial state + WriteAction.run(() -> { + tempDir1.delete(this); + tempDir2.delete(this); + }); + } + } + + public void testMoveInsideDictFolders() throws IOException { + final VirtualFile testDir = findFileByIoFile(new File(getTestDictDirectory()), true); + final VirtualFile file = testDir.findChild(TEST_DIC); + + final String yetAnotherDirName = "yetAnotherDir"; + WriteAction.run(() -> testDir.createChildDirectory(this, yetAnotherDirName)); + final VirtualFile anotherDir = testDir.findChild(yetAnotherDirName); + moveFileToDirAndCheck(file, testDir, anotherDir); + } + + private void moveFileToDirAndCheck(VirtualFile file, VirtualFile from, VirtualFile to) throws IOException { + try { + doBeforeCheck(); + WriteAction.run(() -> file.move(this, to)); + doAfterCheck(); + } + finally { + //back to initial state + WriteAction.run(() -> { + to.findChild(TEST_DIC).move(this, from); + to.delete(this); + }); + } + } + + public void testRenameToDict() throws IOException { + final VirtualFile file = findFileByIoFile(Paths.get(getTestDictDirectory(), TEST_DIC_AFTER).toFile(), true); + try { + doBeforeCheck(); + WriteAction.run(() -> file.rename(this, TEST_DIC)); + doAfterCheck(); + } + finally { + //back to initial state + WriteAction.run(() -> file.rename(this, TEST_DIC_AFTER)); + } + } + + public void testRenameToTxt() throws IOException { + final VirtualFile file = findFileByIoFile(Paths.get(getTestDictDirectory(), TEST_DIC).toFile(), true); + try { + doBeforeCheck(); + WriteAction.run(() -> file.rename(this, "test.txt")); + doAfterCheck(); + } + finally { + //back to initial state + WriteAction.run(() -> file.rename(this, TEST_DIC)); + } + } + + public void testRenameStillDicExtension() throws IOException { + final VirtualFile file = findFileByIoFile(Paths.get(getTestDictDirectory(), TEST_DIC).toFile(), true); + try { + doBeforeCheck(); + WriteAction.run(() -> file.rename(this, "still.dic")); + doAfterCheck(); + } + finally { + //back to initial state + WriteAction.run(() -> file.rename(this, TEST_DIC)); + } + } + + public void testRenameStillNotDicExtension() throws IOException { + final VirtualFile file = findFileByIoFile(Paths.get(getTestDictDirectory(), TEST_DIC_AFTER).toFile(), true); + try { + doBeforeCheck(); + WriteAction.run(() -> file.rename(this, "still_not_dic.extension")); + doAfterCheck(); + } + finally { + //back to initial state + WriteAction.run(() -> file.rename(this, TEST_DIC_AFTER)); + } + } + + + public void testRemoveDictDir() throws IOException { + final VirtualFile tempDir = findFileByIoFile(createTempDirectory(TEST_DIC_DIR, TEMP), true); + final VirtualFile testDir = findFileByIoFile(new File(getTestDictDirectory()), true); + final VirtualFile testDictDir = testDir.findChild(TEST_DIC_DIR); + try { + doBeforeCheck(); + WriteAction.run(() -> { + testDictDir.copy(this, tempDir, TEST_DIC_DIR); // to revert it back + testDictDir.delete(this); + }); + doAfterCheck(); + } + finally { + //back to initial state + WriteAction.run(() -> { + tempDir.findChild(TEST_DIC_DIR).copy(this, testDir, TEST_DIC_DIR); + tempDir.delete(this); + }); + } + } + + public void testAddDictDir() throws IOException { + final VirtualFile testDir = findFileByIoFile(new File(getTestDictDirectory()), true); + final VirtualFile tempDir = findFileByIoFile(createTempDirectory(TEST_DIC_DIR, TEMP), true); + WriteAction.run(() -> testDir.findChild(TEST_DIC_AFTER).copy(this, tempDir, TEST_DIC)); + try { + doBeforeCheck(); + WriteAction.run(() -> tempDir.copy(this, testDir, TEST_DIC_DIR)); + doAfterCheck(); + } + finally { + //back to initial state + WriteAction.run(() -> { + testDir.findChild(TEST_DIC_DIR).delete(this); + tempDir.delete(this); + }); + } + } }