diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98b94eb --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +*.elc +*~ +/*-pkg.el +/*.tar.gz +/.byebug_history +/.python-version +/.ruby-version +/README +/aclocal.m4 +/autom4te.cache +/common.mk +/config.log +/config.status +/configure +/configure.lineno +/elpa +/install-sh +/missing +/test/npm-debug.log +/tmp +Makefile +Makefile.in +elc-stamp +elc-temp +script diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..e950187 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,49 @@ +# Note: This makefile include remake-style target comments. +# These comments before the targets start with #: +# remake --tasks to shows the targets and the comments + +SUBDIRS = ipdb test + +GIT2CL ?= git2cl +RUBY ?= ruby + +lispdir = @lispdir@ + +lisp_files = $(wildcard *.el) +lisp_LISP = $(lisp_files) +include common.mk + +PHONY=check clean dist distclean test check-short check-terse install-short + +EXTRA_DIST = common.mk.in INSTALL.md README.md THANKS $(lisp_files) + +if MAINTAINER_MODE + +#: Remove change log: ChangeLog +rmChangeLog: + rm ChangeLog || true + +#: Create a ChangeLog file from git logs +ChangeLog: rmChangeLog + git log --pretty --numstat --summary | $(GIT2CL) > $@ + +ACLOCAL_AMFLAGS=-I . + +endif + +#: Run all tests +test: check + +#: Run all tests without bloated output +check-short: + $(MAKE) check 2>&1 | $(RUBY) test/make-check-filter.rb + +#: Run all tests without and show just the failure lines +check-terse: + $(MAKE) check 2>&1 | $(RUBY) tes/make-check-filter.rb | grep failure + +#: Run "make install" non-verbosely +install-short: + $(MAKE) install 2>&1 | $(RUBY) test/make-check-filter.rb + +.PHONY: test check check-short rmChangeLog diff --git a/README.md b/README.md new file mode 100644 index 0000000..576a8f3 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Emacs Lisp Module to add [ipdb](https://pypi.org/project/ipdb/) support to [realgud](http://github.com/rocky/emacs-dbgr). diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..7beb4b9 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# An autoconf setup script. +# From inside emacs, make sure test-simple is installed and then +# Press C-x C-e at the end of the next line run this script +# (test-simple-run "EMACSLOADPATH=%s ./autogen.sh" (mapconcat 'identity load-path ":")) +ln -fs README.md README +touch common.mk +autoreconf -vfi && \ +autoconf && { + echo "Running configure with --enable-maintainer-mode $@" + ./configure --enable-maintainer-mode $@ +} diff --git a/common.mk.in b/common.mk.in new file mode 100644 index 0000000..88f3a9d --- /dev/null +++ b/common.mk.in @@ -0,0 +1,24 @@ +MOSTLYCLEANFILES = *.elc +EMACSLOADPATH=@EMACSLOADPATH@ + +short: + $(MAKE) 2>&1 >/dev/null | ruby $(top_srcdir)/make-check-filter.rb + +%.short: + $(MAKE) $(@:.short=) 2>&1 >/dev/null + +# This is the default target but We need to include an EMACSLOADPATH +.el.elc: + if test "$(EMACS)" != "no"; then \ + am__dir=. am__subdir_includes=''; \ + case $@ in */*) \ + am__dir=`echo '$@' | sed 's,/[^/]*$$,,'`; \ + am__subdir_includes="-L $$am__dir -L $(srcdir)/$$am__dir"; \ + esac; \ + test -d "$$am__dir" || $(MKDIR_P) "$$am__dir" || exit 1; \ + EMACSLOADPATH=$(EMACSLOADPATH) $(EMACS) --batch \ + $(AM_ELCFLAGS) --eval "(progn(package-initialize))" \ + $$am__subdir_includes -L $(builddir) -L $(srcdir) \ + --eval "(defun byte-compile-dest-file (f) \"$@\")" \ + --eval "(unless (byte-compile-file \"$<\") (kill-emacs 1))"; \ + else :; fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..000c4e9 --- /dev/null +++ b/configure.ac @@ -0,0 +1,55 @@ +dnl FIXME: pick up from realgud.el +AC_INIT(realgud-ipdb, 1.0.0,) +AC_CONFIG_SRCDIR(ipdb/ipdb.el) +AM_INIT_AUTOMAKE([foreign]) +AM_MAINTAINER_MODE + +AC_PATH_PROG([EMACS], [emacs], [emacs]) +AC_ARG_WITH(emacs, AC_HELP_STRING([--with-emacs], + [location of emacs program]), EMACS=$withval) + +AC_MSG_NOTICE("Checking emacs version") +$EMACS -batch -q --no-site-file -eval \ + '(if (<= emacs-major-version 23) + (progn + (error "You need GNU Emacs 24 or better.") + (kill-emacs 1) + ) + )' +if test $? -ne 0 ; then + AC_MSG_ERROR([Can't continue until above error is corrected.]) +fi + +################################################################## +# See if --with-lispdir was set. If not, set it to a reasonable default +# based on where bash thinks bashdb is supposed to be installed. +################################################################## + +AM_MISSING_PROG(GIT2CL, git2cl, $missing_dir) + +# Check whether --with-lispdir was given. +if test "${with_lispdir+set}" = set; then : +else + my_lispdir=$(EMACS_PROG=$EMACS $SH_PROG $(dirname $0)/compute-lispdir.sh) + if test "${my_lispdir+set}" = set; then : + with_lispdir=$my_lispdir + echo "'compute-lispdir.sh' lispdir install directory override: '$with_lispdir'" + fi +fi + +## +## Find out where to install the debugger emacs lisp files +## +AM_PATH_LISPDIR +lispdir_realgud=$lispdir/realgud +AC_SUBST([lispdir]) +AC_SUBST([lispdir_realgud]) + +AM_CONDITIONAL(INSTALL_EMACS_LISP, test "x$lispdir_realgud" != "x") + +AC_CONFIG_FILES([Makefile \ + common.mk \ + ipdb/Makefile \ + test/Makefile \ + ]) +AC_OUTPUT diff --git a/ipdb/Makefile.am b/ipdb/Makefile.am new file mode 100644 index 0000000..e8453c0 --- /dev/null +++ b/ipdb/Makefile.am @@ -0,0 +1,5 @@ +lisp_files = $(wildcard *.el) +EXTRA_DIST=$(lisp_files) +lisp_LISP = $(lisp_files) +include ../common.mk +lispdir = @lispdir@/lldb diff --git a/ipdb/core.el b/ipdb/core.el new file mode 100644 index 0000000..c505ebc --- /dev/null +++ b/ipdb/core.el @@ -0,0 +1,249 @@ +;; Copyright (C) 2016-2017, 2019 Free Software Foundation, Inc + +;; Author: Sean Farley , Rocky Bernstein (rocky@gnu.org) + +;; 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 3 of the License, or +;; (at your option) any later version. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + + +(require 'comint) +(require 'realgud) + +(declare-function realgud-lang-mode? 'realgud-lang) +(declare-function realgud-parse-command-arg 'realgud-core) +(declare-function realgud-query-cmdline 'realgud-core) +(declare-function realgud-suggest-invocation 'realgud-core) +(declare-function realgud-get-cmdbuf 'realgud-buffer-helper) + +;; FIXME: I think the following could be generalized and moved to +;; realgud-... probably via a macro. +(defvar realgud:ipdb-minibuffer-history nil + "minibuffer history list for the command `ipdb'.") + +(defvar realgud:ipdb-remote-minibuffer-history nil + "minibuffer history list for the command `ipdb-remote'.") + +(easy-mmode-defmap ipdb-minibuffer-local-map + '(("\C-i" . comint-dynamic-complete-filename)) + "Keymap for minibuffer prompting of debugger startup command." + :inherit minibuffer-local-map) + +;; FIXME: I think this code and the keymaps and history +;; variable chould be generalized, perhaps via a macro. +(defun ipdb-query-cmdline (&optional opt-debugger) + (realgud-query-cmdline + 'ipdb-suggest-invocation + ipdb-minibuffer-local-map + 'realgud:ipdb-minibuffer-history + opt-debugger)) + +;; FIXME: I think this code and the keymaps and history +;; variable chould be generalized, perhaps via a macro. +(defun ipdb-remote-query-cmdline () + (realgud-query-cmdline + 'ipdb-suggest-invocation + ipdb-minibuffer-local-map + 'realgud:ipdb-remote-minibuffer-history + "telnet")) + +(defun ipdb-parse-cmd-args (orig-args) + "Parse command line ORIG-ARGS for the annotate level and name of script to debug. + +ORIG-ARGS should contain a tokenized list of the command line to run. + +We return the a list containing: +* the command processor (e.g. python) and it's arguments if any - a list of strings +* the name of the debugger given (e.g. ipdb) and its arguments - a list of strings +* the script name and its arguments - list of strings +* whether the annotate or emacs option was given ('-A', '--annotate' or '--emacs) - a boolean + +For example for the following input: + (map 'list 'symbol-name + '(python2.6 -O -Qold ./gcd.py a b)) + +we might return: + ((\"python2.6\" \"-O\" \"-Qold\") (\"ipdb\") (\"/tmp/gcd.py\" \"a\" \"b\") nil) + +Note that the script name path has been expanded via `expand-file-name'. +" + + ;; Parse the following kind of pattern: + ;; [python python-options] ipdb ipdb-options script-name script-options + (let ( + (args orig-args) + (pair) ;; temp return from + (python-opt-two-args '()) + ;; Python doesn't have mandatory 2-arg options in our sense, + ;; since the two args can be run together, e.g. "-C/tmp" or "-C /tmp" + ;; + (python-two-args '()) + ;; ipdb doesn't have any arguments + (ipdb-two-args '()) + (ipdb-opt-two-args '()) + (interp-regexp + (if (member system-type (list 'windows-nt 'cygwin 'msdos)) + "^python[-0-9.]*\\(.exe\\)?$" + "^python[-0-9.]*$")) + + ;; Things returned + (annotate-p nil) + (debugger-args '()) + (debugger-name nil) + (interpreter-args '()) + (script-args '()) + (script-name nil) + ) + + (if (not (and args)) + ;; Got nothing: return '(nil, nil) + (list interpreter-args debugger-args script-args annotate-p) + ;; else + ;; Strip off optional "python" or "python182" etc. + (when (string-match interp-regexp + (file-name-sans-extension + (file-name-nondirectory (car args)))) + (setq interpreter-args (list (pop args))) + + ;; Strip off Python-specific options + (while (and args + (string-match "^-" (car args))) + (setq pair (realgud-parse-command-arg + args python-two-args python-opt-two-args)) + (nconc interpreter-args (car pair)) + (setq args (cadr pair)))) + + ;; Remove "ipdb" from "ipdb --ipdb-options script + ;; --script-options" + (setq debugger-name (file-name-sans-extension + (file-name-nondirectory (car args)))) + (unless (string-match "^\\(ipdb\\|cli.py\\)$" debugger-name) + (message + "Expecting debugger name `%s' to be `ipdb' or `cli.py'" + debugger-name)) + (setq debugger-args (list (pop args))) + + ;; Skip to the first non-option argument. + (while (and args (not script-name)) + (let ((arg (car args))) + (cond + ;; Options with arguments. + ((string-match "^-" arg) + (setq pair (realgud-parse-command-arg + args ipdb-two-args ipdb-opt-two-args)) + (nconc debugger-args (car pair)) + (setq args (cadr pair))) + ;; Anything else must be the script to debug. + (t (setq script-name (expand-file-name arg)) + (setq script-args (cons script-name (cdr args)))) + ))) + (list interpreter-args debugger-args script-args annotate-p)))) + +(defun ipdb-parse-remote-cmd-args (orig-args) + "Parse command line ORIG-ARGS +ORIG-ARGS should contain a tokenized list of the command line to run. + +We return the a list containing: +* the command processor (e.g. python) and it's arguments if any - a list of strings +* the name of the debugger given (e.g. ipdb) and its arguments - a list of strings +* the script name and its arguments - list of strings +* nil + +For example for the following input: + (map 'list 'symbol-name + '(telnet localhost 6900)) + +we might return: + ((\"telnet\" \"localhost\" \"6900\") nil nil nil) + +Note that the script name path has been expanded via `expand-file-name'. +" + (list orig-args '("ipdb") nil nil nil) + ) + + ;; To silence Warning: reference to free variable +(defvar realgud:ipdb-command-name) + +(defun ipdb-remote-suggest-invocation (debugger-name) + "Suggest an ipdb command invocation via `realgud-suggest-invocaton'" + "telnet 127.0.0.1 4000") + +(defun ipdb-suggest-invocation (debugger-name) + "Suggest a ipdb command invocation via `realgud-suggest-invocaton'" + (realgud-suggest-invocation (or debugger-name realgud:ipdb-command-name) + realgud:ipdb-minibuffer-history + "python" "\\.py")) + +(defun ipdb-reset () + "Ipdb cleanup - remove debugger's internal buffers (frame, +breakpoints, etc.)." + (interactive) + ;; (ipdb-breakpoint-remove-all-icons) + (dolist (buffer (buffer-list)) + (when (string-match "\\*ipdb-[a-z]+\\*" (buffer-name buffer)) + (let ((w (get-buffer-window buffer))) + (when w + (delete-window w))) + (kill-buffer buffer)))) + +;; (defun ipdb-reset-keymaps() +;; "This unbinds the special debugger keys of the source buffers." +;; (interactive) +;; (setcdr (assq 'ipdb-debugger-support-minor-mode minor-mode-map-alist) +;; ipdb-debugger-support-minor-mode-map-when-deactive)) + +(defconst realgud:ipdb-complete-script + (concat + "from IPython import get_ipython;" + "comp = '''%s''';" + "prefix, candidates = get_ipython().Completer.complete(line_buffer = comp);" + "print(';'.join([prefix] + candidates))")) + +(defun realgud:ipdb-backend-complete () + "Send a command to the ipdb buffer and parse the output. + +The idea here is to rely on the +`comint-redirect-send-command-to-process' function to send a +python command `realgud:ipdb-complete-script' that will return +the completions for the given input." + (interactive) + (let ((buffer (current-buffer)) + (cmdbuf (realgud-get-cmdbuf)) + (process (get-buffer-process (current-buffer))) + (start-pos (save-excursion (comint-goto-process-mark) (point))) + (end-pos (point))) + + ;; get the input string + (when (> end-pos start-pos) + (let* ((input-str (buffer-substring-no-properties start-pos end-pos)) + (command-str (format realgud:ipdb-complete-script input-str)) + (output-str (with-temp-buffer + (comint-redirect-send-command-to-process + command-str (current-buffer) process nil t) + ;; Wait for the process to complete + (with-current-buffer (process-buffer process) + (while (null comint-redirect-completed) + (accept-process-output nil 0 5))) ;; wait 5ms + (buffer-substring (point-min) (1- (point-max))))) + (output-values (split-string output-str ";")) + (prefix (car output-values))) + (list (- end-pos (length prefix)) end-pos (cdr output-values)))))) + +(defun realgud:ipdb-completion-at-point () + (let ((ipdb (realgud:ipdb-backend-complete))) + (when ipdb + (list (nth 0 ipdb) + (nth 1 ipdb) + (nth 2 ipdb) + :exclusive 'yes)))) + +(defun realgud:ipdb-customize () + "Use `customize' to edit the settings of the `ipdb' debugger." + (interactive) + (customize-group 'realgud:ipdb)) + +(provide-me "realgud:ipdb-") diff --git a/ipdb/init.el b/ipdb/init.el new file mode 100644 index 0000000..3a0ac80 --- /dev/null +++ b/ipdb/init.el @@ -0,0 +1,161 @@ +;; Copyright (C) 2016, 2018-2019 Free Software Foundation, Inc + +;; Author: Rocky Bernstein +;; Author: Sean Farley + +;; 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 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . +;; ipdb: "interactive" debugger extension to Python debugger pdb + +(eval-when-compile (require 'cl-lib)) ;For setf. + +(require 'realgud) + +(defvar realgud-pat-hash) +(declare-function make-realgud-loc-pat (realgud-loc)) + +(defvar realgud:ipdb-pat-hash (make-hash-table :test 'equal) + "Hash key is the what kind of pattern we want to match: +backtrace, prompt, etc. The values of a hash entry is a +realgud-loc-pat struct") + +(declare-function make-realgud-loc "realgud-loc" (a b c d e f)) + +;; ------------------------------------------------------------------- +;; User-definable variables +;; + +;; realgud-loc-pat that describes a ipdb location generally shown +;; before a command prompt. +;; +;; Program-location lines look like this: +;; > /usr/bin/zonetab2pot.py(15)() +;; or MS Windows: +;; > c:\\mydirectory\\gcd.py(10) +(setf (gethash "loc" realgud:ipdb-pat-hash) + (make-realgud-loc-pat + :regexp "^> \\(\\(?:[a-zA-Z]:\\)?[-a-zA-Z0-9_/.\\\\ ]+\\)(\\([0-9]+\\))" + :file-group 1 + :line-group 2)) + +;; An initial list of regexps that don't generally have files +;; associated with them and therefore we should not try to find file +;; associations for them. This list is used to seed a field of the +;; same name in the cmd-info structure inside a command buffer. A user +;; may add additional files to the command-buffer's re-ignore-list. +(setf (gethash "ignore-re-file-list" realgud:ipdb-pat-hash) + (list realgud-python-ignore-file-re)) + +(setf (gethash "prompt" realgud:ipdb-pat-hash) + (make-realgud-loc-pat + :regexp "^ipdb[>] " + )) + +;; realgud-loc-pat that describes a Python backtrace line. +(setf (gethash "lang-backtrace" realgud:ipdb-pat-hash) + realgud-python-backtrace-loc-pat) + +(setf (gethash "debugger-backtrace" realgud:ipdb-pat-hash) + realgud:python-trepan-backtrace-pat) + +;; realgud-loc-pat that describes a line a Python "info break" line. +;; For example: +;; 1 breakpoint keep y at /usr/local/bin/trepan3k:7 +(setf (gethash "debugger-breakpoint" realgud:ipdb-pat-hash) + (make-realgud-loc-pat + :regexp (format "^%s[ \t]+\\(breakpoint\\)[ \t]+\\(keep\\|del\\)[ \t]+\\(yes\\|no\\)[ \t]+.*at \\(.+\\):%s" + realgud:regexp-captured-num realgud:regexp-captured-num) + :num 1 + :text-group 2 ;; misnamed Is "breakpoint" or "watchpoint" + :string 3 ;; misnamed. Is "keep" or "del" + :file-group 5 + :line-group 6)) + +;; realgud-loc-pat that describes location in a pytest error +(setf (gethash "pytest-error" realgud:ipdb-pat-hash) + realgud-pytest-error-loc-pat) + +;; realgud-loc-pat that describes location in a flake8 message +(setf (gethash "flake8-msg" realgud:ipdb-pat-hash) + realgud-flake8-msg-loc-pat) + +;; realgud-loc-pat that describes a "breakpoint set" line. For example: +;; Breakpoint 1 at /usr/bin/ipdb:7 +(setf (gethash "brkpt-set" realgud:ipdb-pat-hash) + (make-realgud-loc-pat + :regexp "^Breakpoint \\([0-9]+\\) at[ \t\n]+\\(.+\\):\\([0-9]+\\)\\(\n\\|$\\)" + :num 1 + :file-group 2 + :line-group 3)) + +;; realgud-loc-pat that describes a "delete breakpoint" line +;; Python 3 includes a file name and line number; Python 2 doesn't +(setf (gethash "brkpt-del" realgud:ipdb-pat-hash) + (make-realgud-loc-pat + :regexp "^Deleted breakpoint \\([0-9]+\\)" + :num 1)) + +(setf (gethash "font-lock-keywords" realgud:ipdb-pat-hash) + '( + ;; The frame number and first type name, if present. + ("^\\(->\\|##\\)\\([0-9]+\\) \\(\\)? *\\([a-zA-Z_][a-zA-Z0-9_]*\\)(\\(.+\\))?" + (2 realgud-backtrace-number-face) + (4 font-lock-function-name-face nil t)) ; t means optional. + + ;; Parameter sequence, E.g. gcd(a=3, b=5) + ;; ^^^^^^^^^ + ("(\\(.+\\))" + (1 font-lock-variable-name-face)) + + ;; File name. E.g file '/test/gcd.py' + ;; ------^^^^^^^^^^^^- + ("[ \t]+file '\\([^ ]+*\\)'" + (1 realgud-file-name-face)) + + ;; Line number. E.g. at line 28 + ;; ---------^^ + ("[ \t]+at line \\([0-9]+\\)$" + (1 realgud-line-number-face)) + + ;; Function name. + ("\\<\\([a-zA-Z_][a-zA-Z0-9_]*\\)\\.\\([a-zA-Z_][a-zA-Z0-9_]*\\)" + (1 font-lock-type-face) + (2 font-lock-function-name-face)) + ;; (ipdb-frames-match-current-line + ;; (0 ipdb-frames-current-frame-face append)) + )) + +(setf (gethash "ipdb" realgud-pat-hash) realgud:ipdb-pat-hash) + +(defvar realgud:ipdb-command-hash (make-hash-table :test 'equal) + "Hash key is command name like 'finish' and the value is +the ipdb command to use, like 'return'") + +;; Mappings between ipdb-specific names and GUD names +(setf (gethash "finish" realgud:ipdb-command-hash) "return") +(setf (gethash "kill" realgud:ipdb-command-hash) "quit") +(setf (gethash "backtrace" realgud:ipdb-command-hash) "where") +;; Clear in Python does both the usual “delete” and “clear” +(setf (gethash "delete" realgud:ipdb-command-hash) "clear %p") +(setf (gethash "clear" realgud:ipdb-command-hash) "clear %X:%l") +;; Use ‘!’ instead of ‘p’, since ‘p’ only works for expressions, not statements +(setf (gethash "eval" realgud:ipdb-command-hash) "pp %s") +(setf (gethash "info-breakpoints" realgud:ipdb-command-hash) "break") + +;; Unsupported features: +(setf (gethash "shell" realgud:ipdb-command-hash) "*not-implemented*") +(setf (gethash "frame" realgud:ipdb-command-hash) "*not-implemented*") + +(setf (gethash "ipdb" realgud-command-hash) realgud:ipdb-command-hash) + +(provide-me "realgud:ipdb-") diff --git a/ipdb/ipdb.el b/ipdb/ipdb.el new file mode 100644 index 0000000..a17a041 --- /dev/null +++ b/ipdb/ipdb.el @@ -0,0 +1,125 @@ +;; Copyright (C) 2016, 2019 Free Software Foundation, Inc + +;; Author: Rocky Bernstein +;; Author: Sean Farley + +;; 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 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;; `ipdb' Main interface to ipdb via Emacs +(require 'load-relative) +(require-relative-list '("core" "track-mode") "realgud:ipdb-") +(require-relative-list '("../../common/run") "realgud:") +(require-relative-list '("core" "track-mode") "realgud:ipdb-") + +;; This is needed, or at least the docstring part of it is needed to +;; get the customization menu to work in Emacs 24. +(defgroup realgud:ipdb nil + "The realgud interface to the Python ipdb debugger" + :group 'realgud + :version "24.3") + +(declare-function make-realgud-loc "realgud-loc" (a b c d e f)) + +;; ------------------------------------------------------------------- +;; User-definable variables +;; + +(defcustom realgud:ipdb-command-name + "ipdb" + "File name for executing the stock Python debugger and command options. +This should be an executable on your path, or an absolute file name." + :type 'string + :group 'realgud:ipdb) +;; ------------------------------------------------------------------- +;; The end. +;; + +(declare-function ipdb-track-mode 'realgud:ipdb-track) +(declare-function ipdb-query-cmdline 'realgud:ipdb-core) +(declare-function ipdb-parse-cmd-args 'realgud:ipdb-core) +(declare-function realgud:ipdb-completion-at-point 'realgud:ipdb-core) +(declare-function realgud:run-debugger 'realgud:run) + +;;;###autoload +(defun realgud:ipdb (&optional opt-cmd-line no-reset) + "Invoke the ipdb Python debugger and start the Emacs user interface. + +String OPT-CMD-LINE specifies how to run ipdb. You will be prompted +for a command line is one isn't supplied. + +OPT-COMMAND-LINE is treated like a shell string; arguments are +tokenized by `split-string-and-unquote'. The tokenized string is +parsed by `ipdb-parse-cmd-args' and path elements found by that +are expanded using `realgud:expand-file-name-if-exists'. + +Normally, command buffers are reused when the same debugger is +reinvoked inside a command buffer with a similar command. If we +discover that the buffer has prior command-buffer information and +NO-RESET is nil, then that information which may point into other +buffers and source buffers which may contain marks and fringe or +marginal icons is reset. See `loc-changes-clear-buffer' to clear +fringe and marginal icons. +" + (interactive) + (let ((cmd-buf (realgud:run-debugger "ipdb" 'ipdb-query-cmdline + 'ipdb-parse-cmd-args + 'realgud:ipdb-minibuffer-history + opt-cmd-line no-reset)) + ) + (add-hook 'completion-at-point-functions + 'realgud:ipdb-completion-at-point nil t) + (with-current-buffer cmd-buf + (add-hook 'completion-at-point-functions + 'realgud:ipdb-completion-at-point nil t) + ) + cmd-buf) + ) + + +;;;###autoload +(defun realgud:ipdb-remote (&optional opt-cmd-line no-reset) + "Invoke the ipdb Python debugger and start the Emacs user interface. + +String OPT-CMD-LINE specifies how to run ipdb. You will be prompted +for a command line is one isn't supplied. + +OPT-COMMAND-LINE is treated like a shell string; arguments are +tokenized by `split-string-and-unquote'. The tokenized string is +parsed by `ipdb-parse-remote-cmd-args' and path elements found by that +are expanded using `realgud:expand-file-name-if-exists'. + +Normally, command buffers are reused when the same debugger is +reinvoked inside a command buffer with a similar command. If we +discover that the buffer has prior command-buffer information and +NO-RESET is nil, then that information which may point into other +buffers and source buffers which may contain marks and fringe or +marginal icons is reset. See `loc-changes-clear-buffer' to clear +fringe and marginal icons. +" + (interactive) + (let ((cmd-buf (realgud:run-debugger "ipdb" 'ipdb-remote-query-cmdline + 'ipdb-parse-remote-cmd-args + 'realgud:ipdb-remote-minibuffer-history + opt-cmd-line no-reset "remote-ipdb")) + ) + (add-hook 'completion-at-point-functions + 'realgud:ipdb-completion-at-point nil t) + cmd-buf) + ) + + +;;;###autoload +(defalias 'ipdb 'realgud:ipdb) + +(provide-me "realgud-") diff --git a/ipdb/track-mode.el b/ipdb/track-mode.el new file mode 100644 index 0000000..c6415e6 --- /dev/null +++ b/ipdb/track-mode.el @@ -0,0 +1,74 @@ +;; Copyright (C) 2016, 2018, 2019 Free Software Foundation, Inc + +;; Author: Rocky Bernstein +;; Author: Sean Farley + +;; 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 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . +;; Python "ipdb" Debugger tracking a comint buffer. + +(eval-when-compile (require 'cl)) + +(require 'realgud) + +(require-relative-list '("core" "init") "realgud:ipdb-") + +(realgud-track-mode-vars "ipdb") + +(declare-function realgud-track-mode 'realgud-track-mode) +(declare-function realgud-track-mode-setup 'realgud-track-mode) +(declare-function realgud:track-set-debugger 'realgud-track-mode) +(declare-function realgud-python-populate-command-keys 'realgud-lang-python) +(declare-function realgud:ipdb-completion-at-point 'realgud:ipdb-core) + +(realgud-python-populate-command-keys ipdb-track-mode-map) + +(defun ipdb-track-mode-hook() + (if ipdb-track-mode + (progn + (use-local-map ipdb-track-mode-map) + (realgud:remove-ansi-schmutz) + (add-hook 'completion-at-point-functions + 'realgud:ipdb-completion-at-point nil t) + (message "using ipdb mode map") + ) + (message "ipdb track-mode-hook disable called") + ) +) + +(define-minor-mode ipdb-track-mode + "Minor mode for tracking ipdb source locations inside a process shell via realgud. ipdb is a Python debugger based on ipython. + +If called interactively with no prefix argument, the mode is toggled. A prefix argument, captured as ARG, enables the mode if the argument is positive, and disables it otherwise. + +a process shell. + +\\{ipdb-track-mode-map} +" + :init-value nil + ;; :lighter " ipdb" ;; mode-line indicator from realgud-track is sufficient. + ;; The minor mode bindings. + :global nil + :group 'realgud:ipdb + :keymap ipdb-track-mode-map + (realgud:track-set-debugger "ipdb") + (if ipdb-track-mode + (progn + (realgud-track-mode-setup 't) + (ipdb-track-mode-hook)) + (progn + (setq realgud-track-mode nil) + )) +) + +(provide-me "realgud:ipdb-") diff --git a/realgud-ipdb.el b/realgud-ipdb.el new file mode 100644 index 0000000..ad58c7d --- /dev/null +++ b/realgud-ipdb.el @@ -0,0 +1,45 @@ +;;; realgud-ipdb.el --- realgud front-end to ipdb + +;; Author: Rocky Bernstein +;; Version: 1.0 +;; Package-Requires: ((realgud "1.4.5") (emacs "24")) +;; URL: http://github.com/rocky/realgud-ipdb +;; Compatibility: GNU Emacs 24.x + +;; Copyright (C) 2019 Free Software Foundation, Inc + +;; Author: Rocky Bernstein + +;; 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 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; realgud support for the Python ipdb + +;;; Code: + +;; Press C-x C-e at the end of the next line configure the program in +;; for building via "make" to get set up. +;; (compile (format "EMACSLOADPATH=:%s:%s:%s:%s ./autogen.sh" (file-name-directory (locate-library "loc-changes.elc")) (file-name-directory (locate-library "test-simple.elc")) (file-name-directory (locate-library "load-relative.elc")) (file-name-directory (locate-library "realgud.elc")))) + +(require 'load-relative) + +(defgroup realgud-ipdb nil + "Realgud interface to ipdb" + :group 'realgud + :version "24.3") + +(require-relative-list '( "./ipdb/ipdb" ) "realgud-") + +(provide-me) diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..972821b --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,73 @@ +lisp_files = $(wildcard *.el) +EXTRA_DIST = $(lisp_files) +lisp_LISP = $(lisp_files) +include $(top_srcdir)/common.mk + +PHONY=check test all check-elget test-elget help + +#: overall help on running the make targets +help: + @echo "The main function of this Makefile is to facilitate running tests." + @echo + @echo "To run all of the tests, use targets \"test\", \"check\" or \"check-short\"." + @echo "For example:" + @echo + @echo " make check" + @echo "or:" + @echo " make check-short" + @echo + @echo "The -short target uses a filter written in Ruby to remove extreanous output." + @echo + @echo "To run a specific test like test-srcbuf.el, change \".el\" to" + @echo "\".run\". For example:" + @echo + @echo " make test-srcbuf.run" + @echo + @echo "Tests can also be run via the Emacs el-get package and this loads dependent emacs " + @echo "package, like load-relative. To do this, use targets, \"check-elget\"," + @echo "\"test-elget\", or \"check-short-elget\"." + @echo + @echo "To run a specific test like test-srcbuf.el via el-get change \".el\"" + @echo "to \".elrun\" For example:" + @echo + @echo " make test-srcbuf.elrun" + + +#: same thing as "check" +test: check + +#: same thing as "check-elget" +test-elget: check-elget + +test_files := $(wildcard test-*.el) + +CHECK_FILES = $(notdir $(test_files:.el=.run)) +EL_GET_CHECK_FILES = $(notdir $(test_files:.el=.elrun)) + +#: Run all tests +check: $(CHECK_FILES) + +#: Run all tests via el-get +check-elget: $(EL_GET_CHECK_FILES) + +#: Run all tests with minimum verbosity +check-short: + $(MAKE) check 2>&1 | ruby ../make-check-filter.rb + +#: Run all tests with minimum verbosity via el-get +check-short-elget: + $(MAKE) check-elget 2>&1 | ruby ../make-check-filter.rb + +test-%.run: + (cd $(top_srcdir)/test && EMACSLOADPATH=$(EMACSLOADPATH) $(EMACS) --batch --no-site-file --no-splash --load $(@:.run=.el)) + +#: Run tests using el-get to specify external Lisp dependencies +test-%.elrun: + (cd $(top_srcdir)/test && EMACSLOADPATH=$(EMACSLOADPATH) $(EMACS) --batch --no-site-file --no-splash --load ../el-get-install.el --load $(@:.elrun=.el)) + +install-lispLISP: $(lisp_LISP) $(ELCFILES) + +# Whatever it is you want to do, it should be forwarded to the +# to top-level directories +# %: +# $(MAKE) -C .. $@ diff --git a/test/make-check-filter.rb b/test/make-check-filter.rb new file mode 100644 index 0000000..508c8f6 --- /dev/null +++ b/test/make-check-filter.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby +# Use this to cut out the crud from make check. +# Use like this: +# make check 2>&1 | ruby ../make-check-filter.rb +# See Makefile.am +pats = ["^(?:Loading", + '(re)?make\[', + "Making check in", + '\(cd \.\.', + "make -C", + "Test-Unit", + "Fontifying", + "`flet'", + '\s*$', + '##[<>]+$' + ].join('|') + ')' +# puts pats +skip_re = /#{pats}/ + +while gets() + next if $_.encode!('UTF-8', 'binary', + invalid: :replace, undef: :replace, replace: '') =~ skip_re + puts $_ +end diff --git a/test/regexp-helper.el b/test/regexp-helper.el new file mode 100644 index 0000000..cdcecae --- /dev/null +++ b/test/regexp-helper.el @@ -0,0 +1,61 @@ +(require 'test-simple) +(require 'realgud) + +(eval-when-compile + (defvar helper-bps) + (defvar helper-info-brkpt) + (defvar helper-loc) + (defvar helper-tb) + (defvar prompt-pat) +) + +(declare-function realgud-loc-pat-regexp 'realgud-backtrace-mode) +(declare-function realgud-cmdbuf-info-loc-regexp 'realgud-buffer-command) +(declare-function test-simple-start 'test-simple) + + +(defun setup-regexp-vars(pat-hash) + (setq helper-bps (gethash "brkpt-set" pat-hash)) + (setq helper-info-brkpt (gethash "debugger-breakpoint" pat-hash)) + (setq helper-loc (gethash "loc" pat-hash)) + (setq helper-tb (gethash "lang-backtrace" pat-hash)) +) + +(defun loc-match(text regexp-list) + "Match TEXT against regexp field REGEXP" + (let ((regexp) + (ret-val nil)) + (unless (listp regexp-list) + (setq regexp-list (list regexp-list))) + (while regexp-list + (setq regexp (car regexp-list)) + (setq regexp-list (cdr regexp-list)) + (when (setq ret-val (string-match (realgud-loc-pat-regexp regexp) text)) + (setq regexp-list nil))) + ret-val + )) + +(defun bp-loc-match(text) + (string-match (realgud-loc-pat-regexp helper-bps) text) +) + +(defun tb-loc-match(text) + (string-match (realgud-loc-pat-regexp helper-tb) text) +) + +(defun cmdbuf-loc-match(text dbgr) + "Match TEXT against cmdbuf-info-loc field VAR" + (string-match (realgud-cmdbuf-info-loc-regexp dbgr) text) + ) + +(defun prompt-match(prompt-str &optional num-str fmt-str) + (unless fmt-str (setq fmt-str "debugger prompt %s")) + (assert-equal 0 (string-match (realgud-loc-pat-regexp prompt-pat) + prompt-str) + (format fmt-str prompt-str)) + (cond (num-str + (assert-equal num-str (substring prompt-str + (match-beginning 1) (match-end 1)))) + ('t 't)) + ) +(provide 'realgud-regexp-helper) diff --git a/test/test-ipdb.el b/test/test-ipdb.el new file mode 100644 index 0000000..030a94b --- /dev/null +++ b/test/test-ipdb.el @@ -0,0 +1,40 @@ +;; Press C-x C-e at the end of the next line to run this file test non-interactively +;; (test-simple-run "emacs -batch -L %s -l %s" (file-name-directory (locate-library "test-simple.elc")) buffer-file-name) + +(require 'test-simple) +(require 'load-relative) +(load-file "../ipdb/ipdb.el") + +(eval-when-compile (defvar test:run-process-save)) + +(declare-function ipdb-parse-cmd-args 'realgud:ipdb-core) +(declare-function realgud:ipdb 'realgud:ipdb) +(declare-function __FILE__ 'load-relative) + +(test-simple-start) + +;; Save value realgud:run-process and change it to something we want +(setq test:run-process-save (symbol-function 'realgud:run-process)) +(defun realgud:run-process(debugger-name script-filename cmd-args + minibuffer-histroy &optional no-reset) + "Fake realgud:run-process used in testing" + (note + (format "%s %s %s" debugger-name script-filename cmd-args)) + (assert-equal "ipdb" debugger-name "debugger name gets passed") + (assert-equal (expand-file-name "./gcd.py") script-filename "file name check") + (assert-equal '("3" "5") (cddr cmd-args) "command args listified") + (generate-new-buffer "*cmdbuf-test*") + ) + +(note "ipdb-parse-cmd-args") +(assert-equal (list nil '("ipdb") (list (expand-file-name "foo")) nil) + (ipdb-parse-cmd-args '("ipdb" "foo"))) +(assert-equal (list nil '("ipdb") (list (expand-file-name "program.py") "foo") nil) + (ipdb-parse-cmd-args + '("ipdb" "program.py" "foo"))) + +(realgud:ipdb "ipdb ./gcd.py 3 5") +;; Restore the old value of realgud:run-process +(fset 'realgud:run-process test:run-process-save) + +(end-tests) diff --git a/test/test-regexp-ipdb.el b/test/test-regexp-ipdb.el new file mode 100644 index 0000000..b9214d0 --- /dev/null +++ b/test/test-regexp-ipdb.el @@ -0,0 +1,108 @@ +;; Press C-x C-e at the end of the next line to run this file test non-interactively +;; (test-simple-run "emacs -batch -L %s -l %s" (file-name-directory (locate-library "test-simple.elc")) buffer-file-name) + +(require 'test-simple) +(load-file "../ipdb/init.el") +(load-file "./regexp-helper.el") + +(declare-function __FILE__ 'load-relative) +(declare-function loc-match 'realgud-helper) +(declare-function prompt-match 'regexp-helper) +(declare-function realgud-loc-pat-file-group 'realgud-regexp) +(declare-function realgud-loc-pat-line-group 'realgud-regexp) + +(test-simple-start) + +(eval-when-compile + (defvar loc-pat) (defvar prompt-pat) (defvar realgud:ipdb-pat-hash) + (defvar tb-pat) (defvar test-text) (defvar prompt-str) + (defvar bps-pat) +) + +(set (make-local-variable 'bps-pat) + (gethash "brkpt-set" realgud:ipdb-pat-hash)) + +(set (make-local-variable 'loc-pat) + (gethash "loc" realgud:ipdb-pat-hash)) + +(set (make-local-variable 'prompt-pat) + (gethash "prompt" realgud:ipdb-pat-hash)) + +(set (make-local-variable 'tb-pat) + (gethash "lang-backtrace" realgud:ipdb-pat-hash)) + +;; FIXME: we get a void variable somewhere in here when running +;; even though we define it in lexical-let. Dunno why. +;; setq however will workaround this. +(setq test-text + " File \"/usr/lib/python2.6/code.py\", line 281, in raw_input") +(note "traceback location matching") + +(assert-t (numberp (loc-match test-text tb-pat)) "basic traceback location") + +(assert-equal "/usr/lib/python2.6/code.py" + (match-string (realgud-loc-pat-file-group tb-pat) + test-text)) + +(setq test-text + " File \"/usr/lib/python2.6/code.py\", line 281, in raw_input") +(loc-match test-text tb-pat) +(assert-equal "281" + (match-string (realgud-loc-pat-line-group tb-pat) + test-text) "extract line number") + +(note "breakpoint location matching") + +(setq test-text "Breakpoint 1 at /src/git/code/gcd.py:13") +(assert-t (numberp (loc-match test-text bps-pat)) + "basic breakpoint location") + +(assert-equal "/src/git/code/gcd.py" + (match-string (realgud-loc-pat-file-group + bps-pat) + test-text) + "extract breakpoint file name") + + +(assert-equal "13" + (match-string (realgud-loc-pat-line-group + bps-pat) + test-text) "extract breakpoint line number") + +;; (set test-text "(c:\\working\\python\\helloworld.py:30): ") +;; +;; (assert-t (numberp (loc-match test-text loc-pat)) "MS DOS position location") +;; ;; +;; (assert-equal "c:\\working\\python\\helloworld.py" +;; (match-string (realgud-loc-pat-file-group loc-pat) +;; test-text) +;; (format "Failing file group is %s" +;; (realgud-loc-pat-file-group tb-pat)) +;; "extract file name") +;; (assert-equal "30" +;; (match-string (realgud-loc-pat-line-group loc-pat) +;; test-text) "extract line number") + +(setq test-text "> /usr/bin/ipython(24)") +(assert-t (numberp (loc-match test-text loc-pat)) "position location") +(assert-equal "/usr/bin/ipython" + (match-string (realgud-loc-pat-file-group loc-pat) + test-text) + (format "Failing file group is %s" + (realgud-loc-pat-file-group tb-pat))) +(assert-equal "24" + (match-string (realgud-loc-pat-line-group + loc-pat) + test-text) + "extract line number") + + +(note "prompt matching") +(set (make-local-variable 'prompt-str) "ipdb> ") +(prompt-match prompt-str nil "debugger prompt: %s") +(setq prompt-str "ipdb") +(assert-nil (numberp (loc-match prompt-str prompt-pat)) + (format "%s %s" "invalid debugger prompt" + prompt-str)) + +(end-tests)