package com.siberika.idea.pascal.debugger.gdb; import com.intellij.diagnostic.logging.LogConsoleImpl; import com.intellij.execution.ExecutionException; import com.intellij.execution.ExecutionResult; import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.ui.ConsoleView; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.execution.ui.ExecutionConsole; import com.intellij.execution.ui.RunnerLayoutUi; import com.intellij.execution.ui.layout.PlaceInGrid; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.ActionPlaces; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.SystemInfo; import com.intellij.psi.PsiElement; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.testFramework.LightVirtualFile; import com.intellij.ui.content.Content; import com.intellij.xdebugger.XDebugProcess; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerUtil; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.breakpoints.XBreakpointHandler; import com.intellij.xdebugger.evaluation.EvaluationMode; import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider; import com.intellij.xdebugger.evaluation.XDebuggerEvaluator; import com.intellij.xdebugger.frame.XCompositeNode; import com.intellij.xdebugger.frame.XStackFrame; import com.intellij.xdebugger.frame.XSuspendContext; import com.intellij.xdebugger.frame.XValueChildrenList; import com.intellij.xdebugger.ui.XDebugTabLayouter; import com.siberika.idea.pascal.PascalBundle; import com.siberika.idea.pascal.PascalFileType; import com.siberika.idea.pascal.debugger.PascalDebuggerValue; import com.siberika.idea.pascal.debugger.PascalLineBreakpointHandler; import com.siberika.idea.pascal.debugger.gdb.parser.GdbMiResults; import com.siberika.idea.pascal.editor.ContextAwareVirtualFile; import com.siberika.idea.pascal.jps.sdk.PascalSdkData; import com.siberika.idea.pascal.lang.psi.impl.PasField; import com.siberika.idea.pascal.run.PascalRunConfiguration; import com.siberika.idea.pascal.sdk.BasePascalSdkType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Author: George Bakhtadze * Date: 26/03/2017 */ public class GdbXDebugProcess extends XDebugProcess { private static final Logger LOG = Logger.getInstance(GdbXDebugProcess.class); private final XBreakpointHandler<?>[] MY_BREAKPOINT_HANDLERS = new XBreakpointHandler[] {new PascalLineBreakpointHandler(this)}; private final ExecutionResult executionResult; private ConsoleView console; private LogConsoleImpl outputConsole; private XCompositeNode lastQueriedVariablesCompositeNode; private XCompositeNode lastParentNode; private Map<String, GdbVariableObject> variableObjectMap; private static final String VAR_PREFIX_LOCAL = "l%"; private static final String VAR_PREFIX_WATCHES = "w%"; private static final AnAction[] EMPTY_ACTIONS = new AnAction[0]; private boolean inferiorRunning = false; private File outputFile; private Sdk sdk; Options options = new Options(); public GdbXDebugProcess(XDebugSession session, ExecutionEnvironment environment, ExecutionResult executionResult) { super(session); RunProfile conf = environment.getRunProfile(); if (conf instanceof PascalRunConfiguration) { Module module = ((PascalRunConfiguration) conf).getConfigurationModule().getModule(); sdk = module != null ? ModuleRootManager.getInstance(module).getSdk() : null; } if (null == sdk) { sdk = ProjectRootManager.getInstance(environment.getProject()).getProjectSdk(); } this.executionResult = executionResult; try { createGdbProcess(environment); } catch (ExecutionException e) { LOG.warn("Error running GDB", e); } } private void createGdbProcess(ExecutionEnvironment env) throws ExecutionException { if (isOutputConsoleNeeded()) { createOutputConsole(env.getProject()); } console = (ConsoleView) executionResult.getExecutionConsole(); variableObjectMap = new HashMap<String, GdbVariableObject>(); sendCommand("-break-delete"); } private boolean isOutputConsoleNeeded() { return !SystemInfo.isWindows && getData().getBoolean(PascalSdkData.Keys.DEBUGGER_REDIRECT_CONSOLE); } private void createOutputConsole(Project project) { try { outputFile = File.createTempFile("ipas_run_out_", ".tmp"); outputConsole = new LogConsoleImpl(project, outputFile, Charset.forName("utf-8"), 0, PascalBundle.message("debug.output.title"), false, GlobalSearchScope.allScope(project)) { @Override public boolean isActive() { return true; } }; outputConsole.activate(); } catch (IOException e) { LOG.warn("Error creating output console"); } } @Nullable @Override protected ProcessHandler doGetProcessHandler() { return executionResult.getProcessHandler(); } @NotNull @Override public ExecutionConsole createConsole() { return console; } public void printToConsole(String text, ConsoleViewContentType contentType) { if (console != null) { console.print(text, contentType); } } @Override public void sessionInitialized() { super.sessionInitialized(); getProcessHandler().addProcessListener(new GdbProcessAdapter(this)); sendCommand("-gdb-set target-async on"); if (getData().getBoolean(PascalSdkData.Keys.DEBUGGER_REDIRECT_CONSOLE)) { if (SystemInfo.isWindows) { sendCommand("-gdb-set new-console on"); } else { sendCommand("-exec-arguments > " + outputFile.getAbsolutePath()); } } sendCommand("-exec-run"); getSession().setPauseActionSupported(true); } @Override public void startPausing() { sendCommand("-exec-interrupt"); } @Override public void resume(@Nullable XSuspendContext context) { sendCommand("-exec-continue --all"); } @Override public void startStepOver(@Nullable XSuspendContext context) { sendCommand("-exec-next"); } @Override public void startStepInto(@Nullable XSuspendContext context) { sendCommand("-exec-step"); } @Override public void startStepOut(@Nullable XSuspendContext context) { sendCommand("-exec-finish"); } @Override public void stop() { // finalize something } @Override public void runToPosition(@NotNull XSourcePosition position, @Nullable XSuspendContext context) { sendCommand(String.format("-exec-until %s:%d", position.getFile().getCanonicalPath(), position.getLine())); } public void sendCommand(String command) { if (getSession().isStopped()) { return; } try { OutputStream commandStream = getProcessHandler().getProcessInput(); if (commandStream != null) { commandStream.write((command + "\n").getBytes("UTF-8")); commandStream.flush(); printToConsole(">>>> " + command + "\n", ConsoleViewContentType.NORMAL_OUTPUT); } } catch (IOException e) { LOG.warn("ERROR: sending command to GDB", e); } } @NotNull @Override public XDebuggerEditorsProvider getEditorsProvider() { return new XDebuggerEditorsProvider() { @NotNull @Override public FileType getFileType() { return PascalFileType.INSTANCE; } @NotNull @Override public Document createDocument(@NotNull Project project, @NotNull String text, @Nullable XSourcePosition sourcePosition, @NotNull EvaluationMode mode) { LightVirtualFile file; if (sourcePosition != null) { PsiElement psiElement = XDebuggerUtil.getInstance().findContextElement(sourcePosition.getFile(), sourcePosition.getOffset(), project, false); file = new ContextAwareVirtualFile("_debug.pas", text, psiElement); } else { file = new LightVirtualFile("_debug.pas", text); } return FileDocumentManager.getInstance().getDocument(file); } }; } @NotNull @Override public XDebugTabLayouter createTabLayouter() { return new XDebugTabLayouter() { @Override public void registerAdditionalContent(@NotNull RunnerLayoutUi ui) { if (!isOutputConsoleNeeded()) { return; } Content gdbConsoleContent = ui.createContent("PascalDebugConsoleContent", outputConsole.getComponent(), PascalBundle.message("debug.output.title"), AllIcons.Debugger.Console, outputConsole.getPreferredFocusableComponent()); gdbConsoleContent.setCloseable(false); DefaultActionGroup consoleActions = new DefaultActionGroup(); AnAction[] actions = outputConsole.getConsole() != null ? outputConsole.getConsole().createConsoleActions() : EMPTY_ACTIONS; for (AnAction action : actions) { consoleActions.add(action); } gdbConsoleContent.setActions(consoleActions, ActionPlaces.DEBUGGER_TOOLBAR, outputConsole.getPreferredFocusableComponent()); ui.addContent(gdbConsoleContent, 2, PlaceInGrid.bottom, false); } }; } @NotNull @Override public XBreakpointHandler<?>[] getBreakpointHandlers() { return MY_BREAKPOINT_HANDLERS; } public PascalLineBreakpointHandler getBreakpointHandler() { return (PascalLineBreakpointHandler) MY_BREAKPOINT_HANDLERS[0]; } public void setLastQueriedVariablesCompositeNode(XCompositeNode lastQueriedVariablesCompositeNode) { this.lastQueriedVariablesCompositeNode = lastQueriedVariablesCompositeNode; } public void evaluate(String expression, XDebuggerEvaluator.XEvaluationCallback callback) { String key = VAR_PREFIX_WATCHES + expression; GdbVariableObject var = variableObjectMap.get(key); if (null == var) { variableObjectMap.put(key, new GdbVariableObject(key, expression, callback)); sendCommand(String.format("-var-create \"%s\" @ \"%s\"", key, expression)); } else { var.setCallback(callback); updateVariableObjectUI(var); sendCommand(String.format("-var-update --all-values \"%s\"", key)); } } public void handleVarResult(GdbMiResults res) { String key = res.getString("name"); GdbVariableObject var = variableObjectMap.get(key); if (var != null) { var.updateFromResult(res); if (var.getCallback() != null) { updateVariableObjectUI(var); } } } private void updateVariableObjectUI(@NotNull GdbVariableObject var) { var.getCallback().evaluated(new PascalDebuggerValue(this, var.getKey(), var.getType(), var.getValue(), var.getChildrenCount())); } public void handleVarUpdate(GdbMiResults results) { List<Object> changes = results.getList("changelist"); for (Object o : changes) { GdbMiResults change = (GdbMiResults) o; handleVarResult(change); } } synchronized public void computeValueChildren(String name, XCompositeNode node) { lastParentNode = node; sendCommand("-var-list-children --all-values " + name); } synchronized void handleVariablesResponse(List<Object> variables) { handleVariables(lastQueriedVariablesCompositeNode, variables, false); } synchronized void handleChildrenResult(List<Object> variables) { handleVariables(lastParentNode, variables, true); lastParentNode = null; } private void handleVariables(XCompositeNode node, List<Object> variables, boolean children) { if (null == node) { return; } if (variables.isEmpty()) { node.addChildren(XValueChildrenList.EMPTY, true); } else { XValueChildrenList childrenList = new XValueChildrenList(variables.size()); for (Object o : variables) { if (o instanceof GdbMiResults) { GdbMiResults res = (GdbMiResults) o; if (children) { res = res.getTuple("child"); } String varName = res.getString("name"); String varKey = (children ? "" : VAR_PREFIX_LOCAL) + varName; if (varName.startsWith(VAR_PREFIX_LOCAL) || varName.startsWith(VAR_PREFIX_WATCHES)) { varName = varName.substring(2); } PasField.FieldType fieldType = PasField.FieldType.VARIABLE; XStackFrame frame = getSession().getCurrentStackFrame(); if (frame instanceof GdbStackFrame) { PasField field = ((GdbStackFrame) frame).resolveIdentifierName(varName, PasField.TYPES_LOCAL); if (field != null) { varName = formatVariableName(field); fieldType = field.fieldType; } } GdbVariableObject var = variableObjectMap.get(varKey); if (null != var) { var.updateFromResult(res); if (!children) { sendCommand(String.format("-var-update --all-values \"%s\"", varKey)); } } else { var = new GdbVariableObject(varKey, varName, null, res); variableObjectMap.put(varKey, var); if (!children) { sendCommand(String.format("-var-create \"%s\" @ \"%s\"", varKey, varName)); } } childrenList.add(varName.substring(varName.lastIndexOf('.')+1), new PascalDebuggerValue(this, var.getKey(), var.getType(), var.getValue(), var.getChildrenCount(), fieldType)); } else { node.setErrorMessage("Invalid variables list entry"); return; } } node.addChildren(childrenList, true); } } private String formatVariableName(@NotNull PasField field) { return field.name + (field.fieldType == PasField.FieldType.ROUTINE ? "()" : ""); } public boolean isInferiorRunning() { return inferiorRunning; } public void setInferiorRunning(boolean inferiorRunning) { this.inferiorRunning = inferiorRunning; } public PascalSdkData getData() { return sdk != null ? BasePascalSdkType.getAdditionalData(sdk) : PascalSdkData.EMPTY; } final class Options { boolean resolveNames() { return getData().getBoolean(PascalSdkData.Keys.DEBUGGER_RESOLVE_NAMES); } boolean callGetters() { return false; } String asmFormat() { return getData().getString(PascalSdkData.Keys.DEBUGGER_ASM_FORMAT); } public boolean needPosition() { return resolveNames() || callGetters(); } } }