/*
* 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.java.trace;
import com.intellij.debugger.SourcePosition;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiForStatement;
import com.intellij.psi.PsiStatement;
import com.intellij.psi.PsiWhiteSpace;
import jetbrains.mps.ide.navigation.NodeNavigatable;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.idea.core.project.SolutionIdea;
import jetbrains.mps.nodefs.MPSNodeVirtualFile;
import jetbrains.mps.nodefs.NodeVirtualFileSystem;
import jetbrains.mps.smodel.ModelAccessHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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;
public class MpsSourcePosition extends SourcePosition {
private final SNodeReference myNodePointer;
private final Project myProject;
private final NodeNavigatable myNavigatable;
private final GeneratedSourcePosition mySourcePosition;
private MpsSourcePosition(GeneratedSourcePosition position, Project project) {
mySourcePosition = position;
myNodePointer = position.getNode();
myProject = project;
myNavigatable = new NodeNavigatable(ProjectHelper.fromIdeaProject(myProject), myNodePointer);
}
public MPSNodeVirtualFile getVirtualFile() {
return NodeVirtualFileSystem.getInstance().getFileFor(ProjectHelper.getProjectRepository(myProject), myNodePointer);
}
public MPSNodeVirtualFile getRootVirtualFile() {
final SRepository repo = ProjectHelper.getProjectRepository(myProject);
SNodeReference rootPointer = new ModelAccessHelper(repo).runReadAction(() -> {
SNode resolved = myNodePointer.resolve(repo);
return resolved == null ? null : resolved.getContainingRoot().getReference();
});
// FIXME [artem] I have no idea whether it's right to return VF of the node if it's not resolved, just decided that null
// would be worse, provided refactored code always had rootPointer (though it assumed myNodePointer always resolves).
return rootPointer == null ? getVirtualFile() : NodeVirtualFileSystem.getInstance().getFileFor(repo, rootPointer);
}
@NotNull
@Override
public PsiFile getFile() {
return mySourcePosition.getPsiFile(myProject);
}
@Override
public PsiElement getElementAt() {
PsiFile psiFile = getFile();
return ReadAction.compute(() -> {
Document document = PsiDocumentManager.getInstance(myProject).getDocument(psiFile);
if (document == null) {
return null;
}
int line = getLine();
if (line < 0) {
return psiFile;
}
int startOffset = document.getLineStartOffset(line);
PsiElement element = psiFile.findElementAt(startOffset);
for (; element instanceof PsiWhiteSpace || element instanceof PsiComment; element = psiFile.findElementAt(startOffset)) {
startOffset = element.getTextRange().getEndOffset();
}
if (element != null && element.getParent() instanceof PsiForStatement) {
PsiStatement initialization = ((PsiForStatement) element.getParent()).getInitialization();
if (initialization != null) {
element = initialization;
}
}
return element;
});
}
@Override
public int getLine() {
// our lines start from 1, theirs from 0
return mySourcePosition.getLineNumber() - 1;
}
@Override
public int getOffset() {
PsiElement psiElement = getElementAt();
if (psiElement != null) {
return psiElement.getTextOffset();
}
return 0;
}
@Override
public Editor openEditor(boolean requestFocus) {
FileEditorManager.getInstance(myProject).openFile(getRootVirtualFile(), requestFocus);
return null;
}
@Override
public void navigate(boolean requestFocus) {
myNavigatable.navigate(requestFocus);
}
@Override
public boolean canNavigate() {
return myNavigatable.canNavigate();
}
@Override
public boolean canNavigateToSource() {
return myNavigatable.canNavigateToSource();
}
@NotNull
public SNodeReference getNode() {
return myNodePointer;
}
@Nullable
public static MpsSourcePosition createPosition(Project project, String typeName, String fileName, int lineNumber) {
final GeneratedSourcePosition sourcePosition = GeneratedSourcePosition.fromLocation(project, typeName, fileName, lineNumber);
if (sourcePosition.getNode() == null) {
return null;
}
final SRepository repo = ProjectHelper.getProjectRepository(project);
final boolean isSolutionIdea = new ModelAccessHelper(repo).runReadAction(() -> {
SNode node = sourcePosition.getNode().resolve(repo);
if (node == null) {
return false;
}
SModel modelDescriptor = node.getModel();
SModule module = modelDescriptor.getModule();
return module instanceof SolutionIdea;
});
return isSolutionIdea ? new MpsSourcePosition(sourcePosition, project) : null;
}
}