package com.siberika.idea.pascal.debugger.gdb; import com.intellij.icons.AllIcons; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.ui.ColoredTextContainer; import com.intellij.ui.SimpleTextAttributes; import com.intellij.xdebugger.XDebuggerUtil; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.evaluation.XDebuggerEvaluator; import com.intellij.xdebugger.frame.XCompositeNode; import com.intellij.xdebugger.frame.XStackFrame; import com.siberika.idea.pascal.debugger.gdb.parser.GdbMiResults; import com.siberika.idea.pascal.jps.util.FileUtil; import com.siberika.idea.pascal.lang.parser.NamespaceRec; import com.siberika.idea.pascal.lang.psi.impl.PasField; import com.siberika.idea.pascal.lang.references.PasReferenceUtil; import com.siberika.idea.pascal.util.StrUtil; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.Collection; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Author: George Bakhtadze * Date: 01/04/2017 */ public class GdbStackFrame extends XStackFrame { private final GdbXDebugProcess process; private final GdbExecutionStack executionStack; private final GdbMiResults frame; private final int level; private final ConcurrentMap<String, Collection<PasField>> fieldsMap = new ConcurrentHashMap<String, Collection<PasField>>(); private XSourcePosition sourcePosition; public GdbStackFrame(GdbExecutionStack executionStack, GdbMiResults frame) { this.process = executionStack.getProcess(); this.executionStack = executionStack; this.frame = frame; level = (frame != null) && (frame.getValue("level") != null) ? StrUtil.strToIntDef(frame.getString("level"), 0) : 0; if (process.options.needPosition()) { sourcePosition = getSourcePosition(); } } @Override public XSourcePosition getSourcePosition() { if (sourcePosition != null) { return sourcePosition; } if (null == frame) { return null; } Integer line = frame.getInteger("line"); String filename = frame.getString("fullname"); if ((null == filename) || (null == line)) { return null; } String path = filename.replace(File.separatorChar, '/'); VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(path); if (null == virtualFile) { return null; } if (null == sourcePosition) { sourcePosition = XDebuggerUtil.getInstance().createPosition(virtualFile, line - 1); } return sourcePosition; } @Override public void customizePresentation(@NotNull ColoredTextContainer component) { if (null == frame) { return; } String filename = frame.getString("fullname"); filename = filename != null ? FileUtil.getFilename(filename) : "-"; String line = frame.getString("line"); component.append(formatRoutine(frame), SimpleTextAttributes.REGULAR_ATTRIBUTES); component.append(String.format(" (%s:%s)", filename, line != null ? line : "-"), SimpleTextAttributes.GRAYED_ATTRIBUTES); component.setIcon(AllIcons.Debugger.StackFrame); } private String formatRoutine(GdbMiResults frame) { String name = frame.getString("func"); PasField routine = resolveIdentifierName(name, PasField.TYPES_ROUTINE); name = routine != null ? routine.name : name; if (StringUtils.isEmpty(name) || "??".equals(name)) { String addr = frame.getString("addr"); name = String.format("?? (%s)", addr != null ? addr : "-"); } return name + "()"; } PasField resolveIdentifierName(final String name, final Set<PasField.FieldType> types) { if (!process.options.resolveNames() || (null == sourcePosition)) { return null; } return ApplicationManager.getApplication().runReadAction(new Computable<PasField>() { @Override public PasField compute() { PsiElement el = XDebuggerUtil.getInstance().findContextElement(sourcePosition.getFile(), sourcePosition.getOffset(), process.getSession().getProject(), false); if (el != null) { Collection<PasField> fields = getFields(el, name); String id = name.substring(name.lastIndexOf('.') + 1); for (PasField field : fields) { if (types.contains(field.fieldType) && id.equalsIgnoreCase(field.name)) { return field; } } } return null; } }); } private Collection<PasField> getFields(@NotNull PsiElement el, String name) { Collection<PasField> fields = fieldsMap.get(name); if (fields != null) { return fields; } NamespaceRec namespace; int dotIndex = name.lastIndexOf('.'); if (dotIndex > 0) { if (name.startsWith("this.")) { namespace = NamespaceRec.fromFQN(el, name.substring(5)); } else { namespace = NamespaceRec.fromFQN(el, name); } } else { namespace = NamespaceRec.fromFQN(el, PasField.DUMMY_IDENTIFIER); } namespace.clearTarget(); namespace.setIgnoreVisibility(true); fields = PasReferenceUtil.resolveExpr(null, namespace, PasField.TYPES_LOCAL, true, 0); fieldsMap.put(name, fields); return fields; } @Nullable @Override public XDebuggerEvaluator getEvaluator() { return new GdbEvaluator(this); } @Override public void computeChildren(@NotNull XCompositeNode node) { process.setLastQueriedVariablesCompositeNode(node); process.sendCommand(String.format("-stack-list-variables --thread %s --frame %d --simple-values", executionStack.getThreadId(), level)); } public GdbExecutionStack getExecutionStack() { return executionStack; } public int getLevel() { return level; } }