/* * Copyright 2003-2016 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.editor; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.NavigatableFileEditor; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.Navigatable; import jetbrains.mps.ide.editor.MPSFileNodeEditor; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.ide.vfs.VirtualFileUtils; import jetbrains.mps.idea.core.psi.impl.MPSPsiModel; import jetbrains.mps.idea.core.psi.impl.MPSPsiNode; import jetbrains.mps.idea.core.psi.impl.MPSPsiNodeBase; import jetbrains.mps.idea.core.psi.impl.MPSPsiProvider; import jetbrains.mps.smodel.ModelAccessHelper; import jetbrains.mps.smodel.SModelFileTracker; import jetbrains.mps.util.Computable; import jetbrains.mps.nodefs.MPSNodeVirtualFile; import jetbrains.mps.nodefs.NodeVirtualFileSystem; import org.jetbrains.annotations.NotNull; 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.SRepository; /** * This editor is a work-around for the problem that idea navigates to PSI element (e.g. in goto super method) * in such a way: PsiFile.getVirtualFile() -> open editor for it -> navigate to the psi element's offset * In case of a single-file model we're asked to open the editor for the model file. * We know which root node to open the moment idea navigates to this editor via navigateTo(). * We could internally use NodeEditor like MPSFileNodeEditor does, but we do a more hacky thing instead: * we open another MPSFileNodeEditor for the right MPSNodeVirtualFile and close this artificial editor. * The reason: if we do our work inside this editor then idea keeps thinking that it's an editor for * the model file (.mps) and * 1) will not open another editor for another root inside the same model * 2) shows the wrong name for the editor: modelname.mps * 3) opens a separate editor for the root node if the user opens it from, say, project pane * * The right solution seems to be: our MPSNodeFileSystem should be NewFileSystem and our virtual files should be * NewVirtualFile, then a lot of problems will be gone and there will be no need for hacks like this. Because * we will be able to return our node virtual files anywhere and idea will not think they're bad. * * Created by danilla on 20/11/15. */ public class ModelFileToRootDispatchingEditor extends MPSFileNodeEditor implements NavigatableFileEditor { private Project myProject; private VirtualFile myModelFile; ModelFileToRootDispatchingEditor(Project project, VirtualFile virtualFile) { super(ProjectHelper.fromIdeaProject(project), null); myProject = project; myModelFile = virtualFile; } @Override public boolean canNavigateTo(@NotNull final Navigatable navigatable) { if (!(navigatable instanceof OpenFileDescriptor)) { return false; } final int offset = ((OpenFileDescriptor) navigatable).getOffset(); if (offset < 0) { return false; } final SRepository repository = ProjectHelper.fromIdeaProject(myProject).getRepository(); return new ModelAccessHelper(repository.getModelAccess()).runReadAction(new Computable<Boolean>() { @Override public Boolean compute() { return iKnowThisNode(offset, repository); } }); } @Override public void navigateTo(@NotNull final Navigatable navigatable) { assert navigatable instanceof OpenFileDescriptor; final int offset = ((OpenFileDescriptor) navigatable).getOffset(); if (offset == -1) { return; } final SRepository repository = ProjectHelper.fromIdeaProject(myProject).getRepository(); repository.getModelAccess().runReadAction(new Runnable() { @Override public void run() { SNodeReference nodeRef = getNode(offset, repository); SNode node = nodeRef.resolve(repository); if (node == null) { return; } SNode root = node.getContainingRoot(); MPSNodeVirtualFile nodeFile = NodeVirtualFileSystem.getInstance().getFileFor(repository, root); // opening or finding the editor for the right root node MPSFileNodeEditor editor = getEditorForRoot(nodeFile); // selecting the node inside the root which is designated by the given 'offset' editor.getNodeEditor().showNode(node, true); // now closing this artificial editor FileEditorManager.getInstance(myProject).closeFile(myModelFile); } }); } @Override public void selectNotify() { // guard from NPE in super.selectNotify() } @Override public void deselectNotify() { // guard from NPE in super.deselectNotify() } private boolean iKnowThisNode(int offset, SRepository repository) { return getNode(offset, repository) != null; } private SNodeReference getNode(int offset, SRepository repository) { SModel model = SModelFileTracker.getInstance(repository).findModel(VirtualFileUtils.toIFile(myModelFile)); if (model == null) { return null; } MPSPsiModel psiModel = MPSPsiProvider.getInstance(myProject).getPsi(model); if (psiModel == null) { return null; } MPSPsiNodeBase node = psiModel.findNodeByPosition(offset); if (!(node instanceof MPSPsiNode)) { // we've been asked to navigate by an offset that corresponds not to a real node in the model // but some auxilary psi node (like MPSPsiParameterList) that has no counterpart in the model return null; } return ((MPSPsiNode) node).getSNodeReference(); } @NotNull private MPSFileNodeEditor getEditorForRoot(MPSNodeVirtualFile nodeFile) { // finding currently open editor, if any // FileEditor[] editors = FileEditorManager.getInstance(myProject).getEditors(nodeFile); // MPSFileNodeEditor result = null; // for (FileEditor editor: editors) { // if (editor instanceof MPSFileNodeEditor) { // result = (MPSFileNodeEditor) editor; // } // } // if (result != null) { // return result; // } // creating a new editor FileEditor[] editors = FileEditorManager.getInstance(myProject).openFile(nodeFile, true); MPSFileNodeEditor result = null; for (FileEditor editor: editors) { if (editor instanceof MPSFileNodeEditor) { result = (MPSFileNodeEditor) editor; } } assert result != null : "Couldn't create MPSFileNodeEditor"; return result; } }