/* * Copyright 2003-2013 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 jetbrains.mps.idea.core.projectView; import com.intellij.ide.DeleteProvider; import com.intellij.ide.projectView.SelectableTreeStructureProvider; import com.intellij.ide.projectView.ViewSettings; import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; import com.intellij.ide.projectView.impl.nodes.PsiFileNode; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.options.ex.SingleConfigurableEditor; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import jetbrains.mps.extapi.persistence.FolderDataSource; import jetbrains.mps.fileTypes.MPSFileTypeFactory; import jetbrains.mps.ide.actions.MPSCommonDataKeys; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.ide.ui.dialogs.properties.MPSPropertiesConfigurable; import jetbrains.mps.ide.ui.dialogs.properties.ModelPropertiesConfigurable; import jetbrains.mps.ide.vfs.VirtualFileUtils; import jetbrains.mps.idea.core.MPSBundle; import jetbrains.mps.idea.core.MPSDataKeys; import jetbrains.mps.idea.core.projectView.edit.SNodeCutCopyProvider; import jetbrains.mps.idea.core.projectView.edit.SNodeDeleteProvider; import jetbrains.mps.idea.core.projectView.edit.SNodePasteProvider; import jetbrains.mps.idea.core.projectView.edit.SingleFileModelDeleteProvider; import jetbrains.mps.idea.core.psi.impl.MPSPsiModel; import jetbrains.mps.idea.core.psi.impl.MPSPsiNodeBase; import jetbrains.mps.idea.core.psi.impl.MPSPsiProvider; import jetbrains.mps.idea.core.psi.impl.MPSPsiRealNode; import jetbrains.mps.idea.core.psi.impl.MPSPsiRootNode; import jetbrains.mps.smodel.SModelFileTracker; import jetbrains.mps.util.ModelComputeRunnable; import jetbrains.mps.vfs.FileSystem; import jetbrains.mps.vfs.IFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.EditableSModel; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeReference; import org.jetbrains.mps.openapi.module.SModule; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.openapi.persistence.DataSource; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; /** * User: fyodor * Date: 2/27/13 */ public class MPSTreeStructureProvider implements SelectableTreeStructureProvider, DumbAware { @Nullable @Override public PsiElement getTopLevelElement(PsiElement element) { return null; } @NotNull @Override public Collection<AbstractTreeNode> modify(@NotNull final AbstractTreeNode treeNode, @NotNull final Collection<AbstractTreeNode> children, final ViewSettings settings) { final Ref<Collection<AbstractTreeNode>> result = new Ref<>(children); SRepository repository = ProjectHelper.getProjectRepository(treeNode.getProject()); // we're actually in EDT here, but we work with SModels, and various routines assert that we can read, thus read action repository.getModelAccess().runReadAction(new Runnable() { @Override public void run() { List<AbstractTreeNode> updatedChildren = null; final MPSPsiProvider mpsPsiProvider = MPSPsiProvider.getInstance(treeNode.getProject()); // if current dir is data source from some model FolderDataSource currentDirectoryDataSource = null; if (treeNode instanceof PsiDirectoryNode) { // let's see if we have a model built from this dir, e.g. in per-root persistence SModel sModel = SModelFileTracker.getInstance(repository).findModel(VirtualFileUtils.toIFile(((PsiDirectoryNode) treeNode).getVirtualFile())); if (sModel != null) { // adding root nodes (removing their corresponding files' nodes from the tree is further below) List<MPSPsiElementTreeNode> rootsTreeNodes = new ArrayList<>(); for (SNode root : sModel.getRootNodes()) { rootsTreeNodes.add(new MPSPsiElementTreeNode(treeNode.getProject(), (MPSPsiRootNode) mpsPsiProvider.getPsi(root).getContainingFile(), settings)); } if (!rootsTreeNodes.isEmpty()) { updatedChildren = new ArrayList<>(children); updatedChildren.addAll(rootsTreeNodes); } DataSource dataSource = sModel.getSource(); if (dataSource instanceof FolderDataSource) { // could be assert as currently SModelFileTracker only tracks FileDataSource and FolderDataSource currentDirectoryDataSource = (FolderDataSource) dataSource; } } } else if (treeNode instanceof MPSPsiModelTreeNode) { MPSPsiModel psiModel = ((MPSPsiModelTreeNode) treeNode).extractPsiFromValue(); updatedChildren = new ArrayList<>(); for (PsiElement psiElement : psiModel.getChildren()) { updatedChildren.add(new MPSPsiElementTreeNode(treeNode.getProject(), (MPSPsiRootNode) psiElement, settings)); } } for (final AbstractTreeNode child : children) { if (child instanceof PsiFileNode) { VirtualFile vFile = ((PsiFileNode) child).getVirtualFile(); if (vFile == null) { continue; } // check if it's a single file model final SModel sModel = SModelFileTracker.getInstance(repository).findModel(VirtualFileUtils.toIFile(vFile)); if (sModel != null) { if (updatedChildren == null) updatedChildren = new ArrayList<>(children); int idx = updatedChildren.indexOf(child); updatedChildren.remove(idx); updatedChildren.add(idx, new MPSPsiModelTreeNode(treeNode.getProject(), mpsPsiProvider.getPsi(sModel), settings)); continue; } if (currentDirectoryDataSource != null && currentDirectoryDataSource.isIncluded(VirtualFileUtils.toIFile(vFile))) { // it's a file that constitutes a FolderDataSource-backed model, remove it from the tree (root nodes are shown instead) if (updatedChildren == null) updatedChildren = new ArrayList<>(children); int idx = updatedChildren.indexOf(child); updatedChildren.remove(idx); } } else if (child instanceof PsiDirectoryNode) { // below code only attaches our action to the directory and makes it show added children - our root nodes final SModel perRootModel = SModelFileTracker.getInstance(repository).findModel(VirtualFileUtils.toIFile(((PsiDirectoryNode) child).getVirtualFile())); if (perRootModel != null) { if (updatedChildren == null) updatedChildren = new ArrayList<>(children); int idx = updatedChildren.indexOf(child); updatedChildren.remove(idx); updatedChildren.add(idx, new PsiDirectoryNode(treeNode.getProject(), ((PsiDirectoryNode) child).getValue(), settings) { @Override public boolean canNavigate() { return true; } @Override public String getNavigateActionText(boolean focusEditor) { return MPSBundle.message("open.model.properties.action"); } @Override public void navigate(boolean requestFocus) { MPSPropertiesConfigurable configurable = new ModelPropertiesConfigurable(perRootModel, ProjectHelper.fromIdeaProject(myProject), true); final SingleConfigurableEditor dialog = new SingleConfigurableEditor(myProject, configurable); configurable.setParentForCallBack(dialog); ApplicationManager.getApplication().invokeLater(dialog::show, ModalityState.current()); } }); } } } if (updatedChildren != null) { result.set(updatedChildren); } } }); return result.get(); } @Nullable @Override public Object getData(Collection<AbstractTreeNode> selected, String dataName) { if (selected == null) { return null; } if (PlatformDataKeys.COPY_PROVIDER.is(dataName) || PlatformDataKeys.CUT_PROVIDER.is(dataName)) { return getProvider(selected, CUT_COPY_PROVIDER_FACTORY); } if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataName)) { DeleteProvider deleteModelProvider = getDeleteModelProvider(selected); if (deleteModelProvider != null) { return deleteModelProvider; } return getProvider(selected, DELETE_PROVIDER_FACTORY); } if (MPSDataKeys.MODEL_FILES.is(dataName)) { return getModelFiles(selected); } if (selected.size() != 1) { return null; } // Applicable only to single element selection AbstractTreeNode selectedNode = selected.iterator().next(); if (PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataName)) { return getModelFilesArray(selectedNode); } if (PlatformDataKeys.PASTE_PROVIDER.is(dataName)) { return getModelProvider(selectedNode, PASTE_PROVIDER_FACTORY); } if (MPSCommonDataKeys.NODE.is(dataName)) { return getNode(selectedNode); } if (MPSCommonDataKeys.CONTEXT_MODEL.is(dataName) || MPSCommonDataKeys.MODEL.is(dataName)) { return getModel(selectedNode); } if (MPSCommonDataKeys.CONTEXT_MODULE.is(dataName) || MPSCommonDataKeys.MODULE.is(dataName)) { return getModule(selectedNode); } if (LangDataKeys.MODULE.is(dataName)) { return getIdeaModule(selectedNode); } return null; } private <T> T getProvider(Collection<AbstractTreeNode> selected, ProviderFactory<T> createProvider) { if (selected.size() == 0) return null; List<SNodeReference> selectedNodePointers = new ArrayList<>(); Project project = null; EditableSModel modelDescriptor = null; for (AbstractTreeNode treeNode : selected) { if (!(treeNode instanceof MPSPsiElementTreeNode)) return null; // only root nodes please MPSPsiRootNode mpsPsiNode = ((MPSPsiElementTreeNode) treeNode).getValue(); if (!mpsPsiNode.isValid()) return null; selectedNodePointers.add(mpsPsiNode.getSNodeReference()); if (project == null) { project = treeNode.getProject(); } else if (project != treeNode.getProject()) { return null; // only same project } if (modelDescriptor == null) { modelDescriptor = getModel(mpsPsiNode); } else if (modelDescriptor != getModel(mpsPsiNode)) { return null; // only same model } } jetbrains.mps.project.Project mpsProject = ProjectHelper.fromIdeaProject(project); if (mpsProject == null || modelDescriptor == null) return null; return createProvider.create(selectedNodePointers, modelDescriptor, modelDescriptor, mpsProject); } private DeleteProvider getDeleteModelProvider(Collection<AbstractTreeNode> selected) { final List<MPSPsiModel> psiModels = new ArrayList<>(); for (AbstractTreeNode treeNode : selected) { if (!(treeNode instanceof MPSPsiModelTreeNode)) { return null; } MPSPsiModel psiModel = ((MPSPsiModelTreeNode) treeNode).getModel(); psiModels.add(psiModel); } return new SingleFileModelDeleteProvider(psiModels); } private <T> T getModelProvider(AbstractTreeNode treeNode, ModelProviderFactory<T> createProvider) { if (!(treeNode instanceof MPSPsiModelTreeNode)) return null; // only model MPSPsiModel psiModel = ((MPSPsiModelTreeNode) treeNode).getModel(); if (psiModel == null || !psiModel.isValid()) return null; Project project = treeNode.getProject(); EditableSModel modelDescriptor = getModel(treeNode); jetbrains.mps.project.Project mpsProject = ProjectHelper.fromIdeaProject(project); if (mpsProject == null || modelDescriptor == null) return null; return createProvider.create(modelDescriptor, modelDescriptor, mpsProject); } private Set<IFile> getModelFiles(Collection<AbstractTreeNode> selected) { Set<IFile> modelFiles = new HashSet<>(); for (AbstractTreeNode nextTreeNode : selected) { IFile nextModelFile = getModelFile(nextTreeNode); if (nextModelFile != null) { modelFiles.add(nextModelFile); } } return modelFiles; } private IFile getModelFile(AbstractTreeNode treeNode) { if (treeNode instanceof MPSPsiModelTreeNode) { MPSPsiModelTreeNode fileNode = (MPSPsiModelTreeNode) treeNode; VirtualFile virtualFile = fileNode.getVirtualFile(); if (virtualFile == null || virtualFile.getFileType() != MPSFileTypeFactory.MPS_FILE_TYPE && virtualFile.getFileType() != MPSFileTypeFactory.MPS_HEADER_FILE_TYPE) { return null; } return FileSystem.getInstance().getFile(virtualFile.getPath()); } else if (treeNode instanceof PsiDirectoryNode) { VirtualFile virtualFile = ((PsiDirectoryNode) treeNode).getVirtualFile(); if (virtualFile == null) { return null; } IFile ifile = FileSystem.getInstance().getFile(virtualFile.getPath()); SModel model = SModelFileTracker.getInstance(ProjectHelper.getProjectRepository(treeNode.getProject())).findModel(ifile); if (model != null) return ifile; } return null; } private VirtualFile[] getModelFilesArray(AbstractTreeNode treeNode) { VirtualFile virtualFile = getVirtualFile(treeNode); if (virtualFile == null) return null; return new VirtualFile[]{virtualFile}; } private VirtualFile getVirtualFile(AbstractTreeNode treeNode) { if (!(treeNode instanceof MPSPsiModelTreeNode)) { return null; } MPSPsiModelTreeNode modelTreeNode = (MPSPsiModelTreeNode) treeNode; VirtualFile modelVFile = modelTreeNode.getVirtualFile(); if (modelVFile == null || (modelVFile.getFileType() != MPSFileTypeFactory.MPS_FILE_TYPE && modelVFile.getFileType() != MPSFileTypeFactory.MPS_HEADER_FILE_TYPE)) return null; return modelVFile; } private SNode getNode(AbstractTreeNode treeNode) { if (!(treeNode instanceof MPSPsiElementTreeNode)) { return null; } MPSPsiNodeBase psiNode = ((MPSPsiElementTreeNode) treeNode).getValue(); if (!(psiNode instanceof MPSPsiRealNode)) { return null; } final SNodeReference nodeRef = ((MPSPsiRealNode) psiNode).getSNodeReference(); final SRepository repository = ProjectHelper.getProjectRepository(treeNode.getProject()); // TODO remove read action from here once SModelFileTracker stops doing the same (creating read action if not already in one) return new ModelComputeRunnable<>(() -> nodeRef.resolve(repository)).runRead(repository.getModelAccess()); } private Module getIdeaModule(AbstractTreeNode treeNode) { VirtualFile modelVFile = getVirtualFile(treeNode); if (modelVFile == null) return null; return ModuleUtilCore.findModuleForFile(modelVFile, treeNode.getProject()); } private SModule getModule(AbstractTreeNode selectedNode) { EditableSModel contextModel = getContextModel(selectedNode); return contextModel != null ? contextModel.getModule() : null; } private EditableSModel getModel(AbstractTreeNode selectedNode) { SRepository repository = ProjectHelper.getProjectRepository(selectedNode.getProject()); if (selectedNode instanceof MPSPsiElementTreeNode) { MPSPsiNodeBase value = ((MPSPsiElementTreeNode) selectedNode).getValue(); return getModel(value); } else if (selectedNode instanceof MPSPsiModelTreeNode) { MPSPsiModel psiModel = ((MPSPsiModelTreeNode) selectedNode).getModel(); SModel sModel = psiModel.getSModelReference().resolve(repository); return (EditableSModel) sModel; } else if (selectedNode instanceof PsiDirectoryNode) { SModel sModel = SModelFileTracker.getInstance(repository).findModel(VirtualFileUtils.toIFile(((PsiDirectoryNode) selectedNode).getVirtualFile())); if (sModel instanceof EditableSModel) { return (EditableSModel) sModel; } } return null; } private EditableSModel getContextModel(AbstractTreeNode selectedNode) { if (selectedNode instanceof MPSPsiElementTreeNode) { MPSPsiNodeBase value = ((MPSPsiElementTreeNode) selectedNode).getValue(); return getModel(value); } else if (selectedNode instanceof MPSPsiModelTreeNode) { MPSPsiModel psiModel = ((MPSPsiModelTreeNode) selectedNode).getModel(); SModel sModel = psiModel.getSModelReference().resolve(ProjectHelper.getProjectRepository(selectedNode.getProject())); return (EditableSModel) sModel; } return null; } private EditableSModel getModel(MPSPsiNodeBase mpsPsiNode) { MPSPsiModel containingModel = mpsPsiNode.getContainingModel(); SModel sModel = containingModel.getSModelReference().resolve(ProjectHelper.getProjectRepository(mpsPsiNode.getProject())); return (EditableSModel) sModel; } private interface ProviderFactory<T> { T create(Collection<SNodeReference> selectedNodes, @NotNull EditableSModel modelDescriptor, SModel sModel, @NotNull jetbrains.mps.project.Project project); } private interface ModelProviderFactory<T> { T create(@NotNull EditableSModel modelDescriptor, SModel sModel, @NotNull jetbrains.mps.project.Project project); } private static final ProviderFactory<SNodeCutCopyProvider> CUT_COPY_PROVIDER_FACTORY = (selectedNodes, modelDescriptor, sModel, project) -> new SNodeCutCopyProvider(selectedNodes, modelDescriptor, project); private static final ProviderFactory<SNodeDeleteProvider> DELETE_PROVIDER_FACTORY = (selectedNodes, modelDescriptor, sModel, project) -> new SNodeDeleteProvider(selectedNodes, project); private static final ModelProviderFactory<SNodePasteProvider> PASTE_PROVIDER_FACTORY = (modelDescriptor, sModel, project) -> new SNodePasteProvider(sModel, project, modelDescriptor); }