Spellchecker: WI-37857 Spellchecker doesn't reload dictionaries

- if whole dictionaries folder is deleted externally [IDEA-CR-24731]
This commit is contained in:
Olga Strizhenko
2017-09-07 17:41:20 +03:00
parent d8c4d250a5
commit 56763e1cff
34 changed files with 328 additions and 25 deletions

View File

@@ -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));
}
}
}
}

View File

@@ -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) {

View File

@@ -43,4 +43,6 @@ public interface SpellCheckerEngine {
boolean isDictionaryLoad(@NotNull String name);
void removeDictionary(@NotNull String name);
void removeDictionariesRecursively(@NotNull String directory);
}

View File

@@ -0,0 +1,2 @@
<?php
echo "newword";

View File

@@ -0,0 +1,2 @@
<?php
echo "<TYPO descr="Typo: In word 'newword'">newword</TYPO>";

View File

@@ -0,0 +1 @@
newword

View File

@@ -0,0 +1,2 @@
<?php
echo "newword";

View File

@@ -0,0 +1,2 @@
<?php
echo "<TYPO descr="Typo: In word 'newword'">newword</TYPO>";

View File

@@ -0,0 +1 @@
newword

View File

@@ -0,0 +1,2 @@
<?php
echo "<TYPO descr="Typo: In word 'newword'">newword</TYPO>";

View File

@@ -0,0 +1,2 @@
<?php
echo "newword";

View File

@@ -0,0 +1 @@
newword

View File

@@ -0,0 +1,2 @@
<?php
echo "newword";

View File

@@ -0,0 +1,2 @@
<?php
echo "newword";

View File

@@ -0,0 +1,2 @@
<?php
echo "<TYPO descr="Typo: In word 'newword'">newword</TYPO>";

View File

@@ -0,0 +1,2 @@
<?php
echo "<TYPO descr="Typo: In word 'newword'">newword</TYPO>";

View File

@@ -0,0 +1,2 @@
<?php
echo "<TYPO descr="Typo: In word 'newword'">newword</TYPO>";

View File

@@ -0,0 +1,2 @@
<?php
echo "newword";

View File

@@ -0,0 +1,2 @@
<?php
echo "newword";

View File

@@ -0,0 +1,2 @@
<?php
echo "newword";

View File

@@ -0,0 +1,2 @@
<?php
echo "<TYPO descr="Typo: In word 'newword'">newword</TYPO>";

View File

@@ -0,0 +1,2 @@
<?php
echo "<TYPO descr="Typo: In word 'newword'">newword</TYPO>";

View File

@@ -0,0 +1,2 @@
<?php
echo "newword";

View File

@@ -0,0 +1,2 @@
<?php
echo "<TYPO descr="Typo: In word 'newword'">newword</TYPO>";

View File

@@ -0,0 +1 @@
newword

View File

@@ -0,0 +1,2 @@
<?php
echo "<TYPO descr="Typo: In word 'newword'">newword</TYPO>";

View File

@@ -0,0 +1,2 @@
<?php
echo "newword";

View File

@@ -0,0 +1 @@
newword

View File

@@ -30,6 +30,7 @@ import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import static com.intellij.openapi.util.io.FileUtil.createTempDirectory;
import static com.intellij.openapi.vfs.VfsUtil.findFileByIoFile;
public class CustomDictionaryTest extends SpellcheckerInspectionTestCase {
@@ -37,6 +38,8 @@ public class CustomDictionaryTest extends SpellcheckerInspectionTestCase {
private static final String NEW_TEST_DIC = "new_" + TEST_DIC;
private static final String TEST_DIC_AFTER = TEST_DIC + ".after";
private static final String TEMP_DIC = TEST_DIC + ".temp";
private static final String TEST_DIC_DIR = "testDir" ;
public static final String TEMP = "temp";
private List<String> 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);
});
}
}
}