diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 114a878c64ce6f8223cfd22d76a20eb16d177e5e (patch) | |
tree | acaf47eb0fa12142d3896416a69e74cbf5a72242 /languages/ruby/debugger | |
download | tdevelop-114a878c64ce6f8223cfd22d76a20eb16d177e5e.tar.gz tdevelop-114a878c64ce6f8223cfd22d76a20eb16d177e5e.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdevelop@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'languages/ruby/debugger')
38 files changed, 9712 insertions, 0 deletions
diff --git a/languages/ruby/debugger/Makefile.am b/languages/ruby/debugger/Makefile.am new file mode 100644 index 00000000..f4c512c2 --- /dev/null +++ b/languages/ruby/debugger/Makefile.am @@ -0,0 +1,25 @@ +# Here resides the debugger part. + +INCLUDES = -I$(top_srcdir)/languages/lib/debugger \ + -I$(top_srcdir)/lib/interfaces -I$(top_srcdir)/lib/interfaces/extensions -I$(top_srcdir)/lib/util \ + -I$(top_srcdir)/lib/widgets $(all_includes) + +kde_module_LTLIBRARIES = libkdevrbdebugger.la +libkdevrbdebugger_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) +libkdevrbdebugger_la_LIBADD = $(top_builddir)/lib/libkdevelop.la $(top_builddir)/lib/widgets/libkdevwidgets.la $(LIB_KHTML) \ + $(top_builddir)/languages/lib/debugger/liblang_debugger.la + +libkdevrbdebugger_la_SOURCES = debuggerpart.cpp dbgcontroller.cpp rdbcontroller.cpp dbgcommand.cpp rdbcommand.cpp rdbparser.cpp stty.cpp breakpoint.cpp variablewidget.cpp rdbbreakpointwidget.cpp framestackwidget.cpp dbgpsdlg.cpp dbgtoolbar.cpp rdboutputwidget.cpp rdbtable.cpp + +METASOURCES = AUTO +KDE_ICON = AUTO + +rubysrc_DATA = debuggee.rb +rubysrcdir = $(kde_datadir)/kdevrbdebugger + +servicedir = $(kde_servicesdir) +service_DATA = kdevrbdebugger.desktop + +rcdir = $(kde_datadir)/kdevrbdebugger +rc_DATA = kdevrbdebugger.rc +noinst_HEADERS = rdbtable.h diff --git a/languages/ruby/debugger/breakpoint.cpp b/languages/ruby/debugger/breakpoint.cpp new file mode 100644 index 00000000..f210e359 --- /dev/null +++ b/languages/ruby/debugger/breakpoint.cpp @@ -0,0 +1,343 @@ +/*************************************************************************** + begin : Tue May 13 2003 + copyright : (C) 2003 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "breakpoint.h" + +#include <klocale.h> + +#include <qfileinfo.h> +#include <qfontmetrics.h> +#include <qpainter.h> +#include <qregexp.h> +#include <qstring.h> + +#include <stdio.h> + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +namespace RDBDebugger +{ + +static int BPKey_ = 0; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +Breakpoint::Breakpoint(bool temporary, bool enabled) + : s_pending_(true), + s_actionAdd_(true), + s_actionClear_(false), + s_actionModify_(false), + s_actionDie_(false), + s_dbgProcessing_(false), + s_enabled_(enabled), + s_temporary_(temporary), + s_changedEnable_(false), + key_(BPKey_++), + active_(-1) +{ +} + +/***************************************************************************/ + +Breakpoint::~Breakpoint() +{ +} + +/***************************************************************************/ + +QString Breakpoint::dbgRemoveCommand() const +{ +// if (dbgId_>0) +// return QString("delete %1").arg(dbgId_); // gdb command - not translatable + + return QString(); +} + +/***************************************************************************/ + +// called when debugger ends +void Breakpoint::reset() +{ + dbgId_ = -1; + s_pending_ = true; + s_actionAdd_ = true; // waiting for the debugger to start + s_actionClear_ = false; + s_changedEnable_ = !s_enabled_; + s_actionModify_ = s_changedEnable_; + s_dbgProcessing_ = false; +// hits_ = 0; + active_ = -1; +} + +/***************************************************************************/ + +void Breakpoint::setActive(int active, int id) +{ + active_ = active; + dbgId_ = id; + + if (s_pending_ && !(s_actionAdd_ && s_actionModify_)) { + s_pending_ = false; + s_actionModify_ = false; + } + + s_actionAdd_ = false; + s_actionClear_ = false; + s_actionDie_ = false; + s_dbgProcessing_ = false; + + if (!s_actionModify_) { + s_changedEnable_ = false; + } +} + +/***************************************************************************/ + +QString Breakpoint::statusDisplay(int activeFlag) const +{ + QString status=""; + if (!s_enabled_) + status = i18n("Disabled"); + else + if (s_pending_) + { + if (s_actionAdd_) + status = i18n("Pending (add)"); + if (s_actionClear_) + status = i18n("Pending (clear)"); + if (s_actionModify_) + status = i18n("Pending (modify)"); + } + else + if (isActive(activeFlag)) + status = i18n("Active"); + + return status; +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +FilePosBreakpoint::FilePosBreakpoint(const QString &fileName, int lineNum, + bool temporary, bool enabled) + : Breakpoint(temporary, enabled), + fileName_(fileName), + lineNo_(lineNum) +{ +} + +/***************************************************************************/ + +FilePosBreakpoint::~FilePosBreakpoint() +{ +} + +/***************************************************************************/ + +QString FilePosBreakpoint::dbgSetCommand() const +{ + QString cmdStr; + if (fileName_.isEmpty()) + cmdStr = QString("break %1").arg(lineNo_); // gdb command - not translatable + else { + cmdStr = QString("break %1:%2").arg(fileName_).arg(lineNo_); + } + + if (isTemporary()) + cmdStr = "t"+cmdStr; // gdb command + + return cmdStr; +} + +/***************************************************************************/ + +bool FilePosBreakpoint::match(const Breakpoint *brkpt) const +{ + // simple case + if (this == brkpt) + return true; + + // Type case + const FilePosBreakpoint* check = dynamic_cast<const FilePosBreakpoint*>(brkpt); + if (!check) + return false; + + // member case + return ( (fileName_ == check->fileName_) && + (lineNo_ == check->lineNo_)); +} + +/***************************************************************************/ + +QString FilePosBreakpoint::location(bool compact) +{ + if (compact) + return QFileInfo(fileName_).fileName()+":"+QString::number(lineNo_); + + return fileName_+":"+QString::number(lineNo_); +} + +/***************************************************************************/ + +void FilePosBreakpoint::setLocation(const QString& location) +{ + QRegExp regExp1("(.*):(\\d+)$"); + regExp1.setMinimal(true); + if ( regExp1.search(location, 0) >= 0 ) + { + QString t = regExp1.cap(1); + QString dirPath = QFileInfo(t).dirPath(); + if ( dirPath == "." ) + fileName_ = QFileInfo(fileName_).dirPath()+"/"+regExp1.cap(1); + else + fileName_ = regExp1.cap(1); + + lineNo_ = regExp1.cap(2).toInt(); + } +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +Watchpoint::Watchpoint(const QString& varName, bool temporary, bool enabled) + : Breakpoint(temporary, enabled), + varName_(varName) +{ +} + +/***************************************************************************/ + +Watchpoint::~Watchpoint() +{ +} + +/***************************************************************************/ + +QString Watchpoint::dbgSetCommand() const +{ + return QString("watch ")+varName_; // gdb command - not translatable +} + +/***************************************************************************/ + +bool Watchpoint::match(const Breakpoint* brkpt) const +{ + // simple case + if (this == brkpt) + return true; + + // Type case + const Watchpoint *watch = dynamic_cast<const Watchpoint*>(brkpt); + if (watch == 0) + return false; + + // member case + return (varName_ == watch->varName_); +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +Catchpoint::Catchpoint(const QString& varName, bool temporary, bool enabled) + : Breakpoint(temporary, enabled), + varName_(varName) +{ +} + +/***************************************************************************/ + +Catchpoint::~Catchpoint() +{ +} + +/***************************************************************************/ + +QString Catchpoint::dbgSetCommand() const +{ + return QString("catch ")+varName_; // gdb command - not translatable +} + +/***************************************************************************/ + +bool Catchpoint::match(const Breakpoint* brkpt) const +{ + // simple case + if (this == brkpt) + return true; + + // Type case + const Catchpoint *check = dynamic_cast<const Catchpoint*>(brkpt); + if (check == 0) + return false; + + // member case + return (varName_ == check->varName_); +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +FunctionBreakpoint::FunctionBreakpoint(const QString& functionName, bool temporary, bool enabled) + : Breakpoint(temporary, enabled), + m_functionName(functionName) +{ +} + +/***************************************************************************/ + +FunctionBreakpoint::~FunctionBreakpoint() +{ +} + +/***************************************************************************/ + +QString FunctionBreakpoint::dbgSetCommand() const +{ + return QString("break ")+m_functionName; // gdb command - not translatable +} + +/***************************************************************************/ + +bool FunctionBreakpoint::match(const Breakpoint* brkpt) const +{ + // simple case + if (this == brkpt) + return true; + + // Type case + const FunctionBreakpoint *check = dynamic_cast<const FunctionBreakpoint*>(brkpt); + if (!check) + return false; + + // member case + return (m_functionName == check->m_functionName); +} + + +} diff --git a/languages/ruby/debugger/breakpoint.h b/languages/ruby/debugger/breakpoint.h new file mode 100644 index 00000000..e45e3856 --- /dev/null +++ b/languages/ruby/debugger/breakpoint.h @@ -0,0 +1,214 @@ +/*************************************************************************** + begin : Tue May 13 2003 + copyright : (C) 2003 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _BREAKPOINT_H_ +#define _BREAKPOINT_H_ + +#include <klocale.h> + +#include <qstring.h> + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +namespace RDBDebugger +{ + +enum BP_TYPES +{ + BP_TYPE_Invalid, + BP_TYPE_FilePos, + BP_TYPE_Watchpoint, + BP_TYPE_Catchpoint, + BP_TYPE_Function +}; + +class Breakpoint +{ +public: + Breakpoint(bool temporary=false, bool enabled=true); + virtual ~Breakpoint(); + + virtual QString dbgSetCommand() const = 0; + virtual QString dbgRemoveCommand() const; + virtual bool match(const Breakpoint* brkpt) const = 0; + virtual void reset(); + + void setActive(int active, int id); + bool isActive(int active) const { return (active_ == active) || + (s_pending_ && !s_actionClear_); } + void setEnabled(bool enabled) { s_changedEnable_ = (s_enabled_ != enabled); + s_enabled_ = enabled; } + bool isEnabled() const { return s_enabled_; } + void setTemporary(bool temporary) { s_temporary_ = temporary; } + bool isTemporary() const { return s_temporary_; } + + bool changedEnable() const { return s_changedEnable_; } + + void setPending(bool pending) { s_pending_ = pending; } + bool isPending() const { return s_pending_; } + void setActionAdd(bool actionAdd) { s_actionDie_ = false; + s_actionAdd_ = actionAdd; } + bool isActionAdd() const { return s_actionAdd_; } + void setActionClear(bool actionClear) { s_actionClear_ = actionClear; } + bool isActionClear() const { return s_actionClear_; } + void setActionModify(bool actionModify) { s_actionDie_ = false; + s_actionModify_ = actionModify; } + bool isActionModify() const { return s_actionModify_; } + void setDbgProcessing(bool dbgProcessing) { s_dbgProcessing_ = dbgProcessing; } + bool isDbgProcessing() const { return s_dbgProcessing_; } + void setActionDie() { s_actionDie_ = true; + s_actionClear_ = false; } + bool isActionDie() const { return s_actionDie_; } + + int key() const { return key_; } + void setDbgId(int dbgId) { dbgId_ = dbgId; } + int dbgId() const { return dbgId_; } + + virtual QString statusDisplay(int activeFlag) const; + virtual BP_TYPES type() const { return BP_TYPE_Invalid; } + virtual QString displayType() const { return i18n( "Invalid" ); } + + virtual QString location(bool compact=true) = 0; + virtual void setLocation(const QString& ) = 0; + virtual bool isValid() const = 0; + +private: + bool s_pending_ :1; + bool s_actionAdd_ :1; + bool s_actionClear_ :1; + bool s_actionModify_ :1; + bool s_actionDie_ :1; + bool s_dbgProcessing_ :1; + bool s_enabled_ :1; + bool s_temporary_ :1; + bool s_changedEnable_ :1; + + int dbgId_; // assigned by gdb + + int key_; // internal unique key + int active_; // counter incremented on receipt of all BP's + + int ignoreCount_; + QString condition_; +// QString type_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ +class FilePosBreakpoint : public Breakpoint +{ +public: + FilePosBreakpoint(const QString &fileName, int lineNum, + bool temporary=false, bool enabled=true); + virtual ~FilePosBreakpoint(); + virtual QString dbgSetCommand() const; + virtual bool match(const Breakpoint *brkpt) const; + + BP_TYPES type () const { return BP_TYPE_FilePos; } + QString displayType() const { return i18n( "File:line" ); } + void setFileName(const QString& fileName) { fileName_ = fileName; } + QString fileName() const { return fileName_; } + void setLineNum(int lineNum) { lineNo_ = lineNum; } + int lineNum() const { return lineNo_; } + QString location(bool compact=true); + void setLocation(const QString& location); + bool isValid() const { return lineNo_>0 && !fileName_.isEmpty(); } + +private: + QString fileName_; + int lineNo_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ +class Watchpoint : public Breakpoint +{ +public: + Watchpoint(const QString &varName, bool temporary=false, bool enabled=true); + virtual ~Watchpoint(); + virtual QString dbgSetCommand() const; + bool match(const Breakpoint *brkpt) const; + + BP_TYPES type () const { return BP_TYPE_Watchpoint; } + QString displayType() const { return i18n("Watchpoint"); } + void setVarName(const QString& varName) { varName_ = varName; } + QString varName() const { return varName_; } + QString location(bool) { return varName_; } + void setLocation(const QString& location) { varName_ = location; } + bool isValid() const { return !varName_.isEmpty(); } + +private: + QString varName_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ +class Catchpoint : public Breakpoint +{ +public: + Catchpoint(const QString &varName, bool temporary=false, bool enabled=true); + virtual ~Catchpoint(); + virtual QString dbgSetCommand() const; + bool match(const Breakpoint *brkpt) const; + + BP_TYPES type () const { return BP_TYPE_Catchpoint; } + QString displayType() const { return i18n("Catchpoint"); } + void setVarName(const QString& varName) { varName_ = varName; } + QString varName() const { return varName_; } + QString location(bool) { return varName_; } + void setLocation(const QString& location) { varName_ = location; } + bool isValid() const { return !varName_.isEmpty(); } + +private: + QString varName_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ +class FunctionBreakpoint : public Breakpoint +{ +public: + FunctionBreakpoint(const QString &functionName, bool temporary=false, bool enabled=true); + virtual ~FunctionBreakpoint(); + virtual QString dbgSetCommand() const; + bool match(const Breakpoint *brkpt) const; + + BP_TYPES type () const { return BP_TYPE_Function; } + QString displayType() const { return i18n("Method()"); } + void setfunctionName(const QString& functionName) { m_functionName = functionName; } + QString functionName() const { return m_functionName; } + QString location(bool) { return m_functionName; }; + void setLocation(const QString& location) { m_functionName = location; } + bool isValid() const { return !m_functionName.isEmpty(); } + +private: + QString m_functionName; +}; + +} + +#endif diff --git a/languages/ruby/debugger/dbgcommand.cpp b/languages/ruby/debugger/dbgcommand.cpp new file mode 100644 index 00000000..6c6a24ec --- /dev/null +++ b/languages/ruby/debugger/dbgcommand.cpp @@ -0,0 +1,47 @@ +/*************************************************************************** + begin : Sun Aug 8 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "dbgcommand.h" + +#include <qstring.h> + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ +namespace RDBDebugger +{ + + +DbgCommand::DbgCommand(const QCString& command, bool isRunCmd, bool isInfoCmd) : + command_(command), + isRunCmd_(isRunCmd), + isInfoCmd_(isInfoCmd), + sent_(false), + waitForReply_(true) +{ + cmdBuffer_ = command_+"\n"; +} + +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ diff --git a/languages/ruby/debugger/dbgcommand.h b/languages/ruby/debugger/dbgcommand.h new file mode 100644 index 00000000..32ec7f7a --- /dev/null +++ b/languages/ruby/debugger/dbgcommand.h @@ -0,0 +1,64 @@ +/*************************************************************************** + begin : Sun Aug 8 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef DBGCOMMAND_H +#define DBGCOMMAND_H + +#include <qstring.h> + +/** + * @author John Birch + */ + +namespace RDBDebugger +{ + +class DbgCommand +{ +public: + DbgCommand(const QCString& command, bool isRunCmd, bool isInfoCmd); + virtual ~DbgCommand() {}; + + virtual QCString& cmdToSend() { sent_ = true; return cmdBuffer_; } + virtual int cmdLength() { return cmdBuffer_.length(); } + + QCString rawDbgCommand() const { return command_; } + bool isARunCmd() const { return isRunCmd_;} + bool isAnInfoCmd() const { return isInfoCmd_; } + bool moreToSend() const { return !sent_; } + bool expectReply() const { return waitForReply_; } + +protected: + QCString cmdBuffer_; + QCString command_; + bool isRunCmd_; + bool isInfoCmd_; + bool sent_; + bool waitForReply_; +}; + +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +#endif diff --git a/languages/ruby/debugger/dbgcontroller.cpp b/languages/ruby/debugger/dbgcontroller.cpp new file mode 100644 index 00000000..7ee560b7 --- /dev/null +++ b/languages/ruby/debugger/dbgcontroller.cpp @@ -0,0 +1,46 @@ +/*************************************************************************** + begin : Sun Aug 8 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "dbgcontroller.h" +#include <kprocess.h> + +/***************************************************************************/ + +namespace RDBDebugger +{ + +DbgController::DbgController() + : dbgProcess_(0) +{ +} + +/***************************************************************************/ + +DbgController::~DbgController() +{ + delete dbgProcess_; +} + +/***************************************************************************/ + +} + +#include "dbgcontroller.moc" diff --git a/languages/ruby/debugger/dbgcontroller.h b/languages/ruby/debugger/dbgcontroller.h new file mode 100644 index 00000000..13ee4382 --- /dev/null +++ b/languages/ruby/debugger/dbgcontroller.h @@ -0,0 +1,161 @@ +/*************************************************************************** + begin : Sun Aug 8 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _DBGCONTROLLER_H_ +#define _DBGCONTROLLER_H_ + +#include <qobject.h> +#include <domutil.h> + +class KProcess; +class QString; +class QStrList; + +namespace RDBDebugger +{ + +class Breakpoint; +class DbgCommand; +class LazyFetchItem; +class VarItem; + +/***************************************************************************/ +/** + * @author jbb + */ +/***************************************************************************/ +// sigh - namespace's don't work on some of the older compilers +enum DBGStateFlags +{ + s_dbgNotStarted = 1 << 0, + s_appNotStarted = 1 << 1, + s_appBusy = 1 << 2, + s_waitForWrite = 1 << 3, + s_programExited = 1 << 4, + s_silent = 1 << 5, + s_fetchLocals = 1 << 6, + s_viewBT = 1 << 7, + s_viewBP = 1 << 8, + s_attached = 1 << 9, + s_fetchGlobals = 1 << 10, + s_waitTimer = 1 << 11, + s_shuttingDown = 1 << 12, + s_viewThreads = 1 << 13 +}; + + +enum RttiValues { + RTTI_WATCH_ROOT = 1001, + RTTI_GLOBAL_ROOT = 1002, + RTTI_VAR_FRAME_ROOT = 1003, + RTTI_LAZY_FETCH_ITEM = 1004, + RTTI_VAR_ITEM = 1005, + RTTI_WATCH_VAR_ITEM = 1006, + RTTI_THREAD_STACK_ITEM = 1007, + RTTI_FRAME_STACK_ITEM = 1008 +}; + + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ +class DbgController : public QObject +{ + Q_OBJECT + +public: + + DbgController(); + virtual ~DbgController(); + + virtual bool stateIsOn( int state ) = 0; + +protected: + virtual void queueCmd(DbgCommand *cmd, bool executeNext) = 0; + virtual void parse(char *str) = 0; + +public slots: + virtual void configure() = 0; + + /** + * Start the debugger + * \param ruby_interpreter shell + * \param character_coding -K option + * \param run_directory Directory from where the program should be run + * \param debuggee_path Absolute path to debuggee.rb debugger script + * \param application Absolute path to application + * \param run_arguments Command line arguments to be passed to the application + * \param show_constants Show ruby constants in the variables view + */ + virtual void slotStart(const QString& ruby_interpreter, + const QString& character_coding, + const QString& run_directory, + const QString& debuggee_path, + const QString& application, + const QString& run_arguments, + bool show_constants, + bool trace_into_ruby) = 0; + + virtual void slotStopDebugger() = 0; + + virtual void slotRun() = 0; + virtual void slotRunUntil(const QString &fileName, int lineNum) = 0; + virtual void slotStepInto() = 0; + virtual void slotStepOver() = 0; + virtual void slotStepOutOff() = 0; + + virtual void slotBreakInto() = 0; + virtual void slotBPState(const Breakpoint&) = 0; + + + virtual void slotExpandItem(VarItem *parent, + const QCString &userRequest) = 0; + virtual void slotSelectFrame(int frame, int thread, + const QString& frameName) = 0; + virtual void slotFetchGlobals(bool fetch) = 0; + +protected slots: + virtual void slotDbgStdout(KProcess *proc, char *buf, int buflen) = 0; + virtual void slotDbgStderr(KProcess*, char*, int) {} ; + virtual void slotDbgWroteStdin(KProcess *proc) = 0; + virtual void slotDbgProcessExited(KProcess *proc) = 0; + + virtual void slotAcceptConnection(int passive_socket) = 0; + virtual void slotReadFromSocket(int socket) = 0; + +signals: + void gotoSourcePosition (const QString &fileName, int lineNum); + void rawRDBBreakpointList (char *buf); + void rawRDBBreakpointSet (char *buf, int key); + void ttyStdout (const char *output); + void ttyStderr (const char *output); + void rdbStdout (const char *output); + void rdbStderr (const char *output); + void showStepInSource (const QString &fileName, int lineNum, const QString &address); + void dbgStatus (const QString &status, int statusFlag); + +protected: + KProcess *dbgProcess_; +}; + +} + +#endif diff --git a/languages/ruby/debugger/dbgpsdlg.cpp b/languages/ruby/debugger/dbgpsdlg.cpp new file mode 100644 index 00000000..83dd1666 --- /dev/null +++ b/languages/ruby/debugger/dbgpsdlg.cpp @@ -0,0 +1,170 @@ +/*************************************************************************** + begin : Mon Sep 20 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "dbgpsdlg.h" + +#include <kbuttonbox.h> +#include <kdialog.h> +#include <kglobalsettings.h> +#include <klocale.h> +#include <kprocess.h> +#include <kstdguiitem.h> +#include <kdeversion.h> + +#include <qframe.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qlistbox.h> +#include <qtoolbutton.h> +#include <qpushbutton.h> + +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> + +namespace RDBDebugger +{ + +/***************************************************************************/ + +// Display a list of processes for the user to select one +// only display processes that they can do something with so if the user +// is root then display all processes +// For use with the internal debugger, but this dialog doesn't know anything +// about why it's doing it. + +Dbg_PS_Dialog::Dbg_PS_Dialog(QWidget *parent, const char *name) + : KDialog(parent, name, true), // modal + psProc_(0), + pids_(new QListBox(this)), + heading_(new QLabel(" ", this)), + pidLines_(QString()) +{ + setCaption(i18n("Attach to Process")); + + QBoxLayout *topLayout = new QVBoxLayout(this, 5); + + heading_->setFont(KGlobalSettings::fixedFont()); + heading_->setFrameStyle(QFrame::Panel|QFrame::Sunken); + heading_->setMaximumHeight(heading_->sizeHint().height()); +// heading_->setMinimumSize(heading_->sizeHint()); + topLayout->addWidget(heading_, 5); + + topLayout->addWidget(pids_, 5); + pids_->setFont(KGlobalSettings::fixedFont()); + + KButtonBox *buttonbox = new KButtonBox(this, Qt::Horizontal, 5); + QPushButton *ok = buttonbox->addButton(KStdGuiItem::ok()); + buttonbox->addStretch(); + QPushButton *cancel = buttonbox->addButton(KStdGuiItem::cancel()); + buttonbox->layout(); + topLayout->addWidget(buttonbox); + + connect(ok, SIGNAL(clicked()), SLOT(accept())); + connect(cancel, SIGNAL(clicked()), SLOT(reject())); + + psProc_ = new KShellProcess("/bin/sh"); + #ifdef USE_SOLARIS + *psProc_ << "ps"; + *psProc_ << "-opid"; + *psProc_ << "-otty"; + *psProc_ << "-os"; + *psProc_ << "-otime"; + *psProc_ << "-oargs"; + pidCmd_ = "ps -opid -otty -os -otime -oargs"; + + if (getuid() == 0) { + *psProc_ << "-e"; + pidCmd_ += " -e"; + } + #else + *psProc_ << "ps"; + *psProc_ << "x"; + pidCmd_ = "ps x"; + + if (getuid() == 0) { + *psProc_ << "a"; + pidCmd_ += " a"; + } + #endif + + connect( psProc_, SIGNAL(processExited(KProcess *)), SLOT(slotProcessExited()) ); + connect( psProc_, SIGNAL(receivedStdout(KProcess *, char *, int)), SLOT(slotReceivedOutput(KProcess *, char *, int)) ); + psProc_->start(KProcess::NotifyOnExit, KProcess::Stdout); + + // Default display to 40 chars wide, default height is okay + resize( ((KGlobalSettings::fixedFont()).pointSize())*40, height()); + topLayout->activate(); +} + +/***************************************************************************/ + +Dbg_PS_Dialog::~Dbg_PS_Dialog() +{ + delete psProc_; +} + +/***************************************************************************/ + +int Dbg_PS_Dialog::pidSelected() +{ + QString pidText = pids_->text(pids_->currentItem()); + if (!pidText.isEmpty()) + return atoi(pidText.latin1()); + + return 0; +} + +/***************************************************************************/ + +void Dbg_PS_Dialog::slotReceivedOutput(KProcess */*proc*/, char *buffer, int buflen) +{ + pidLines_ += QString::fromLocal8Bit(buffer, buflen+1); +} + +/***************************************************************************/ + +void Dbg_PS_Dialog::slotProcessExited() +{ + delete psProc_; + psProc_ = 0; + + pidLines_ += '\n'; + + int start = pidLines_.find('\n', 0); // Skip the first line (header line) + int pos; + if (start != -1) + heading_->setText(pidLines_.left(start)); + while ( (pos = pidLines_.find('\n', start)) != -1) { + QString item = pidLines_.mid(start, pos-start); + if (!item.isEmpty()) { + if (item.find(pidCmd_) == -1) + pids_->insertItem(item); + } + + start = pos+1; + } +} + +} + +/***************************************************************************/ +#include "dbgpsdlg.moc" diff --git a/languages/ruby/debugger/dbgpsdlg.h b/languages/ruby/debugger/dbgpsdlg.h new file mode 100644 index 00000000..641f7aa5 --- /dev/null +++ b/languages/ruby/debugger/dbgpsdlg.h @@ -0,0 +1,60 @@ +/*************************************************************************** + begin : Mon Sep 20 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _DBGPSDLG_H_ +#define _DBGPSDLG_H_ + +#include <kdialog.h> + +class QListBox; +class KProcess; +class QLabel; + +namespace RDBDebugger +{ + +/***************************************************************************/ + +class Dbg_PS_Dialog : public KDialog +{ + Q_OBJECT + +public: + Dbg_PS_Dialog( QWidget *parent=0, const char *name=0 ); + ~Dbg_PS_Dialog(); + + int pidSelected(); + +private slots: + void slotReceivedOutput(KProcess *proc, char *buffer, int buflen); + void slotProcessExited(); + +private: + KProcess* psProc_; + QListBox* pids_; + QLabel* heading_; + QString pidLines_; + QString pidCmd_; +}; + +} + +#endif diff --git a/languages/ruby/debugger/dbgtoolbar.cpp b/languages/ruby/debugger/dbgtoolbar.cpp new file mode 100644 index 00000000..702c8e83 --- /dev/null +++ b/languages/ruby/debugger/dbgtoolbar.cpp @@ -0,0 +1,483 @@ +/*************************************************************************** + begin : Thu Dec 23 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "dbgtoolbar.h" +#include "debuggerpart.h" +#include "dbgcontroller.h" + +#include <kdockwindow.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kpopupmenu.h> +#include <kstandarddirs.h> +#include <kwin.h> +#include <kwinmodule.h> + +#include <qapplication.h> +#include <qcursor.h> +#include <qframe.h> +#include <qlayout.h> +#include <qpainter.h> +#include <qpushbutton.h> +#include <qtooltip.h> +#include <qwhatsthis.h> + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +// Implements a floating toolbar for the debugger. + +// Unfortunately, I couldn't get the KToolBar to work nicely when it +// was floating, so I was forced to write these classes. I'm not sure whether +// I didn't try hard enough or ... and I've forgotten what the problems were +// now. + +// The problem with using this is that it will not dock as a normal toolbar. +// I'm not convince that this is a real problem though. + +// So, if you can get it to work as a KToolBar, and it works well when the +// app is running, then all these classes can be removed. + +// This code is very specific to the internal debugger in kdevelop. + +namespace RDBDebugger +{ + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +// This just allows the user to click on the toolbar and drag it somewhere else. +// I would have preferred to use normal decoration on the toolbar and removed +// the iconify, close, etc buttons from the window title but again I kept running +// into problems. Instead, I used no decoration and this class. Also this looks +// similar to the KToolBar floating style. +class DbgMoveHandle : public QFrame +{ +public: + DbgMoveHandle(DbgToolBar *parent=0, const char * name=0, WFlags f=0); + virtual ~DbgMoveHandle(); + + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + +private: + DbgToolBar* toolBar_; + QPoint offset_; + bool moving_; +}; + +// ************************************************************************** + +DbgMoveHandle::DbgMoveHandle(DbgToolBar *parent, const char * name, WFlags f) + : QFrame(parent, name, f), + toolBar_(parent), + offset_(QPoint(0,0)), + moving_(false) +{ + setFrameStyle(QFrame::Panel|QFrame::Raised); + setFixedHeight(12); +} + +// ************************************************************************** + +DbgMoveHandle::~DbgMoveHandle() +{ +} + +// ************************************************************************** + +void DbgMoveHandle::mousePressEvent(QMouseEvent *e) +{ + QFrame::mousePressEvent(e); + if (moving_) + return; + + if (e->button() == RightButton) { + KPopupMenu *menu = new KPopupMenu(this); + menu->insertTitle(i18n("Debug Toolbar")); + menu->insertItem(i18n("Dock to Panel"), + parent(), SLOT(slotDock())); + menu->insertItem(i18n("Dock to Panel && Iconify KDevelop"), + parent(), SLOT(slotIconifyAndDock())); + menu->popup(e->globalPos()); + } else { + moving_ = true; + offset_ = parentWidget()->pos() - e->globalPos(); + setFrameStyle(QFrame::Panel|QFrame::Sunken); + QApplication::setOverrideCursor(QCursor(sizeAllCursor)); + setPalette(QPalette(colorGroup().background())); + repaint(); + } +} + +// ************************************************************************** + +void DbgMoveHandle::mouseReleaseEvent(QMouseEvent *e) +{ + QFrame::mouseReleaseEvent(e); + moving_ = false; + offset_ = QPoint(0,0); + setFrameStyle(QFrame::Panel|QFrame::Raised); + QApplication::restoreOverrideCursor(); + setPalette(QPalette(colorGroup().background())); + repaint(); +} + +// ************************************************************************** + +void DbgMoveHandle::mouseMoveEvent(QMouseEvent *e) +{ + QFrame::mouseMoveEvent(e); + if (!moving_) + return; + + toolBar_->move(e->globalPos() + offset_); +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +// This class adds text _and_ a pixmap to a button. Why doesn't QPushButton +// support that? It only allowed text _or_ pixmap. +class DbgButton : public QPushButton +{ +public: + DbgButton(const QPixmap &pixmap, const QString &text, + DbgToolBar *parent, const char *name=0); + virtual ~DbgButton() {}; + void drawButtonLabel(QPainter *painter); + QSize sizeHint() const; + +private: + QPixmap pixmap_; +}; + +// ************************************************************************** + +DbgButton::DbgButton(const QPixmap& pixmap, const QString& text, + DbgToolBar* parent, const char* name) + : QPushButton(parent, name), + pixmap_(pixmap) +{ + setText(text); +} + +// ************************************************************************** + +void DbgButton::drawButtonLabel(QPainter *painter) +{ + // We always have a pixmap (today...) + // Centre it if there's no text + + bool hasText = !text().isEmpty(); + int x = ((hasText ? height() : width()) - pixmap_.width()) / 2; + int y = (height() - pixmap_.height()) / 2; + painter->drawPixmap(x, y, pixmap_); + + if (hasText) { + painter->setPen(colorGroup().text()); + painter->drawText(height()+2, 0, width()-(height()+2), height(), AlignLeft|AlignVCenter, text()); + } +} + +// ************************************************************************** + +QSize DbgButton::sizeHint() const +{ + if (text().isEmpty()) + return pixmap_.size(); + else + return QPushButton::sizeHint(); +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +DbgDocker::DbgDocker(QWidget* parent, DbgToolBar* toolBar, const QPixmap& pixmap) : + KSystemTray(parent, "DbgDocker"), + toolBar_(toolBar) +{ + setPixmap(pixmap); + QToolTip::add( this, i18n("KDevelop ruby debugger: Click to execute one line of code (\"step\")") ); +} + +// ************************************************************************** + +void DbgDocker::mousePressEvent(QMouseEvent *e) +{ + if (!rect().contains( e->pos())) + return; + + switch (e->button()) { + case LeftButton: + { + // Not really a click, but it'll hold for the time being !!! + emit clicked(); + break; + } + case RightButton: + { + KPopupMenu* menu = new KPopupMenu(this); + menu->insertTitle(i18n("Debug Toolbar")); + menu->insertItem(i18n("Activate"), toolBar_, SLOT(slotUndock())); + menu->insertItem(i18n("Activate (KDevelop gets focus)"), toolBar_, SLOT(slotActivateAndUndock())); + menu->popup(e->globalPos()); + break; + } + default: + break; + } +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +DbgToolBar::DbgToolBar(RubyDebuggerPart* part, + QWidget* parent, const char* name) + : QFrame(0, name), + part_(part), + activeWindow_(0), + winModule_(0), + bKDevFocus_(0), + bPrevFocus_(0), + appIsActive_(false), + docked_(false), + docker_(0), + dockWindow_(new KSystemTray(parent)) +{ + winModule_ = new KWinModule(this); + docker_ = new DbgDocker(parent, this, BarIcon("dbgnext")); + connect(docker_, SIGNAL(clicked()), part_, SLOT(slotStepOver())); + + // Must have noFocus set so that we can see what window was active. + // see slotDbgKdevFocus() for more comments + // I do not want the user to be able to "close" this widget. If we have any + // decoration then they can and that is bad. + // This widget is closed when the debugger finishes i.e. they press "Stop" + + // Do we need NoFocus??? + KWin::setState(winId(), NET::StaysOnTop | NET::Modal | NET::SkipTaskbar); +// KWin::setType(winId(), NET::Override); // So it has no decoration + KWin::setType(winId(), NET::Dock); + + setFocusPolicy(NoFocus); + setFrameStyle( QFrame::Box | QFrame::Plain ); + setLineWidth(4); + setMidLineWidth(0); + + QBoxLayout* topLayout = new QVBoxLayout(this); + + QBoxLayout* nextLayout = new QHBoxLayout(); + QBoxLayout* stepLayout = new QHBoxLayout(); + QBoxLayout* focusLayout = new QHBoxLayout(); + + DbgMoveHandle* moveHandle= new DbgMoveHandle(this); + + QPushButton* bRun = new DbgButton(BarIcon("dbgrun"), i18n("Run"), this); + QPushButton* bInterrupt = new DbgButton(BarIcon("player_pause"), i18n("Interrupt"), this); + QPushButton* bNext = new DbgButton(BarIcon("dbgnext"), i18n("Step Over"), this); + QPushButton* bStep = new DbgButton(BarIcon("dbgstep"), i18n("Step Into"), this); + QPushButton* bFinish = new DbgButton(BarIcon("dbgstepout"), i18n("Step Out"), this); + QPushButton* bRunTo = new DbgButton(BarIcon("dbgrunto"), i18n("Run to Cursor"), this); + bPrevFocus_ = new DbgButton(BarIcon("dbgmemview"), QString::null, this); + bKDevFocus_ = new DbgButton(BarIcon("kdevelop"), QString::null, this); + + connect(bRun, SIGNAL(clicked()), part_, SLOT(slotRun())); + connect(bInterrupt, SIGNAL(clicked()), part_, SLOT(slotPause())); + connect(bNext, SIGNAL(clicked()), part_, SLOT(slotStepOver())); + connect(bStep, SIGNAL(clicked()), part_, SLOT(slotStepInto())); + connect(bFinish, SIGNAL(clicked()), part_, SLOT(slotStepOut())); + connect(bRunTo, SIGNAL(clicked()), part_, SLOT(slotRunToCursor())); + connect(bKDevFocus_, SIGNAL(clicked()), this, SLOT(slotKdevFocus())); + connect(bPrevFocus_, SIGNAL(clicked()), this, SLOT(slotPrevFocus())); + + QToolTip::add( bRun, i18n("Continue with application execution, may start the application") ); + QToolTip::add( bInterrupt, i18n("Interrupt the application execution") ); + QToolTip::add( bNext, i18n("Execute one line of code, but run through methods") ); + QToolTip::add( bStep, i18n("Execute one line of code, stepping into methods if appropriate") ); + QToolTip::add( bFinish, i18n("Execute to end of current stack frame") ); + QToolTip::add( bRunTo, i18n("Continues execution until the cursor position is reached.") ); + QToolTip::add( bKDevFocus_, i18n("Set focus on KDevelop") ); + QToolTip::add( bPrevFocus_, i18n("Set focus on window that had focus when KDevelop got focus") ); + + QWhatsThis::add( bRun, i18n("Continue with application execution. May start the application.") ); + QWhatsThis::add( bInterrupt, i18n("Interrupt the application execution.") ); + QWhatsThis::add( bNext, i18n("Execute one line of code, but run through methods.") ); + + QWhatsThis::add( bStep, i18n("Execute one line of code, stepping into methods if appropriate.") ); + + QWhatsThis::add( bFinish, i18n("Execute to end of current stack frame.") ); + QWhatsThis::add( bRunTo, i18n("Continues execution until the cursor position is reached.") ); + QWhatsThis::add( bKDevFocus_, i18n("Set focus on KDevelop.") ); + QWhatsThis::add( bPrevFocus_, i18n("Set focus on window that had focus when KDevelop got focus.") ); + + topLayout->addWidget(moveHandle); + topLayout->addWidget(bRun); + topLayout->addLayout(nextLayout); + topLayout->addLayout(stepLayout); + topLayout->addWidget(bFinish); + topLayout->addWidget(bRunTo); + topLayout->addWidget(bInterrupt); + topLayout->addLayout(focusLayout); + + focusLayout->addWidget(bKDevFocus_); + focusLayout->addWidget(bPrevFocus_); + + stepLayout->addWidget(bStep); + + nextLayout->addWidget(bNext); + +// int w = QMAX(bRun->sizeHint().width(), bFinish->sizeHint().width()); +// w = QMAX(w, bInterrupt->sizeHint().width()); +// w = QMAX(w, bView->sizeHint().width()); + + // they should have the same height, so don't be too fussy +// int h = bFinish->sizeHint().height(); +// +// bNext->setMinimumHeight(h); +// bNexti->setMinimumHeight(h); +// bStep->setMinimumHeight(h); +// bStepi->setMinimumHeight(h); +// bKDevFocus_->setMinimumHeight(h); +// bPrevFocus_->setMinimumHeight(h); + +// setMinimumSize(w+10, h*7); +// setMaximumSize(w+10, h*7); + + setAppIndicator(appIsActive_); + topLayout->activate(); +} + +// ************************************************************************** + +DbgToolBar::~DbgToolBar() +{ + slotUndock(); +} + +// ************************************************************************** + +void DbgToolBar::slotKdevFocus() +{ + // I really want to be able to set the focus on the _application_ being debugged + // but this is the best compromise I can come up with. All we do is save the + // window that had focus when they switch to the kdevelop window. To do this + // the toolbar _cannot_ accept focus. + // If anyone has a way of determining what window the app is _actually_ running on + // then please fix and send a patch. + + if (winModule_->activeWindow() != topLevelWidget()->winId()) + activeWindow_ = winModule_->activeWindow(); + + KWin::activateWindow(topLevelWidget()->winId()); +} + +// ************************************************************************** + +void DbgToolBar::slotPrevFocus() +{ + KWin::activateWindow(activeWindow_); +} + +// ************************************************************************** + +// If the app is active then the app button is highlighted, otherwise +// kdev button is highlighted. +void DbgToolBar::slotDbgStatus(const QString&, int state) +{ + bool appIndicator = state & s_appBusy; + if (appIndicator != appIsActive_) { + setAppIndicator(appIndicator); + appIsActive_ = appIndicator; + } +} + +// ************************************************************************** + +void DbgToolBar::setAppIndicator(bool appIndicator) +{ + if (appIndicator) { + bPrevFocus_->setPalette(QPalette(colorGroup().mid())); + bKDevFocus_->setPalette(QPalette(colorGroup().background())); + } else { + bPrevFocus_->setPalette(QPalette(colorGroup().background())); + bKDevFocus_->setPalette(QPalette(colorGroup().mid())); + } +} + +// ************************************************************************** + +void DbgToolBar::slotDock() +{ + if (docked_) + return; + + // Q_ASSERT(!docker_); + hide(); + + docker_->show(); + docked_ = true; +} + +// ************************************************************************** + +void DbgToolBar::slotIconifyAndDock() +{ + if (docked_) + return; + + // KWin::iconifyWindow(ckDevelop_->winId(), true); + slotDock(); +} + +// ************************************************************************** + +void DbgToolBar::slotUndock() +{ + if (!docked_) + return; + + show(); + docker_->hide(); + docked_ = false; +} + +// ************************************************************************** + +void DbgToolBar::slotActivateAndUndock() +{ + if (!docked_) + return; + + KWin::activateWindow(topLevelWidget()->winId()); + slotUndock(); +} + +} + +// ************************************************************************** +#include "dbgtoolbar.moc" diff --git a/languages/ruby/debugger/dbgtoolbar.h b/languages/ruby/debugger/dbgtoolbar.h new file mode 100644 index 00000000..a19b2eae --- /dev/null +++ b/languages/ruby/debugger/dbgtoolbar.h @@ -0,0 +1,90 @@ +/*************************************************************************** + begin : Thu Dec 23 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by *q + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _DBGTOOLBAR_H_ +#define _DBGTOOLBAR_H_ + +class KWinModule; + +#include <ksystemtray.h> +#include <kwin.h> // needed for WId :( + +#include <qframe.h> + +namespace RDBDebugger +{ + +class DbgButton; +class DbgToolBar; +class RubyDebuggerPart; + +class DbgDocker : public KSystemTray +{ + Q_OBJECT + +public: + DbgDocker(QWidget *parent, DbgToolBar *toolBar, const QPixmap &pixmap); + virtual ~DbgDocker() {}; + virtual void mousePressEvent(QMouseEvent *e); + +signals: + void clicked(); + +private: + DbgToolBar* toolBar_; +}; + + +class DbgToolBar : public QFrame +{ + Q_OBJECT + +public: + DbgToolBar(RubyDebuggerPart *part, QWidget* parent, const char* name=0); + virtual ~DbgToolBar(); + +private slots: + void slotDbgStatus(const QString&, int); + void slotDock(); + void slotUndock(); + void slotIconifyAndDock(); + void slotActivateAndUndock(); + + void slotKdevFocus(); + void slotPrevFocus(); + +private: + void setAppIndicator(bool appIndicator); + + RubyDebuggerPart* part_; + WId activeWindow_; + KWinModule* winModule_; + DbgButton* bKDevFocus_; + DbgButton* bPrevFocus_; + bool appIsActive_; + bool docked_; + DbgDocker* docker_; + KSystemTray* dockWindow_; +}; + +} + +#endif diff --git a/languages/ruby/debugger/debuggee.rb b/languages/ruby/debugger/debuggee.rb new file mode 100644 index 00000000..38e2dea7 --- /dev/null +++ b/languages/ruby/debugger/debuggee.rb @@ -0,0 +1,1214 @@ +# Copyright (C) 2000 Network Applied Communication Laboratory, Inc. +# Copyright (C) 2000 Information-technology Promotion Agency, Japan +# Copyright (C) 2000-2003 NAKAMURA, Hiroshi <nahi@ruby-lang.org> + +# Changes for the FreeRIDE IDE by Laurent JULLIARD. FreeRIDE uses +# Distributed ruby (DRuby) to communicate with the debugger back +# end. However, this can't interoperate with C++ in KDevelop and so +# a Unix domain socket connection is used instead. + +# Adapted for KDevelop debugging +# ------------------------------ +# begin : Mon Nov 1 2004 +# copyright : (C) 2004 by Richard Dale +# email : Richard_Dale@tipitina.demon.co.uk + +if $SAFE > 0 + STDERR.print "-r debug.rb is not available in safe mode\n" + exit 1 +end + +require 'tracer' +require 'pp' +require 'rbconfig' + +class Tracer + def Tracer.trace_func(*vars) + Single.trace_func(*vars) + end +end + +# FreeRIDE/KDevelop must always intercept exits hence the exit! redefinition +# at_exit calls the quit method to cleanly disconnect from the +# FreeRIDE/KDevelop debugger client +module Kernel + alias_method :exit!, :exit +end + +BEGIN { + at_exit do + set_trace_func nil + DEBUGGER__.quit + end +} + +SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__ + +class DEBUGGER__ +class Mutex + def initialize + @locker = nil + @waiting = [] + @locked = false; + end + + def locked? + @locked + end + + def lock + return if Thread.critical + return if @locker == Thread.current + while (Thread.critical = true; @locked) + @waiting.push Thread.current + Thread.stop + end + @locked = true + @locker = Thread.current + Thread.critical = false + self + end + + def unlock + return if Thread.critical + return unless @locked + unless @locker == Thread.current + raise RuntimeError, "unlocked by other" + end + Thread.critical = true + t = @waiting.shift + @locked = false + @locker = nil + Thread.critical = false + t.run if t + self + end +end +MUTEX = Mutex.new + +class Context + DEBUG_LAST_CMD = [] + + def readline(prompt_cmd, hist) + DEBUGGER__.client.readline(prompt_cmd) + end + + def initialize + if Thread.current == Thread.main + @stop_next = 1 + else + @stop_next = 0 + end + @last_file = nil + @file = nil + @line = nil + @no_step = nil + @frames = [] + @finish_pos = 0 + @trace = false + @trace_ruby = false + @catch = "StandardError" + @suspend_next = false + end + + def stop_next(n=1) + @stop_next = n + end + + def set_suspend + @suspend_next = true + end + + def clear_suspend + @suspend_next = false + end + + def suspend_all + DEBUGGER__.suspend + end + + def resume_all + DEBUGGER__.resume + end + + def check_suspend + return if Thread.critical + while (Thread.critical = true; @suspend_next) + DEBUGGER__.waiting.push Thread.current + @suspend_next = false + Thread.stop + end + Thread.critical = false + end + + def trace? + @trace + end + + def set_trace(arg) + @trace = arg + end + + def trace_ruby? + @trace_ruby + end + + def set_trace_ruby(arg) + @trace_ruby = arg + end + + def stdout + DEBUGGER__.stdout + end + + def break_points + DEBUGGER__.break_points + end + + def display + DEBUGGER__.display + end + + def context(th) + DEBUGGER__.context(th) + end + + def set_trace_all(arg) + DEBUGGER__.set_trace(arg) + end + + def set_last_thread(th) + DEBUGGER__.set_last_thread(th) + end + + def debug_eval(str, binding) + begin + val = eval(str, binding) + rescue StandardError, ScriptError => e + at = eval("caller(1)", binding) + stdout.printf "%s:%s\n", at.shift, e.to_s.sub(/\(eval\):1:(in `.*?':)?/, '') + for i in at + stdout.printf "\tfrom %s\n", i + end + throw :debug_error + end + end + + def debug_silent_eval(str, binding) + begin + eval(str, binding) + rescue StandardError, ScriptError + nil + end + end + + # Temporarily change the pretty_print methods to not expand arrays + # and hashes, just give the length + def customize_debug_pp + Array.module_eval %q{ + def pretty_print(pp) + pp.pp "Array (%d element(s))" % length + end + } + + Hash.module_eval %q{ + def pretty_print(pp) + pp.pp "Hash (%d element(s))" % length + end + } + end + + # Restore the original pretty_print methods for arrays and hashes + def restore_debug_pp + Array.module_eval %q{ + def pretty_print(q) + q.group(1, '[', ']') { + self.each {|v| + q.comma_breakable unless q.first? + q.pp v + } + } + end + } + Hash.module_eval %q{ + def pretty_print(q) + q.pp_hash self + end + } + end + + # Prevent the 'var *' commands from expanding Arrays and Hashes + # This could be done by redefining inspect, but that would affect + # everywhere not just here and in the pp command. + def debug_inspect(obj) + if obj.kind_of? Array + "Array (%d element(s))" % obj.length + elsif obj.kind_of? Hash + "Hash (%d element(s))" % obj.length + elsif obj.kind_of? String + str = obj.inspect + if str.length > 255 + "String (length %d)" % obj.length + else + str + end + else + obj.inspect + end + end + + def var_list(ary, binding) + ary.sort! + for v in ary + stdout.printf " %s => %s\n", v, debug_inspect(eval(v, binding)) + end + end + + def const_list(ary, obj) + ary.sort! + for c in ary + str = debug_inspect(obj.module_eval(c)) + if c.to_s != str && + str !~ /^Qt::|^KDE::/ && c.to_s !~ /@@classes$|@@cpp_names$|@@idclass$|@@debug_level$/ && + c.to_s !~ /^DCOPMeta$|^Meta$|SCRIPT_LINES__|TRUE|FALSE|NIL|MatchingData/ && + c.to_s !~ /^PLATFORM$|^RELEASE_DATE$|^VERSION$|SilentClient|SilentObject/ && + c.to_s !~ /^Client$|^Context$|^DEBUG_LAST_CMD$|^MUTEX$|^Mutex$|^SimpleDelegater$|^Delegater$/ && + c.to_s !~ /IPsocket|IPserver|UDPsocket|UDPserver|TCPserver|TCPsocket|UNIXserver|UNIXsocket/ + if c.to_s == "ENV" + stdout.printf " %s => Hash (%d element(s))\n", c, obj.module_eval(c).length + else + stdout.printf " %s => %s\n", c, str + end + end + end + end + + def debug_variable_info(input, binding) + case input + when /^\s*g(?:lobal)?$/ + var_list(global_variables, binding) + + when /^\s*l(?:ocal)?$/ + var_list(eval("local_variables", binding) << "self", binding) + + when /^\s*i(?:nstance)?\s+/ + obj = debug_eval($', binding) + var_list(obj.instance_variables, obj.instance_eval{binding()}) + + when /^\s*cl(?:ass)?\s+/ + obj = debug_eval($', binding) + unless obj.kind_of? Module + stdout.print "Should be Class/Module: ", $', "\n" + else + const_list(obj.class_variables, obj) + end + + when /^\s*c(?:onst(?:ant)?)?\s+/ + obj = debug_eval($', binding) + unless obj.kind_of? Module + stdout.print "Should be Class/Module: ", $', "\n" + else + const_list(obj.constants, obj) + end + end + end + + def debug_method_info(input, binding) + case input + when /^i(:?nstance)?\s+/ + obj = debug_eval($', binding) + + len = 0 + for v in obj.methods.sort + len += v.size + 1 + if len > 70 + len = v.size + 1 + stdout.print "\n" + end + stdout.print v, " " + end + stdout.print "\n" + + else + obj = debug_eval(input, binding) + unless obj.kind_of? Module + stdout.print "Should be Class/Module: ", input, "\n" + else + len = 0 + for v in obj.instance_methods(false).sort + len += v.size + 1 + if len > 70 + len = v.size + 1 + stdout.print "\n" + end + stdout.print v, " " + end + stdout.print "\n" + end + end + end + + def thnum + num = DEBUGGER__.instance_eval{@thread_list[Thread.current]} + unless num + DEBUGGER__.make_thread_list + num = DEBUGGER__.instance_eval{@thread_list[Thread.current]} + end + num + end + + def debug_command(file, line, id, binding) + MUTEX.lock + set_last_thread(Thread.current) + frame_pos = 0 + binding_file = file + binding_line = line + previous_line = nil + if ENV['EMACS'] + stdout.printf "\032\032%s:%d:\n", binding_file, binding_line + else + stdout.printf "%s:%d:%s", binding_file, binding_line, + line_at(binding_file, binding_line) + end + @frames[0] = [binding, file, line, id] + display_expressions(binding) + prompt = true + while prompt and input = readline("(rdb:%d) "%thnum(), true) + catch(:debug_error) do + if input == "" + next unless DEBUG_LAST_CMD[0] + input = DEBUG_LAST_CMD[0] + stdout.print input, "\n" + else + DEBUG_LAST_CMD[0] = input + end + + case input + when /^\s*trace_ruby(?:\s+(on|off))?$/ + if defined?( $1 ) + if $1 == 'on' + set_trace_ruby true + else + set_trace_ruby false + end + end + + when /^\s*tr(?:ace)?(?:\s+(on|off))?(?:\s+(all))?$/ + if defined?( $2 ) + if $1 == 'on' + set_trace_all true + else + set_trace_all false + end + elsif defined?( $1 ) + if $1 == 'on' + set_trace true + else + set_trace false + end + end + if trace? + stdout.print "Trace on.\n" + else + stdout.print "Trace off.\n" + end + + when /^\s*b(?:reak)?\s+(?:(.+):)?([^.:]+)$/ + pos = $2 + if $1 + klass = debug_silent_eval($1, binding) +# file = $1 + file = File.expand_path($1) + end + if pos =~ /^\d+$/ + pname = pos + pos = pos.to_i + else + pname = pos = pos.intern.id2name + end + break_points.push [true, 0, klass || file, pos] + stdout.printf "Set breakpoint %d at %s:%s\n", break_points.size, klass || file, pname + + when /^\s*b(?:reak)?\s+(.+)[#.]([^.:]+)$/ + pos = $2.intern.id2name + klass = debug_eval($1, binding) + break_points.push [true, 0, klass, pos] + stdout.printf "Set breakpoint %d at %s.%s\n", break_points.size, klass, pos + + when /^\s*wat(?:ch)?\s+(.+)$/ + exp = $1 + break_points.push [true, 1, exp] + stdout.printf "Set watchpoint %d\n", break_points.size, exp + + when /^\s*b(?:reak)?$/ + if break_points.find{|b| b[1] == 0} + n = 1 + stdout.print "Breakpoints:\n" + for b in break_points + if b[0] and b[1] == 0 + stdout.printf " %d %s:%s\n", n, b[2], b[3] + end + n += 1 + end + end + if break_points.find{|b| b[1] == 1} + n = 1 + stdout.print "\n" + stdout.print "Watchpoints:\n" + for b in break_points + if b[0] and b[1] == 1 + stdout.printf " %d %s\n", n, b[2] + end + n += 1 + end + end + if break_points.size == 0 + stdout.print "No breakpoints\n" + else + stdout.print "\n" + end + + when /^\s*del(?:ete)?(?:\s+(\d+))?$/ + pos = $1 + unless pos +# input = readline("Clear all breakpoints? (y/n) ", false) +# if input == "y" + for b in break_points + b[0] = false + end +# end + else + pos = pos.to_i + if break_points[pos-1] + break_points[pos-1][0] = false + else + stdout.printf "Breakpoint %d is not defined\n", pos + end + end + + when /^\s*disp(?:lay)?\s+(.+)$/ + exp = $1 + display.push [true, exp] + stdout.printf "%d: ", display.size + display_expression(exp, binding) + + when /^\s*disp(?:lay)?$/ + display_expressions(binding) + + when /^\s*undisp(?:lay)?(?:\s+(\d+))?$/ + pos = $1 + unless pos +# input = readline("Clear all expressions? (y/n) ", false) +# if input == "y" + for d in display + d[0] = false + end +# end + else + pos = pos.to_i + if display[pos-1] + display[pos-1][0] = false + else + stdout.printf "Display expression %d is not defined\n", pos + end + end + + when /^\s*c(?:ont)?$/ + prompt = false + + when /^\s*s(?:tep)?(?:\s+(\d+))?$/ + if $1 + lev = $1.to_i + else + lev = 1 + end + @stop_next = lev + prompt = false + + when /^\s*n(?:ext)?(?:\s+(\d+))?$/ + if $1 + lev = $1.to_i + else + lev = 1 + end + @stop_next = lev + @no_step = @frames.size - frame_pos + prompt = false + + when /^\s*w(?:here)?$/, /^\s*f(?:rame)?$/ + display_frames(frame_pos) + + when /^\s*l(?:ist)?(?:\s+(.+))?$/ + if not $1 + b = previous_line ? previous_line + 10 : binding_line - 5 + e = b + 9 + elsif $1 == '-' + b = previous_line ? previous_line - 10 : binding_line - 5 + e = b + 9 + else + b, e = $1.split(/[-,]/) + if e + b = b.to_i + e = e.to_i + else + b = b.to_i - 5 + e = b + 9 + end + end + previous_line = b + display_list(b, e, binding_file, binding_line) + + when /^\s*up(?:\s+(\d+))?$/ + previous_line = nil + if $1 + lev = $1.to_i + else + lev = 1 + end + frame_pos += lev + if frame_pos >= @frames.size + frame_pos = @frames.size - 1 + stdout.print "At toplevel\n" + end + binding, binding_file, binding_line = @frames[frame_pos] + stdout.print format_frame(frame_pos) + + when /^\s*down(?:\s+(\d+))?$/ + previous_line = nil + if $1 + lev = $1.to_i + else + lev = 1 + end + frame_pos -= lev + if frame_pos < 0 + frame_pos = 0 + stdout.print "At stack bottom\n" + end + binding, binding_file, binding_line = @frames[frame_pos] + stdout.print format_frame(frame_pos) + + when /^\s*fin(?:ish)?$/ + if frame_pos == @frames.size + stdout.print "\"finish\" not meaningful in the outermost frame.\n" + else + @finish_pos = @frames.size - frame_pos + frame_pos = 0 + prompt = false + end + + when /^\s*cat(?:ch)?(?:\s+(.+))?$/ + if $1 + excn = $1 + if excn == 'off' + @catch = nil + stdout.print "Clear catchpoint.\n" + else + @catch = excn + stdout.printf "Set catchpoint %s.\n", @catch + end + else + if @catch + stdout.printf "Catchpoint %s.\n", @catch + else + stdout.print "No catchpoint.\n" + end + end + + when /^\s*q(?:uit)?$/ +# input = readline("Really quit? (y/n) ", false) +# if input == "y" + exit! # exit -> exit!: No graceful way to stop threads... +# end + + + when /^\s*v(?:ar)?\s+/ + debug_variable_info($', binding) + + when /^\s*m(?:ethod)?\s+/ + debug_method_info($', binding) + + when /^\s*th(?:read)?\s+/ + if DEBUGGER__.debug_thread_info($', binding) == :cont + prompt = false + end + + when /^\s*pp\s+/ + obj_name = $' + obj = debug_eval($', binding) + customize_debug_pp + if obj.kind_of? Array + obj.each_index { |i| stdout.printf "[%d]=%s\n", i.to_s, debug_inspect(obj[i]) } + elsif obj.kind_of? Hash or obj_name =~ /^ENV$/ + # Special case ENV to print like a hash + obj.each { |key, value| stdout.printf "[%s]=%s\n", key.inspect, debug_inspect(value) } + elsif obj.kind_of?(String) && obj.inspect.length > 255 + # Assume long strings contain packed data and show them as a + # sequence of 12 byte slices in hex + i = 0 + while i < obj.length + j = (i + 12 < obj.length ? i + 12 : obj.length) - 1 + stdout.printf "[%d..%d]=0x", i, j + for k in i..j + stdout.printf "%2.2x", obj[k] + end + stdout.printf " %s\n", obj[i..j].dump + + i += 12 + end + else + PP.pp(obj, stdout) + end + restore_debug_pp + + when /^\s*p\s+/ + stdout.printf "%s\n", debug_eval($', binding).inspect + + when /^\s*h(?:elp)?$/ + debug_print_help() + + else + v = debug_eval(input, binding) + stdout.printf "%s\n", v.inspect + end + end + end + MUTEX.unlock + resume_all + end + + def debug_print_help + stdout.print <<EOHELP +Debugger help v.-0.002b +Commands + b[reak] [file|class:]<line|method> + b[reak] [class.]<line|method> + set breakpoint to some position + wat[ch] <expression> set watchpoint to some expression + cat[ch] <an Exception> set catchpoint to an exception + b[reak] list breakpoints + cat[ch] show catchpoint + del[ete][ nnn] delete some or all breakpoints + disp[lay] <expression> add expression into display expression list + undisp[lay][ nnn] delete one particular or all display expressions + c[ont] run until program ends or hit breakpoint + s[tep][ nnn] step (into methods) one line or till line nnn + n[ext][ nnn] go over one line or till line nnn + w[here] display frames + f[rame] alias for where + l[ist][ (-|nn-mm)] list program, - lists backwards + nn-mm lists given lines + up[ nn] move to higher frame + down[ nn] move to lower frame + fin[ish] return to outer frame + tr[ace] (on|off) set trace mode of current thread + tr[ace] (on|off) all set trace mode of all threads + q[uit] exit from debugger + v[ar] g[lobal] show global variables + v[ar] l[ocal] show local variables + v[ar] i[nstance] <object> show instance variables of object + v[ar] cl[ass] <object> show class variables of object + v[ar] c[onst] <object> show constants of object + m[ethod] i[nstance] <obj> show methods of object + m[ethod] <class|module> show instance methods of class or module + th[read] l[ist] list all threads + th[read] c[ur[rent]] show current thread + th[read] [sw[itch]] <nnn> switch thread context to nnn + th[read] stop <nnn> stop thread nnn + th[read] resume <nnn> resume thread nnn + p expression evaluate expression and print its value + h[elp] print this help + <everything else> evaluate +EOHELP + end + + def display_expressions(binding) + n = 1 + for d in display + if d[0] + stdout.printf "%d: ", n + display_expression(d[1], binding) + end + n += 1 + end + end + + def display_expression(exp, binding) + stdout.printf "%s = %s\n", exp, debug_silent_eval(exp, binding).to_s + end + + def frame_set_pos(file, line) + if @frames[0] + @frames[0][1] = file + @frames[0][2] = line + end + end + + def display_frames(pos) + 0.upto(@frames.size - 1) do |n| + if n == pos + stdout.print "--> " + else + stdout.print " " + end + stdout.print format_frame(n) + end + end + + def format_frame(pos) + bind, file, line, id = @frames[pos] + sprintf "#%d %s:%s%s\n", pos + 1, file, line, + (id ? ":in `#{id.id2name}'" : "") + end + + def display_list(b, e, file, line) + stdout.printf "[%d, %d] in %s\n", b, e, file + if lines = SCRIPT_LINES__[file] and lines != true + n = 0 + b.upto(e) do |n| + if n > 0 && lines[n-1] + if n == line + stdout.printf "=> %d %s\n", n, lines[n-1].chomp + else + stdout.printf " %d %s\n", n, lines[n-1].chomp + end + end + end + else + stdout.printf "No sourcefile available for %s\n", file + end + end + + def line_at(file, line) + lines = SCRIPT_LINES__[file] + if lines + return "\n" if lines == true + line = lines[line-1] + return "\n" unless line + return line + end + return "\n" + end + + def debug_funcname(id) + if id.nil? + "toplevel" + else + id.id2name + end + end + + def check_break_points(file, klass, pos, binding, id) + return false if break_points.empty? + n = 1 + for b in break_points + if b[0] # valid + if b[1] == 0 # breakpoint + if (b[2] == file and b[3] == pos) or + (klass and b[2] == klass and b[3] == pos) + stdout.printf "Breakpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos + return true + end + elsif b[1] == 1 # watchpoint + if debug_silent_eval(b[2], binding) + stdout.printf "Watchpoint %d, %s at %s:%s\n", n, debug_funcname(id), file, pos + return true + end + end + end + n += 1 + end + return false + end + + def excn_handle(file, line, id, binding) + if $!.class <= SystemExit + set_trace_func nil + exit + end + + if @catch and ($!.class.ancestors.find { |e| e.to_s == @catch }) + stdout.printf "%s:%d: `%s' (%s)\n", file, line, $!, $!.class + fs = @frames.size + tb = caller(0)[-fs..-1] + if tb + for i in tb + stdout.printf "\tfrom %s\n", i + end + end + suspend_all + debug_command(file, line, id, binding) + end + end + + def trace_func(event, file, line, id, binding, klass) + Tracer.trace_func(event, file, line, id, binding, klass) if trace? + context(Thread.current).check_suspend + + if not trace_ruby? and + ( file =~ /#{Config::CONFIG['sitelibdir']}/ or + file =~ /#{Config::CONFIG['rubylibdir']}/ or + file =~ %r{/debuggee.rb} ) + case event + when 'line' + frame_set_pos(file, line) + + when 'call' + @frames.unshift [binding, file, line, id] + + when 'c-call' + frame_set_pos(file, line) + + when 'class' + @frames.unshift [binding, file, line, id] + + when 'return', 'end' + @frames.shift + + when 'end' + @frames.shift + + when 'raise' + excn_handle(file, line, id, binding) + + end + return + end + + @file = file + @line = line + case event + when 'line' + frame_set_pos(file, line) + if !@no_step or @frames.size == @no_step + @stop_next -= 1 + @stop_next = -1 if @stop_next < 0 + elsif @frames.size < @no_step + @stop_next = 0 # break here before leaving... + else + # nothing to do. skipped. + end + #LJ reverse the test here because we always want the breakpoint reached + # message to be display. if stop_next is null *AND* there is also a break point + # the message will never display. + if check_break_points(file, nil, line, binding, id) or @stop_next == 0 + # LJ this test doesn't make sense and cause troubles when + # on a line with a recursive call and a breakpoint on it (e.g factorial) + # or when in a while loop with one line only inside the loop + # + # RJD: reinstated the test with a check on whether '@frames.size' + # has changed to catch the recursive factorial case LJ describes + # above. The while loop problem still exists though + if [file, line, @frames.size] == @last + @stop_next = 1 + else + @no_step = nil + suspend_all + debug_command(file, line, id, binding) + @last = [file, line, @frames.size] + end + end + + when 'call' + @frames.unshift [binding, file, line, id] + if check_break_points(file, klass, id.id2name, binding, id) + suspend_all + debug_command(file, line, id, binding) + end + + when 'c-call' + frame_set_pos(file, line) + + when 'class' + @frames.unshift [binding, file, line, id] + + when 'return', 'end' + if @frames.size == @finish_pos + @stop_next = 1 + @finish_pos = 0 + end + @frames.shift + + when 'end' + @frames.shift + + when 'raise' + excn_handle(file, line, id, binding) + + end + @last_file = file + end +end + +trap("INT") { DEBUGGER__.interrupt } +@last_thread = Thread::main +@max_thread = 1 +@thread_list = {Thread::main => 1} +@break_points = [] +@display = [] +@waiting = [] +@stdout = STDOUT + + class SilentObject + def method_missing( msg_id, *a, &b ); end + end + SilentClient = SilentObject.new() + @client = SilentClient + @attached = false + +class << DEBUGGER__ + def stdout + @stdout + end + + def stdout=(s) + @stdout = s + end + + def display + @display + end + + def break_points + @break_points + end + + def client + @client + end + + def set_client( client ) + @client = client + DEBUGGER__.stdout = Tracer.stdout = @client + end + + def quit + #LJ flush STDOUT and ERR +# @stdout.print "Quitting debugger" + STDERR.flush; STDOUT.flush + $stderr.flush; $stdout.flush +# STDERR.close; STDOUT.close + detach + #DebugSvr.stop_service + end + + def detach + @attached = false + @client.detach + set_client( SilentClient ) + end + + def waiting + @waiting + end + + def set_trace( arg ) + saved_crit = Thread.critical + Thread.critical = true + make_thread_list + for th, in @thread_list + context(th).set_trace arg + end + Thread.critical = saved_crit + arg + end + + def set_last_thread(th) + @last_thread = th + end + + def suspend + saved_crit = Thread.critical + Thread.critical = true + make_thread_list + for th, in @thread_list + next if th == Thread.current + context(th).set_suspend + end + Thread.critical = saved_crit + # Schedule other threads to suspend as soon as possible. + Thread.pass unless Thread.critical + end + + def resume + saved_crit = Thread.critical + Thread.critical = true + make_thread_list + for th, in @thread_list + next if th == Thread.current + context(th).clear_suspend + end + waiting.each do |th| + th.run + end + waiting.clear + Thread.critical = saved_crit + # Schedule other threads to restart as soon as possible. + Thread.pass + end + + def context(thread=Thread.current) + c = thread[:__debugger_data__] + unless c + thread[:__debugger_data__] = c = Context.new + end + c + end + + def interrupt + context(@last_thread).stop_next + end + + def get_thread(num) + th = @thread_list.index(num) + unless th + @stdout.print "No thread ##{num}\n" + throw :debug_error + end + th + end + + def thread_list(num) + th = get_thread(num) + if th == Thread.current + @stdout.print "+" + else + @stdout.print " " + end + @stdout.printf "%d ", num + @stdout.print th.inspect, "\t" + file = context(th).instance_eval{@file} + if file + @stdout.print file,":",context(th).instance_eval{@line} + end + @stdout.print "\n" + end + + def thread_list_all + for th in @thread_list.values.sort + thread_list(th) + end + end + + def make_thread_list + hash = {} + for th in Thread::list + if @thread_list.key? th + hash[th] = @thread_list[th] + else + @max_thread += 1 + hash[th] = @max_thread + end + end + @thread_list = hash + end + + def debug_thread_info(input, binding) + case input + when /^l(?:ist)?/ + make_thread_list + thread_list_all + + when /^c(?:ur(?:rent)?)?$/ + make_thread_list + thread_list(@thread_list[Thread.current]) + + when /^(?:sw(?:itch)?\s+)?(\d+)/ + make_thread_list + th = get_thread($1.to_i) + if th == Thread.current + @stdout.print "It's the current thread.\n" + else + thread_list(@thread_list[th]) + context(th).stop_next + th.run + return :cont + end + + when /^stop\s+(\d+)/ + make_thread_list + th = get_thread($1.to_i) + if th == Thread.current + @stdout.print "It's the current thread.\n" + elsif th.stop? + @stdout.print "Already stopped.\n" + else + thread_list(@thread_list[th]) + context(th).suspend + end + + when /^resume\s+(\d+)/ + make_thread_list + th = get_thread($1.to_i) + if th == Thread.current + @stdout.print "It's the current thread.\n" + elsif !th.stop? + @stdout.print "Already running." + else + thread_list(@thread_list[th]) + th.run + end + end + end +end + +require 'socket' + + ## + # DEBUGGEE -> socket -> KDevelop + # The Client class holds all the methods invoked from the debuggee that send and + # receive data from KDevelop via the Unix domain socket. + # + class Client + def initialize(path) + @debugger = UNIXSocket.open(path) + @debugger.sync=true + end + + def detach +# @debugger.close + end + + def printf( *args ) + str = sprintf(*args) + @debugger.send(str, 0) + end + + def print( *args ) + str = args.to_s + @debugger.send(str, 0) + end + + def <<( arg ) + @debugger.send(arg, 0) + end + + # Return next command + def readline(prompt_cmd) + @debugger.send(prompt_cmd, 0) + msg = @debugger.recvfrom(2048) + return msg[0] + end + end + +#stdout.printf "Debug.rb\n" +#stdout.printf "Emacs support available.\n\n" + +STDERR.sync=true +STDOUT.sync=true + +path = $stdin.gets.chomp + +DEBUGGER__.set_client( Client.new(path) ) + +set_trace_func proc { |event, file, line, id, binding, klass, *rest| + + # LJ make sure the file path is always absolute. It is needed by + # the Debugger plugin in KDevelop and can only be determined here + # in the context of the debugged process + file = File.expand_path(file) + + DEBUGGER__.context.trace_func event, file, line, id, binding, klass +} + +end diff --git a/languages/ruby/debugger/debuggerpart.cpp b/languages/ruby/debugger/debuggerpart.cpp new file mode 100644 index 00000000..95b4dd09 --- /dev/null +++ b/languages/ruby/debugger/debuggerpart.cpp @@ -0,0 +1,785 @@ +/*************************************************************************** + * Copyright (C) 1999-2001 by John Birch * + * jbb@kdevelop.org * + * Copyright (C) 2001 by Bernd Gehrmann * + * bernd@kdevelop.org * + * * + * Adapted for ruby debugging * + * -------------------------- * + * begin : Mon Nov 1 2004 * + * copyright : (C) 2004 by Richard Dale * + * email : Richard_Dale@tipitina.demon.co.uk * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include "debuggerpart.h" + +#include <qdir.h> +#include <qvbox.h> +#include <qwhatsthis.h> +#include <qpopupmenu.h> + +#include <kaction.h> +#include <kdebug.h> +#include <kfiledialog.h> +#include <kdevgenericfactory.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kmainwindow.h> +#include <kstatusbar.h> +#include <kparts/part.h> +#include <ktexteditor/viewcursorinterface.h> +#include <kmessagebox.h> +#include <kapplication.h> +#include <dcopclient.h> +#include <qtimer.h> +#include <kstringhandler.h> +#include <kstandarddirs.h> + +#include "kdevcore.h" +#include "kdevproject.h" +#include "kdevmainwindow.h" +#include "kdevappfrontend.h" +#include "kdevpartcontroller.h" +#include "kdevdebugger.h" +#include "domutil.h" +#include "variablewidget.h" +#include "rdbbreakpointwidget.h" +#include "framestackwidget.h" +#include "processwidget.h" +#include "rdbcontroller.h" +#include "breakpoint.h" +#include "dbgpsdlg.h" +#include "dbgtoolbar.h" +#include "rdbparser.h" +#include "rdboutputwidget.h" +#include "processlinemaker.h" + +#include <iostream> + +#include <kdevplugininfo.h> +#include <debugger.h> + + +namespace RDBDebugger +{ +static const KDevPluginInfo data("kdevrbdebugger"); + +typedef KDevGenericFactory<RubyDebuggerPart> RubyDebuggerFactory; +K_EXPORT_COMPONENT_FACTORY( libkdevrbdebugger, RubyDebuggerFactory( data ) ) + +RubyDebuggerPart::RubyDebuggerPart( QObject *parent, const char *name, const QStringList & ) : + KDevPlugin( &data, parent, name ? name : "RubyDebuggerPart" ), + controller(0) +{ +// setObjId("RubyDebuggerInterface"); + setInstance(RubyDebuggerFactory::instance()); + + setXMLFile("kdevrbdebugger.rc"); + + m_debugger = new Debugger( partController() ); + + statusBarIndicator = new QLabel(" ", mainWindow()->statusBar()); + statusBarIndicator->setFixedWidth(15); + mainWindow()->statusBar()->addWidget(statusBarIndicator, 0, true); + statusBarIndicator->show(); + + // Setup widgets and dbgcontroller + variableWidget = new VariableWidget( 0, "rdbVariablewidget"); +// /*variableWidget*/->setEnabled(false); + variableWidget->setIcon(SmallIcon("math_brace")); + variableWidget->setCaption(i18n("Variable Tree")); + QWhatsThis::add + (variableWidget, i18n("<b>Variable tree</b><p>" + "The variable tree allows you to see " + "the variable values as you step " + "through your program using the internal " + "debugger. Click the right mouse button on items in " + "this view to get a popup menu.\n" + "To speed up stepping through your code " + "leave the tree items closed.\n")); + mainWindow()->embedSelectView(variableWidget, i18n("Variables"), i18n("Debugger variable-view")); + +// mainWindow()->setViewAvailable(variableWidget, false); + + rdbBreakpointWidget = new RDBBreakpointWidget( 0, "rdbBreakpointWidget" ); + rdbBreakpointWidget->setCaption(i18n("Breakpoint List")); + QWhatsThis::add + (rdbBreakpointWidget, i18n("<b>Breakpoint list</b><p>" + "Displays a list of breakpoints with " + "their current status. Clicking on a " + "breakpoint item allows you to change " + "the breakpoint and will take you " + "to the source in the editor window.")); + rdbBreakpointWidget->setIcon( SmallIcon("stop") ); + mainWindow()->embedOutputView(rdbBreakpointWidget, i18n("Breakpoints"), i18n("Debugger breakpoints")); + + framestackWidget = new FramestackWidget( 0, "rdbFramestackWidget" ); + framestackWidget->setEnabled(false); + framestackWidget->setCaption(i18n("Frame Stack")); + QWhatsThis::add + (framestackWidget, i18n("<b>Frame stack</b><p>" + "Often referred to as the \"call stack\", " + "this is a list showing what method is " + "currently active and who called each " + "method to get to this point in your " + "program. By clicking on an item you " + "can see the values in any of the " + "previous calling methods.")); + framestackWidget->setIcon( SmallIcon("table") ); + mainWindow()->embedOutputView(framestackWidget, i18n("Frame Stack"), i18n("Debugger method call stack")); + mainWindow()->setViewAvailable(framestackWidget, false); + + + rdbOutputWidget = new RDBOutputWidget( 0, "rdbOutputWidget" ); + rdbOutputWidget->setEnabled(false); + rdbOutputWidget->setIcon( SmallIcon("inline_image") ); + rdbOutputWidget->setCaption(i18n("RDB Output")); + QWhatsThis::add + (rdbOutputWidget, i18n("<b>RDB output</b><p>" + "Shows all rdb commands being executed. " + "You can also issue any other rdb command while debugging.")); + mainWindow()->embedOutputView(rdbOutputWidget, i18n("RDB"), + i18n("RDB output")); + mainWindow()->setViewAvailable(rdbOutputWidget, false); + + // rdbBreakpointWidget -> this + connect( rdbBreakpointWidget, SIGNAL(refreshBPState(const Breakpoint&)), + this, SLOT(slotRefreshBPState(const Breakpoint&))); + connect( rdbBreakpointWidget, SIGNAL(publishBPState(const Breakpoint&)), + this, SLOT(slotRefreshBPState(const Breakpoint&))); + connect( rdbBreakpointWidget, SIGNAL(gotoSourcePosition(const QString&, int)), + this, SLOT(slotGotoSource(const QString&, int)) ); + + // Now setup the actions + KAction *action; + +// action = new KAction(i18n("&Start"), "1rightarrow", CTRL+SHIFT+Key_F9, + action = new KAction(i18n("&Start"), "dbgrun", CTRL+SHIFT+Key_F9, + this, SLOT(slotRun()), + actionCollection(), "debug_run"); + action->setToolTip( i18n("Start in debugger") ); + action->setWhatsThis( i18n("<b>Start in debugger</b><p>" + "Starts the debugger with the project's main " + "executable. You may set some breakpoints " + "before this, or you can interrupt the program " + "while it is running, in order to get information " + "about variables, frame stack, and so on.") ); + + action = new KAction(i18n("Sto&p"), "stop", 0, + this, SLOT(slotStop()), + actionCollection(), "debug_stop"); + action->setToolTip( i18n("Stop debugger") ); + action->setWhatsThis(i18n("<b>Stop debugger</b><p>Kills the executable and exits the debugger.")); + + action = new KAction(i18n("Interrupt"), "player_pause", 0, + this, SLOT(slotPause()), + actionCollection(), "debug_pause"); + action->setToolTip( i18n("Interrupt application") ); + action->setWhatsThis(i18n("<b>Interrupt application</b><p>Interrupts the debugged process or current RDB command.")); + + action = new KAction(i18n("Run to &Cursor"), "dbgrunto", 0, + this, SLOT(slotRunToCursor()), + actionCollection(), "debug_runtocursor"); + action->setToolTip( i18n("Run to cursor") ); + action->setWhatsThis(i18n("<b>Run to cursor</b><p>Continues execution until the cursor position is reached.")); + + + action = new KAction(i18n("Step &Over"), "dbgnext", 0, + this, SLOT(slotStepOver()), + actionCollection(), "debug_stepover"); + action->setToolTip( i18n("Step over the next line") ); + action->setWhatsThis( i18n("<b>Step over</b><p>" + "Executes one line of source in the current source file. " + "If the source line is a call to a method the whole " + "method is executed and the app will stop at the line " + "following the method call.") ); + + + action = new KAction(i18n("Step &Into"), "dbgstep", 0, + this, SLOT(slotStepInto()), + actionCollection(), "debug_stepinto"); + action->setToolTip( i18n("Step into the next statement") ); + action->setWhatsThis( i18n("<b>Step into</b><p>" + "Executes exactly one line of source. If the source line " + "is a call to a method then execution will stop after " + "the method has been entered.") ); + + + + action = new KAction(i18n("Step O&ut"), "dbgstepout", 0, + this, SLOT(slotStepOut()), + actionCollection(), "debug_stepout"); + action->setToolTip( i18n("Steps out of the current method") ); + action->setWhatsThis( i18n("<b>Step out</b><p>" + "Executes the application until the currently executing " + "method is completed. The debugger will then display " + "the line after the original call to that method. If " + "program execution is in the outermost frame (i.e. in " + "the topleveltoggleWatchpoint) then this operation has no effect.") ); + + + action = new KAction(i18n("Toggle Breakpoint"), 0, 0, + this, SLOT(toggleBreakpoint()), + actionCollection(), "debug_toggle_breakpoint"); + action->setToolTip(i18n("Toggle breakpoint")); + action->setWhatsThis(i18n("<b>Toggle breakpoint</b><p>Toggles the breakpoint at the current line in editor.")); + + connect( mainWindow()->main()->guiFactory(), SIGNAL(clientAdded(KXMLGUIClient*)), + this, SLOT(guiClientAdded(KXMLGUIClient*)) ); + + + connect( partController(), SIGNAL(loadedFile(const KURL &)), + rdbBreakpointWidget, SLOT(slotRefreshBP(const KURL &)) ); + connect( debugger(), SIGNAL(toggledBreakpoint(const QString &, int)), + rdbBreakpointWidget, SLOT(slotToggleBreakpoint(const QString &, int)) ); + connect( debugger(), SIGNAL(editedBreakpoint(const QString &, int)), + rdbBreakpointWidget, SLOT(slotEditBreakpoint(const QString &, int)) ); + connect( debugger(), SIGNAL(toggledBreakpointEnabled(const QString &, int)), + rdbBreakpointWidget, SLOT(slotToggleBreakpointEnabled(const QString &, int)) ); + + connect( core(), SIGNAL(contextMenu(QPopupMenu *, const Context *)), + this, SLOT(contextMenu(QPopupMenu *, const Context *)) ); + + connect( core(), SIGNAL(stopButtonClicked(KDevPlugin*)), + this, SLOT(slotStop(KDevPlugin*)) ); + connect( core(), SIGNAL(projectClosed()), + this, SLOT(projectClosed()) ); + + connect( partController(), SIGNAL(activePartChanged(KParts::Part*)), + this, SLOT(slotActivePartChanged(KParts::Part*)) ); + + procLineMaker = new ProcessLineMaker(); + + connect( procLineMaker, SIGNAL(receivedStdoutLine(const QCString&)), + appFrontend(), SLOT(insertStdoutLine(const QCString&)) ); + connect( procLineMaker, SIGNAL(receivedStderrLine(const QCString&)), + appFrontend(), SLOT(insertStderrLine(const QCString&)) ); + connect( procLineMaker, SIGNAL(receivedPartialStdoutLine(const QCString&)), + appFrontend(), SLOT(addPartialStdoutLine(const QCString&)) ); + connect( procLineMaker, SIGNAL(receivedPartialStderrLine(const QCString&)), + appFrontend(), SLOT(addPartialStderrLine(const QCString&)) ); + + setupController(); + QTimer::singleShot(0, this, SLOT(setupDcop())); +} + +RubyDebuggerPart::~RubyDebuggerPart() +{ + kapp->dcopClient()->setNotifications(false); + + if (variableWidget) + mainWindow()->removeView(variableWidget); + if (rdbBreakpointWidget) + mainWindow()->removeView(rdbBreakpointWidget); + if (framestackWidget) + mainWindow()->removeView(framestackWidget); + if(rdbOutputWidget) + mainWindow()->removeView(rdbOutputWidget); + + delete variableWidget; + delete rdbBreakpointWidget; + delete framestackWidget; + delete rdbOutputWidget; + delete controller; + delete floatingToolBar; + delete statusBarIndicator; + delete procLineMaker; +} + + +void RubyDebuggerPart::guiClientAdded( KXMLGUIClient* client ) +{ + // Can't change state until after XMLGUI has been loaded... + // Anyone know of a better way of doing this? + if( client == this ) + stateChanged( QString("stopped") ); +} + +void RubyDebuggerPart::contextMenu(QPopupMenu *popup, const Context *context) +{ + if (!context->hasType( Context::EditorContext )) + return; + + const EditorContext *econtext = static_cast<const EditorContext*>(context); + m_contextIdent = econtext->currentWord(); + + popup->insertSeparator(); + if (econtext->url().isLocalFile()) + { + int id = popup->insertItem( i18n("Toggle Breakpoint"), this, SLOT(toggleBreakpoint()) ); + popup->setWhatsThis(id, i18n("<b>Toggle breakpoint</b><p>Toggles breakpoint at the current line.")); + } + if (!m_contextIdent.isEmpty()) + { + QString squeezed = KStringHandler::csqueeze(m_contextIdent, 30); + int id = popup->insertItem( i18n("Watch: %1").arg(squeezed), this, SLOT(contextWatch()) ); + popup->setWhatsThis(id, i18n("<b>Watch</b><p>Adds an expression under the cursor to the Variables/Watch list.")); + + id = popup->insertItem( i18n("Inspect: %1").arg(squeezed), this, SLOT(contextRubyInspect()) ); + popup->setWhatsThis(id, i18n("<b>Inspect</b><p>Evaluates an expression under the cursor.")); + } +} + + +void RubyDebuggerPart::toggleBreakpoint() +{ + KParts::ReadWritePart *rwpart + = dynamic_cast<KParts::ReadWritePart*>(partController()->activePart()); + KTextEditor::ViewCursorInterface *cursorIface + = dynamic_cast<KTextEditor::ViewCursorInterface*>(partController()->activeWidget()); + + if (!rwpart || !cursorIface) + return; + + uint line, col; + cursorIface->cursorPositionReal(&line, &col); + + rdbBreakpointWidget->slotToggleBreakpoint(rwpart->url().path(), line); +} + + +void RubyDebuggerPart::contextWatch() +{ + variableWidget->slotAddWatchExpression(m_contextIdent); +} + +// Evaluates the selected text +void RubyDebuggerPart::contextRubyInspect() +{ + emit rubyInspect(m_contextIdent); +} + + +void RubyDebuggerPart::setupController() +{ + VariableTree *variableTree = variableWidget->varTree(); + + controller = new RDBController(variableTree, framestackWidget, *projectDom()); + + // this -> controller + connect( this, SIGNAL(rubyInspect(const QString&)), + controller, SLOT(slotRubyInspect(const QString&))); + + // variableTree -> framestackWidget + connect( variableTree, SIGNAL(selectFrame(int, int)), + framestackWidget, SLOT(slotSelectFrame(int, int))); + + // framestackWidget -> variableTree + connect( framestackWidget, SIGNAL(frameActive(int, int, const QString&)), + variableTree, SLOT(slotFrameActive(int, int, const QString&))); + + // variableTree -> controller + connect( variableTree, SIGNAL(expandItem(VarItem*, const QCString&)), + controller, SLOT(slotExpandItem(VarItem*, const QCString&))); + connect( variableTree, SIGNAL(fetchGlobals(bool)), + controller, SLOT(slotFetchGlobals(bool))); + connect( variableTree, SIGNAL(addWatchExpression(const QString&, bool)), + controller, SLOT(slotAddWatchExpression(const QString&, bool))); + connect( variableTree, SIGNAL(removeWatchExpression(int)), + controller, SLOT(slotRemoveWatchExpression(int))); + + // framestackWidget -> controller + connect( framestackWidget, SIGNAL(selectFrame(int,int,const QString&)), + controller, SLOT(slotSelectFrame(int,int,const QString&))); + + // rdbBreakpointWidget -> controller + connect( rdbBreakpointWidget, SIGNAL(clearAllBreakpoints()), + controller, SLOT(slotClearAllBreakpoints())); + connect( rdbBreakpointWidget, SIGNAL(publishBPState(const Breakpoint&)), + controller, SLOT(slotBPState(const Breakpoint &))); + + + // rdbOutputWidget -> controller + connect( rdbOutputWidget, SIGNAL(userRDBCmd(const QString &)), + controller, SLOT(slotUserRDBCmd(const QString&))); + connect( rdbOutputWidget, SIGNAL(breakInto()), + controller, SLOT(slotBreakInto())); + + // controller -> rdbBreakpointWidget + connect( controller, SIGNAL(acceptPendingBPs()), + rdbBreakpointWidget, SLOT(slotSetPendingBPs())); + connect( controller, SIGNAL(unableToSetBPNow(int)), + rdbBreakpointWidget, SLOT(slotUnableToSetBPNow(int))); + connect( controller, SIGNAL(rawRDBBreakpointList (char*)), + rdbBreakpointWidget, SLOT(slotParseRDBBrkptList(char*))); + connect( controller, SIGNAL(rawRDBBreakpointSet(char*, int)), + rdbBreakpointWidget, SLOT(slotParseRDBBreakpointSet(char*, int))); + + + // controller -> this + connect( controller, SIGNAL(dbgStatus(const QString&, int)), + this, SLOT(slotStatus(const QString&, int))); + connect( controller, SIGNAL(showStepInSource(const QString&, int, const QString&)), + this, SLOT(slotShowStep(const QString&, int))); + + // controller -> procLineMaker + connect( controller, SIGNAL(ttyStdout(const char*)), + procLineMaker, SLOT(slotReceivedStdout(const char*))); + connect( controller, SIGNAL(ttyStderr(const char*)), + procLineMaker, SLOT(slotReceivedStderr(const char*))); + + // controller -> rdbOutputWidget + connect( controller, SIGNAL(rdbStdout(const char*)), + rdbOutputWidget, SLOT(slotReceivedStdout(const char*)) ); + connect( controller, SIGNAL(rdbStderr(const char*)), + rdbOutputWidget, SLOT(slotReceivedStderr(const char*)) ); + connect( controller, SIGNAL(dbgStatus(const QString&, int)), + rdbOutputWidget, SLOT(slotDbgStatus(const QString&, int))); + +} + + +bool RubyDebuggerPart::startDebugger() +{ + QString build_dir; // Currently selected build directory + QString run_directory; // Directory from where the program should be run + QString program; // Absolute path to application + QString run_arguments; // Command line arguments to be passed to the application + QString ruby_interpreter; // Absolute path to the ruby interpreter + QString debuggee_path; // Absolute path to debuggee.rb debugger script + bool show_constants; // Show constants in the debugger + bool trace_into_ruby; // Trace into the ruby code installed under sitedir + + if (project()) { + build_dir = project()->buildDirectory(); + run_directory = DomUtil::readEntry(*projectDom(), "/kdevscriptproject/run/globalcwd"); + if (run_directory.isEmpty()) + run_directory = project()->buildDirectory(); + } + + int runMainProgram = DomUtil::readIntEntry(*projectDom(), "/kdevrubysupport/run/runmainprogram"); + + if (runMainProgram == 0) { + program = project()->projectDirectory() + "/" + DomUtil::readEntry(*projectDom(), "/kdevrubysupport/run/mainprogram"); + } else { + KParts::ReadOnlyPart *ro_part = dynamic_cast<KParts::ReadOnlyPart*>(partController()->activePart()); + if (ro_part != 0) { + program = ro_part->url().path(); + } + } + + run_arguments = DomUtil::readEntry(*projectDom(), "/kdevrubysupport/run/programargs"); + + QString shell = DomUtil::readEntry(*projectDom(), "/kdevrbdebugger/general/dbgshell"); + if( !shell.isEmpty() ) + { + QFileInfo info( shell ); + if( info.isRelative() ) + { + shell = build_dir + "/" + shell; + info.setFile( shell ); + } + if( !info.exists() ) + { + KMessageBox::error( + mainWindow()->main(), + i18n("Could not locate the debugging shell '%1'.").arg( shell ), + i18n("Debugging Shell Not Found") ); + return false; + } + } + + core()->running(this, true); + + stateChanged( QString("active") ); + + KActionCollection *ac = actionCollection(); + ac->action("debug_run")->setText( i18n("&Continue") ); +// ac->action("debug_run")->setIcon( "dbgrun" ); + ac->action("debug_run")->setToolTip( i18n("Continues the application execution") ); + ac->action("debug_run")->setWhatsThis( i18n("Continue application execution\n\n" + "Continues the execution of your application in the " + "debugger. This only takes effect when the application " + "has been halted by the debugger (i.e. a breakpoint has " + "been activated or the interrupt was pressed).") ); + + +// mainWindow()->setViewAvailable(variableWidget, true); + mainWindow()->setViewAvailable(framestackWidget, true); + mainWindow()->setViewAvailable(rdbOutputWidget, true); + +// variableWidget->setEnabled(true); + framestackWidget->setEnabled(true); + + rdbOutputWidget->clear(); + rdbOutputWidget->setEnabled(true); + + if (DomUtil::readBoolEntry(*projectDom(), "/kdevrbdebugger/general/floatingtoolbar", false)) + { + floatingToolBar = new DbgToolBar(this, mainWindow()->main()); + floatingToolBar->show(); + } + + ruby_interpreter = DomUtil::readEntry(*projectDom(), "/kdevrubysupport/run/interpreter"); + + int coding = DomUtil::readIntEntry(*projectDom(), "/kdevrubysupport/run/charactercoding"); + QString character_coding("-K"); + + switch (coding) { + case 0: + character_coding.append("A"); + break; + case 1: + character_coding.append("E"); + break; + case 2: + character_coding.append("S"); + break; + case 3: + character_coding.append("U"); + break; + } + +// ruby_interpreter.append(QString(" -K") + code); + + debuggee_path = ::locate("data", "kdevrbdebugger/debuggee.rb", instance()); + + show_constants = DomUtil::readBoolEntry(*projectDom(), "/kdevrbdebugger/general/showconstants"); + trace_into_ruby = DomUtil::readBoolEntry(*projectDom(), "/kdevrbdebugger/general/traceintoruby"); + + controller->slotStart(ruby_interpreter, character_coding, run_directory, debuggee_path, program, run_arguments, show_constants, trace_into_ruby); + return true; +} + +void RubyDebuggerPart::slotStopDebugger() +{ + controller->slotStopDebugger(); + debugger()->clearExecutionPoint(); + + delete floatingToolBar; + floatingToolBar = 0; + + rdbBreakpointWidget->reset(); + framestackWidget->clear(); + variableWidget->varTree()->clear(); + +// variableWidget->setEnabled(false); + framestackWidget->setEnabled(false); + rdbOutputWidget->setEnabled(false); + +// mainWindow()->setViewAvailable(variableWidget, false); + mainWindow()->setViewAvailable(framestackWidget, false); + mainWindow()->setViewAvailable(rdbOutputWidget, false); + + KActionCollection *ac = actionCollection(); + ac->action("debug_run")->setText( i18n("&Start") ); +// ac->action("debug_run")->setIcon( "1rightarrow" ); + ac->action("debug_run")->setToolTip( i18n("Runs the program in the debugger") ); + ac->action("debug_run")->setWhatsThis( i18n("Start in debugger\n\n" + "Starts the debugger with the project's main " + "executable. You may set some breakpoints " + "before this, or you can interrupt the program " + "while it is running, in order to get information " + "about variables, frame stack, and so on.") ); + + stateChanged( QString("stopped") ); + + core()->running(this, false); +} + +void RubyDebuggerPart::projectClosed() +{ + slotStopDebugger(); +} + +void RubyDebuggerPart::slotRun() +{ + if (controller->stateIsOn(s_programExited)) { + rdbBreakpointWidget->reset(); + } + + if ( controller->stateIsOn( s_dbgNotStarted ) ) { + mainWindow()->statusBar()->message(i18n("Debugging program"), 1000); + mainWindow()->raiseView(rdbOutputWidget); + appFrontend()->clearView(); + startDebugger(); + } else { + KActionCollection *ac = actionCollection(); + ac->action("debug_run")->setText( i18n("&Continue") ); + ac->action("debug_run")->setToolTip( i18n("Continues the application execution") ); + ac->action("debug_run")->setWhatsThis( i18n("Continue application execution\n\n" + "Continues the execution of your application in the " + "debugger. This only takes effect when the application " + "has been halted by the debugger (i.e. a breakpoint has " + "been activated or the interrupt was pressed).") ); + + mainWindow()->statusBar()->message(i18n("Continuing program"), 1000); + } + + controller->slotRun(); +} + + + +void RubyDebuggerPart::slotStop(KDevPlugin* which) +{ + if( which != 0 && which != this ) + return; + +// if( !controller->stateIsOn( s_dbgNotStarted ) && !controller->stateIsOn( s_shuttingDown ) ) + slotStopDebugger(); +} + + +void RubyDebuggerPart::slotPause() +{ + controller->slotBreakInto(); +} + + +void RubyDebuggerPart::slotRunToCursor() +{ + KParts::ReadWritePart *rwpart + = dynamic_cast<KParts::ReadWritePart*>(partController()->activePart()); + KTextEditor::ViewCursorInterface *cursorIface + = dynamic_cast<KTextEditor::ViewCursorInterface*>(partController()->activeWidget()); + + if (!rwpart || !rwpart->url().isLocalFile() || !cursorIface) + return; + + uint line, col; + cursorIface->cursorPosition(&line, &col); + + controller->slotRunUntil(rwpart->url().path(), line); +} + +void RubyDebuggerPart::slotStepOver() +{ + controller->slotStepOver(); +} + + + +void RubyDebuggerPart::slotStepInto() +{ + controller->slotStepInto(); +} + + +void RubyDebuggerPart::slotStepOut() +{ + controller->slotStepOutOff(); +} + + + + +void RubyDebuggerPart::slotRefreshBPState( const Breakpoint& BP) +{ + if (BP.type() == BP_TYPE_FilePos) + { + const FilePosBreakpoint& bp = dynamic_cast<const FilePosBreakpoint&>(BP); + if (bp.isActionDie()) + debugger()->setBreakpoint(bp.fileName(), bp.lineNum()-1, -1, true, false); + else + debugger()->setBreakpoint(bp.fileName(), bp.lineNum()-1, + 1/*bp->id()*/, bp.isEnabled(), bp.isPending() ); + } +} + + +void RubyDebuggerPart::slotStatus(const QString &msg, int state) +{ + QString stateIndicator; + + if (state & s_dbgNotStarted) + { + stateIndicator = " "; + } + else if (state & s_appBusy) + { + stateIndicator = "A"; + debugger()->clearExecutionPoint(); + stateChanged( QString("active") ); + } + else if (state & s_programExited) + { + stateIndicator = "E"; + stateChanged( QString("stopped") ); + KActionCollection *ac = actionCollection(); + ac->action("debug_run")->setText( i18n("Restart") ); +// ac->action("debug_run")->setIcon( "1rightarrow" ); + ac->action("debug_run")->setToolTip( i18n("Restart the program in the debugger") ); + ac->action("debug_run")->setWhatsThis( i18n("Restart in debugger\n\n" + "Restarts the program in the debugger") ); +// slotStop(); + } + else + { + stateIndicator = "P"; + stateChanged( QString("paused") ); + } + + // And now? :-) + kdDebug(9012) << "Debugger state: " << stateIndicator << ": " << endl; + kdDebug(9012) << " " << msg << endl; + + statusBarIndicator->setText(stateIndicator); + if (!msg.isEmpty()) + mainWindow()->statusBar()->message(msg, 3000); +} + + +void RubyDebuggerPart::slotShowStep(const QString &fileName, int lineNum) +{ + if ( ! fileName.isEmpty() ) + { + // Debugger counts lines from 1 + debugger()->gotoExecutionPoint(KURL( fileName ), lineNum-1); + } +} + + +void RubyDebuggerPart::slotGotoSource(const QString &fileName, int lineNum) +{ + if ( ! fileName.isEmpty() ) + partController()->editDocument(KURL( fileName ), lineNum); +} + + +void RubyDebuggerPart::slotActivePartChanged( KParts::Part* part ) +{ + KAction* action = actionCollection()->action("debug_toggle_breakpoint"); + if(!action) + return; + + if(!part) + { + action->setEnabled(false); + return; + } + KTextEditor::ViewCursorInterface *iface + = dynamic_cast<KTextEditor::ViewCursorInterface*>(part->widget()); + action->setEnabled( iface != 0 ); +} + +void RubyDebuggerPart::restorePartialProjectSession(const QDomElement* el) +{ + rdbBreakpointWidget->restorePartialProjectSession(el); + variableWidget->restorePartialProjectSession(el); +} + +void RubyDebuggerPart::savePartialProjectSession(QDomElement* el) +{ + rdbBreakpointWidget->savePartialProjectSession(el); + variableWidget->savePartialProjectSession(el); +} + +} + +KDevAppFrontend * RDBDebugger::RubyDebuggerPart::appFrontend( ) +{ + return extension<KDevAppFrontend>("KDevelop/AppFrontend"); +} + +KDevDebugger * RDBDebugger::RubyDebuggerPart::debugger() +{ + return m_debugger; +} + +#include "debuggerpart.moc" diff --git a/languages/ruby/debugger/debuggerpart.h b/languages/ruby/debugger/debuggerpart.h new file mode 100644 index 00000000..2f35df9a --- /dev/null +++ b/languages/ruby/debugger/debuggerpart.h @@ -0,0 +1,110 @@ +/*************************************************************************** + * Copyright (C) 1999-2001 by John Birch * + * jbb@kdevelop.org * + * Copyright (C) 2001 by Bernd Gehrmann * + * bernd@kdevelop.org * + * * + * Adapted for ruby debugging * + * -------------------------- * + * begin : Mon Nov 1 2004 * + * copyright : (C) 2004 by Richard Dale * + * email : Richard_Dale@tipitina.demon.co.uk * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _DEBUGGERPART_H_ +#define _DEBUGGERPART_H_ + +#include <qguardedptr.h> +#include "kdevplugin.h" +#include "kdevcore.h" + +namespace KParts { class Part; } + +class QLabel; +class QPopupMenu; +class KDialogBase; +class ProcessWidget; +class ProcessLineMaker; +class KDevAppFrontend; +class KDevDebugger; + +namespace RDBDebugger +{ + +class RDBBreakpointWidget; +class FramestackWidget; +class Breakpoint; +class DbgController; +class DbgToolBar; +class VariableWidget; +class RDBOutputWidget; + +class RubyDebuggerPart : public KDevPlugin +{ + Q_OBJECT + +public: + RubyDebuggerPart( QObject *parent, const char *name, const QStringList & ); + ~RubyDebuggerPart(); + virtual void restorePartialProjectSession(const QDomElement* el); + virtual void savePartialProjectSession(QDomElement* el); + +private slots: + void guiClientAdded(KXMLGUIClient*); + void contextMenu(QPopupMenu *popup, const Context *context); + void toggleBreakpoint(); + void contextWatch(); + void contextRubyInspect(); +// void projectOpened(); + void projectClosed(); + void slotActivePartChanged(KParts::Part*); + + void slotRun(); + void slotStopDebugger(); + void slotStop(KDevPlugin* which = 0); + void slotPause(); + void slotRunToCursor(); + void slotStepOver(); + void slotStepInto(); + void slotStepOut(); + + void slotRefreshBPState(const Breakpoint&); + void slotStatus(const QString &msg, int state); + void slotShowStep(const QString &fileName, int lineNum); + void slotGotoSource(const QString &fileName, int lineNum); + +signals: + void rubyInspect(const QString&); + +private: + KDevAppFrontend *appFrontend(); + KDevDebugger *debugger(); + + bool startDebugger(); + void setupController(); + + QGuardedPtr<VariableWidget> variableWidget; + QGuardedPtr<RDBBreakpointWidget> rdbBreakpointWidget; + QGuardedPtr<FramestackWidget> framestackWidget; + QGuardedPtr<RDBOutputWidget> rdbOutputWidget; + DbgController *controller; + QGuardedPtr<QLabel> statusBarIndicator; + QGuardedPtr<DbgToolBar> floatingToolBar; + ProcessLineMaker* procLineMaker; + ProcessLineMaker* rdbLineMaker; + + QString m_contextIdent; + QCString m_drkonqi; + + KDevDebugger *m_debugger; +}; + +} + +#endif diff --git a/languages/ruby/debugger/framestackwidget.cpp b/languages/ruby/debugger/framestackwidget.cpp new file mode 100644 index 00000000..836350b2 --- /dev/null +++ b/languages/ruby/debugger/framestackwidget.cpp @@ -0,0 +1,272 @@ +/*************************************************************************** + begin : Sun Aug 8 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "framestackwidget.h" +#include "rdbparser.h" + +#include <klocale.h> +#include <kdebug.h> + +#include <qheader.h> +#include <qlistbox.h> +#include <qregexp.h> +#include <qstrlist.h> +#include <qfileinfo.h> + +#include <ctype.h> + + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +namespace RDBDebugger +{ + +FramestackWidget::FramestackWidget(QWidget *parent, const char *name, WFlags f) + : QListView(parent, name, f), + viewedThread_(0) +{ + setRootIsDecorated(true); + setSelectionMode(Single); + addColumn(QString()); + setSorting(0); + header()->hide(); + + connect( this, SIGNAL(clicked(QListViewItem*)), + this, SLOT(slotSelectionChanged(QListViewItem*)) ); +} + + +/***************************************************************************/ + +FramestackWidget::~FramestackWidget() +{ +} + + +// ************************************************************************** + +void FramestackWidget::clear() +{ + viewedThread_ = 0; + QListView::clear(); +} + +/***************************************************************************/ + +void FramestackWidget::slotSelectionChanged(QListViewItem * item) +{ + if (item == 0) { + return; + } + + if (item->rtti() == RTTI_THREAD_STACK_ITEM) { + ThreadStackItem * thread = (ThreadStackItem*) item; + slotSelectFrame(1, thread->threadNo()); + } else if (item->rtti() == RTTI_FRAME_STACK_ITEM) { + FrameStackItem * frame = (FrameStackItem*) item; + slotSelectFrame(frame->frameNo(), frame->threadNo()); + } + + return; +} + +/***************************************************************************/ + +void FramestackWidget::slotSelectFrame(int frameNo, int threadNo) +{ + FrameStackItem * frame = findFrame(frameNo, threadNo); + + if (frame != 0) { + setSelected(frame, true); + emit selectFrame(frameNo, threadNo, frame->frameName()); + } else { + emit selectFrame(frameNo, threadNo, QString()); + } +} + +/***************************************************************************/ + +void FramestackWidget::parseRDBThreadList(char *str) +{ + // on receipt of a thread list we must always clear the list. + clear(); + + QRegExp thread_re("(\\+)?\\s*(\\d+)\\s*(#<[^>]+>\\s*[^:]+:\\d+)"); + int pos = thread_re.search(str); + viewedThread_ = 0; + + while (pos != -1) { + ThreadStackItem* thread; + thread = new ThreadStackItem( this, + thread_re.cap(2).toInt(), + QString("%1 %2").arg(thread_re.cap(2)).arg(thread_re.cap(3)) ); + // The thread with a '+' is always the viewedthread + if (thread_re.cap(1) == "+") { + viewedThread_ = thread; + } + + pos += thread_re.matchedLength(); + pos = thread_re.search(str, pos); + } + + return; +} + +/***************************************************************************/ + +void FramestackWidget::parseRDBBacktraceList(char *str) +{ + QRegExp frame_re("#(\\d+) ([^:]+):(\\d+)(:in `([^\\n]+)')?"); + int pos = frame_re.search(str); + + while (pos != -1) { + QString method(frame_re.cap(5)); + if (method == "") { + method = "toplevel"; + } else { + method.append("(...)"); + } + + int frameNo = frame_re.cap(1).toInt(); + QString frameName = QString("T%1#%2 %3").arg(viewedThread_->threadNo()).arg(frame_re.cap(1)).arg(method); + new FrameStackItem(viewedThread_, frameNo, QString(frame_re.cap(0)), frameName); + + // Tell the Variable Tree that this frame is active + emit frameActive(frameNo, viewedThread_->threadNo(), frameName); + + pos += frame_re.matchedLength(); + pos = frame_re.search(str, pos); + } + + if (viewedThread_ != 0) { + viewedThread_->setOpen(true); + } + + return; +} + +// ************************************************************************** + +ThreadStackItem *FramestackWidget::findThread(int threadNo) +{ + QListViewItem *sibling = firstChild(); + while (sibling != 0) { + ThreadStackItem *thread = (ThreadStackItem*) sibling; + if (thread->threadNo() == threadNo) { + return thread; + } + sibling = sibling->nextSibling(); + } + + return 0; +} + +// ************************************************************************** + +FrameStackItem *FramestackWidget::findFrame(int frameNo, int threadNo) +{ + ThreadStackItem * thread = findThread(threadNo); + if (thread == 0) { + kdDebug(9012) << "FramestackWidget::findFrame: no matching thread " << + frameNo << " thread: " << threadNo << endl; + return 0; // no matching thread? + } + + QListViewItem * frameItem = thread->firstChild(); + + while (frameItem != 0) { + if (((FrameStackItem *) frameItem)->frameNo() == frameNo) { + return (FrameStackItem *) frameItem; + } + + frameItem = frameItem->nextSibling(); + } + + return 0; +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +// ************************************************************************** + +FrameStackItem::FrameStackItem(ThreadStackItem *parent, int frameNo, const QString &frameDesc, const QString& frameName) + : QListViewItem(parent), + frameNo_(frameNo), + threadNo_(parent->threadNo()), + frameName_(frameName) +{ + setText(0, frameDesc); + key_.sprintf("%.6d", frameNo_); +} + +// ************************************************************************** + +FrameStackItem::~FrameStackItem() +{ +} + +// ************************************************************************** + +QString FrameStackItem::key(int /*column*/, bool /*ascending*/) const +{ + + return key_; +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +ThreadStackItem::ThreadStackItem(FramestackWidget *parent, int threadNo, const QString &threadDesc) + : QListViewItem(parent), + threadNo_(threadNo) +{ + setText(0, threadDesc); + setExpandable(true); +} + +// ************************************************************************** + +ThreadStackItem::~ThreadStackItem() +{ +} + +// ************************************************************************** + +void ThreadStackItem::setOpen(bool open) +{ + if (open) + ((FramestackWidget*)listView())->slotSelectFrame(1, threadNo()); + + QListViewItem::setOpen(open); +} + +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +#include "framestackwidget.moc" diff --git a/languages/ruby/debugger/framestackwidget.h b/languages/ruby/debugger/framestackwidget.h new file mode 100644 index 00000000..373d0690 --- /dev/null +++ b/languages/ruby/debugger/framestackwidget.h @@ -0,0 +1,115 @@ +/*************************************************************************** + begin : Sun Aug 8 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _FRAMESTACKWIDGET_H_ +#define _FRAMESTACKWIDGET_H_ + +#include <qlistview.h> +#include <qstringlist.h> + +#include "rdbcontroller.h" + +namespace RDBDebugger +{ + +class FramestackWidget; + + +class ThreadStackItem : public QListViewItem +{ +public: + ThreadStackItem(FramestackWidget *parent, int threadNo, const QString &threadDesc); + virtual ~ThreadStackItem(); + + virtual int rtti() const { return RTTI_THREAD_STACK_ITEM; } + + void setOpen(bool open); + int threadNo() { return threadNo_; } + +private: + int threadNo_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class FrameStackItem : public QListViewItem +{ +public: + FrameStackItem(ThreadStackItem * parent, int frameNo, const QString & frameDesc, const QString & frameName); + virtual ~FrameStackItem(); + + virtual int rtti() const { return RTTI_FRAME_STACK_ITEM; } + virtual QString key(int column, bool ascending) const; + + int frameNo() { return frameNo_; } + int threadNo() { return threadNo_; } + QString frameName() { return frameName_; } + +private: + int frameNo_; + int threadNo_; + QString frameName_; + QString key_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +/** + * @author John Birch + */ +class FramestackWidget : public QListView +{ + Q_OBJECT + +public: + FramestackWidget( QWidget *parent=0, const char *name=0, WFlags f=0 ); + virtual ~FramestackWidget(); + + void clear(); + + void parseRDBThreadList(char *str); + void parseRDBBacktraceList(char *str); + + ThreadStackItem *findThread(int threadNo); + FrameStackItem *findFrame(int frameNo, int threadNo); + + int viewedThread() + { return viewedThread_ ? viewedThread_->threadNo() : -1; } + +public slots: + void slotSelectFrame(int frameNo, int threadNo); + void slotSelectionChanged(QListViewItem *thisItem); + +signals: + void selectFrame(int frameNo, int threadNo, const QString& frameName); + void frameActive(int frameNo, int threadNo, const QString& frameName); + +private: + ThreadStackItem *viewedThread_; +}; + +} + +#endif diff --git a/languages/ruby/debugger/hi16-action-breakpoint_add.png b/languages/ruby/debugger/hi16-action-breakpoint_add.png Binary files differnew file mode 100644 index 00000000..1b41040c --- /dev/null +++ b/languages/ruby/debugger/hi16-action-breakpoint_add.png diff --git a/languages/ruby/debugger/hi16-action-breakpoint_delete.png b/languages/ruby/debugger/hi16-action-breakpoint_delete.png Binary files differnew file mode 100644 index 00000000..c35e039f --- /dev/null +++ b/languages/ruby/debugger/hi16-action-breakpoint_delete.png diff --git a/languages/ruby/debugger/hi16-action-breakpoint_delete_all.png b/languages/ruby/debugger/hi16-action-breakpoint_delete_all.png Binary files differnew file mode 100644 index 00000000..c35e039f --- /dev/null +++ b/languages/ruby/debugger/hi16-action-breakpoint_delete_all.png diff --git a/languages/ruby/debugger/hi16-action-breakpoint_edit.png b/languages/ruby/debugger/hi16-action-breakpoint_edit.png Binary files differnew file mode 100644 index 00000000..ec92ced2 --- /dev/null +++ b/languages/ruby/debugger/hi16-action-breakpoint_edit.png diff --git a/languages/ruby/debugger/kdevrbdebugger.desktop b/languages/ruby/debugger/kdevrbdebugger.desktop new file mode 100644 index 00000000..47e59474 --- /dev/null +++ b/languages/ruby/debugger/kdevrbdebugger.desktop @@ -0,0 +1,73 @@ +[Desktop Entry] +Type=Service +Exec=blubb +Comment=This plugin provides a frontend for the source-level debugger for Ruby. +Comment[ca]=Aquest connector proveeix una interfície per a la depuració a nivell de codi per a Ruby. +Comment[da]=Dette plugin sørger for en grænseflade til Ruby kildekode-fejlretteren. +Comment[de]=Dieses Modul stellt eine Oberfläche für den Quelltext-Debugger für Ruby bereit. +Comment[el]=Αυτό το πρόσθετο προσφέρει ένα πρόγραμμα για τον αποσφαλματωτή πηγαίου κώδικα της γλώσσας Ruby. +Comment[es]=Este complemento proporciona un entorno para depurar código de Ruby. +Comment[et]=See plugin pakub Ruby lähtekoodi siluri kasutajaliidest. +Comment[eu]=Plugin honek Ruby-ren iturburu-mailako araztailearen interfaze bat bat eskeintzen du. +Comment[fa]=این وصله، یک پایانه برای اشکالزدای سطح منبع رابی فراهم میکند. +Comment[fr]=Ce module externe fournit une interface pour le débogueur au niveau source pour Ruby. +Comment[gl]=Esta extensión proporciona un frontal para o depurador a nivel de código de Ruby. +Comment[hu]=Ez a bővítőmodul grafikus felületet biztosít a Ruby forrásszintű nyomkövetőjének használatához. +Comment[it]=Questo plugin fornisce un'interfaccia per il debugger a livello sorgente di Ruby. +Comment[ja]=このプラグインは、Ruby のソースレベルデバッガのフロントエンドを提供します。 +Comment[nds]=Dit Moduul stellt en Böversiet för den Ruby-Borntextfehlersöker praat. +Comment[ne]=यो प्लगइनले रूबिका लागि स्रोत-तह डिबगरका लागि फ्रेन्टइन्ड प्रदान गर्दछ । +Comment[nl]=Deze plugin biedt een grafische schil voor de broncode-debugger voor Ruby. +Comment[pl]=Ta wtyczka udostępnia interfejs do debugera poziomu źródłowego dla języka Ruby. +Comment[pt]=Este 'plugin' oferece uma interface o depurador ao nível do código para Ruby. +Comment[pt_BR]=Este plug-in fornece uma interface para o depurador a nível de código para Ruby. +Comment[ru]=Этот модуль предоставляет интерфейс к отладчику исходного кода для Ruby. +Comment[sk]=Modul pre rozhranie debugera pre Ruby. +Comment[sr]=Овај прикључак обезбеђује интерфејс за исправљач на нивоу изворног кода за Ruby. +Comment[sr@Latn]=Ovaj priključak obezbeđuje interfejs za ispravljač na nivou izvornog koda za Ruby. +Comment[sv]=Insticksprogrammet tillhandahåller ett gränssnitt till källkodsavlusaren för Ruby. +Comment[tr]=Bu eklenti, Ruby için kaynak-düzeyi hata ayıklayıcıya bir önuç sağlar. +Comment[zh_CN]=此插件提供了一个 Ruby 的源代码级调试器的前端。 +Comment[zh_TW]=此外掛程式提供 Ruby 除錯器的前端介面。 +Name=KDevRbDebugger +Name[da]=KDevelop Ruby fejlretter +Name[nds]=Ruby-Fehlersöök för KDevelop +Name[pl]=KDevDebugerRb +Name[sk]=KDev Ruby debuger +Name[sv]=KDevelop Ruby-avlusare +Name[zh_TW]=KDevelop Ruby 除錯器 +GenericName=Ruby Debugger Frontend +GenericName[ca]=Interfície per al depurador Ruby +GenericName[da]=Ruby fejlretningsgrænseflade +GenericName[de]=Debugger-Oberfläche für Ruby +GenericName[el]=Πρόγραμμα αποσφαλματωτή Ruby +GenericName[es]=Interfaz para el depurador de Ruby +GenericName[et]=Ruby siluri kasutajaliides +GenericName[eu]=Ruby araztailearen interfazea +GenericName[fa]=پایانۀ اشکالزدای رابی +GenericName[fr]=Interface du débogueur pour Ruby +GenericName[gl]=Frontal do depurador Ruby +GenericName[hu]=Grafikus felület a Ruby nyomkövetőjéhez +GenericName[it]=Interfaccia di debug per Ruby +GenericName[ja]=Ruby デバッガフロントエンド +GenericName[nds]=Böversiet för den Ruby-Fehlersöker +GenericName[ne]=रूबि डिबगर फ्रेन्टइन्ड +GenericName[nl]=Grafische schil voor de Ruby-debugger +GenericName[pl]=Interfejs do debugera dla języka Ruby +GenericName[pt]=Interface de Depuração Ruby +GenericName[pt_BR]=Interface para o Depurador Ruby +GenericName[ru]=Интегрированный отладчик Ruby +GenericName[sk]=Ruby debuger rozhranie +GenericName[sr]=Интерфејс исправљача за Ruby +GenericName[sr@Latn]=Interfejs ispravljača za Ruby +GenericName[sv]=Gränssnitt för Ruby-avlusare +GenericName[tr]=Ruby Hata Ayıklayıcı Önucu +GenericName[zh_CN]=Ruby 调试器前端 +GenericName[zh_TW]=Ruby 除錯器前端介面 +Icon= +ServiceTypes=KDevelop/Plugin +X-KDE-Library=libkdevrbdebugger +X-KDevelop-Version=5 +X-KDevelop-Scope=Project +X-KDevelop-ProgrammingLanguages=Ruby +X-KDevelop-Properties=RubyDebugger diff --git a/languages/ruby/debugger/kdevrbdebugger.rc b/languages/ruby/debugger/kdevrbdebugger.rc new file mode 100644 index 00000000..c7e3b336 --- /dev/null +++ b/languages/ruby/debugger/kdevrbdebugger.rc @@ -0,0 +1,68 @@ +<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd"> +<kpartgui name="KDevRbDebugger" version="10"> +<MenuBar> + <Menu name="debug"> + <text>&Debug</text> + <Action name="debug_run" group="debug"/> + <Action name="debug_stop" group="debug"/> + <Action name="debug_pause" group="debug"/> + <Action name="debug_runtocursor" group="debug"/> + <Separator group="debug"/> + <Action name="debug_stepover" group="debug"/> + <Action name="debug_stepinto" group="debug"/> + <Action name="debug_stepout" group="debug"/> + <Separator group="debug"/> + <Action name="debug_toggle_breakpoint" group="debug"/> + <Action name="debug_disable_breakpoint" group="debug"/> + <Separator group="debug"/> + </Menu> +</MenuBar> +<ToolBar name="debugToolBar"> + <text>Debugger Toolbar</text> + <Action name="debug_run"/> + <Action name="debug_stepover"/> + <Action name="debug_stepinto"/> + <Action name="debug_stepout"/> + <WeakSeparator/> +</ToolBar> +<State name="stopped"> + <enable> + <Action name="debug_run"/> + </enable> + <disable> + <Action name="debug_stop"/> + <Action name="debug_pause"/> + <Action name="debug_runtocursor"/> + <Action name="debug_stepover"/> + <Action name="debug_stepinto"/> + <Action name="debug_stepout"/> + </disable> +</State> +<State name="paused"> + <enable> + <Action name="debug_run"/> + <Action name="debug_stop"/> + <Action name="debug_runtocursor"/> + <Action name="debug_stepover"/> + <Action name="debug_stepinto"/> + <Action name="debug_stepout"/> + </enable> + <disable> + <Action name="debug_pause"/> + </disable> +</State> +<State name="active"> + <enable> + <Action name="debug_pause"/> + <Action name="debug_stop"/> + </enable> + <disable> + <Action name="debug_run"/> + <Action name="debug_runtocursor"/> + <Action name="debug_stepover"/> + <Action name="debug_stepinto"/> + <Action name="debug_stepout"/> + </disable> +</State> +</kpartgui> + diff --git a/languages/ruby/debugger/rdbbreakpointwidget.cpp b/languages/ruby/debugger/rdbbreakpointwidget.cpp new file mode 100644 index 00000000..e31831de --- /dev/null +++ b/languages/ruby/debugger/rdbbreakpointwidget.cpp @@ -0,0 +1,921 @@ +/*************************************************************************** + begin : Tue May 13 2003 + copyright : (C) 2003 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "rdbbreakpointwidget.h" +#include "rdbtable.h" + +#include "breakpoint.h" +#include "domutil.h" + +#include <kdebug.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kpopupmenu.h> +#include <kurl.h> + +#include <qvbuttongroup.h> +#include <qfileinfo.h> +#include <qheader.h> +#include <qtable.h> +#include <qtoolbutton.h> +#include <qtooltip.h> +#include <qwhatsthis.h> +#include <qvbox.h> +#include <qlayout.h> +#include <qregexp.h> + +#include <stdlib.h> +#include <ctype.h> + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +namespace RDBDebugger +{ + +enum Column { + Control = 0, + Enable = 1, + Type = 2, + Status = 3, + Location = 4 +}; + + +#define numCols 5 + +static int m_activeFlag = 0; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class BreakpointTableRow : public QTableItem +{ +public: + + BreakpointTableRow(QTable* table, EditType editType, Breakpoint* bp); + ~BreakpointTableRow(); + + bool match (Breakpoint* bp) const; + void reset (); + void setRow(); + + Breakpoint* breakpoint() { return m_breakpoint; } + +private: + void appendEmptyRow(); + +private: + Breakpoint* m_breakpoint; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +BreakpointTableRow::BreakpointTableRow(QTable* parent, EditType editType, + Breakpoint* bp) : + QTableItem(parent, editType, ""), + m_breakpoint(bp) +{ + appendEmptyRow(); + setRow(); +} + +/***************************************************************************/ + +BreakpointTableRow::~BreakpointTableRow() +{ + delete m_breakpoint; +} + +/***************************************************************************/ + +bool BreakpointTableRow::match(Breakpoint* breakpoint) const +{ + return m_breakpoint->match(breakpoint); +} + +/***************************************************************************/ + +void BreakpointTableRow::reset() +{ + m_breakpoint->reset(); + setRow(); +} + +/***************************************************************************/ + +void BreakpointTableRow::appendEmptyRow() +{ + int row = table()->numRows(); + table()->setNumRows(row+1); + + table()->setItem(row, Control, this); + + QCheckTableItem* cti = new QCheckTableItem( table(), ""); + table()->setItem(row, Enable, cti); +} + +/***************************************************************************/ + +void BreakpointTableRow::setRow() +{ + if ( m_breakpoint ) + { + QTableItem *item = table()->item ( row(), Enable ); + Q_ASSERT(item->rtti() == 2); + ((QCheckTableItem*)item)->setChecked(m_breakpoint->isEnabled()); + + QString status=m_breakpoint->statusDisplay(m_activeFlag); + + table()->setText(row(), Status, status); + + QString displayType = m_breakpoint->displayType(); + table()->setText(row(), Location, m_breakpoint->location()); + + if (m_breakpoint->isTemporary()) + displayType = i18n(" temporary"); + + table()->setText(row(), Type, displayType); + table()->adjustColumn(Type); + table()->adjustColumn(Status); + table()->adjustColumn(Location); + } +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +RDBBreakpointWidget::RDBBreakpointWidget(QWidget *parent, const char *name) : + QHBox(parent, name) +{ + QFrame* toolbar = new QFrame( this ); + QVBoxLayout *l = new QVBoxLayout(toolbar, 0, 0); + + toolbar->setFrameStyle( QFrame::ToolBarPanel | QFrame::Plain ); + toolbar->setLineWidth( 0 ); + + m_add = new QToolButton( toolbar, "add breakpoint" ); + m_add->setPixmap ( SmallIcon ( "breakpoint_add" ) ); + QToolTip::add ( m_add, i18n ( "Add empty breakpoint" ) + I18N_NOOP(" <Alt+A>")); + QWhatsThis::add( m_add, i18n("<b>Add empty breakpoint</b><p>Shows a popup menu that allows you to choose " + "the type of breakpoint, then adds a breakpoint of the selected type to the breakpoints list.")); + + m_delete = new QToolButton( toolbar, "delete breakpoint" ); + m_delete->setPixmap ( SmallIcon ( "breakpoint_delete" ) ); + QToolTip::add ( m_delete, i18n ( "Delete selected breakpoint" ) + I18N_NOOP(" <Delete>") ); + QWhatsThis::add( m_delete, i18n("<b>Delete selected breakpoint</b><p>Deletes the selected breakpoint in the breakpoints list.")); + + m_edit = new QToolButton( toolbar, "edit breakpoint" ); + m_edit->setPixmap ( SmallIcon ( "breakpoint_edit" ) ); + QToolTip::add ( m_edit, i18n ( "Edit selected breakpoint" ) + I18N_NOOP(" <Return>") ); + QWhatsThis::add( m_edit, i18n("<b>Edit selected breakpoint</b><p>Allows to edit location, condition and ignore count properties of the selected breakpoint in the breakpoints list.")); + + m_removeAll = new QToolButton( toolbar, "Delete all breakppoints" ); + m_removeAll->setPixmap ( SmallIcon ( "breakpoint_delete_all" ) ); + QToolTip::add ( m_removeAll, i18n ( "Remove all breakpoints" ) ); + QWhatsThis::add( m_removeAll, i18n("<b>Remove all breakpoints</b><p>Removes all breakpoints in the project.")); + + l->addWidget(m_add); + l->addWidget(m_edit); + l->addWidget(m_delete); + l->addWidget(m_removeAll); + QSpacerItem* spacer = new QSpacerItem( 5, 5, QSizePolicy::Minimum, QSizePolicy::Expanding ); + l->addItem(spacer); + + QPopupMenu *addMenu = new QPopupMenu( this ); + addMenu->insertItem( i18n( "File:line" ), BP_TYPE_FilePos ); + addMenu->insertItem( i18n( "Watchpoint" ), BP_TYPE_Watchpoint ); + addMenu->insertItem( i18n( "Catchpoint" ), BP_TYPE_Catchpoint ); + addMenu->insertItem( i18n( "Method()" ), BP_TYPE_Function ); + m_add->setPopup( addMenu ); + m_add->setPopupDelay(1); + + m_table = new RDBTable(0, numCols, this, name); + m_table->setSelectionMode(QTable::SingleRow); + m_table->setShowGrid (false); + m_table->setLeftMargin(0); + m_table->setFocusStyle(QTable::FollowStyle); + + m_table->hideColumn(Control); + m_table->setColumnReadOnly(Type, true); + m_table->setColumnReadOnly(Status, true); + m_table->setColumnWidth( Enable, 20); + + QHeader *header = m_table->horizontalHeader(); + + header->setLabel( Enable, "" ); + header->setLabel( Type, i18n("Type") ); + header->setLabel( Status, i18n("Status") ); + header->setLabel( Location, i18n("Location") ); + + m_table->show(); + + m_ctxMenu = new QPopupMenu( this ); + m_ctxMenu->insertItem( i18n( "Show" ), BW_ITEM_Show ); + m_ctxMenu->insertItem( i18n( "Edit" ), BW_ITEM_Edit ); + m_ctxMenu->insertItem( i18n( "Disable" ), BW_ITEM_Disable ); + m_ctxMenu->insertItem( i18n( "Delete" ), BW_ITEM_Delete ); + + connect( addMenu, SIGNAL(activated(int)), + this, SLOT(slotAddBlankBreakpoint(int)) ); + connect( m_delete, SIGNAL(clicked()), + this, SLOT(slotRemoveBreakpoint()) ); + connect( m_edit, SIGNAL(clicked()), + this, SLOT(slotEditBreakpoint()) ); + connect( m_removeAll, SIGNAL(clicked()), + this, SLOT(slotRemoveAllBreakpoints()) ); + + connect( m_table, SIGNAL(contextMenuRequested(int, int, const QPoint &)), + this, SLOT(slotContextMenuShow(int, int, const QPoint & )) ); + connect( m_ctxMenu, SIGNAL(activated(int)), + this, SLOT(slotContextMenuSelect(int)) ); + + connect( m_table, SIGNAL(doubleClicked(int, int, int, const QPoint &)), + this, SLOT(slotRowDoubleClicked(int, int, int, const QPoint &))); + + connect( m_table, SIGNAL(valueChanged(int, int)), + this, SLOT(slotNewValue(int, int))); + + connect( m_table, SIGNAL(returnPressed()), + this, SLOT(slotEditBreakpoint())); +// connect( m_table, SIGNAL(f2Pressed()), +// this, SLOT(slotEditBreakpoint())); + connect( m_table, SIGNAL(deletePressed()), + this, SLOT(slotRemoveBreakpoint())); + connect( m_table, SIGNAL(insertPressed()), + this, SLOT(slotAddBreakpoint())); +} + +/***************************************************************************/ + +RDBBreakpointWidget::~RDBBreakpointWidget() +{ + delete m_table; +} + +/***************************************************************************/ + +void RDBBreakpointWidget::reset() +{ + for ( int row = 0; row < m_table->numRows(); row++ ) + { + BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control); + if (btr) + { + btr->reset(); + emit publishBPState(*(btr->breakpoint())); + } + } +} + +/***************************************************************************/ + +// When a file is loaded then we need to tell the editor (display window) +// which lines contain a breakpoint. +void RDBBreakpointWidget::slotRefreshBP(const KURL &filename) +{ + for ( int row = 0; row < m_table->numRows(); row++ ) + { + BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control); + if (btr) + { + FilePosBreakpoint* bp = dynamic_cast<FilePosBreakpoint*>(btr->breakpoint()); + if (bp && (bp->fileName() == filename.path())) + emit refreshBPState(*bp); + } + } +} + +/***************************************************************************/ + +BreakpointTableRow* RDBBreakpointWidget::find(Breakpoint *breakpoint) +{ + // NOTE:- The match doesn't have to be equal. Each type of bp + // must decide on the match criteria. + Q_ASSERT (breakpoint); + + for ( int row = 0; row < m_table->numRows(); row++ ) + { + BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control); + if (btr && btr->match(breakpoint)) + return btr; + } + + return 0; +} + +/***************************************************************************/ + +// The Id is supplied by the debugger +BreakpointTableRow* RDBBreakpointWidget::findId(int dbgId) +{ + for ( int row = 0; row < m_table->numRows(); row++ ) + { + BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control); + if (btr && btr->breakpoint()->dbgId() == dbgId) + return btr; + } + + return 0; +} + +/***************************************************************************/ + +// The key is a unique number supplied by us +BreakpointTableRow* RDBBreakpointWidget::findKey(int BPKey) +{ + for ( int row = 0; row < m_table->numRows(); row++ ) + { + BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control); + if (btr && btr->breakpoint()->key() == BPKey) + return btr; + } + + return 0; +} + +/***************************************************************************/ + +BreakpointTableRow* RDBBreakpointWidget::addBreakpoint(Breakpoint *bp) +{ + BreakpointTableRow* btr = + new BreakpointTableRow( m_table, QTableItem::WhenCurrent, bp ); + emit publishBPState(*bp); + return btr; +} + +/***************************************************************************/ + +void RDBBreakpointWidget::removeBreakpoint(BreakpointTableRow* btr) +{ + if (!btr) + return; + + // Pending but the debugger hasn't started processing this bp so + // we can just remove it. + Breakpoint* bp = btr->breakpoint(); + if (bp->isPending() && !bp->isDbgProcessing()) + { + bp->setActionDie(); + emit publishBPState(*bp); + m_table->removeRow(btr->row()); + } + else + { + bp->setPending(true); + bp->setActionClear(true); + emit publishBPState(*bp); + btr->setRow(); + } +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotToggleBreakpoint(const QString &fileName, int lineNum) +{ + FilePosBreakpoint *fpBP = new FilePosBreakpoint(fileName, lineNum+1); + + BreakpointTableRow* btr = find(fpBP); + if (btr) + { + delete fpBP; + removeBreakpoint(btr); + } + else + addBreakpoint(fpBP); +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotToggleBreakpointEnabled(const QString &fileName, int lineNum) +{ + FilePosBreakpoint *fpBP = new FilePosBreakpoint(fileName, lineNum+1); + + BreakpointTableRow* btr = find(fpBP); + delete fpBP; + if (btr) + { + Breakpoint* bp=btr->breakpoint(); + bp->setEnabled(!bp->isEnabled()); + emit publishBPState(*bp); + } +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotToggleWatchpoint(const QString &varName) +{ + Watchpoint *watchpoint = new Watchpoint(varName, false, true); + BreakpointTableRow* btr = find(watchpoint); + if (btr) + { + removeBreakpoint(btr); + delete watchpoint; + } + else + addBreakpoint(watchpoint); +} + +/***************************************************************************/ + +// The debugger allows us to set pending breakpoints => do it +void RDBBreakpointWidget::slotSetPendingBPs() +{ + for ( int row = 0; row < m_table->numRows(); row++ ) + { + BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control); + + if (btr) + { + Breakpoint* bp = btr->breakpoint(); + if (bp->isPending() && !bp->isDbgProcessing() && bp->isValid()) + emit publishBPState(*bp); + } + } +} + +/***************************************************************************/ + +// The debugger is having trouble with this bp - probably because a library +// was unloaded and invalidated a bp that was previously set in the library +// code. Reset the bp so that we can try again later. +void RDBBreakpointWidget::slotUnableToSetBPNow(int BPid) +{ + if (BPid == -1) + reset(); + else + if (BreakpointTableRow *btr = findId(BPid)) + btr->reset(); +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotParseRDBBrkptList(char *str) +{ + // Another example of a not too uncommon occurance + // No breakpoints. + + // Set the new active flag so that after we have read the + // breakpoint list we can trim the breakpoints that have been + // removed (temporary breakpoints do this) + m_activeFlag++; + QRegExp breakpoint_re("(\\d+) [^:]+:\\d+"); + int pos = 0; + + pos = breakpoint_re.search(str, pos); + while (pos >= 0) { + int id = breakpoint_re.cap(1).toInt(); + + BreakpointTableRow* btr = findId(id); + if (btr) + { + Breakpoint *bp = btr->breakpoint(); + bp->setActive(m_activeFlag, id); + btr->setRow(); + emit publishBPState(*bp); + } + + pos += breakpoint_re.matchedLength(); + pos = breakpoint_re.search(str, pos); + } + + str = strstr(str, "Watchpoints:"); + if (str != 0) { + QRegExp watchpoint_re("(\\d+) [^\n]+\n"); + int pos = 0; + + pos = watchpoint_re.search(str, pos); + while (pos >= 0) { + int id = watchpoint_re.cap(1).toInt(); + + BreakpointTableRow* btr = findId(id); + if (btr) + { + Breakpoint *bp = btr->breakpoint(); + bp->setActive(m_activeFlag, id); + btr->setRow(); + emit publishBPState(*bp); + } + + pos += watchpoint_re.matchedLength(); + pos = watchpoint_re.search(str, pos); + } + } + + // Remove any inactive breakpoints. + for ( int row = m_table->numRows()-1; row >= 0 ; row-- ) + { + BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control); + if (btr) + { + Breakpoint* bp = btr->breakpoint(); + if (!(bp->isActive(m_activeFlag))) + removeBreakpoint(btr); + } + } +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotParseRDBBreakpointSet(char *str, int BPKey) +{ + BreakpointTableRow* btr = findKey(BPKey); + if (!btr) + return; + + Breakpoint *bp = btr->breakpoint(); + bp->setDbgProcessing(false); + + QRegExp breakpoint_re("Set breakpoint (\\d+) at [^:]+:\\d+"); + QRegExp watchpoint_re("Set watchpoint (\\d+)"); + + int id = 0; + if (breakpoint_re.search(str, 0) != -1) { + id = breakpoint_re.cap(1).toInt(); + } else if (watchpoint_re.search(str, 0) != -1) { + id = watchpoint_re.cap(1).toInt(); + } + + if (id > 0) + { + bp->setActive(m_activeFlag, id); + emit publishBPState(*bp); + btr->setRow(); + } +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotAddBlankBreakpoint(int idx) +{ + BreakpointTableRow* btr = 0; + switch (idx) + { + case BP_TYPE_FilePos: + btr = addBreakpoint(new FilePosBreakpoint("", 0)); + break; + + case BP_TYPE_Watchpoint: + btr = addBreakpoint(new Watchpoint("")); + break; + + case BP_TYPE_Catchpoint: + btr = addBreakpoint(new Catchpoint("")); + break; + + case BP_TYPE_Function: + btr = addBreakpoint(new FunctionBreakpoint("")); + break; + + default: + break; + } + + if (btr) + { + QTableSelection ts; + ts.init(btr->row(), 0); + ts.expandTo(btr->row(), numCols ); + m_table->addSelection(ts); + m_table->editCell(btr->row(), Location, false); + } +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotRemoveBreakpoint() +{ + int row = m_table->currentRow(); + if ( row != -1) + { + BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control); + removeBreakpoint(btr); + } +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotRemoveAllBreakpoints() +{ + while (m_table->numRows() > 0) + { + for ( int row = m_table->numRows()-1; row>=0; row-- ) + { + BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control); + removeBreakpoint(btr); + } + } +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotRowDoubleClicked(int row, int col, int btn, const QPoint &) +{ + if ( btn == Qt::LeftButton ) + { +// kdDebug(9012) << "in slotRowSelected row=" << row << endl; + BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control); + if (btr) + { + FilePosBreakpoint* bp = dynamic_cast<FilePosBreakpoint*>(btr->breakpoint()); + if (bp) + emit gotoSourcePosition(bp->fileName(), bp->lineNum()-1); + + // put the focus back on the clicked item if appropriate + if (col == Location) + m_table->editCell(row, col, false); + } + } +} + +void RDBBreakpointWidget::slotContextMenuShow( int row, int /*col*/, const QPoint &mousePos ) +{ + BreakpointTableRow *btr = (BreakpointTableRow *)m_table->item( row, Control ); + + if (btr != NULL) + { + m_ctxMenu->setItemEnabled( BW_ITEM_Show, (btr->breakpoint( )->type( ) == BP_TYPE_FilePos) ); + if (btr->breakpoint( )->isEnabled( )) + { + m_ctxMenu->changeItem( BW_ITEM_Disable, i18n("Disable") ); + } + else + { + m_ctxMenu->changeItem( BW_ITEM_Disable, i18n("Enable") ); + } + + //m_ctxMenu->popup( mapToGlobal( mousePos ) ); + m_ctxMenu->popup( mousePos ); + } +} + +void RDBBreakpointWidget::slotContextMenuSelect( int item ) +{ + int row, col; + BreakpointTableRow *btr; + Breakpoint *bp; + FilePosBreakpoint *fbp; + + row= m_table->currentRow( ); + if (row == -1) + return; + btr = (BreakpointTableRow *)m_table->item( row, Control ); + if (btr == NULL) + return; + bp = btr->breakpoint( ); + if (bp == NULL) + return; + fbp = dynamic_cast<FilePosBreakpoint*>(bp); + + switch( item ) + { + case BW_ITEM_Show: + if (fbp) + emit gotoSourcePosition(fbp->fileName(), fbp->lineNum()-1); + break; + case BW_ITEM_Edit: + col = m_table->currentColumn( ); + if (col == Location) + m_table->editCell(row, col, false); + break; + case BW_ITEM_Disable: + bp->setEnabled( !bp->isEnabled( ) ); + btr->setRow( ); + emit publishBPState( *bp ); + break; + case BW_ITEM_Delete: + slotRemoveBreakpoint( ); + break; + default: + // oops, check it out! this case is not in sync with the + // m_ctxMenu. Check the enum in the header file. + return; + } +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotEditRow(int row, int col, const QPoint &) +{ +// kdDebug(9012) << "in slotEditRow row=" << row << endl; + BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control); + if (btr) + { + if (col == Location) + m_table->editCell(row, col, false); + } +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotNewValue(int row, int col) +{ +// kdDebug(9012) << "in slotNewValue row=" << row << endl; + BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control); + + if (btr) + { + bool changed=false; + Breakpoint* bp = btr->breakpoint(); + switch (col) + { + + case Enable: + { + QCheckTableItem *item = (QCheckTableItem*)m_table->item ( row, Enable ); + if ( item->isChecked() != bp->isEnabled() ) + { + bp->setEnabled(item->isChecked()); + bp->setPending(true); + bp->setActionModify(true); + changed = true; + } + break; + } + + case Location: + { + if (bp->location() != m_table->text(btr->row(), Location)) + { +// kdDebug(9012) << "Old location [" << bp->location() << "]" << endl; +// kdDebug(9012) << "New location [" << m_table->text(btr->row(), Location) << "]" << endl; + bp->setActionDie(); + emit publishBPState(*bp); + bp->setPending(true); + bp->setActionAdd(true); + bp->setLocation(m_table->text(btr->row(), Location)); + changed = true; + } + break; + } + + case Type: + case Status: + default: + break; + } + + if (changed) + { + btr->setRow(); + emit publishBPState(*bp); + } + } +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotEditBreakpoint(const QString &fileName, int lineNum) +{ + FilePosBreakpoint *fpBP = new FilePosBreakpoint(fileName, lineNum+1); + + BreakpointTableRow* btr = find(fpBP); + delete fpBP; + + if (btr) + { + QTableSelection ts; + ts.init(btr->row(), 0); + ts.expandTo(btr->row(), numCols); + m_table->addSelection(ts); + m_table->editCell(btr->row(), Location, false); + } + +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotEditBreakpoint() +{ + m_table->editCell(m_table->currentRow(), Location, false); +} + +/***************************************************************************/ + +void RDBBreakpointWidget::savePartialProjectSession(QDomElement* el) +{ + QDomDocument domDoc = el->ownerDocument(); + if (domDoc.isNull()) + return; + + QDomElement breakpointListEl = domDoc.createElement("breakpointList"); + for ( int row = 0; row < m_table->numRows(); row++ ) + { + BreakpointTableRow* btr = + (BreakpointTableRow *) m_table->item(row, Control); + Breakpoint* bp = btr->breakpoint(); + + QDomElement breakpointEl = + domDoc.createElement("breakpoint"+QString::number(row)); + + breakpointEl.setAttribute("type", bp->type()); + breakpointEl.setAttribute("location", bp->location(false)); + breakpointEl.setAttribute("enabled", bp->isEnabled()); + + breakpointListEl.appendChild(breakpointEl); + } + + if (!breakpointListEl.isNull()) + el->appendChild(breakpointListEl); +} + +/***************************************************************************/ + +void RDBBreakpointWidget::restorePartialProjectSession(const QDomElement* el) +{ + QDomElement breakpointListEl = el->namedItem("breakpointList").toElement(); + if (!breakpointListEl.isNull()) + { + QDomElement breakpointEl; + for (breakpointEl = breakpointListEl.firstChild().toElement(); + !breakpointEl.isNull(); + breakpointEl = breakpointEl.nextSibling().toElement()) + { + Breakpoint* bp=0; + BP_TYPES type = (BP_TYPES) breakpointEl.attribute( "type", "0").toInt(); + switch (type) + { + case BP_TYPE_FilePos: + { + bp = new FilePosBreakpoint("", 0); + break; + } + case BP_TYPE_Watchpoint: + { + bp = new Watchpoint(""); + break; + } + case BP_TYPE_Catchpoint: + { + bp = new Catchpoint(""); + break; + } + case BP_TYPE_Function: + { + bp = new FunctionBreakpoint(""); + break; + } + default: + break; + } + + // Common settings for any type of breakpoint + if (bp) + { + bp->setLocation(breakpointEl.attribute( "location", "")); + bp->setEnabled(breakpointEl.attribute( "enabled", "1").toInt()); + + // Add the bp if we don't already have it. + if (!find(bp)) + addBreakpoint(bp); + else + delete bp; + } + } + } +} + +/***************************************************************************/ + +void RDBBreakpointWidget::slotAddBreakpoint( ) +{ + if (m_add->popup()) + { + m_add->popup()->popup(mapToGlobal(this->geometry().topLeft())); + } +} + +/***************************************************************************/ + +void RDBBreakpointWidget::focusInEvent( QFocusEvent */* e*/ ) +{ + m_table->setFocus(); +} + +} + + +#include "rdbbreakpointwidget.moc" diff --git a/languages/ruby/debugger/rdbbreakpointwidget.h b/languages/ruby/debugger/rdbbreakpointwidget.h new file mode 100644 index 00000000..65e6e15b --- /dev/null +++ b/languages/ruby/debugger/rdbbreakpointwidget.h @@ -0,0 +1,120 @@ +/*************************************************************************** + begin : Tue May 13 2003 + copyright : (C) 2003 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _RDBBreakpointWidget_H_ +#define _RDBBreakpointWidget_H_ + +#include <qhbox.h> +#include <qpopupmenu.h> + +class QDomElement; +class QToolButton; +class KURL; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +namespace RDBDebugger +{ +class Breakpoint; +class BreakpointTableRow; +class RDBTable; + +class RDBBreakpointWidget : public QHBox +{ + Q_OBJECT + +public: + RDBBreakpointWidget( QWidget* parent=0, const char* name=0 ); + virtual ~RDBBreakpointWidget(); + + void reset(); + + void savePartialProjectSession(QDomElement* el); + void restorePartialProjectSession(const QDomElement* el); + + +public slots: + // Connected to from the editor widget: + void slotToggleBreakpoint(const QString &filename, int lineNum); + void slotToggleBreakpointEnabled(const QString &fileName, int lineNum); + + // Connected to from the variable widget: + void slotToggleWatchpoint(const QString &varName); + + // Connected to from the dbgcontroller: + void slotSetPendingBPs(); + void slotUnableToSetBPNow(int BPNo); + void slotParseRDBBrkptList(char *str); + void slotParseRDBBreakpointSet(char *str, int BPKey); + + void slotRefreshBP(const KURL &filename); + +protected: + enum BW_ITEMS { BW_ITEM_Show, BW_ITEM_Edit, BW_ITEM_Disable, BW_ITEM_Delete }; + virtual void focusInEvent(QFocusEvent *e); + +private slots: + void slotRemoveBreakpoint(); + void slotRemoveAllBreakpoints(); + void slotEditBreakpoint(const QString &fileName, int lineNum); + void slotEditBreakpoint(); + void slotAddBreakpoint(); + void slotAddBlankBreakpoint(int idx); + void slotRowDoubleClicked(int row, int col, int button, const QPoint & mousePos); + void slotContextMenuShow( int row, int col, const QPoint &mousePos ); + void slotContextMenuSelect( int item ); + void slotEditRow(int row, int col, const QPoint & mousePos); + void slotNewValue(int row, int col); + +signals: + void publishBPState(const Breakpoint& brkpt); + void refreshBPState(const Breakpoint& brkpt); + void gotoSourcePosition(const QString &fileName, int lineNum); + void clearAllBreakpoints(); + +private: + BreakpointTableRow* find(Breakpoint *bp); + BreakpointTableRow* findId(int id); + BreakpointTableRow* findKey(int BPKey); + + void setActive(); + BreakpointTableRow* addBreakpoint(Breakpoint *bp); + void removeBreakpoint(BreakpointTableRow* btr); + +private: + RDBTable* m_table; + + QToolButton* m_add; + QToolButton* m_delete; + QToolButton* m_edit; + QToolButton* m_removeAll; + QPopupMenu* m_ctxMenu; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +} + +#endif diff --git a/languages/ruby/debugger/rdbcommand.cpp b/languages/ruby/debugger/rdbcommand.cpp new file mode 100644 index 00000000..98dc3a80 --- /dev/null +++ b/languages/ruby/debugger/rdbcommand.cpp @@ -0,0 +1,81 @@ +/*************************************************************************** + begin : Sun Aug 8 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "rdbcommand.h" +#include "breakpoint.h" +#include "variablewidget.h" + +namespace RDBDebugger +{ + + +RDBCommand::RDBCommand(const QCString &setCommand, bool isRunCmd, bool isInfoCmd) + : DbgCommand(setCommand, isRunCmd, isInfoCmd) +{ +// if (prompt_) { +// cmdBuffer_ = QCString().sprintf("set prompt %c%c\n", BLOCK_START, prompt_) + +// command_ + +// idlePrompt_; +// } +} + +/***************************************************************************/ + +RDBCommand::~RDBCommand() +{ +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +RDBItemCommand::RDBItemCommand( VarItem *item, + const QCString &command, + bool isRunCmd) + : RDBCommand(command, isRunCmd, true), + item_(item) +{ +} + +/***************************************************************************/ + +RDBItemCommand::~RDBItemCommand() +{ +} + + +RDBSetBreakpointCommand::RDBSetBreakpointCommand(const QCString &command, int key) + : RDBCommand(command, false, false), + key_(key) +{ +} + +/***************************************************************************/ + +RDBSetBreakpointCommand::~RDBSetBreakpointCommand() +{ +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +} diff --git a/languages/ruby/debugger/rdbcommand.h b/languages/ruby/debugger/rdbcommand.h new file mode 100644 index 00000000..663c8119 --- /dev/null +++ b/languages/ruby/debugger/rdbcommand.h @@ -0,0 +1,99 @@ +/*************************************************************************** + begin : Sun Aug 8 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _RDBCOMMAND_H_ +#define _RDBCOMMAND_H_ + +#include "dbgcommand.h" + +namespace RDBDebugger +{ + +class Breakpoint; +class VarItem; + +// sigh - namespace's don't work on some of the older compilers +enum RDBCmd +{ + CONSTANTS = 'C', + CVARS = 'V', + IVARS = 'I', + LOCALS = 'L' +}; + +#define RUNCMD (true) +#define NOTRUNCMD (false) +#define INFOCMD (true) +#define NOTINFOCMD (false) + +/** + * @author John Birch + */ + +class RDBCommand : public DbgCommand +{ +public: + RDBCommand(const QCString& command, bool isRunCmd=false, bool isInfoCmd=true); + virtual ~RDBCommand(); + +private: + static QCString idlePrompt_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ +class RDBItemCommand : public RDBCommand +{ +public: + RDBItemCommand(VarItem *item, const QCString &command, + bool isRunCmd=false); + virtual ~RDBItemCommand(); + + VarItem *getItem() { return item_; } + +private: + VarItem *item_; +}; + + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ +class RDBSetBreakpointCommand : public RDBCommand +{ +public: + RDBSetBreakpointCommand(const QCString& setCommand, int key); + virtual ~RDBSetBreakpointCommand(); + + int getKey() const { return key_; } + +private: + int key_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +} + +#endif diff --git a/languages/ruby/debugger/rdbcontroller.cpp b/languages/ruby/debugger/rdbcontroller.cpp new file mode 100644 index 00000000..160754a0 --- /dev/null +++ b/languages/ruby/debugger/rdbcontroller.cpp @@ -0,0 +1,1414 @@ +// ************************************************************************* +// rdbcontroller.cpp - description +// ------------------- +// begin : Sun Aug 8 1999 +// copyright : (C) 1999 by John Birch +// email : jbb@kdevelop.org +// +// Adapted for ruby debugging +// -------------------------- +// begin : Mon Nov 1 2004 +// copyright : (C) 2004 by Richard Dale +// email : Richard_Dale@tipitina.demon.co.uk +// ************************************************************************** +// +// ************************************************************************** +// * * +// * This program is free software; you can redistribute it and/or modify * +// * it under the terms of the GNU General Public License as published by * +// * the Free Software Foundation; either version 2 of the License, or * +// * (at your option) any later version. * +// * * +// ************************************************************************** + +#include "rdbcontroller.h" + +#include <sys/types.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <errno.h> + +#include "breakpoint.h" +#include "framestackwidget.h" +#include "rdbcommand.h" +#include "stty.h" +#include "variablewidget.h" +#include "domutil.h" +#include "settings.h" + +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kglobal.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kprocess.h> + +#include <qdatetime.h> +#include <qfileinfo.h> +#include <qregexp.h> +#include <qstring.h> +#include <qtextstream.h> + +#include <iostream> +#include <ctype.h> +#include <stdlib.h> +using namespace std; + +// ************************************************************************** +// +// Does all the communication between rdb and the kdevelop's debugger code. +// Significatant classes being used here are +// +// RDBParser - parses the "variable" data using the vartree and varitems +// VarTree - where the variable data will end up +// FrameStack - tracks the program frames and allows the user to switch between +// and therefore view the calling funtions and their data +// Breakpoint - Where and what to do with breakpoints. +// STTY - the tty that the _application_ will run on. +// +// Significant variables +// state_ - be very careful setting this. The controller is totally +// dependent on this reflecting the correct state. For instance, +// if the app is busy but we don't think so, then we lose control +// of the app. The only way to get out of these situations is to +// delete (stop) the controller. +// currentFrame_ +// - Holds the frame number where and locals/variable information will +// go to +// +// +// ************************************************************************** + +namespace RDBDebugger +{ + +// This is here so we can check for startup /shutdown problems +int debug_controllerExists = false; + +// At the moment a Unix domain socket is used. It might be better to +// change to tcp/ip and listen on a port instead +QCString RDBController::unixSocketPath_; + + +RDBController::RDBController(VariableTree *varTree, FramestackWidget *frameStack, QDomDocument &projectDom) + : DbgController(), + frameStack_(frameStack), + varTree_(varTree), + currentFrame_(1), + viewedThread_(-1), + stdoutOutputLen_(0), + stdoutOutput_(new char[4096]), + holdingZone_(), + rdbOutputLen_(0), + rdbOutput_(new char[49152]), + socketNotifier_(0), + currentCmd_(0), + currentPrompt_("(rdb:1) "), + tty_(0), + state_(s_dbgNotStarted|s_appNotStarted|s_silent), + programHasExited_(false), + dom(projectDom), + config_forceBPSet_(true), + config_dbgTerminal_(false) +{ + struct sockaddr_un sockaddr; + unixSocketPath_.sprintf("/tmp/.rubydebugger%d", getpid()); + QFileInfo unixSocket(unixSocketPath_); + + stdoutSizeofBuf_ = sizeof(stdoutOutput_); + rdbSizeofBuf_ = sizeof(rdbOutput_); + + if (unixSocket.exists()) { + unlink(unixSocketPath_); + } + + masterSocket_ = socket(AF_UNIX, SOCK_STREAM, 0); + sockaddr.sun_family = AF_UNIX; + strcpy(sockaddr.sun_path, unixSocketPath_); + bind(masterSocket_, (const struct sockaddr*) &sockaddr, sizeof(sockaddr)); + listen(masterSocket_, 1); + acceptNotifier_ = new QSocketNotifier(masterSocket_, QSocketNotifier::Read, this); + QObject::connect( acceptNotifier_, SIGNAL(activated(int)), + this, SLOT(slotAcceptConnection(int)) ); + + configure(); + cmdList_.setAutoDelete(true); + + Q_ASSERT(! debug_controllerExists); + debug_controllerExists = true; +} + +// ************************************************************************** + +// Deleting the controller involves shutting down rdb nicely. +// When were attached to a process, we must first detach so that the process +// can continue running as it was before being attached. rdb is quite slow to +// detach from a process, so we must process events within here to get a "clean" +// shutdown. +RDBController::~RDBController() +{ + delete[] stdoutOutput_; + delete[] rdbOutput_; + debug_controllerExists = false; + + QFileInfo unixSocket(unixSocketPath_); + if (unixSocket.exists()) { + unlink(unixSocketPath_); + } +} + +// ************************************************************************** + +void RDBController::configure() +{ +} + +// ************************************************************************** + +// Fairly obvious that we'll add whatever command you give me to a queue +// If you tell me to, I'll put it at the head of the queue so it'll run ASAP +// Not quite so obvious though is that if we are going to run again. then any +// information requests become redundent and must be removed. +// We also try and run whatever command happens to be at the head of +// the queue. +void RDBController::queueCmd(DbgCommand *cmd, bool executeNext) +{ + // We remove any info command or _run_ command if we are about to + // add a run command. + if (cmd->isARunCmd()) + removeInfoRequests(); + + if (executeNext) + cmdList_.insert(0, cmd); + else + cmdList_.append (cmd); +} + +// ************************************************************************** + +// If the appliction can accept a command and we've got one waiting +// then send it. +// Commands can be just request for data (or change rdbs state in someway) +// or they can be "run" commands. If a command is sent to rdb our internal +// state will get updated. +void RDBController::executeCmd() +{ + if (stateIsOn(s_dbgNotStarted|s_waitForWrite|s_appBusy|s_shuttingDown) || !dbgProcess_) + return; + + if (currentCmd_ == 0) { + if (cmdList_.isEmpty()) + return; + + currentCmd_ = cmdList_.take(0); + } + + if (!currentCmd_->moreToSend()) { + delete currentCmd_; + if (cmdList_.isEmpty()) { + currentCmd_ = 0; + return; + } + + currentCmd_ = cmdList_.take(0); + } + + char * ptr = currentCmd_->cmdToSend().data(); + int bytesToWrite = currentCmd_->cmdLength(); + int bytesWritten = 0; + + while (bytesToWrite > 0) { + bytesWritten = write(socket_, ptr, bytesToWrite); + bytesToWrite -= bytesWritten; + ptr += bytesWritten; + } + + if (currentCmd_->isARunCmd()) { + setStateOn(s_appBusy); + kdDebug(9012) << "App is busy" << endl; + setStateOff(s_appNotStarted|s_programExited|s_silent); + } + + QString prettyCmd = currentCmd_->cmdToSend(); + prettyCmd = currentPrompt_ + prettyCmd; + emit rdbStdout( prettyCmd.latin1() ); + + if (!stateIsOn(s_silent)) + emit dbgStatus("", state_); +} + +// ************************************************************************** + +void RDBController::destroyCmds() +{ + if (currentCmd_) + { + delete currentCmd_; + currentCmd_ = 0; + } + + while (!cmdList_.isEmpty()) + delete cmdList_.take(0); +} + +// ********************************************************************** + +void RDBController::removeInfoRequests() +{ + int i = cmdList_.count(); + while (i) + { + i--; + DbgCommand *cmd = cmdList_.at(i); + if (cmd->isAnInfoCmd() || cmd->isARunCmd()) + delete cmdList_.take(i); + } +} + +// ********************************************************************** + +// Pausing an app removes any pending run commands so that the app doesn't +// start again. If we want to be silent then we remove any pending info +// commands as well. +void RDBController::pauseApp() +{ + int i = cmdList_.count(); + while (i) + { + i--; + DbgCommand *cmd = cmdList_.at(i); + if ((stateIsOn(s_silent) && cmd->isAnInfoCmd()) || cmd->isARunCmd()) + delete cmdList_.take(i); + } + + if (dbgProcess_ && stateIsOn(s_appBusy)) + dbgProcess_->kill(SIGINT); +} + +// ********************************************************************** + +// Whenever the program pauses we need to refresh the data visible to +// the user. The reason we've stopped may be passed in to be emitted. +void RDBController::actOnProgramPause(const QString &msg) +{ + // We're only stopping if we were running, of course. + if (stateIsOn(s_appBusy)) + { + kdDebug(9012) << "App is paused" << endl; + setStateOff(s_appBusy); + if (stateIsOn(s_silent)) + return; + + emit dbgStatus (msg, state_); + + // We're always at frame one when the program stops + // and we must reset the active flag + currentFrame_ = 1; + varTree_->nextActivationId(); + setStateOn(s_fetchLocals); + + queueCmd(new RDBCommand("where", NOTRUNCMD, INFOCMD), true); + queueCmd(new RDBCommand("thread list", NOTRUNCMD, INFOCMD), true); + + if (stateIsOn(s_fetchGlobals)) { + queueCmd(new RDBCommand("var global", NOTRUNCMD, INFOCMD)); + } + + emit acceptPendingBPs(); + } +} + +// ************************************************************************** + +// There is no app anymore. This can be caused by program exiting +// an invalid program specified or ... +// rdb is still running though, but only the run command (may) make sense +// all other commands are disabled. +void RDBController::programNoApp(const QString &msg, bool msgBox) +{ + state_ = (s_appNotStarted|s_programExited|(state_&(s_shuttingDown))); + destroyCmds(); + + // We're always at frame one when the program stops + // and we must reset the active flag + viewedThread_ = -1; + currentFrame_ = 1; + varTree_->nextActivationId(); + + // Now wipe the tree out + varTree_->viewport()->setUpdatesEnabled(false); + varTree_->prune(); + varTree_->viewport()->setUpdatesEnabled(true); + varTree_->repaint(); + + frameStack_->clear(); + + if (msgBox) + KMessageBox::error(0, i18n("rdb message:\n")+msg); + + emit dbgStatus (msg, state_); +} + +// ************************************************************************** + +// The program location falls out of rdb. We treat +// it as a wrapped command. +// The data gets parsed here and emitted in its component parts. +void RDBController::parseProgramLocation(char *buf) +{ + QString buffer(buf); + QString line; + QTextStream input(&buffer, IO_ReadOnly); + QString sourceFile; + int sourceLine = 0; + + // "1: a = 1" + QRegExp display_re("^(\\d+):\\s(.*)$"); + + // "/opt/qt/src/widgets/qlistview.rb:1558:puts 'hello world'" + QRegExp sourcepos_re("^([^:]+):(\\d+):"); + + line = input.readLine(); + while (! line.isNull()) { + if (sourcepos_re.search(line, 0) >= 0) { + sourceFile = sourcepos_re.cap(1); + sourceLine = sourcepos_re.cap(2).toInt(); + } else if (display_re.search(line, 0) >= 0) { + varTree_->watchRoot()->updateWatchExpression(display_re.cap(1).toInt(), display_re.cap(2)); + } + + line = input.readLine(); + } + + if ( !sourceFile.isNull() + && ( traceIntoRuby_ + || ( !sourceFile.endsWith("/qtruby.rb") + && !sourceFile.endsWith("/korundum.rb") ) ) + && !sourceFile.endsWith("/debuggee.rb") ) + { + actOnProgramPause(QString()); + emit showStepInSource(sourceFile, sourceLine, ""); + return; + } + + if (stateIsOn(s_appBusy)) + actOnProgramPause(i18n("No source: %1").arg(sourceFile)); + else + emit dbgStatus (i18n("No source: %1").arg(sourceFile), state_); +} + +// ************************************************************************** + +// parsing the backtrace list will cause the vartree to be refreshed +void RDBController::parseBacktraceList(char *buf) +{ + frameStack_->parseRDBBacktraceList(buf); +} + +// ************************************************************************** + +void RDBController::parseThreadList(char *buf) +{ + frameStack_->parseRDBThreadList(buf); + viewedThread_ = frameStack_->viewedThread(); + varTree_->setCurrentThread(viewedThread_); +} + +// ************************************************************************** + +void RDBController::parseSwitchThread(char *buf) +{ + // Look for the thread number + // 2 #<Thread:0x30091998 sleep> /home/duke/play/testit/trykorundum/src/bar.rb:13 + QRegExp thread_re("(\\d+)"); + if (thread_re.search(buf) != -1) { + viewedThread_ = thread_re.cap(1).toInt(); + currentFrame_ = 1; + } +} + +// ************************************************************************** + +// After an 'up nnn' or 'down nnn' command, get the new source file and line no. +void RDBController::parseFrameMove(char *buf) +{ + QString sourceFile; + int sourceLine = 0; + + if (stateIsOn(s_fetchLocals)) { + return; + } + + // "#2 /home/duke/play/testit/trykorundum/src/main.rb:11" + QRegExp sourcepos_re("#\\d+\\s([^:]+):(\\d+)"); + if (sourcepos_re.search(buf) != -1) { + sourceFile = sourcepos_re.cap(1); + sourceLine = sourcepos_re.cap(2).toInt(); + + if ( !sourceFile.isNull() + && ( traceIntoRuby_ + || ( !sourceFile.endsWith("/qtruby.rb") + && !sourceFile.endsWith("/korundum.rb") ) ) + && !sourceFile.endsWith("/debuggee.rb") ) + { + emit showStepInSource(sourceFile, sourceLine, ""); + return; + } + } + + emit dbgStatus(i18n("No source: %1").arg(sourceFile), state_); +} + +// ************************************************************************** + +// When a breakpoint has been set, rdb responds with some data about the +// new breakpoint. We just inform the breakpoint system about this. +void RDBController::parseBreakpointSet(char *buf) +{ + if (RDBSetBreakpointCommand *BPCmd = dynamic_cast<RDBSetBreakpointCommand*>(currentCmd_)) + { + // ... except in this case :-) A -1 key tells us that this is + // a special internal breakpoint, and we shouldn't do anything + // with it. Currently there are _no_ internal breakpoints. + if (BPCmd->getKey() != -1) { + emit rawRDBBreakpointSet(buf, BPCmd->getKey()); + } + } +} + +// ************************************************************************** + +// Extra data needed by an item was requested. Here's the result. +// If it's an ordinary 'p ' command then just echo the result on +// the RDB console and don't bother parsing. +void RDBController::parseRequestedData(char *buf) +{ + if (RDBItemCommand *rdbItemCommand = dynamic_cast<RDBItemCommand*> (currentCmd_)) + { + // Fish out the item from the command and let it deal with the data + VarItem *item = rdbItemCommand->getItem(); + varTree_->viewport()->setUpdatesEnabled(false); + item->expandValue(buf); + varTree_->viewport()->setUpdatesEnabled(true); + varTree_->repaint(); + } +} + + +// ************************************************************************** + +// Select a different frame to view. We need to get and (maybe) display +// where we are in the program source. +void RDBController::parseFrameSelected(char *buf) +{ + if (!stateIsOn(s_silent)) { + emit showStepInSource("", -1, ""); + emit dbgStatus (i18n("No source: %1").arg(QString(buf)), state_); + } +} + +// ************************************************************************** + +// Sets the id of the display in the VarTree and a current value. +void RDBController::parseDisplay(char *buf, char * expr) +{ + varTree_->viewport()->setUpdatesEnabled(false); + varTree_->watchRoot()->setWatchExpression(buf, expr); + varTree_->viewport()->setUpdatesEnabled(true); + varTree_->repaint(); +} + +// ************************************************************************** + +// Updates the watch expressions with current values +void RDBController::parseUpdateDisplay(char *buf) +{ + varTree_->viewport()->setUpdatesEnabled(false); + + QRegExp display_re("(\\d+):\\s([^\n]*)\n"); + + int pos = display_re.search(buf); + while (pos != -1) { + varTree_->watchRoot()->updateWatchExpression(display_re.cap(1).toInt(), display_re.cap(2)); + + pos += display_re.matchedLength(); + pos = display_re.search(buf, pos); + } + + varTree_->viewport()->setUpdatesEnabled(true); + varTree_->repaint(); +} + +// ************************************************************************** + +// This is called on program stop to process the globals. +void RDBController::parseGlobals(char *buf) +{ + varTree_->viewport()->setUpdatesEnabled(false); + varTree_->globalRoot()->setGlobals(buf); + varTree_->viewport()->setUpdatesEnabled(true); + varTree_->repaint(); +} + +// ************************************************************************** + +// This is called on program stop to process the locals. +// Once the locals have been processed we prune the tree of items that are +// inactive. +void RDBController::parseLocals(char type, char *buf) +{ + varTree_->viewport()->setUpdatesEnabled(false); + + // The locals are always attached to the currentFrame + VarFrameRoot *frame = varTree_->findFrame(currentFrame_, viewedThread_); + if (!frame) + { + frame = new VarFrameRoot(varTree_, currentFrame_, viewedThread_); + frame->setFrameName( + frameStack_->findFrame(currentFrame_, viewedThread_)->frameName()); + } + + Q_ASSERT(frame); + + if (type == (char) CONSTANTS) { + frame->addLocals(buf); + } else if (type == (char) CVARS) { + frame->addLocals(buf); + } else if (type == (char) IVARS) { + frame->addLocals(buf); + } else { + frame->addLocals(buf); + frame->setLocals(); + } + + varTree_->viewport()->setUpdatesEnabled(true); + varTree_->repaint(); +} + + + +// ************************************************************************** + +void RDBController::parse(char *buf) +{ + if (currentCmd_ == 0) { + return; + } + + if (currentCmd_->isARunCmd()) { + parseProgramLocation(buf); + } else if (currentCmd_->rawDbgCommand() == "break") { + emit rawRDBBreakpointList(buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "break ", strlen("break ")) == 0) { + parseBreakpointSet(buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "watch ", strlen("watch ")) == 0) { + parseBreakpointSet(buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "display ", strlen("display ")) == 0) { + parseDisplay(buf, currentCmd_->rawDbgCommand().data() + strlen("display ")); + } else if (currentCmd_->rawDbgCommand() == "display") { + parseUpdateDisplay(buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "undisplay ", strlen("undisplay ")) == 0) { + ; + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "method instance ", strlen("method instance ")) == 0) { + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "method ", strlen("method ")) == 0) { + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "pp ", strlen("pp ")) == 0) { + parseRequestedData(buf); + } else if (currentCmd_->rawDbgCommand() == "thread list") { + parseThreadList(buf); + } else if ( qstrncmp(currentCmd_->rawDbgCommand(), "up ", strlen("up ")) == 0 + || qstrncmp(currentCmd_->rawDbgCommand(), "down ", strlen("down ")) == 0 ) + { + parseFrameMove(buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "thread switch ", strlen("thread switch ")) == 0) { + parseSwitchThread(buf); + } else if (currentCmd_->rawDbgCommand() == "thread current") { + parseThreadList(buf); + } else if (currentCmd_->rawDbgCommand() == "where") { + parseBacktraceList(buf); + } else if (currentCmd_->rawDbgCommand() == "var global") { + parseGlobals(buf); + } else if (currentCmd_->rawDbgCommand() == "var local") { + parseLocals(LOCALS, buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "var instance ", strlen("var instance ")) == 0) { + parseLocals(IVARS, buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "var class ", strlen("var class ")) == 0) { + parseLocals(CVARS, buf); + } else if (qstrncmp(currentCmd_->rawDbgCommand(), "var const ", strlen("var const ")) == 0) { + parseLocals(CONSTANTS, buf); + } + + return; +} + +// ************************************************************************** + +void RDBController::setBreakpoint(const QCString &BPSetCmd, int key) +{ + queueCmd(new RDBSetBreakpointCommand(BPSetCmd, key)); +} + +// ************************************************************************** + +void RDBController::clearBreakpoint(const QCString &BPClearCmd) +{ + queueCmd(new RDBCommand(BPClearCmd, NOTRUNCMD, NOTINFOCMD)); + // Note: this is NOT an info command, because rdb doesn't explictly tell + // us that the breakpoint has been deleted, so if we don't have it the + // BP list doesn't get updated. + queueCmd(new RDBCommand("break", NOTRUNCMD, NOTINFOCMD)); +} + +// ************************************************************************** + +void RDBController::modifyBreakpoint( const Breakpoint& BP ) +{ + Q_ASSERT(BP.isActionModify()); + if (BP.dbgId() > 0) + { + if (BP.changedEnable()) + queueCmd(new RDBCommand(QCString().sprintf("%s %d", + BP.isEnabled() ? "enable" : "disable", + BP.dbgId()), NOTRUNCMD, NOTINFOCMD)); + + // BP.setDbgProcessing(true); + // Note: this is NOT an info command, because rdb doesn't explictly tell + // us that the breakpoint has been deleted, so if we don't have it the + // BP list doesn't get updated. + queueCmd(new RDBCommand("break", NOTRUNCMD, NOTINFOCMD)); + } +} + +// ************************************************************************** +// SLOTS +// ***** +// For most of these slots data can only be sent to rdb when it +// isn't busy and it is running. + +// ************************************************************************** + +void RDBController::slotStart(const QString& ruby_interpreter, const QString& character_coding, const QString& run_directory, const QString& debuggee_path, const QString &application, const QString& run_arguments, bool show_constants, bool trace_into_ruby) +{ + Q_ASSERT (!dbgProcess_ && !tty_); + +// tty_ = new STTY(config_dbgTerminal_, "konsole"); + tty_ = new STTY(config_dbgTerminal_, Settings::terminalEmulatorName( *kapp->config() )); + if (!config_dbgTerminal_) + { + connect( tty_, SIGNAL(OutOutput(const char*)), SIGNAL(ttyStdout(const char*)) ); + connect( tty_, SIGNAL(ErrOutput(const char*)), SIGNAL(ttyStderr(const char*)) ); + } + + QString tty(tty_->getSlave()); + if (tty.isEmpty()) + { + KMessageBox::error(0, i18n("The ruby debugger cannot use the tty* or pty* devices.\n" + "Check the settings on /dev/tty* and /dev/pty*\n" + "As root you may need to \"chmod ug+rw\" tty* and pty* devices " + "and/or add the user to the tty group using " + "\"usermod -G tty username\".")); + + delete tty_; + tty_ = 0; + return; + } + + dbgProcess_ = new KProcess; + + connect( dbgProcess_, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotDbgStdout(KProcess *, char *, int)) ); + + connect( dbgProcess_, SIGNAL(receivedStderr(KProcess *, char *, int)), + this, SLOT(slotDbgStderr(KProcess *, char *, int)) ); + + connect( dbgProcess_, SIGNAL(wroteStdin(KProcess *)), + this, SLOT(slotDbgWroteStdin(KProcess *)) ); + + connect( dbgProcess_, SIGNAL(processExited(KProcess*)), + this, SLOT(slotDbgProcessExited(KProcess*)) ); + + rubyInterpreter_ = ruby_interpreter; + characterCoding_ = character_coding; + runDirectory_ = run_directory; + debuggeePath_ = debuggee_path; + application_ = application; + runArguments_ = run_arguments; + showConstants_ = show_constants; + traceIntoRuby_ = trace_into_ruby; + + *dbgProcess_ << ruby_interpreter; + *dbgProcess_ << character_coding; + *dbgProcess_ << "-C" << QString(QFile::encodeName( run_directory )); + *dbgProcess_ << "-r" << debuggee_path; + *dbgProcess_ << application; + + if (!run_arguments.isNull() && !run_arguments.isEmpty()) { + *dbgProcess_ << run_arguments; + } + + emit rdbStdout(QString( ruby_interpreter + " " + character_coding + + " -C " + QString(QFile::encodeName( run_directory )) + + " -r " + debuggee_path + " " + + application + " " + run_arguments ).latin1() ); + + if (!dbgProcess_->start( KProcess::NotifyOnExit, + KProcess::Communication(KProcess::All)) ) + { + kdDebug(9012) << "Couldn't start ruby debugger" << endl; + } + + // Initialise rdb. At this stage rdb is sitting wondering what to do, + // and to whom. Organise a few things, then set up the tty for the application, + // and the application itself + + // Now the ruby debugger has been started and the application has been loaded, + // BUT the app hasn't been started yet! A run command is about to be issued + // by whoever is controlling us. + + if (!dbgProcess_->writeStdin(QString("%1\n").arg(unixSocketPath_).latin1(), strlen(unixSocketPath_) + 1)) { + kdDebug(9012) << "failed to write Unix domain socket path to rdb " + << QString("%1\n").arg(unixSocketPath_).latin1() << endl; + } + + setStateOff(s_programExited); + setStateOn(s_dbgNotStarted|s_appNotStarted|s_silent); +} + +// ************************************************************************** + +void RDBController::slotStopDebugger() +{ + if (stateIsOn(s_shuttingDown) || !dbgProcess_) + return; + + setStateOn(s_shuttingDown|s_silent); + destroyCmds(); + + QTime start; + QTime now; + + // Get rdb's attention if it's busy. We need rdb to be at the + // command line so we can stop it. + if (stateIsOn(s_appBusy)) + { + kdDebug(9012) << "ruby debugger busy on shutdown - stopping rdb (SIGINT)" << endl; + dbgProcess_->kill(SIGINT); + start = QTime::currentTime(); + while (-1) + { + kapp->processEvents(20); + now = QTime::currentTime(); + if (!stateIsOn(s_appBusy) || start.msecsTo( now ) > 2000) + break; + } + } + + + // Now try to stop the ruby debugger running. + kdDebug(9012) << "App is busy" << endl; + setStateOn(s_appBusy); + const char *quit="quit\n"; + if (!dbgProcess_->writeStdin(quit, strlen(quit))) + kdDebug(9012) << "failed to write 'quit' to ruby debugger" << endl; + + emit rdbStdout("(rdb:1) quit"); + start = QTime::currentTime(); + while (-1) + { + kapp->processEvents(20); + now = QTime::currentTime(); + if (stateIsOn(s_programExited) || start.msecsTo( now ) > 2000) + break; + } + + // We cannot wait forever. + if (!stateIsOn(s_programExited)) + { + kdDebug(9012) << "rdb not shutdown - killing" << endl; + dbgProcess_->kill(SIGKILL); + } + + delete dbgProcess_; dbgProcess_ = 0; + delete tty_; tty_ = 0; + + state_ = s_dbgNotStarted | s_appNotStarted | s_silent; + emit dbgStatus (i18n("Debugger stopped"), state_); +} + + + +// ************************************************************************** + +void RDBController::slotRun() +{ + if (stateIsOn(s_appBusy|s_dbgNotStarted|s_shuttingDown)) + return; + + if (stateIsOn(s_programExited)) { + slotStart(rubyInterpreter_, characterCoding_, runDirectory_, debuggeePath_, application_, runArguments_, showConstants_, traceIntoRuby_); + return; + } + + queueCmd(new RDBCommand("cont", RUNCMD, NOTINFOCMD)); + if (currentCmd_ == 0) { + executeCmd(); + } +} + +// ************************************************************************** + +void RDBController::slotRunUntil(const QString &fileName, int lineNum) +{ + if (stateIsOn(s_appBusy|s_dbgNotStarted|s_shuttingDown)) + return; + + if (fileName.isEmpty()) + queueCmd(new RDBCommand( QCString().sprintf("break %d", lineNum), + RUNCMD, NOTINFOCMD)); + else + queueCmd(new RDBCommand( + QCString().sprintf("break %s:%d", fileName.latin1(), lineNum), + RUNCMD, NOTINFOCMD)); + queueCmd(new RDBCommand("cont", RUNCMD, NOTINFOCMD)); + + if (currentCmd_ == 0) { + executeCmd(); + } +} + +// ************************************************************************** + +void RDBController::slotStepInto() +{ + if (stateIsOn(s_appBusy|s_appNotStarted|s_shuttingDown)) + return; + + queueCmd(new RDBCommand("step", RUNCMD, NOTINFOCMD)); + if (currentCmd_ == 0) { + executeCmd(); + } +} + + +// ************************************************************************** + +void RDBController::slotStepOver() +{ + if (stateIsOn(s_appBusy|s_appNotStarted|s_shuttingDown)) + return; + + queueCmd(new RDBCommand("next", RUNCMD, NOTINFOCMD)); + if (currentCmd_ == 0) { + executeCmd(); + } +} + + +// ************************************************************************** + +void RDBController::slotStepOutOff() +{ + if (stateIsOn(s_appBusy|s_appNotStarted|s_shuttingDown)) + return; + + queueCmd(new RDBCommand("finish", RUNCMD, NOTINFOCMD)); + if (currentCmd_ == 0) { + executeCmd(); + } +} + +// ************************************************************************** + +// Only interrupt a running program. +void RDBController::slotBreakInto() +{ + pauseApp(); +} + +// ************************************************************************** + +// See what, if anything needs doing to this breakpoint. +void RDBController::slotBPState( const Breakpoint& BP ) +{ + // Are we in a position to do anything to this breakpoint? + if (stateIsOn(s_dbgNotStarted|s_shuttingDown) || !BP.isPending() || + BP.isActionDie()) + return; + + // We need this flag so that we can continue execution. I did use + // the s_silent state flag but it can be set prior to this method being + // called, hence is invalid. + bool restart = false; + if (stateIsOn(s_appBusy)) + { + if (!config_forceBPSet_) + return; + + // When forcing breakpoints to be set/unset, interrupt a running app + // and change the state. + setStateOn(s_silent); + pauseApp(); + restart = true; + } + + if (BP.isActionAdd()) + { + setBreakpoint(BP.dbgSetCommand().latin1(), BP.key()); + // BP.setDbgProcessing(true); + } + else + { + if (BP.isActionClear()) + { + clearBreakpoint(BP.dbgRemoveCommand().latin1()); + // BP.setDbgProcessing(true); + } + else + { + if (BP.isActionModify()) + { + modifyBreakpoint(BP); // Note: DbgProcessing gets set in modify fn + } + } + } + + if (restart) + queueCmd(new RDBCommand("cont", RUNCMD, NOTINFOCMD)); +} + +// ************************************************************************** + +void RDBController::slotClearAllBreakpoints() +{ + // Are we in a position to do anything to this breakpoint? + if (stateIsOn(s_dbgNotStarted|s_shuttingDown)) + return; + + bool restart = false; + if (stateIsOn(s_appBusy)) + { + if (!config_forceBPSet_) + return; + + // When forcing breakpoints to be set/unset, interrupt a running app + // and change the state. + setStateOn(s_silent); + pauseApp(); + restart = true; + } + + queueCmd(new RDBCommand("delete", NOTRUNCMD, NOTINFOCMD)); + // Note: this is NOT an info command, because rdb doesn't explictly tell + // us that the breakpoint has been deleted, so if we don't have it the + // BP list doesn't get updated. + queueCmd(new RDBCommand("break", NOTRUNCMD, NOTINFOCMD)); + + if (restart) + queueCmd(new RDBCommand("cont", RUNCMD, NOTINFOCMD)); + + executeCmd(); +} + + + +// ************************************************************************** + +void RDBController::slotSelectFrame(int frameNo, int threadNo, const QString& frameName) +{ + if (stateIsOn(s_appBusy|s_dbgNotStarted|s_shuttingDown)) { + kdDebug(9012) << "RDBController::slotSelectFrame wrong state" << endl; + return; + } + + if (viewedThread_ != threadNo) { + // Note that 'thread switch nnn' is a run command + queueCmd(new RDBCommand(QCString().sprintf("thread switch %d", + threadNo), RUNCMD, INFOCMD)); + executeCmd(); + return; + } + + if (frameNo > currentFrame_) { + queueCmd(new RDBCommand(QCString().sprintf("up %d", frameNo - currentFrame_), NOTRUNCMD, INFOCMD)); + if (!stateIsOn(s_fetchLocals)) { + queueCmd(new RDBCommand("display", NOTRUNCMD, INFOCMD)); + } + } else if (frameNo < currentFrame_) { + queueCmd(new RDBCommand(QCString().sprintf("down %d", currentFrame_ - frameNo), NOTRUNCMD, INFOCMD)); + if (!stateIsOn(s_fetchLocals)) { + queueCmd(new RDBCommand("display", NOTRUNCMD, INFOCMD)); + } + } + + // Hold on to this thread/frame so that we know where to put the local + // variables if generated. + viewedThread_ = threadNo; + currentFrame_ = frameNo; + + VarFrameRoot *frame = varTree_->findFrame(frameNo, viewedThread_); + if (frame == 0) { + frame = new VarFrameRoot(varTree_, currentFrame_, viewedThread_); + } + + frame->setFrameName(frameName); + varTree_->setSelected(frame, true); + + // Have we already got these details? + if (frame->needsVariables()) { + // Ask for the locals + + if (showConstants_) { + queueCmd(new RDBCommand("var const self.class", NOTRUNCMD, INFOCMD)); + } + + queueCmd(new RDBCommand("var instance self", NOTRUNCMD, INFOCMD)); + queueCmd(new RDBCommand("var class self.class", NOTRUNCMD, INFOCMD)); + queueCmd(new RDBCommand("var local", NOTRUNCMD, INFOCMD)); + frame->startWaitingForData(); + } + + if (currentCmd_ == 0) { + executeCmd(); + } + + return; +} + + + +// ************************************************************************** + +// This is called when an item needs special processing to show a value. +void RDBController::slotExpandItem(VarItem *item, const QCString &userRequest) +{ + if (stateIsOn(s_appBusy|s_dbgNotStarted|s_shuttingDown)) + return; + + Q_ASSERT(item != 0); + + // Bad user data!! + if (userRequest.isEmpty()) + return; + + queueCmd(new RDBItemCommand(item, QCString("pp ") + userRequest.data(), false)); + + if (currentCmd_ == 0) { + executeCmd(); + } +} + +// ************************************************************************** + +// This method evaluates text selected with the 'Inspect:' context menu +void RDBController::slotRubyInspect(const QString &inspectText) +{ + queueCmd(new RDBCommand( QCString().sprintf("p %s", inspectText.latin1()), + NOTRUNCMD, + INFOCMD ), true ); + executeCmd(); +} + + +// ************************************************************************** + +// Add a new expression to be displayed in the Watch variable tree +void RDBController::slotAddWatchExpression(const QString& expr, bool execute) +{ + queueCmd(new RDBCommand( QCString().sprintf("display %s", expr.latin1()), + NOTRUNCMD, + NOTINFOCMD ) ); + if (execute) { + executeCmd(); + } +} + +// ************************************************************************** + +// Add a new expression to be displayed in the Watch variable tree +void RDBController::slotRemoveWatchExpression(int displayId) +{ + queueCmd(new RDBCommand( QCString().sprintf("undisplay %d", displayId), + NOTRUNCMD, + INFOCMD ) ); + executeCmd(); +} + + +// ************************************************************************** + +// The user will only get globals if the Global frame is open +void RDBController::slotFetchGlobals(bool fetch) +{ + if (fetch) { + setStateOn(s_fetchGlobals); + queueCmd(new RDBCommand("var global", NOTRUNCMD, INFOCMD)); + executeCmd(); + } else { + setStateOff(s_fetchGlobals); + } + + kdDebug(9012) << (fetch ? "<Globals ON>": "<Globals OFF>") << endl; +} + +// ************************************************************************** + +// Data from the ruby program's stdout gets processed here. +void RDBController::slotDbgStdout(KProcess *, char *buf, int buflen) +{ + QCString msg(buf, buflen+1); + emit ttyStdout(msg); +} + +// ************************************************************************** + +// Data from the ruby program's stderr gets processed here. +void RDBController::slotDbgStderr(KProcess *, char *buf, int buflen) +{ + QCString msg(buf, buflen+1); + emit ttyStderr(msg); +} + +// ************************************************************************** + +void RDBController::slotDbgWroteStdin(KProcess *) +{ +// setStateOff(s_waitForWrite); + // if (!stateIsOn(s_silent)) + // emit dbgStatus ("", state_); +// executeCmd(); +} + +// ************************************************************************** + +void RDBController::slotAcceptConnection(int masterSocket) +{ + Q_ASSERT(masterSocket == masterSocket_); + + struct sockaddr sockaddr; + socklen_t fromlen; + + if (socketNotifier_ != 0) { + close(socket_); + delete socketNotifier_; + } + + socket_ = accept(masterSocket, &sockaddr, &fromlen); + if (fcntl(socket_, F_SETFL, O_NONBLOCK) == -1) { + kdDebug(9012) << "RDBController::slotAcceptConnection can't set nonblocking socket " << errno << endl; + } + + socketNotifier_ = new QSocketNotifier(socket_, QSocketNotifier::Read, 0); + QObject::connect( socketNotifier_, SIGNAL(activated(int)), + this, SLOT(slotReadFromSocket(int)) ); + + setStateOff(s_dbgNotStarted); + emit dbgStatus ("", state_); + + cmdList_.clear(); + rdbOutputLen_ = 0; + + // Organise any breakpoints. + emit acceptPendingBPs(); + + if (traceIntoRuby_) { + queueCmd(new RDBCommand("trace_ruby on", NOTRUNCMD, NOTINFOCMD)); + } + + queueCmd(new RDBCommand("cont", RUNCMD, NOTINFOCMD)); + + // Reset the display id for any watch expressions already in the variable tree + varTree_->resetWatchVars(); +} + +// ************************************************************************** + +// Output from rdb via the Unix socket gets processed here. +void RDBController::slotReadFromSocket(int socket) +{ + Q_ASSERT(socket == socket_); + + static bool parsing = false; + + int bytesRead = read(socket, rdbOutput_ + rdbOutputLen_, rdbSizeofBuf_); + + rdbOutputLen_ += bytesRead; + *(rdbOutput_ + rdbOutputLen_) = 0; + + + // Already parsing? then get out quick. + if (parsing) + { + kdDebug(9012) << "Already parsing" << endl; + return; + } + +// kdDebug(9012) << "RDBController::slotReadFromSocket length: " << rdbOutputLen_ << " input: " << rdbOutput_ << endl; + + QRegExp prompt_re("(\\(rdb:(\\d+)\\) )$"); + int promptPos = prompt_re.search(rdbOutput_, 0); + + // Keep appending output to the rbdOutput_ buffer until the + // ruby debugger writes the next prompt + if (promptPos == -1) { + return; + } + +// kdDebug(9012) << "RDBController::slotReadFromSocket length: " << rdbOutputLen_ << " input: " << rdbOutput_ << endl; + + // Save the prompt, and remove it from the buffer + currentPrompt_ = prompt_re.cap(1).latin1(); + rdbOutputLen_ -= prompt_re.matchedLength(); + *(rdbOutput_ + rdbOutputLen_) = 0; + + emit rdbStdout(rdbOutput_); + + parsing = true; + parse(rdbOutput_); + parsing = false; + rdbOutputLen_ = 0; + + executeCmd(); + + if (currentCmd_ == 0 && stateIsOn(s_fetchLocals)) { + if (!varTree_->schedule()) { + setStateOff(s_fetchLocals); + } + } +} + +// ************************************************************************** + +void RDBController::slotDbgProcessExited(KProcess*) +{ + destroyCmds(); + state_ = s_appNotStarted|s_programExited|(state_&(s_shuttingDown)); + emit dbgStatus (i18n("Process exited"), state_); + emit rdbStdout("(rdb:1) Process exited\n"); + frameStack_->clear(); + varTree_->clear(); + + if (socketNotifier_ != 0) { + delete socketNotifier_; + socketNotifier_ = 0; + close(socket_); + } + + delete dbgProcess_; dbgProcess_ = 0; + delete tty_; tty_ = 0; +} + + +// ************************************************************************** + +// Takes abbreviated commands and expands them, before passing them on to rdb +// +void RDBController::slotUserRDBCmd(const QString& cmd) +{ + kdDebug(9012) << "Requested user cmd: " << cmd << endl; + QRegExp break_re("^b(reak)?(\\s.*)?"); + QRegExp watch_re("^wat(ch)?\\s+(.*)"); + QRegExp delete_re("^del(ete)?(\\s.*)?"); + QRegExp display_re("^disp(lay)?(\\s.*)?"); + QRegExp undisplay_re("^undisp(lay)?(\\s.*)?"); + QRegExp step_re("^s(tep)?(\\s[\\d]+)?$"); + QRegExp next_re("^n(ext)?(\\s[\\d]+)?$"); + QRegExp varlocal_re("^v(ar)?\\s+l(ocal)?"); + QRegExp varglobal_re("^v(ar)?\\s+g(lobal)?"); + QRegExp varinstance_re("^v(ar)?\\s+i(nstance)?\\s(.*)"); + QRegExp varconst_re("^v(ar)?\\s+c(onst)?\\s(.*)"); + QRegExp threadlist_re("^th(read)?\\s+l(ist)?"); + QRegExp threadcurrent_re("^th(read)?(\\sc(ur(rent)?)?)?$"); + QRegExp threadswitch_re("^th(read)?(\\ssw(itch)?)?(\\s.*)"); + QRegExp thread_re("^th(read)?(\\s+.*)?"); + QRegExp methodinstance_re("^m(ethod)?\\s+i(nstance)?\\s+(.*)"); + QRegExp method_re("^m(ethod)?\\s+(.*)"); + QRegExp list_re("^l(ist)?(\\s+\\d+-\\d+)?$"); + + if ( break_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("break%s", break_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( watch_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("watch %s", watch_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( delete_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("delete%s", delete_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( display_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("display%s", display_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( undisplay_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("undisplay%s", undisplay_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( step_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("step%s", step_re.cap(2).latin1()), + RUNCMD, + INFOCMD ), true ); + } else if ( next_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("next%s", next_re.cap(2).latin1()), + RUNCMD, + INFOCMD ), true ); + } else if ( varlocal_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand("var local", NOTRUNCMD, INFOCMD)); + } else if ( varglobal_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand("var global", NOTRUNCMD, INFOCMD)); + } else if ( varinstance_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("var instance %s", varinstance_re.cap(3).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( varconst_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("var const %s", varconst_re.cap(3).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( methodinstance_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("method instance %s", methodinstance_re.cap(3).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( method_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("method %s", method_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if ( list_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("list%s", list_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if (cmd == "c" || cmd == "cont") { + queueCmd(new RDBCommand("cont", RUNCMD, NOTINFOCMD)); + } else if (cmd == "fi" || cmd == "finish") { + queueCmd(new RDBCommand("finish", RUNCMD, NOTINFOCMD)); + } else if ( threadlist_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand("thread list", NOTRUNCMD, INFOCMD), true); + } else if ( threadcurrent_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand("thread current", NOTRUNCMD, INFOCMD), true ); + } else if ( threadswitch_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("thread switch%s", threadswitch_re.cap(4).latin1()), + RUNCMD, + INFOCMD ), true ); + } else if ( thread_re.search(cmd) >= 0 ) { + queueCmd(new RDBCommand( QCString().sprintf("thread%s", thread_re.cap(2).latin1()), + NOTRUNCMD, + INFOCMD ), true ); + } else if (cmd == "frame" || cmd == "f" || cmd == "where" || cmd == "w") { + queueCmd(new RDBCommand("where", NOTRUNCMD, INFOCMD), true); + } else if (cmd == "q" || cmd == "quit") { + slotStopDebugger(); + return; + } else { + kdDebug(9012) << "Passing directly to rdb: " << cmd << endl; + queueCmd(new RDBCommand(cmd.latin1(), NOTRUNCMD, INFOCMD)); + } + + executeCmd(); +} + +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** +#include "rdbcontroller.moc" diff --git a/languages/ruby/debugger/rdbcontroller.h b/languages/ruby/debugger/rdbcontroller.h new file mode 100644 index 00000000..2d303218 --- /dev/null +++ b/languages/ruby/debugger/rdbcontroller.h @@ -0,0 +1,192 @@ +/*************************************************************************** + rdbcontroller.h - description + ------------------- + begin : Sun Aug 8 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + + + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _RDBCONTROLLER_H_ +#define _RDBCONTROLLER_H_ + +#include "dbgcontroller.h" + +#include <qcstring.h> +#include <qdom.h> +#include <qobject.h> +#include <qptrlist.h> +#include <qstring.h> +#include <qsocketnotifier.h> + +class KProcess; + +namespace RDBDebugger +{ + +class Breakpoint; +class DbgCommand; +class FramestackWidget; +class VarItem; +class VariableTree; +class STTY; + +/** + * A front end implementation to the ruby command line debugger + * @author jbb + */ + +class RDBController : public DbgController +{ + Q_OBJECT + +public: + RDBController(VariableTree *varTree, FramestackWidget *frameStack, QDomDocument &projectDom); + ~RDBController(); + +protected: + void queueCmd(DbgCommand *cmd, bool executeNext=false); + +private: + void parseProgramLocation (char *buf); + void parseBacktraceList (char *buf); + void parseThreadList (char* buf); + void parseSwitchThread (char* buf); + void parseFrameMove (char *buf); + void parseBreakpointSet (char *buf); + void parseDisplay (char *buf, char * expr); + void parseUpdateDisplay (char *buf); + void parseGlobals (char *buf); + void parseLocals (char type, char *buf); + void parseRequestedData (char *buf); + void parseFrameSelected (char *buf); + + void parse (char *buf); + + void pauseApp(); + void executeCmd (); + void destroyCmds(); + void removeInfoRequests(); + void actOnProgramPause(const QString &msg); + void programNoApp(const QString &msg, bool msgBox); + + void setBreakpoint(const QCString &BPSetCmd, int key); + void clearBreakpoint(const QCString &BPClearCmd); + void modifyBreakpoint(const Breakpoint&); + + void setStateOn(int stateOn) { state_ |= stateOn; } + void setStateOff(int stateOff) { state_ &= ~stateOff; } + bool stateIsOn(int state) { return state_ &state; } + +public slots: + void configure(); + + void slotStart( const QString& shell, const QString& characterCoding, + const QString& run_directory, const QString& debuggee_path, + const QString &application, const QString& run_arguments, + bool show_constants, bool trace_into_ruby ); + //void slotStart(const QString& shell, const QString &application); + + void slotStopDebugger(); + + void slotRun(); + void slotRunUntil(const QString &filename, int lineNum); + void slotStepInto(); + void slotStepOver(); + void slotStepOutOff(); + + void slotBreakInto(); + void slotBPState( const Breakpoint& ); + void slotClearAllBreakpoints(); + + void slotExpandItem(VarItem *parent, const QCString &userRequest); + void slotRubyInspect(const QString &inspectText); + void slotSelectFrame(int frameNo, int threadNo, const QString& frameName); + void slotFetchGlobals(bool fetch); + void slotAddWatchExpression(const QString& expr, bool execute); + void slotRemoveWatchExpression(int displayId); + + void slotUserRDBCmd(const QString&); + +protected slots: + void slotDbgStdout(KProcess *proc, char *buf, int buflen); + void slotDbgStderr(KProcess *proc, char *buf, int buflen); + void slotDbgWroteStdin(KProcess *proc); + void slotDbgProcessExited(KProcess *proc); + + void slotAcceptConnection(int masterSocket); + void slotReadFromSocket(int socket); + +signals: + void acceptPendingBPs (); + void unableToSetBPNow (int BPNo); + void addWatchExpression (const QString&); + +private: + FramestackWidget* frameStack_; + VariableTree* varTree_; + int currentFrame_; + int viewedThread_; + + int stdoutSizeofBuf_; // size of the buffer for holding stdout piped + // from the ruby program + int stdoutOutputLen_; // amount of data in the output buffer + char* stdoutOutput_; // buffer for the output from kprocess + QCString holdingZone_; + + int rdbSizeofBuf_; // size of the output buffer from rdb + int rdbOutputLen_; // amount of data in the rdb buffer + char* rdbOutput_; // buffer for the output from rdb via the Unix socket + + int masterSocket_; // The socket to accept connections + QSocketNotifier* acceptNotifier_; + static QCString unixSocketPath_; // The name of the Unix Domain socket + int socket_; // The socket to read and write to the debuggee + QSocketNotifier* socketNotifier_; + + QPtrList<DbgCommand> cmdList_; + DbgCommand* currentCmd_; + QString currentPrompt_; + + STTY* tty_; + + // Details for starting the ruby debugger process + QString rubyInterpreter_; + QString characterCoding_; + QString runDirectory_; + QString debuggeePath_; + QString application_; + QString runArguments_; + bool showConstants_; + bool traceIntoRuby_; + + // Some state variables + int state_; + bool programHasExited_; + + // Configuration values + QDomDocument &dom; + bool config_forceBPSet_; + bool config_dbgTerminal_; +}; + +} + +#endif diff --git a/languages/ruby/debugger/rdboutputwidget.cpp b/languages/ruby/debugger/rdboutputwidget.cpp new file mode 100644 index 00000000..5e08e116 --- /dev/null +++ b/languages/ruby/debugger/rdboutputwidget.cpp @@ -0,0 +1,171 @@ +// ************************************************************************* +// rdboutputwidget.cpp - description +// ------------------- +// begin : 10th April 2003 +// copyright : (C) 2003 by John Birch +// email : jbb@kdevelop.org +// +// Adapted for ruby debugging +// -------------------------- +// begin : Mon Nov 1 2004 +// copyright : (C) 2004 by Richard Dale +// email : Richard_Dale@tipitina.demon.co.uk +// ************************************************************************** +// +// ************************************************************************** +// * * +// * This program is free software; you can redistribute it and/or modify * +// * it under the terms of the GNU General Public License as published by * +// * the Free Software Foundation; either version 2 of the License, or * +// * (at your option) any later version. * +// * * +// ************************************************************************** + +#include "rdboutputwidget.h" +#include "dbgcontroller.h" + +#include <kcombobox.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <klocale.h> + +#include <qlabel.h> +#include <qlayout.h> +#include <qtextedit.h> +#include <qtoolbutton.h> +#include <qtooltip.h> + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +namespace RDBDebugger +{ + +/***************************************************************************/ + +RDBOutputWidget::RDBOutputWidget( QWidget *parent, const char *name) : + QWidget(parent, name), + m_userRDBCmdEditor(0), + m_Interrupt(0), + m_rdbView(0) +{ + + m_rdbView = new QTextEdit (this, name); + m_rdbView->setReadOnly(true); + + QBoxLayout *userRDBCmdEntry = new QHBoxLayout(); + m_userRDBCmdEditor = new KHistoryCombo (this, "rdb-user-cmd-editor"); + + QLabel *label = new QLabel(i18n("&RDB cmd:"), this); + label->setBuddy(m_userRDBCmdEditor); + userRDBCmdEntry->addWidget(label); + + userRDBCmdEntry->addWidget(m_userRDBCmdEditor); + userRDBCmdEntry->setStretchFactor(m_userRDBCmdEditor, 1); + + m_Interrupt = new QToolButton( this, "add breakpoint" ); + m_Interrupt->setSizePolicy ( QSizePolicy ( (QSizePolicy::SizeType)0, + ( QSizePolicy::SizeType)0, + 0, + 0, + m_Interrupt->sizePolicy().hasHeightForWidth()) + ); + m_Interrupt->setPixmap ( SmallIcon ( "player_pause" ) ); + userRDBCmdEntry->addWidget(m_Interrupt); + QToolTip::add ( m_Interrupt, i18n ( "Pause execution of the app to enter rdb commands" ) ); + + QVBoxLayout *topLayout = new QVBoxLayout(this, 2); + topLayout->addWidget(m_rdbView, 10); + topLayout->addLayout(userRDBCmdEntry); + + slotDbgStatus( "", s_dbgNotStarted); + + connect( m_userRDBCmdEditor, SIGNAL(returnPressed()), SLOT(slotRDBCmd()) ); + connect( m_Interrupt, SIGNAL(clicked()), SIGNAL(breakInto())); +} + +/***************************************************************************/ + +RDBOutputWidget::~RDBOutputWidget() +{ + delete m_rdbView; + delete m_userRDBCmdEditor; +} + +/***************************************************************************/ + +void RDBOutputWidget::clear() +{ + if (m_rdbView) + m_rdbView->clear(); +} + +/***************************************************************************/ + +void RDBOutputWidget::slotReceivedStdout(const char* line) +{ + if (strncmp(line, "(rdb:", 5) == 0) + m_rdbView->append(QString("<font color=\"blue\">").append( line ).append("</font>") ); + else + m_rdbView->append(line); +} + +/***************************************************************************/ + +void RDBOutputWidget::slotReceivedStderr(const char* line) +{ + m_rdbView->append(QString("<font color=\"red\">").append( line ).append("</font>") ); +} + +/***************************************************************************/ + +void RDBOutputWidget::slotRDBCmd() +{ + QString RDBCmd(m_userRDBCmdEditor->currentText()); + if (!RDBCmd.isEmpty()) + { + m_userRDBCmdEditor->addToHistory(RDBCmd); + m_userRDBCmdEditor->clearEdit(); + emit userRDBCmd(RDBCmd); + } +} + +/***************************************************************************/ + +void RDBOutputWidget::slotDbgStatus(const QString &, int statusFlag) +{ + if (statusFlag & s_dbgNotStarted) + { + m_Interrupt->setEnabled(false); + m_userRDBCmdEditor->setEnabled(false); + return; + } + + if (statusFlag & s_appBusy) + { + m_Interrupt->setEnabled(true); + m_userRDBCmdEditor->setEnabled(false); + } + else + { + m_Interrupt->setEnabled(false); + m_userRDBCmdEditor->setEnabled(true); + } +} + +/***************************************************************************/ + +void RDBOutputWidget::focusInEvent(QFocusEvent */*e*/) +{ + m_userRDBCmdEditor->setFocus(); +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ +} + + +#include "rdboutputwidget.moc" + diff --git a/languages/ruby/debugger/rdboutputwidget.h b/languages/ruby/debugger/rdboutputwidget.h new file mode 100644 index 00000000..ef466757 --- /dev/null +++ b/languages/ruby/debugger/rdboutputwidget.h @@ -0,0 +1,69 @@ +// ************************************************************************* +// gdboutputwidget.cpp - description +// ------------------- +// begin : 10th April 2003 +// copyright : (C) 2003 by John Birch +// email : jbb@kdevelop.org +// +// Adapted for ruby debugging +// -------------------------- +// begin : Mon Nov 1 2004 +// copyright : (C) 2004 by Richard Dale +// email : Richard_Dale@tipitina.demon.co.uk +// ************************************************************************** +// +// ************************************************************************** +// * * +// * This program is free software; you can redistribute it and/or modify * +// * it under the terms of the GNU General Public License as published by * +// * the Free Software Foundation; either version 2 of the License, or * +// * (at your option) any later version. * +// * * +// ************************************************************************** + +#ifndef _RDBOUTPUTWIDGET_H_ +#define _RDBOUTPUTWIDGET_H_ + +#include <qwidget.h> + +class KHistoryCombo; + +class QTextEdit; +class QToolButton; + +namespace RDBDebugger +{ + +class RDBOutputWidget : public QWidget +{ + Q_OBJECT + +public: + RDBOutputWidget( QWidget *parent=0, const char *name=0 ); + ~RDBOutputWidget(); + + void clear(); + +public slots: + void slotReceivedStdout(const char* line); + void slotReceivedStderr(const char* line); + void slotDbgStatus (const QString &status, int statusFlag); + + void slotRDBCmd(); + +protected: + virtual void focusInEvent(QFocusEvent *e); + +signals: + void userRDBCmd(const QString &cmd); + void breakInto(); + +private: + KHistoryCombo* m_userRDBCmdEditor; + QToolButton* m_Interrupt; + QTextEdit* m_rdbView; +}; + +} + +#endif diff --git a/languages/ruby/debugger/rdbparser.cpp b/languages/ruby/debugger/rdbparser.cpp new file mode 100644 index 00000000..7792acc5 --- /dev/null +++ b/languages/ruby/debugger/rdbparser.cpp @@ -0,0 +1,350 @@ +// ************************************************************************** +// begin : Tue Aug 17 1999 +// copyright : (C) 1999 by John Birch +// email : jbb@kdevelop.org +// +// Adapted for ruby debugging +// -------------------------- +// begin : Mon Nov 1 2004 +// copyright : (C) 2004 by Richard Dale +// email : Richard_Dale@tipitina.demon.co.uk +// ************************************************************************** + +// ************************************************************************** +// * +// This program is free software; you can redistribute it and/or modify * +// it under the terms of the GNU General Public License as published by * +// the Free Software Foundation; either version 2 of the License, or * +// (at your option) any later version. * +// * +// ************************************************************************** + +#include "rdbparser.h" +#include "variablewidget.h" + +#include <qregexp.h> + +#include <ctype.h> +#include <stdlib.h> +#include <kdebug.h> + +namespace RDBDebugger +{ + + +// ************************************************************************** + +void RDBParser::parseVariables(LazyFetchItem *parent, char *buf) +{ + static const char *unknown = "?"; + + QString varName; + QCString value; + int pos; + + Q_ASSERT(parent); + if (buf == 0 || strlen(buf) == 0) { + return; + } + + if (buf[0] == 0) { + buf = (char*)unknown; + } + + QRegExp var_re("\\s*([^\\n\\s]+) => ([^\\n]+)"); + QRegExp ref_re("(#<([^:]|::)+:0x[\\da-f]+)\\s*([^=]*)>?"); + QRegExp struct_re("#<struct Struct::(\\w+)"); + + // Look for 'dataitem => value' pairs. For example: + // a => 1 + // m => #<MyClass:0x30093540 @temp={"z"=>"zed", "p"=>"pee"}, @foobar="hello"> + // + pos = var_re.search(buf); + if (pos != -1) { + while (pos != -1) { + varName = var_re.cap(1); + if (ref_re.search(var_re.cap(2)) != -1) { + if (var_re.cap(2).contains("=") > 0) { + value = (ref_re.cap(1) + ">").latin1(); + } else { + // There are no 'name=value' pairs, as in #<Qt::Color:0x0 #ff0000> + value = var_re.cap(2).latin1(); + } + } else if (struct_re.search(var_re.cap(2)) != -1) { + value = (QString("#<Struct::") + struct_re.cap(1) + ">").latin1(); + } else { + value = var_re.cap(2).latin1(); + } + + DataType dataType = determineType((char *) var_re.cap(2).latin1()); + + // 'self' variables don't need to be expandable, as their details are + // already shown in the current frame. So always make them VALUE_TYPE's. + if (varName == "self") { + dataType = VALUE_TYPE; + } + + setItem(parent, varName, dataType, value); + + pos += var_re.matchedLength(); + pos = var_re.search(buf, pos); + } + + return; + } +} + +void RDBParser::parseExpandedVariable(VarItem *parent, char *buf) +{ + DataType dataType; + int pos; + QString varName; + QCString value; + QRegExp ppref_re("(#<([^:]|::)+:0x[\\da-f]+)([^\\n>]*)(>?)"); + + switch (parent->dataType()) { + case REFERENCE_TYPE: + { + // Look for a reference type which has been printed via a 'pp' command, to + // expand its sub items on multiple lines. For example: + // #<MyClass:0x30093540 + // @foobar="hello", + // @sleeper=#<Thread:0x3008fd18 sleep>, + // @temp={"z"=>"zed", "p"=>"pee"}> + // + QRegExp ppvalue_re("\\s*([^\\n\\s=]+)=([^\\n]+)[,>]"); + + pos = ppref_re.search(buf); + if (pos != -1) { + if (ppref_re.cap(4) != "") { + // The value is all on one line, so match against name=value + // pairs which can't have commas in their values + ppvalue_re = QRegExp("\\s*([^\\s=]+)=([^,>]+)([,>])"); + } + + pos = ppvalue_re.search(buf, pos); + + while (pos != -1) { + varName = ppvalue_re.cap(1); + + if (ppref_re.search(ppvalue_re.cap(2)) != -1) { + if (ppvalue_re.cap(2).contains("=") > 0) { + value = (ppref_re.cap(1) + ">").latin1(); + } else { + // There are no 'name=value' pairs, as in #<Qt::Color:0x0 #ff0000> + value = ppvalue_re.cap(2).latin1(); + } + } else { + value = ppvalue_re.cap(2).latin1(); + } + + dataType = determineType((char *) ppvalue_re.cap(2).latin1()); + setItem(parent, varName, dataType, value); + + pos += ppvalue_re.matchedLength(); + pos = ppvalue_re.search(buf, pos); + } + + } + return; + } + + case ARRAY_TYPE: + { + // Look for a array type which has been printed via a 'pp' command, to + // expand its sub items. For example: + // [0]="hello" + // [1]=#"goodbye" + // + QRegExp pparray_re("\\s*([^=]+)=([^\\n]+)\\n"); + + pos = pparray_re.search(buf); + + while (pos != -1) { + varName = pparray_re.cap(1); + + if (ppref_re.search(pparray_re.cap(2)) != -1) { + value = (ppref_re.cap(1) + ">").latin1(); + } else { + value = pparray_re.cap(2).latin1(); + } + + DataType dataType = determineType((char *) pparray_re.cap(2).latin1()); + setItem(parent, varName, dataType, value); + + pos += pparray_re.matchedLength(); + pos = pparray_re.search(buf, pos); + } + + return; + } + + case HASH_TYPE: + { + // Look for a hash type which has been printed via a 'pp' command, to + // expand its sub items. For example: + // ["greeting"]="hello" + // ["farewell"]="goodbye" + // + QRegExp pphash_re("\\s*(\\[[^\\]]+\\])=([^\\n]+)\\n"); + pos = pphash_re.search(buf); + + while (pos != -1) { + varName = pphash_re.cap(1); + value = pphash_re.cap(2).latin1(); + DataType dataType = determineType(value.data()); + setItem(parent, varName, dataType, value); + + pos += pphash_re.matchedLength(); + pos = pphash_re.search(buf, pos); + } + + return; + } + + case STRUCT_TYPE: + { + // Look for a reference type which has been printed via a 'pp' command, to + // expand its sub items. For example: + // #<Struct::Customer + // @foobar="hello", + // @sleeper=#<Thread:0x3008fd18 sleep>, + // @temp={"z"=>"zed", "p"=>"pee"}> + // + QRegExp ppstruct_re("(#<Struct::\\w+)\\s([^\\n>]*)(>?)"); + QRegExp ppvalue_re("\\s*([^\\n\\s=]+)=([^\\n]+)[,>]"); + + pos = ppstruct_re.search(buf); + if (pos != -1) { + if (ppstruct_re.cap(3) != "" && ppvalue_re.search(ppstruct_re.cap(0)) != -1) { + // The line ends with a '>', but we have this case now.. + // If there is only one instance variable, pp puts everything + // on a single line: + // #<Struct::Customer @foobar="hello"> + // So search for '@foobar="hello"', to use as the + // first name=value pair + pos = 0; + } else { + // Mltiple lines with name=value pairs: + // #<Struct::Customer + // @foobar="hello", + pos = ppvalue_re.search(buf, pos); + } + + while (pos != -1) { + varName = ppvalue_re.cap(1); + value = ppvalue_re.cap(2).latin1(); + dataType = determineType(value.data()); + setItem(parent, varName, dataType, value); + + pos += ppvalue_re.matchedLength(); + pos = ppvalue_re.search(buf, pos); + } + + } + return; + } + + case STRING_TYPE: + { + // Look for a long String which has been printed via a 'pp' command, to + // show it as a sequence of 12 bytes slices in hex. For example: + // [0..11]=0x89504e470d0a1a0a0000000d + // [12..23]=0x494844520000001600000016 + // + QRegExp ppstring_re("\\s*(\\[[^\\]]+\\])=([^\\n]+)\\n"); + pos = ppstring_re.search(buf); + + while (pos != -1) { + varName = ppstring_re.cap(1); + value = ppstring_re.cap(2).latin1(); + DataType dataType = determineType(value.data()); + setItem(parent, varName, dataType, value); + + pos += ppstring_re.matchedLength(); + pos = ppstring_re.search(buf, pos); + } + + return; + } + + default: + Q_ASSERT(false); + } + + return; +} + + +// ************************************************************************** + +void RDBParser::setItem(LazyFetchItem *parent, const QString &varName, + DataType dataType, const QCString &value) +{ + VarItem *item = parent->findItem(varName); + if (item == 0) { + item = new VarItem(parent, varName, dataType); + } else { + // The dataType of an item can change, so update it + item->setDataType(dataType); + } + + switch (dataType) { + case HASH_TYPE: + case ARRAY_TYPE: + case REFERENCE_TYPE: + case STRUCT_TYPE: + case STRING_TYPE: + item->setText(VALUE_COLUMN, value); + item->setExpandable(true); + item->update(); + break; + + case COLOR_TYPE: + case VALUE_TYPE: + item->setText(VALUE_COLUMN, value); + item->setExpandable(false); + break; + + default: + break; + } +} + +// ************************************************************************** + +DataType RDBParser::determineType(char *buf) +{ + QRegExp array_re("(Array \\(\\d+ element\\(s\\)\\))"); + QRegExp hash_re("(Hash \\(\\d+ element\\(s\\)\\))"); + QRegExp string_re("(String \\(length \\d+\\))"); + + if (qstrncmp(buf, "#<struct", strlen("#<struct")) == 0) { + return STRUCT_TYPE; + } else if (qstrncmp(buf, "#<Qt::Color:0x", strlen("#<Qt::Color:0x")) == 0) { + return COLOR_TYPE; + } else if (qstrncmp(buf, "#<", strlen("#<")) == 0 && strstr(buf, "=") != 0) { + // An object instance reference is only expandable and a 'REFERENCE_TYPE' + // if it contains an '=' (ie it has at least one '@instance_variable=value'). + // Otherwise, treat it as a 'VALUE_TYPE'. + return REFERENCE_TYPE; + } else if (array_re.search(buf) != -1) { + return ARRAY_TYPE; + } else if (hash_re.search(buf) != -1) { + return HASH_TYPE; + } else if (string_re.search(buf) != -1) { + return STRING_TYPE; + } else if (qstrncmp(buf, "nil", strlen("nil")) == 0) { +// return UNKNOWN_TYPE; + return VALUE_TYPE; + } else { + return VALUE_TYPE; + } +} + + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +} diff --git a/languages/ruby/debugger/rdbparser.h b/languages/ruby/debugger/rdbparser.h new file mode 100644 index 00000000..12bc937e --- /dev/null +++ b/languages/ruby/debugger/rdbparser.h @@ -0,0 +1,41 @@ +/*************************************************************************** + begin : Tue Aug 17 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _RDBPARSER_H_ +#define _RDBPARSER_H_ + +#include "variablewidget.h" + +namespace RDBDebugger +{ + +namespace RDBParser +{ + void parseVariables(LazyFetchItem *parent, char *buf); + void parseExpandedVariable(VarItem *parent, char *buf); + DataType determineType(char *buf); + void setItem( LazyFetchItem *parent, const QString &varName, + DataType dataType, const QCString &value ); +} + +} + +#endif diff --git a/languages/ruby/debugger/rdbtable.cpp b/languages/ruby/debugger/rdbtable.cpp new file mode 100644 index 00000000..2f470b2d --- /dev/null +++ b/languages/ruby/debugger/rdbtable.cpp @@ -0,0 +1,62 @@ +/*************************************************************************** +* Copyright (C) 2003 by Alexander Dymo * +* cloudtemple@mksat.net * +* * +* * +* Adapted for ruby debugging * +* -------------------------- * +* begin : Mon Nov 1 2004 * +* copyright : (C) 2004 by Richard Dale * +* email : Richard_Dale@tipitina.demon.co.uk * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +***************************************************************************/ +#include "rdbtable.h" + +namespace RDBDebugger { + +RDBTable::RDBTable(QWidget *parent, const char *name) + : QTable(parent, name) +{ +} + +RDBTable::RDBTable(int nr, int nc, QWidget * parent, const char * name) + : QTable(nr, nc, parent, name) +{ +} + +RDBTable::~RDBTable() +{ +} + +void RDBTable::keyPressEvent( QKeyEvent * e ) +{ + emit keyPressed(e->key()); + + if (e->key() == Key_Return) + emit returnPressed(); + else if (e->key() == Key_F2) + emit f2Pressed(); + else if ((e->text() == QString("a")) && (e->state() == AltButton)) + { + emit insertPressed(); + return; + } + else if ((e->text() == QString("A")) && (e->state() == AltButton)) + { + emit insertPressed(); + return; + } + else if (e->key() == Key_Delete) + emit deletePressed(); + + QTable::keyPressEvent(e); +} + +} + +#include "rdbtable.moc" + diff --git a/languages/ruby/debugger/rdbtable.h b/languages/ruby/debugger/rdbtable.h new file mode 100644 index 00000000..04f47c2b --- /dev/null +++ b/languages/ruby/debugger/rdbtable.h @@ -0,0 +1,45 @@ +/*************************************************************************** +* Copyright (C) 2003 by Alexander Dymo * +* cloudtemple@mksat.net * +* * +* Adapted for ruby debugging * +* -------------------------- * +* begin : Mon Nov 1 2004 * +* copyright : (C) 2004 by Richard Dale * +* email : Richard_Dale@tipitina.demon.co.uk * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +***************************************************************************/ +#ifndef RDBDEBUGGERRDBTABLE_H +#define RDBDEBUGGERRDBTABLE_H + +#include <qtable.h> + +namespace RDBDebugger { + +class RDBTable : public QTable +{ +Q_OBJECT +public: + RDBTable(QWidget *parent = 0, const char *name = 0); + RDBTable( int numRows, int numCols, QWidget * parent = 0, const char * name = 0 ); + ~RDBTable(); + + virtual void keyPressEvent ( QKeyEvent * e ); + +signals: + void keyPressed(int key); + + void returnPressed(); + void f2Pressed(); + void insertPressed(); + void deletePressed(); +}; + +} + +#endif + diff --git a/languages/ruby/debugger/stty.cpp b/languages/ruby/debugger/stty.cpp new file mode 100644 index 00000000..44cb3795 --- /dev/null +++ b/languages/ruby/debugger/stty.cpp @@ -0,0 +1,370 @@ +/*************************************************************************** + begin : Mon Sep 13 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + This code was originally written by Judin Maxim, from the + KDEStudio project. + + It was then updated with later code from konsole (KDE). + + It has also been enhanced with an idea from the code in kdbg + written by Johannes Sixt<Johannes.Sixt@telecom.at> + + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef __osf__ +#define _XOPEN_SOURCE_EXTENDED +#define O_NDELAY O_NONBLOCK +#endif + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/resource.h> + +#ifdef HAVE_SYS_STROPTS_H +#include <sys/stropts.h> +#define _NEW_TTY_CTRL +#endif + +#include <assert.h> +#include <fcntl.h> +#include <grp.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <termios.h> +#include <time.h> +#include <unistd.h> + +#if defined (_HPUX_SOURCE) +#define _TERMIOS_INCLUDED +#include <bsdtty.h> +#endif + +#include <qintdict.h> +#include <qsocketnotifier.h> +#include <qstring.h> +#include <qfile.h> + +#include <klocale.h> +#include <kstandarddirs.h> +#include <kapplication.h> + +#include "stty.h" + +#define PTY_FILENO 3 +#define BASE_CHOWN "konsole_grantpty" + +namespace RDBDebugger +{ + +static int chownpty(int fd, int grant) +// param fd: the fd of a master pty. +// param grant: 1 to grant, 0 to revoke +// returns 1 on success 0 on fail +{ + void(*tmp)(int) = signal(SIGCHLD,SIG_DFL); + pid_t pid = fork(); + if (pid < 0) { + signal(SIGCHLD,tmp); + return 0; + } + if (pid == 0) { + /* We pass the master pseudo terminal as file descriptor PTY_FILENO. */ + if (fd != PTY_FILENO && dup2(fd, PTY_FILENO) < 0) + ::exit(1); + + QString path = locate("exe", BASE_CHOWN); + execle(QFile::encodeName(path), BASE_CHOWN, grant?"--grant":"--revoke", (void *)0, NULL); + ::exit(1); // should not be reached + } + if (pid > 0) { + int w; + // retry: + int rc = waitpid (pid, &w, 0); + if (rc != pid) + ::exit(1); + + // { // signal from other child, behave like catchChild. + // // guess this gives quite some control chaos... + // Shell* sh = shells.find(rc); + // if (sh) { shells.remove(rc); sh->doneShell(w); } + // goto retry; + // } + signal(SIGCHLD,tmp); + return (rc != -1 && WIFEXITED(w) && WEXITSTATUS(w) == 0); + } + signal(SIGCHLD,tmp); + return 0; //dummy. +} + +// ************************************************************************** + +STTY::STTY(bool ext, const QString &termAppName) + : QObject(), + out(0), + err(0), + ttySlave(""), + pid_(0) +{ + if (ext) { + findExternalTTY(termAppName); + } else { + fout = findTTY(); + if (fout >= 0) { + ttySlave = QString(tty_slave); + out = new QSocketNotifier(fout, QSocketNotifier::Read, this); + connect( out, SIGNAL(activated(int)), this, SLOT(OutReceived(int)) ); + } + } +} + +// ************************************************************************** + +STTY::~STTY() +{ + if (pid_) + ::kill(pid_, SIGTERM); + + if (out) { + ::close(fout); + delete out; + } + + // if ( err ) + // { + // ::close( ferr ); + // delete err; + // } +} + +// ************************************************************************** + +int STTY::findTTY() +{ + int ptyfd = -1; + bool needGrantPty = TRUE; + + // Find a master pty that we can open //////////////////////////////// + +#ifdef __sgi__ + ptyfd = open("/dev/ptmx",O_RDWR); + if (ptyfd < 0) { + perror("Can't open a pseudo teletype"); + return(-1); + } + strncpy(tty_slave, ptsname(ptyfd), 50); + grantpt(ptyfd); + unlockpt(ptyfd); + needGrantPty = FALSE; +#endif + + // first we try UNIX PTY's +#ifdef TIOCGPTN + strcpy(pty_master,"/dev/ptmx"); + strcpy(tty_slave,"/dev/pts/"); + ptyfd = open(pty_master,O_RDWR); + if (ptyfd >= 0) { // got the master pty + int ptyno; + if (ioctl(ptyfd, TIOCGPTN, &ptyno) == 0) { + struct stat sbuf; + sprintf(tty_slave,"/dev/pts/%d",ptyno); + if (stat(tty_slave,&sbuf) == 0 && S_ISCHR(sbuf.st_mode)) + needGrantPty = FALSE; + else { + close(ptyfd); + ptyfd = -1; + } + } else { + close(ptyfd); + ptyfd = -1; + } + } +#endif + +#if defined(_SCO_DS) || defined(__USLC__) /* SCO OSr5 and UnixWare */ + if (ptyfd < 0) { + for (int idx = 0; idx < 256; idx++) + { sprintf(pty_master, "/dev/ptyp%d", idx); + sprintf(tty_slave, "/dev/ttyp%d", idx); + if (access(tty_slave, F_OK) < 0) { idx = 256; break; } + if ((ptyfd = open (pty_master, O_RDWR)) >= 0) + { if (access (tty_slave, R_OK|W_OK) == 0) break; + close(ptyfd); ptyfd = -1; + } + } + } +#endif + if (ptyfd < 0) { /// \FIXME Linux, Trouble on other systems? + for (const char* s3 = "pqrstuvwxyzabcde"; *s3 != 0; s3++) { + for (const char* s4 = "0123456789abcdef"; *s4 != 0; s4++) { + sprintf(pty_master,"/dev/pty%c%c",*s3,*s4); + sprintf(tty_slave,"/dev/tty%c%c",*s3,*s4); + if ((ptyfd = open(pty_master, O_RDWR)) >= 0) { + if (geteuid() == 0 || access(tty_slave, R_OK|W_OK) == 0) + break; + + close(ptyfd); + ptyfd = -1; + } + } + + if (ptyfd >= 0) + break; + } + } + + if (ptyfd >= 0) { + if (needGrantPty && !chownpty(ptyfd, TRUE)) { + fprintf(stderr,"kdevelop: chownpty failed for device %s::%s.\n",pty_master,tty_slave); + fprintf(stderr," : This means the session can be eavesdroped.\n"); + fprintf(stderr," : Make sure konsole_grantpty is installed and setuid root.\n"); + } + + ::fcntl(ptyfd, F_SETFL, O_NDELAY); +#ifdef TIOCSPTLCK + int flag = 0; + ioctl(ptyfd, TIOCSPTLCK, &flag); // unlock pty +#endif + } + + return ptyfd; +} + +// ************************************************************************** + +void STTY::OutReceived(int f) +{ + char buf[1024]; + int n; + + // read until socket is empty. We shouldn't be receiving a continuous + // stream of data, so the loop is unlikely to cause problems. + while ((n = ::read(f, buf, sizeof(buf)-1)) > 0) { + *(buf+n) = 0; // a standard string + if ( f == fout ) + emit OutOutput(buf); + else + emit ErrOutput(buf); + } +} + +// ************************************************************************** + +#define FIFO_FILE "/tmp/debug_tty.XXXXXX" + +bool STTY::findExternalTTY(const QString &termApp) +{ + QString appName(termApp.isEmpty() ? QString("xterm") : termApp); + + char fifo[] = FIFO_FILE; + int fifo_fd; + if ((fifo_fd = mkstemp(fifo)) == -1) + return false; + + ::close(fifo_fd); + ::unlink(fifo); + + // create a fifo that will pass in the tty name +#ifdef HAVE_MKFIFO + if (::mkfifo(fifo, S_IRUSR|S_IWUSR) < 0) +#else + if (::mknod(fifo, S_IFIFO | S_IRUSR|S_IWUSR, 0) < 0) +#endif + return false; + + int pid = ::fork(); + if (pid < 0) { // No process + ::unlink(fifo); + return false; + } + + if (pid == 0) { // child process + /* + * Spawn a console that in turn runs a shell script that passes us + * back the terminal name and then only sits and waits. + */ + + const char* prog = appName.latin1(); + QString script = QString("tty>") + QString(fifo) + + QString(";" // fifo name + "trap \"\" INT QUIT TSTP;" // ignore various signals + "exec<&-;exec>&-;" // close stdin and stdout + "while :;do sleep 3600;done"); + const char* scriptStr = script.latin1(); + const char* end = 0; + + if ( termApp == "konsole" ) + { + ::execlp( prog, prog, + "-caption", i18n("kdevelop: Debug application console").local8Bit().data(), + "-e", "sh", + "-c", scriptStr, + end); + } + else + { + ::execlp( prog, prog, + "-e", "sh", + "-c", scriptStr, + end); + } + + // Should not get here, as above should always work + ::exit(1); + } + + // parent process + if (pid <= 0) + ::exit(1); + + // Open the communication between us (the parent) and the + // child (the process running on a tty console) + fifo_fd = ::open(fifo, O_RDONLY); + if (fifo_fd < 0) + return false; + + // Get the ttyname from the fifo buffer that the child process + // has sent. + char ttyname[50]; + int n = ::read(fifo_fd, ttyname, sizeof(ttyname)-sizeof(char)); + + ::close(fifo_fd); + ::unlink(fifo); + + // No name?? + if (n <= 0) + return false; + + // remove whitespace + ttyname[n] = 0; + if (char* newline = strchr(ttyname, '\n')) + *newline = 0; // clobber the new line + + ttySlave = ttyname; + pid_ = pid; + + return true; +} + +} + +// ************************************************************************** +#include "stty.moc" diff --git a/languages/ruby/debugger/stty.h b/languages/ruby/debugger/stty.h new file mode 100644 index 00000000..31c8fbfe --- /dev/null +++ b/languages/ruby/debugger/stty.h @@ -0,0 +1,71 @@ +/*************************************************************************** + begin : Mon Sep 13 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + This code was originally written by Judin Maxim, from the + KDEStudio project. + + It was then updated with later code from konsole (KDE). + + It has also been enhanced with an idea from the code in kdbg + written by Johannes Sixt<Johannes.Sixt@telecom.at> + + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _STTY_H_ +#define _STTY_H_ + +class QSocketNotifier; + +#include <qobject.h> +#include <qstring.h> + +namespace RDBDebugger +{ + +class STTY : public QObject +{ + Q_OBJECT + +public: + STTY(bool ext=false, const QString &termAppName=QString()); + ~STTY(); + + QString getSlave() { return ttySlave; }; + +private slots: + void OutReceived(int); + +signals: + void OutOutput(const char *); + void ErrOutput(const char*); + +private: + int findTTY(); + bool findExternalTTY(const QString &termApp); + +private: + int fout; + int ferr; + QSocketNotifier *out; + QSocketNotifier *err; + QString ttySlave; + int pid_; + + char pty_master[50]; // "/dev/ptyxx" | "/dev/ptmx" + char tty_slave[50]; // "/dev/ttyxx" | "/dev/pts/########..." +}; + +} + +#endif diff --git a/languages/ruby/debugger/variablewidget.cpp b/languages/ruby/debugger/variablewidget.cpp new file mode 100644 index 00000000..0dbdce9a --- /dev/null +++ b/languages/ruby/debugger/variablewidget.cpp @@ -0,0 +1,1018 @@ +// ************************************************************************** +// begin : Sun Aug 8 1999 +// copyright : (C) 1999 by John Birch +// email : jbb@kdevelop.org +// +// Adapted for ruby debugging +// -------------------------- +// begin : Mon Nov 1 2004 +// copyright : (C) 2004 by Richard Dale +// email : Richard_Dale@tipitina.demon.co.uk +// ************************************************************************** + +// ************************************************************************** +// * * +// * This program is free software; you can redistribute it and/or modify * +// * it under the terms of the GNU General Public License as published by * +// * the Free Software Foundation; either version 2 of the License, or * +// * (at your option) any later version. * +// * * +// ************************************************************************** + +#include "variablewidget.h" +#include "rdbparser.h" +#include "rdbcommand.h" + +#include <kdebug.h> +#include <kpopupmenu.h> +#include <klineedit.h> +#include <kdeversion.h> + +#include <qheader.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qhbox.h> +#include <qpainter.h> +#include <qpushbutton.h> +#include <qregexp.h> +#include <qcursor.h> +#include <klocale.h> + +#include <qpoint.h> +#include <qclipboard.h> +#include <kapplication.h> + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +namespace RDBDebugger +{ + +VariableWidget::VariableWidget(QWidget *parent, const char *name) + : QWidget(parent, name) +{ + varTree_ = new VariableTree(this); + QLabel *label = new QLabel(i18n("E&xpression to watch:"), this); + + QHBox *watchEntry = new QHBox( this ); + watchVarEditor_ = new KHistoryCombo( watchEntry, "var-to-watch editor"); + label->setBuddy(watchVarEditor_); + + QPushButton *addButton = new QPushButton(i18n("&Add"), watchEntry ); + addButton->adjustSize(); + addButton->setFixedWidth(addButton->width()); + + QBoxLayout * vbox = new QVBoxLayout(); + vbox->addWidget( label ); + vbox->addWidget( watchEntry ); + + QVBoxLayout *topLayout = new QVBoxLayout(this, 2); + topLayout->addWidget(varTree_, 10); + topLayout->addLayout( vbox ); + + connect( addButton, SIGNAL(clicked()), SLOT(slotAddWatchExpression()) ); + connect( watchVarEditor_, SIGNAL(returnPressed()), SLOT(slotAddWatchExpression()) ); +} + + +// ************************************************************************** + +void VariableWidget::setEnabled(bool bEnabled) +{ + QWidget::setEnabled(bEnabled); + if (bEnabled && parentWidget() != 0) { + varTree_->setColumnWidth(0, parentWidget()->width()/2); + } +} +// ************************************************************************** + +void VariableWidget::slotAddWatchExpression() +{ + QString watchVar(watchVarEditor_->currentText()); + if (!watchVar.isEmpty()) { + slotAddWatchExpression(watchVar); + } +} + +// ************************************************************************** + +void VariableWidget::slotAddWatchExpression(const QString &ident) +{ + if (!ident.isEmpty()) { + watchVarEditor_->addToHistory(ident); + varTree_->slotAddWatchExpression(ident); + watchVarEditor_->clearEdit(); + } +} + +// ************************************************************************** + +void VariableWidget::focusInEvent(QFocusEvent */*e*/) +{ + varTree_->setFocus(); +} + +void VariableWidget::restorePartialProjectSession(const QDomElement* el) +{ + varTree_->watchRoot()->restorePartialProjectSession(el); +} + +void VariableWidget::savePartialProjectSession(QDomElement* el) +{ + varTree_->watchRoot()->savePartialProjectSession(el); +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +VariableTree::VariableTree(VariableWidget *parent, const char *name) + : KListView(parent, name), + QToolTip( viewport() ), + activationId_(0), + currentThread_(-1), + selectedFrame_(0), + watchRoot_(0), + globalRoot_(0) +{ + setRootIsDecorated(true); + setAllColumnsShowFocus(true); + setColumnWidthMode(0, Manual); + setSorting(VAR_NAME_COLUMN); + QListView::setSelectionMode(QListView::Single); + + addColumn(i18n("Variable"), 100 ); + addColumn(i18n("Value"), 100 ); + + connect( this, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)), + SLOT(slotContextMenu(KListView*, QListViewItem*)) ); + + connect( this, SIGNAL(pressed(QListViewItem*)), + this, SLOT(slotPressed(QListViewItem*)) ); + + watchRoot_ = new WatchRoot(this); +} + +// ************************************************************************** + +VariableTree::~VariableTree() +{ +} + +// ************************************************************************** + +void VariableTree::clear() +{ + QListViewItem *sibling = firstChild(); + while (sibling != 0) { + QListViewItem * current = sibling; + sibling = sibling->nextSibling(); + if (current->rtti() != RTTI_WATCH_ROOT) { + delete current; + } + } + + globalRoot_ = 0; + selectedFrame_ = 0; + return; +} + +// ************************************************************************** + +void VariableTree::slotContextMenu(KListView *, QListViewItem *item) +{ + if (item == 0) + return; + + setSelected(item, true); // Need to select this item. + + if (item->parent() != 0) { + KPopupMenu popup(this); + popup.insertTitle(item->text(VAR_NAME_COLUMN)); + int idRemoveWatch = -2; + if (item->rtti() == RTTI_WATCH_VAR_ITEM) { + idRemoveWatch = popup.insertItem( i18n("Remove Watch Expression") ); + } + + int idCopyToClipboard = popup.insertItem( i18n("Copy to Clipboard") ); + int res = popup.exec(QCursor::pos()); + + if (res == idRemoveWatch) { + emit removeWatchExpression(((WatchVarItem*)item)->displayId()); + delete item; + } else if (res == idCopyToClipboard) { + QClipboard *qb = KApplication::clipboard(); + QString text = "{ \"" + item->text( VAR_NAME_COLUMN ) + "\", " + + "\"" + item->text( VALUE_COLUMN ) + "\" }"; + + qb->setText( text, QClipboard::Clipboard ); + } + } +} + +/***************************************************************************/ + +void VariableTree::setSelected(QListViewItem * item, bool selected) +{ + // Save the last selected VarFrameRoot for slotPressed() to restore + if (item->rtti() == RTTI_VAR_FRAME_ROOT && selected) { + selectedFrame_ = (VarFrameRoot *) item; + } + + QListView::setSelected(item, selected); +} + +/***************************************************************************/ + +// Makes sure that only VarFrameRoot items can be selected +void VariableTree::slotPressed(QListViewItem * item) +{ + if (item == 0) { + return; + } + + while (item->rtti() == RTTI_VAR_ITEM) { + item = item->parent(); + } + + if ( item->rtti() == RTTI_GLOBAL_ROOT + || item->rtti() == RTTI_WATCH_ROOT + || item->rtti() == RTTI_WATCH_VAR_ITEM ) + { + if (selectedFrame_ != 0) { + setSelected(selectedFrame_, true); + } + return; + } + + if (item->rtti() == RTTI_VAR_FRAME_ROOT) { + VarFrameRoot * frame = (VarFrameRoot*) item; + emit selectFrame(frame->frameNo(), frame->threadNo()); + } + + return; +} + +// ************************************************************************** + +void VariableTree::prune() +{ + QListViewItem *child = firstChild(); + + while (child != 0) { + QListViewItem *nextChild = child->nextSibling(); + + // Only prune var frames, not the watch or global root + if (child->rtti() == RTTI_VAR_FRAME_ROOT) { + if (((VarFrameRoot*) child)->isActive()) { + if (child->isOpen()) { + ((VarFrameRoot*) child)->prune(); + } + } else { + delete child; + } + } + + child = nextChild; + } +} + +// ************************************************************************** + +// The debugger has moved onto the next program pause, so invalidate +// everything in the Variable Tree +void VariableTree::nextActivationId() +{ + activationId_++; + globalRoot()->setActivationId(); + watchRoot()->setActivationId(); + // ..but that's only the Watch and Global roots +} + +// ************************************************************************** + +// VarFrameRoot frames in the Variable Tree from the previous program pause, +// are set active here. Notified by the Frame Stack widget when it parses the +// backtrace from the 'where' command after a pause. +// +// After that, any frames which aren't marked as active must have gone +// out of scope and will end up pruned. +void VariableTree::slotFrameActive(int frameNo, int threadNo, const QString& frameName) +{ + VarFrameRoot * frame = findFrame(frameNo, threadNo); + if (frameNo == 1) { + // If the current frame 1 doesn't exist, create it + if (frame == 0) { + frame = new VarFrameRoot(this, frameNo, threadNo); + } + + frame->setFrameName(frameName); + } + + if (frame != 0 && frame->text(VAR_NAME_COLUMN) == frameName) { + frame->setActivationId(); + } +} + +// ************************************************************************** + +bool VariableTree::schedule() +{ + QListViewItem * child = firstChild(); + VarFrameRoot * frame = 0; + + while (child != 0) { + if (child->rtti() == RTTI_VAR_FRAME_ROOT) { + frame = (VarFrameRoot *) child; + Q_ASSERT( !frame->isWaitingForData() ); + + if (frame->needsVariables()) { + if (QApplication::overrideCursor() == 0) { + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + } + + // Tell the controller to fetch the variable values + emit selectFrame(frame->frameNo(), frame->threadNo()); + return true; + } + } + + child = child->nextSibling(); + } + + frame = findFrame(1, currentThread_); + Q_ASSERT( frame != 0 ); + Q_ASSERT( !frame->needsVariables() ); + + // All over, nothing left to fetch. + // Return to frame 1, and prune the inactive items + // from the variable tree.. + QApplication::restoreOverrideCursor(); + emit selectFrame(1, currentThread_); + prune(); + + return false; +} + +// ************************************************************************** + +void VariableTree::slotAddWatchExpression(const QString &watchVar) +{ + new WatchVarItem(watchRoot(), watchVar, UNKNOWN_TYPE); + emit addWatchExpression(watchVar, true); +} + + +// ************************************************************************** + +void VariableTree::setFetchGlobals(bool fetch) +{ + emit fetchGlobals(fetch); +} + +// ************************************************************************** + +VarFrameRoot *VariableTree::findFrame(int frameNo, int threadNo) const +{ + // frames only exist on the top level so we only need to + // check the siblings + QListViewItem *sibling = firstChild(); + while (sibling != 0) { + if ( sibling->rtti() == RTTI_VAR_FRAME_ROOT + && ((VarFrameRoot*) sibling)->frameNo() == frameNo + && ((VarFrameRoot*) sibling)->threadNo() == threadNo ) + { + return (VarFrameRoot*) sibling; + } + + sibling = sibling->nextSibling(); + } + + return 0; +} + +// ************************************************************************** + +WatchRoot *VariableTree::watchRoot() +{ + return watchRoot_; +} + +// ************************************************************************** + +GlobalRoot *VariableTree::globalRoot() +{ + if (globalRoot_ == 0) { + globalRoot_ = new GlobalRoot(this); + } + + return globalRoot_; +} + +// ************************************************************************** + +// Watch variables can be added before the start of a debugging session, +// so tell the controller about any already in the tree at start. +void VariableTree::resetWatchVars() +{ + for (QListViewItem *child = watchRoot()->firstChild(); child != 0; child = child->nextSibling()) { + ((WatchVarItem*) child)->setDisplayId(-1); + emit addWatchExpression(child->text(VAR_NAME_COLUMN), false); + } +} + +// ************************************************************************** + +void VariableTree::maybeTip(const QPoint &p) +{ + VarItem * item = dynamic_cast<VarItem*>( itemAt(p) ); + if (item != 0) { + QRect r = itemRect(item); + if (r.isValid()) { + tip(r, item->tipText()); + } + } +} + + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +LazyFetchItem::LazyFetchItem(VariableTree *parent) + : KListViewItem(parent), + activationId_(0), + waitingForData_(false) +{ + setActivationId(); +} + +// ************************************************************************** + +LazyFetchItem::LazyFetchItem(LazyFetchItem *parent) + : KListViewItem(parent), + activationId_(0), + waitingForData_(false) +{ + setActivationId(); +} + +// ************************************************************************** + +LazyFetchItem::~LazyFetchItem() +{ +} + +// ************************************************************************** + +void LazyFetchItem::paintCell(QPainter *p, const QColorGroup &cg, + int column, int width, int align) +{ + if (p == 0) { + return; + } + + // make toplevel item (watch and frame items) names bold + if (column == VAR_NAME_COLUMN && parent() == 0) { + QFont f = p->font(); + f.setBold(true); + p->setFont(f); + } + + QListViewItem::paintCell( p, cg, column, width, align ); +} + +// ************************************************************************** + +VarItem *LazyFetchItem::findItem(const QString &name) const +{ + QListViewItem *child = firstChild(); + + // Check the siblings on this branch + while (child != 0) { + if (child->text(VAR_NAME_COLUMN) == name) { + return (VarItem*) child; + } + + child = child->nextSibling(); + } + + return 0; +} + +// ************************************************************************** + +void LazyFetchItem::prune() +{ + QListViewItem *child = firstChild(); + + while (child != 0) { + LazyFetchItem *item = (LazyFetchItem*) child; + child = child->nextSibling(); + // Never prune a branch if we are waiting on data to arrive. + if (!waitingForData_) { + if (item->isActive()) { + item->prune(); + } else { + delete item; + } + } + } +} + + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +VarItem::VarItem(LazyFetchItem *parent, const QString &varName, DataType dataType) + : LazyFetchItem (parent), + cache_(QCString()), + dataType_(dataType), + highlight_(false) +{ + setText(VAR_NAME_COLUMN, varName); + setSelectable(false); + + // Order the VarItems so that globals are first, then + // constants, class variables, instance variables and + // finally local variables + + // Matches either an array element or a string slice, + // Order on the array index or the first number in the + // range specifying the slice. + QRegExp arrayelement_re("\\[(\\d+)(\\.\\.\\d+)?\\]"); + key_ = varName; + + if (arrayelement_re.search(varName) != -1) { + key_.sprintf("%.6d", arrayelement_re.cap(1).toInt()); + } else if (key_.startsWith("$")) { + key_.prepend("1001"); // Global variable + } else if (QRegExp("^[A-Z]").search(varName) != -1) { + key_.prepend("1002"); // Constant + } else if (key_.startsWith("@@")) { + key_.prepend("1003"); // Class variable + } else if (key_.startsWith("@")) { + key_.prepend("1004"); // Instance variable + } else { + key_.prepend("1005"); // Local variable or parameter + } + +// kdDebug(9012) << " ### VarItem::VarItem *CONSTR* " << varName << endl; +} + +// ************************************************************************** + +VarItem::~VarItem() +{ +} + +QString VarItem::key(int /*column*/, bool /*ascending*/) const +{ + return key_; +} + +// ************************************************************************** + +// Returns the path of a ruby item. If it is an instance variable, assume +// that there is an attr_accessor method for it. +// For example, @foobar within instance obj is accessed as obj.foobar. +// But don't strip off the @ for an instance variable with no path, +// and leave a plain '@foobar' as it is. +QString VarItem::fullName() const +{ + QString itemName = text(VAR_NAME_COLUMN); + QString vPath(""); + const VarItem *item = this; + + if (item->parent()->rtti() != RTTI_VAR_ITEM) { + return itemName; + } + + // This stops at the root item (FrameRoot or GlobalRoot) + while (item->rtti() == RTTI_VAR_ITEM) { + QString itemName = item->text(VAR_NAME_COLUMN); + + if (vPath.startsWith("[")) { + // If it's a Hash or an Array, then just insert the value. As + // in adding '[0]' to foo.bar to give foo.bar[0] + vPath.prepend(itemName); + } else { + if (vPath.isEmpty()) { + vPath = itemName; + } else { + vPath.prepend(itemName + "."); + } + } + item = (VarItem*) item->parent(); + } + + // Change 'self.@foobar' to '@foobar' + vPath.replace(QRegExp("^self\\.@"), "@"); + + // Use instance_variable_get() to access any '@var's in the middle of a path + QRegExp re_instance_var("\\.(@[^\\[.]+)"); + int pos = re_instance_var.search(vPath); + while (pos != -1) { + vPath.replace( pos, + re_instance_var.matchedLength(), + QString(".instance_variable_get(:") + re_instance_var.cap(1) + ")" ); + pos = re_instance_var.search(vPath, pos); + } + + return vPath; +} + +// ************************************************************************** + +void VarItem::setText(int column, const QString &data) +{ + setActivationId(); + + if (column == VALUE_COLUMN) { + highlight_ = (!text(VALUE_COLUMN).isEmpty() && text(VALUE_COLUMN) != data); + } + + QListViewItem::setText(column, data); + repaint(); +} + +// ************************************************************************** + +void VarItem::expandValue(char *buf) +{ + LazyFetchItem::stopWaitingForData(); + RDBParser::parseExpandedVariable(this, buf); +} + +// ************************************************************************** + +void VarItem::setOpen(bool open) +{ + QListViewItem::setOpen(open); + + Q_ASSERT( dataType_ == REFERENCE_TYPE + || dataType_ == ARRAY_TYPE + || dataType_ == HASH_TYPE + || dataType_ == STRING_TYPE + || dataType_ == STRUCT_TYPE ); + + update(); + return; +} + +// ************************************************************************** + +void VarItem::update() +{ + if (isOpen()) { + startWaitingForData(); +// emit ((VariableTree*)listView())->expandItem(this, fullName().latin1()); + ((VariableTree*)listView())->expandItem(this, fullName().latin1()); + } + + return; +} + +// ************************************************************************** + +DataType VarItem::dataType() const +{ + return dataType_; +} + +// ************************************************************************** + +void VarItem::setDataType(DataType dataType) +{ + dataType_ = dataType; +} + +// ************************************************************************** + +// Overridden to highlight the changed items +void VarItem::paintCell(QPainter *p, const QColorGroup &cg, + int column, int width, int align) +{ + if (p == 0) { + return; + } + + if (column == VALUE_COLUMN) { + // Show color values as colors, and make the text color the same + // as the base color + if (dataType_ == COLOR_TYPE) { + QRegExp color_re("\\s(#.*)>"); + + if (color_re.search(text(column)) != -1) { + QColorGroup color_cg( cg.foreground(), cg.background(), + cg.light(), cg.dark(), cg.mid(), + QColor(color_re.cap(1)), QColor(color_re.cap(1)) ); + QListViewItem::paintCell(p, color_cg, column, width, align); + return; + } + } + + // Highlight recently changed items in red + if (highlight_) { + QColorGroup hl_cg( cg.foreground(), cg.background(), + cg.light(), cg.dark(), cg.mid(), + red, cg.base() ); + QListViewItem::paintCell(p, hl_cg, column, width, align); + return; + } + } + + QListViewItem::paintCell(p, cg, column, width, align); + return; +} + +// ************************************************************************** + +QString VarItem::tipText() const +{ + const unsigned int MAX_TOOLTIP_SIZE = 70; + QString tip = text(VALUE_COLUMN); + + if (tip.length() < MAX_TOOLTIP_SIZE) { + return tip; + } else { + return tip.mid(0, MAX_TOOLTIP_SIZE - 1) + " [...]"; + } +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +VarFrameRoot::VarFrameRoot(VariableTree *parent, int frameNo, int threadNo) + : LazyFetchItem(parent), + needsVariables_(true), + frameNo_(frameNo), + threadNo_(threadNo), + cache_("") +{ + setExpandable(true); +} + +// ************************************************************************** + +VarFrameRoot::~VarFrameRoot() +{ +} + +// ************************************************************************** + +void VarFrameRoot::addLocals(char *variables) +{ + cache_.append(variables); +} + +// ************************************************************************** + +void VarFrameRoot::setLocals() +{ + RDBParser::parseVariables(this, cache_.data()); + cache_ = ""; + needsVariables_ = false; + stopWaitingForData(); + prune(); + + return; +} + +// ************************************************************************** + +// Override setOpen so that we can decide what to do when we do change +// state. +void VarFrameRoot::setOpen(bool open) +{ + bool localsViewChanged = (isOpen() != open); + QListViewItem::setOpen(open); + + if (localsViewChanged) { + ((VariableTree*)listView())->selectFrame(frameNo_, threadNo_); + } + + return; +} + +void VarFrameRoot::setFrameName(const QString &frameName) +{ + setText(VAR_NAME_COLUMN, frameName); + setText(VALUE_COLUMN, ""); + + return; +} + +void VarFrameRoot::setActivationId() +{ + LazyFetchItem::setActivationId(); + stopWaitingForData(); + needsVariables_ = true; + cache_ = ""; +} + +bool VarFrameRoot::needsVariables() const +{ + return ( text(VAR_NAME_COLUMN).contains("try_initialize") == 0 + && isOpen() + && !isWaitingForData() + && needsVariables_ ); +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + + +GlobalRoot::GlobalRoot(VariableTree *parent) + : LazyFetchItem(parent) +{ + setText(0, i18n("Global")); + setExpandable(true); + setOpen(false); + setSelectable(false); +} + +// ************************************************************************** + +GlobalRoot::~GlobalRoot() +{ +} + +// ************************************************************************** + +void GlobalRoot::setGlobals(char * globals) +{ + setActivationId(); + RDBParser::parseVariables(this, globals); + + return; +} + +// ************************************************************************** + +void GlobalRoot::setOpen(bool open) +{ + bool globalsViewChanged = (isOpen() != open); + QListViewItem::setOpen(open); + + if (globalsViewChanged) { + ((VariableTree*)listView())->setFetchGlobals(isOpen()); + } + + return; +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +WatchVarItem::WatchVarItem( LazyFetchItem *parent, const QString &varName, DataType dataType, int displayId ) + : VarItem(parent, varName, dataType), + displayId_(displayId) +{ +} + +// ************************************************************************** + +WatchVarItem::~WatchVarItem() +{ +} + +// ************************************************************************** + +void WatchVarItem::setDisplayId(int id) +{ + displayId_ = id; +} + +// ************************************************************************** + +int WatchVarItem::displayId() +{ + return displayId_; +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +WatchRoot::WatchRoot(VariableTree *parent) + : LazyFetchItem(parent) +{ + setText(VAR_NAME_COLUMN, i18n("Watch")); + setOpen(true); + setSelectable(false); +} + +// ************************************************************************** + +WatchRoot::~WatchRoot() +{ +} + +// ************************************************************************** + +// Sets the initial value of a new Watch item, along with the +// display id +void WatchRoot::setWatchExpression(char * buf, char * expression) +{ + QString expr(expression); + QRegExp display_re("^(\\d+):\\s([^\n]+)\n"); + + for ( QListViewItem *child = firstChild(); + child != 0; + child = child->nextSibling() ) + { + WatchVarItem *varItem = (WatchVarItem*) child; + if ( varItem->text(VAR_NAME_COLUMN) == expr + && varItem->displayId() == -1 + && display_re.search(buf) >= 0 ) + { + varItem->setDisplayId(display_re.cap(1).toInt()); + // Skip over the 'thing = ' part of expr to get the value + varItem->setText( VALUE_COLUMN, + display_re.cap(2).mid(varItem->text(VAR_NAME_COLUMN).length() + strlen(" = ")) ); + return; + } + } +} + +// After a program pause, this updates the new value of a Watch item +// expr is the thing = value part of "1: a = 1", id is the display number +void WatchRoot::updateWatchExpression(int id, const QString& expr) +{ + for ( QListViewItem *child = firstChild(); + child != 0; + child = child->nextSibling() ) + { + WatchVarItem *varItem = (WatchVarItem*) child; + if (varItem->displayId() == id) { + Q_ASSERT( expr.startsWith(varItem->text(VAR_NAME_COLUMN)) ); + // Skip over the 'thing = ' part of expr to get the value + varItem->setText( VALUE_COLUMN, + expr.mid(varItem->text(VAR_NAME_COLUMN).length() + strlen(" = ")) ); + return; + } + } +} + +void WatchRoot::savePartialProjectSession(QDomElement* el) +{ + QDomDocument domDoc = el->ownerDocument(); + if (domDoc.isNull()) { + return; + } + + QDomElement watchEl = domDoc.createElement("watchExpressions"); + + for ( QListViewItem *child = firstChild(); + child != 0; + child = child->nextSibling() ) + { + QDomElement subEl = domDoc.createElement("el"); + subEl.appendChild(domDoc.createTextNode(child->text(VAR_NAME_COLUMN))); + watchEl.appendChild(subEl); + } + + if (!watchEl.isNull()) { + el->appendChild(watchEl); + } + + return; +} + +void WatchRoot::restorePartialProjectSession(const QDomElement* el) +{ + QDomDocument domDoc = el->ownerDocument(); + if (domDoc.isNull()) { + return; + } + + QDomElement watchEl = el->namedItem("watchExpressions").toElement(); + QDomElement subEl = watchEl.firstChild().toElement(); + + while (!subEl.isNull()) { + new WatchVarItem(this, subEl.firstChild().toText().data(), UNKNOWN_TYPE); + + subEl = subEl.nextSibling().toElement(); + } + + return; +} + +// ************************************************************************** +// ************************************************************************** +// ************************************************************************** + +} + + +#include "variablewidget.moc" + diff --git a/languages/ruby/debugger/variablewidget.h b/languages/ruby/debugger/variablewidget.h new file mode 100644 index 00000000..c608ec45 --- /dev/null +++ b/languages/ruby/debugger/variablewidget.h @@ -0,0 +1,348 @@ +/*************************************************************************** + begin : Sun Aug 8 1999 + copyright : (C) 1999 by John Birch + email : jbb@kdevelop.org + + Adapted for ruby debugging + -------------------------- + begin : Mon Nov 1 2004 + copyright : (C) 2004 by Richard Dale + email : Richard_Dale@tipitina.demon.co.uk + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _VARIABLEWIDGET_H_ +#define _VARIABLEWIDGET_H_ + +#include "rdbcontroller.h" + +#include <klistview.h> +#include <kcombobox.h> +#include <qwidget.h> +#include <qtooltip.h> +#include <kdebug.h> + +class KLineEdit; + +namespace RDBDebugger +{ + +class LazyFetchItem; +class VarFrameRoot; +class GlobalRoot; +class WatchRoot; +class VarItem; +class VariableTree; +class DbgController; +class Breakpoint; + +enum { + VAR_NAME_COLUMN = 0, + VALUE_COLUMN = 1 +}; + +enum DataType { + UNKNOWN_TYPE, + VALUE_TYPE, + REFERENCE_TYPE, + ARRAY_TYPE, + HASH_TYPE, + STRUCT_TYPE, + COLOR_TYPE, + STRING_TYPE +}; + +class VariableWidget : public QWidget +{ + Q_OBJECT + +public: + VariableWidget( QWidget *parent=0, const char *name=0 ); + + VariableTree *varTree() const + { return varTree_; } + + virtual void setEnabled(bool b); + + void restorePartialProjectSession(const QDomElement* el); + void savePartialProjectSession(QDomElement* el); + +protected: + virtual void focusInEvent(QFocusEvent *e); + +public slots: + void slotAddWatchExpression(); + void slotAddWatchExpression(const QString &expr); + +private: + VariableTree *varTree_; + KHistoryCombo *watchVarEditor_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class VariableTree : public KListView, public QToolTip +{ + Q_OBJECT +//we need this to be able to emit expandItem() from within LazyFetchItem +friend class LazyFetchItem; + +public: + VariableTree( VariableWidget *parent, const char *name=0 ); + virtual ~VariableTree(); + + // Clear everything but the Watch frame + void clear(); + + int activationId() const { return activationId_; } + void nextActivationId(); + + VarFrameRoot *findFrame(int frameNo, int threadNo) const; + + GlobalRoot *globalRoot(); + WatchRoot *watchRoot(); + + void resetWatchVars(); + void setCurrentThread(int currentThread) { currentThread_ = currentThread; } + + // Remove items that are not active + void prune(); + + // Look for a frame where 'needsVariables()' is true. + // If found, send commands to the debugger to fetch + // the variable values. + // Return true if a fetch has been scheduled, otherwise + // false. + bool schedule(); + + // Tell the controller whether or not to fetch the + // values of the global variables + void setFetchGlobals(bool fetch); + + // (from QToolTip) Display a tooltip when the cursor is over an item + virtual void maybeTip(const QPoint &); + + virtual void setSelected(QListViewItem * item, bool selected); + +signals: + void toggleWatchpoint(const QString &varName); + void selectFrame(int frame, int thread); + void expandItem(VarItem *item, const QCString &request); + void fetchGlobals(bool fetch); + void addWatchExpression(const QString& expr, bool execute); + void removeWatchExpression(int displayId); + +public slots: + void slotAddWatchExpression(const QString& watchVar); + void slotFrameActive(int frameNo, int threadNo, const QString& frameName); + void slotPressed(QListViewItem * item); + +private slots: + void slotContextMenu(KListView *, QListViewItem *item); + +private: + int activationId_; + int currentThread_; + VarFrameRoot * selectedFrame_; + + WatchRoot * watchRoot_; + GlobalRoot * globalRoot_; + + friend class VarFrameRoot; + friend class VarItem; + friend class WatchRoot; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class LazyFetchItem : public KListViewItem +{ +public: + LazyFetchItem(VariableTree *parent); + LazyFetchItem(LazyFetchItem *parent); + + virtual ~LazyFetchItem(); + + virtual int rtti() const { return RTTI_LAZY_FETCH_ITEM; } + + virtual void prune(); + virtual VarItem *findItem(const QString& name) const; + + int currentActivationId() const { return ((VariableTree*) listView())->activationId(); } + virtual void setActivationId() { activationId_ = currentActivationId(); } + bool isActive() const { return activationId_ == currentActivationId(); } + + void startWaitingForData() { waitingForData_ = true; } + void stopWaitingForData() { waitingForData_ = false; } + bool isWaitingForData() const { return waitingForData_; } + +protected: + void paintCell( QPainter *p, const QColorGroup &cg, + int column, int width, int align ); + +private: + int activationId_; + bool waitingForData_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class VarItem : public LazyFetchItem +{ +public: + VarItem( LazyFetchItem *parent, const QString &varName, DataType dataType ); + + virtual ~VarItem(); + + virtual int rtti() const { return RTTI_VAR_ITEM; } + virtual QString key(int column, bool ascending) const; + + QString fullName() const; + + DataType dataType() const; + void setDataType(DataType dataType); + + void setOpen(bool open); + void setText (int column, const QString& text); + + // Returns the text to be displayed as tooltip (the value) + QString tipText() const; + + // If the item is open, fetch details via a pp command + void update(); + + // The details from the pp command have arrived, parse them + // and update the UI + void expandValue(char *data); + +private: + void paintCell( QPainter *p, const QColorGroup &cg, + int column, int width, int align ); + +private: + QString key_; + QCString cache_; + DataType dataType_; + bool highlight_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class WatchVarItem : public VarItem +{ +public: + WatchVarItem( LazyFetchItem *parent, const QString &varName, DataType dataType, int displayId = -1); + + virtual ~WatchVarItem(); + + virtual int rtti() const { return RTTI_WATCH_VAR_ITEM; } + + // The id returned by rdb after a display expression is added + void setDisplayId(int id); + int displayId(); + +private: + int displayId_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class VarFrameRoot : public LazyFetchItem +{ +public: + VarFrameRoot(VariableTree *parent, int frame, int thread); + virtual ~VarFrameRoot(); + + virtual int rtti() const { return RTTI_VAR_FRAME_ROOT; } + + virtual QString key(int column, bool /*ascending*/) const { + return QString("%1%2").arg(RTTI_VAR_FRAME_ROOT).arg(text(column)); + } + + void addLocals(char *variables); + void setLocals(); + void setOpen(bool open); + + void setFrameName(const QString &frameName); + + virtual void setActivationId(); + bool needsVariables() const; + + int frameNo() { return frameNo_; } + int threadNo() { return threadNo_; } + +private: + bool needsVariables_; + int frameNo_; + int threadNo_; + QCString cache_; +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class WatchRoot : public LazyFetchItem +{ +public: + WatchRoot(VariableTree * parent); + virtual ~WatchRoot(); + + virtual int rtti() const { return RTTI_WATCH_ROOT; } + + virtual QString key(int column, bool /*ascending*/) const { + return QString("%1%2").arg(RTTI_WATCH_ROOT).arg(text(column)); + } + + void setWatchExpression(char * buf, char * expr); + void updateWatchExpression(int id, const QString& expr); + + void restorePartialProjectSession(const QDomElement* el); + void savePartialProjectSession(QDomElement* el); +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class GlobalRoot : public LazyFetchItem +{ +public: + GlobalRoot(VariableTree * parent); + virtual ~GlobalRoot(); + + virtual int rtti() const { return RTTI_GLOBAL_ROOT; } + + virtual QString key(int column, bool /*ascending*/) const { + return QString("%1%2").arg(RTTI_GLOBAL_ROOT).arg(text(column)); + } + + void setOpen(bool open); + void setGlobals(char * globals); +}; + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +} + +#endif |