package fr.adrienbrault.idea.symfony2plugin.stubs.indexes; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.psi.PsiFile; import com.intellij.util.indexing.*; import com.intellij.util.io.DataExternalizer; import com.intellij.util.io.EnumeratorStringDescriptor; import com.intellij.util.io.KeyDescriptor; import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.externalizer.StringSetDataExternalizer; import fr.adrienbrault.idea.symfony2plugin.translation.collector.YamlTranslationVistor; import fr.adrienbrault.idea.symfony2plugin.translation.dict.TranslationUtil; import gnu.trove.THashMap; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.yaml.YAMLFileType; import org.jetbrains.yaml.psi.YAMLFile; import java.io.IOException; import java.io.InputStream; import java.util.*; /** * @author Daniel Espendiller <daniel@espendiller.net> */ public class TranslationStubIndex extends FileBasedIndexExtension<String, Set<String>> { public static final ID<String, Set<String>> KEY = ID.create("fr.adrienbrault.idea.symfony2plugin.translations"); private final KeyDescriptor<String> myKeyDescriptor = new EnumeratorStringDescriptor(); @NotNull @Override public DataIndexer<String, Set<String>, FileContent> getIndexer() { return new DataIndexer<String, Set<String>, FileContent>() { @NotNull @Override public Map<String, Set<String>> map(@NotNull FileContent inputData) { if(!Symfony2ProjectComponent.isEnabledForIndex(inputData.getProject())) { return Collections.emptyMap(); } String extension = inputData.getFile().getExtension(); if("xlf".equalsIgnoreCase(extension) || "xliff".equalsIgnoreCase(extension)) { Map<String, Set<String>> map = new THashMap<>(); getXlfStringMap(inputData, map); return map; } PsiFile psiFile = inputData.getPsiFile(); if(!(psiFile instanceof YAMLFile)) { return Collections.emptyMap(); } // check physical file position if (!isValidTranslationFile(inputData, psiFile)) { return Collections.emptyMap(); } String domainName = this.getDomainName(inputData.getFileName()); if(domainName == null) { return Collections.emptyMap(); } Set<String> translationKeySet = new HashSet<>(); YamlTranslationVistor.collectFileTranslations((YAMLFile) psiFile, (keyName, yamlKeyValue) -> { translationKeySet.add(keyName); return true; }); if(translationKeySet.size() == 0) { return Collections.emptyMap(); } Map<String, Set<String>> map = new THashMap<>(); map.put(domainName, translationKeySet); return map; } private boolean isValidTranslationFile(FileContent inputData, PsiFile psiFile) { // dont index all yaml files; "Resources/translations" should be good for now String relativePath = VfsUtil.getRelativePath(inputData.getFile(), psiFile.getProject().getBaseDir(), '/'); if(relativePath != null) { return relativePath.contains("Resources/translations"); } // Resources/translations/messages.de.yml // @TODO: Resources/translations/de/messages.yml String path = inputData.getFile().getPath(); if(path.endsWith("Resources/translations/" + inputData.getFileName())) { return true; } return false; } private void getXlfStringMap(FileContent inputData, Map<String, Set<String>> map) { // testing files are not that nice String relativePath = VfsUtil.getRelativePath(inputData.getFile(), inputData.getProject().getBaseDir(), '/'); if(relativePath != null && (relativePath.contains("/Test/") || relativePath.contains("/Tests/") || relativePath.contains("/Fixture/") || relativePath.contains("/Fixtures/"))) { return; } String domainName = this.getDomainName(inputData.getFileName()); if(domainName == null) { return; } InputStream inputStream; try { inputStream = inputData.getFile().getInputStream(); } catch (IOException e) { return; } Set<String> set = TranslationUtil.getXliffTranslations(inputStream); if(set.size() > 0) { map.put(domainName, set); } } @Nullable private String getDomainName(@NotNull String fileName) { String[] split = fileName.split("\\."); if(split.length < 2 || Arrays.stream(split).anyMatch(s -> s.length() == 0)) { return null; } // foo.fr.yml // dont index fr.yml int domainSplit = fileName.lastIndexOf("."); if(domainSplit <= 2) { return null; } return StringUtils.join(Arrays.copyOfRange(split, 0, split.length - 2), "."); } }; } @NotNull @Override public ID<String, Set<String>> getName() { return KEY; } @NotNull @Override public KeyDescriptor<String> getKeyDescriptor() { return this.myKeyDescriptor; } @NotNull public DataExternalizer<Set<String>> getValueExternalizer() { return new StringSetDataExternalizer(); } @NotNull @Override public FileBasedIndex.InputFilter getInputFilter() { return file -> file.getFileType() == YAMLFileType.YML || "xlf".equalsIgnoreCase(file.getExtension()) || "xliff".equalsIgnoreCase(file.getExtension()); } @Override public boolean dependsOnFileContent() { return true; } @Override public int getVersion() { return 6; } }