/*
* Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan
*
* 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 com.goide.dlv;
import com.goide.dlv.protocol.DlvApi;
import com.goide.dlv.protocol.DlvRequest;
import com.goide.psi.GoNamedElement;
import com.goide.psi.GoTopLevelDeclaration;
import com.goide.psi.GoTypeSpec;
import com.goide.stubs.index.GoTypesIndex;
import com.intellij.icons.AllIcons;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SyntaxTraverser;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.XDebuggerUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.frame.*;
import com.intellij.xdebugger.frame.presentation.XNumericValuePresentation;
import com.intellij.xdebugger.frame.presentation.XRegularValuePresentation;
import com.intellij.xdebugger.frame.presentation.XStringValuePresentation;
import com.intellij.xdebugger.frame.presentation.XValuePresentation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
class DlvXValue extends XNamedValue {
@NotNull
private final DlvApi.Variable myVariable;
private final Icon myIcon;
private final DlvDebugProcess myProcess;
private final DlvCommandProcessor myProcessor;
private final int myFrameId;
public DlvXValue(@NotNull DlvDebugProcess process,
@NotNull DlvApi.Variable variable,
@NotNull DlvCommandProcessor processor,
int frameId,
@Nullable Icon icon) {
super(variable.name);
myProcess = process;
myVariable = variable;
myIcon = icon;
myProcessor = processor;
myFrameId = frameId;
}
@Override
public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
XValuePresentation presentation = getPresentation();
boolean hasChildren = myVariable.children.length > 0;
node.setPresentation(myIcon, presentation, hasChildren);
}
@Override
public void computeChildren(@NotNull XCompositeNode node) {
DlvApi.Variable[] children = myVariable.children;
if (children.length == 0) {
super.computeChildren(node);
}
else {
XValueChildrenList list = new XValueChildrenList();
for (DlvApi.Variable child : children) {
list.add(child.name, new DlvXValue(myProcess, child, myProcessor, myFrameId, AllIcons.Nodes.Field));
}
node.addChildren(list, true);
}
}
@Nullable
@Override
public XValueModifier getModifier() {
return new XValueModifier() {
@Override
public void setValue(@NotNull String newValue, @NotNull XModificationCallback callback) {
myProcessor.send(new DlvRequest.SetSymbol(myVariable.name, newValue, myFrameId))
.processed(o -> {
if (o != null) {
callback.valueModified();
}
})
.rejected(throwable -> callback.errorOccurred(throwable.getMessage()));
}
};
}
@NotNull
private XValuePresentation getPresentation() {
String value = myVariable.value;
if (myVariable.isNumber()) return new XNumericValuePresentation(value);
if (myVariable.isString()) return new XStringValuePresentation(value);
if (myVariable.isBool()) {
return new XValuePresentation() {
@Override
public void renderValue(@NotNull XValueTextRenderer renderer) {
renderer.renderValue(value);
}
};
}
String type = myVariable.type;
boolean isSlice = myVariable.isSlice();
boolean isArray = myVariable.isArray();
if (isSlice || isArray) {
return new XRegularValuePresentation("len:" + myVariable.len + (isSlice ? ", cap:" + myVariable.cap : ""),
type.replaceFirst("struct ", ""));
}
String prefix = myVariable.type + " ";
return new XRegularValuePresentation(StringUtil.startsWith(value, prefix) ? value.replaceFirst(Pattern.quote(prefix), "") : value,
type);
}
@Nullable
private static PsiElement findTargetElement(@NotNull Project project,
@NotNull XSourcePosition position,
@NotNull Editor editor,
@NotNull String name) {
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file == null || !file.getVirtualFile().equals(position.getFile())) return null;
ASTNode leafElement = file.getNode().findLeafElementAt(position.getOffset());
if (leafElement == null) return null;
GoTopLevelDeclaration topLevel = PsiTreeUtil.getTopmostParentOfType(leafElement.getPsi(), GoTopLevelDeclaration.class);
SyntaxTraverser<PsiElement> traverser = SyntaxTraverser.psiTraverser(topLevel)
.filter(e -> e instanceof GoNamedElement && Comparing.equal(name, ((GoNamedElement)e).getName()));
Iterator<PsiElement> iterator = traverser.iterator();
return iterator.hasNext() ? iterator.next() : null;
}
@Override
public void computeSourcePosition(@NotNull XNavigatable navigatable) {
readActionInPooledThread(new Runnable() {
@Override
public void run() {
navigatable.setSourcePosition(findPosition());
}
@Nullable
private XSourcePosition findPosition() {
XDebugSession debugSession = getSession();
if (debugSession == null) return null;
XStackFrame stackFrame = debugSession.getCurrentStackFrame();
if (stackFrame == null) return null;
Project project = debugSession.getProject();
XSourcePosition position = debugSession.getCurrentPosition();
Editor editor = ((FileEditorManagerImpl)FileEditorManager.getInstance(project)).getSelectedTextEditor(true);
if (editor == null || position == null) return null;
String name = myName.startsWith("&") ? myName.replaceFirst("\\&", "") : myName;
PsiElement resolved = findTargetElement(project, position, editor, name);
if (resolved == null) return null;
VirtualFile virtualFile = resolved.getContainingFile().getVirtualFile();
return XDebuggerUtil.getInstance().createPositionByOffset(virtualFile, resolved.getTextOffset());
}
});
}
private static void readActionInPooledThread(@NotNull Runnable runnable) {
ApplicationManager.getApplication().executeOnPooledThread(() -> ApplicationManager.getApplication().runReadAction(runnable));
}
@Nullable
private Project getProject() {
XDebugSession session = getSession();
return session != null ? session.getProject() : null;
}
@Nullable
private XDebugSession getSession() {
return myProcess.getSession();
}
@NotNull
@Override
public ThreeState computeInlineDebuggerData(@NotNull XInlineDebuggerDataCallback callback) {
computeSourcePosition(callback::computed);
return ThreeState.YES;
}
@Override
public boolean canNavigateToSource() {
return true; // for the future compatibility
}
@Override
public boolean canNavigateToTypeSource() {
return (myVariable.isStructure() || myVariable.isPtr()) && getProject() != null;
}
@Override
public void computeTypeSourcePosition(@NotNull XNavigatable navigatable) {
readActionInPooledThread(() -> {
boolean isStructure = myVariable.isStructure();
boolean isPtr = myVariable.isPtr();
if (!isStructure && !isPtr) return;
Project project = getProject();
if (project == null) return;
String dlvType = myVariable.type;
String fqn = dlvType.replaceFirst(isPtr ? "\\*struct " : "struct ", "");
List<String> split = StringUtil.split(fqn, ".");
boolean noFqn = split.size() == 1;
if (split.size() == 2 || noFqn) {
String name = ContainerUtil.getLastItem(split);
assert name != null;
Collection<GoTypeSpec> types = GoTypesIndex.find(name, project, GlobalSearchScope.allScope(project), null);
for (GoTypeSpec type : types) {
if (noFqn || Comparing.equal(fqn, type.getQualifiedName())) {
navigatable.setSourcePosition(XDebuggerUtil.getInstance().createPositionByOffset(
type.getContainingFile().getVirtualFile(), type.getTextOffset()));
return;
}
}
}
});
}
}