]> code.delx.au - gnu-emacs-elpa/commitdiff
Add poptoshell.el and pdbtrack.el, and describe them in README.md.
authorKen Manheimer <ken.manheimer@gmail.com>
Sat, 12 Jul 2014 23:12:50 +0000 (19:12 -0400)
committerKen Manheimer <ken.manheimer@gmail.com>
Sat, 12 Jul 2014 23:12:50 +0000 (19:12 -0400)
README.md
pdbtrack.el [new file with mode: 0644]
poptoshell.el [new file with mode: 0644]

index 7ad1217f373aa681410db118c600a151d34635ed..08df459b2b025a9dc8187bbc8dd3c3dbb57b183e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,3 +2,49 @@ EmacsUtils
 ==========
 
 Handy Emacs utilities
+
+I've been using Emacs since it was publicly available (1985 or 1986), and have contributed some items which are included with Emacs, notably the [Allout outliner](http://myriadicity.net/software-and-systems/craft/emacs-allout), [icomplete mode](http://www.emacswiki.org/emacs/IcompleteMode). And python-mode's [pdbtrack functionality](http://myriadicity.net/software-and-systems/craft/crafty-hacks#section-1). Like many long-time Emacs users, I've got some personal custom code, some of which I wouldn't do without. Here's some - I hope to include more that I think would be useful to others, as time allows.
+
+* **pdbtrack.el**
+  * Add sensitivity to comint shells so the source file lines are automatically
+    presented in a separate window when the Python PDB debugger steps to them.
+
+    This is derived from the pdb tracking code, which I originally wrote, and
+    which has been included in (various) official Emacs Python modes. I wanted
+    a version that I could more easily tweak and maintain, independently of
+    the python-mode code.
+
+    It would eventually be nice to generalize this code, to work for things
+    like the node.js debugger. We'll see if I (or anyone) ever gets around to
+    that.
+
+
+* **poptoshell.el**
+  * I use the emacs shell a lot. This code enables me to streamline and
+    extend how I can a single one, or multiple ones in a project-oriented
+    fashion:
+
+    * It simplifies getting to the input prompt, by doing the right thing when
+      I hit the key I have bound to pop-to-shell (I use [M-space], ie
+      meta-space:
+    * If the cursor is in a buffer that has no subprocess, pop the window to
+      the primary shell buffer
+    * If there is no shell buffer, start one.
+    * If the cursor is in a buffer which has a process, move the cursor to
+      the process input point.
+    * With a universal argument, even if the current buffer has a subprocess,
+      solicit the name of the target shell buffer - defaulting to the current
+      main one - and pop to that.
+      * This enables starting an alternate shell buffer, for instance, and/or
+        switching between the main and alternate ones.
+      * (The expected name is without the surrounding asterisks, and
+        completion is done against existing shell buffer names stripped of
+        their asterisks.)
+      * With a doubled universal arg, prompt for the target shell buffer and
+        set the provided name as the primary shell buffer.
+    The last few things enable a kind of project-focus mode.  I often have
+    various shell buffers, each one associated with a project. As I switch
+    which project is currently my primary focus, I use the double universal
+    argument to switch which shell buffer is the default. I can still use the
+    single universal argument to easily switch to any of the shells, but most
+    easily to my current primary.
diff --git a/pdbtrack.el b/pdbtrack.el
new file mode 100644 (file)
index 0000000..a374102
--- /dev/null
@@ -0,0 +1,103 @@
+;;; pdbtrack - Track source file lines as you run python/pdb in an emacs shell.
+
+;;; Standalone Python PDB dynamic file tracking.
+
+(define-minor-mode pdbtrack-minor-mode
+  "Show lines in source file when Python PDB debugger steps through them."
+  nil ":PDBtrack" :require 'pdbtrack :version "2.1"
+
+  (add-hook 'comint-output-filter-functions
+            'pdbtrack-comint-output-filter-function)
+  (make-local-variable 'pdbtrack-buffers-to-kill)
+  (make-local-variable 'pdbtrack-tracked-buffer)
+)
+
+(defcustom pdbtrack-stacktrace-info-regexp
+  "> \\([^\"(<]+\\)(\\([0-9]+\\))\\([?a-zA-Z0-9_<>]+\\)()"
+  "Regular Expression matching stacktrace information.
+Used to extract the current line and module being inspected."
+  :type 'string
+  :group 'python
+  :safe 'stringp)
+
+(defvar pdbtrack-tracked-buffer nil
+  "Variable containing the value of the current tracked buffer.
+Never set this variable directly, use
+`pdbtrack-set-tracked-buffer' instead.")
+
+(defcustom pdbtrack-remove-new-buffers-after-tracking t
+  "Remove buffers visited for the sake of tracking, on pdb session conclusion."
+  :type 'boolean
+  :group 'python)
+(defvar pdbtrack-buffers-to-kill nil
+  "List of buffers to be deleted after tracking finishes.")
+
+(defun pdbtrack-set-tracked-buffer (file-name)
+  "Set the buffer for FILE-NAME as the tracked buffer.
+Internally it uses the `pdbtrack-tracked-buffer' variable.
+Returns the tracked buffer."
+  (let ((file-buffer (get-file-buffer
+                      (concat (file-remote-p default-directory)
+                              file-name))))
+    (if file-buffer
+        (setq pdbtrack-tracked-buffer file-buffer)
+      (setq file-buffer (find-file-noselect file-name))
+      (when (not (member file-buffer pdbtrack-buffers-to-kill))
+        (add-to-list 'pdbtrack-buffers-to-kill file-buffer)))
+    file-buffer))
+
+(defun pdbtrack-comint-output-filter-function (output)
+  "Move overlay arrow to current pdb line in tracked buffer.
+Argument OUTPUT is a string with the output from the comint process."
+  (when (and pdbtrack-minor-mode (not (string= output "")))
+    (let* ((full-output (ansi-color-filter-apply
+                         (buffer-substring comint-last-input-end (point-max))))
+           (line-number)
+           (file-name
+            (with-temp-buffer
+              (insert full-output)
+              ;; When the debugger encounters a pdb.set_trace()
+              ;; command, it prints a single stack frame.  Sometimes
+              ;; it prints a bit of extra information about the
+              ;; arguments of the present function.  When ipdb
+              ;; encounters an exception, it prints the _entire_ stack
+              ;; trace.  To handle all of these cases, we want to find
+              ;; the _last_ stack frame printed in the most recent
+              ;; batch of output, then jump to the corresponding
+              ;; file/line number.
+              (goto-char (point-max))
+              (when (re-search-backward pdbtrack-stacktrace-info-regexp nil t)
+                (setq line-number (string-to-number
+                                   (match-string-no-properties 2)))
+                (match-string-no-properties 1)))))
+      (if (and file-name line-number)
+          (let* ((tracked-buffer
+                  (pdbtrack-set-tracked-buffer file-name))
+                 (shell-buffer (current-buffer))
+                 (tracked-buffer-window (get-buffer-window tracked-buffer))
+                 (tracked-buffer-line-pos))
+            (with-current-buffer tracked-buffer
+              (set (make-local-variable 'overlay-arrow-string) "=>")
+              (set (make-local-variable 'overlay-arrow-position) (make-marker))
+              (setq tracked-buffer-line-pos (progn
+                                              (goto-char (point-min))
+                                              (forward-line (1- line-number))
+                                              (point-marker)))
+              (when tracked-buffer-window
+                (set-window-point
+                 tracked-buffer-window tracked-buffer-line-pos))
+              (set-marker overlay-arrow-position tracked-buffer-line-pos))
+            (pop-to-buffer tracked-buffer)
+            (switch-to-buffer-other-window shell-buffer))
+        (when pdbtrack-tracked-buffer
+          (with-current-buffer pdbtrack-tracked-buffer
+            (set-marker overlay-arrow-position nil))
+          (when (not pdbtrack-remove-new-buffers-after-tracking)
+            (mapc #'(lambda (buffer)
+                      (ignore-errors (kill-buffer buffer)))
+                  pdbtrack-buffers-to-kill))
+          (setq pdbtrack-tracked-buffer nil
+                pdbtrack-buffers-to-kill nil)))))
+  output)
+
+(provide 'pdbtrack)
diff --git a/poptoshell.el b/poptoshell.el
new file mode 100644 (file)
index 0000000..e1269f7
--- /dev/null
@@ -0,0 +1,231 @@
+;;; poptoshell.el --- get to the process buffer and input mark
+
+;; Copyright (C) 1999-2011 Free Software Foundation, Inc. and Ken Manheimer
+
+;; Author: Ken Manheimer <ken dot manheimer at gmail...>
+;; Maintainer: Ken Manheimer <ken dot manheimer at gmail...>
+;; Created: 1999 -- first public release
+;; Keywords: processes
+;; Website: http://myriadicity.net/software-and-systems/craft/crafty-hacks/emacs-sundries/poptoshell.el/view
+;;
+;;; Commentary:
+;;
+;; I bind to M-<space>, via eg: (global-set-key "\M- " 'pop-to-shell)
+;; See the pop-to-shell docstring for details.
+;;
+;; klm, 02/09/1999.
+
+(defvar non-interactive-process-buffers '("*compilation*" "*grep*"))
+
+(require 'comint)
+(require 'shell)
+
+(provide 'poptoshell)
+
+(defcustom pop-to-shell-frame nil
+  "*If non-nil, jump to a frame already showing the shell, if any.
+
+Otherwise, open a new window in the current frame."
+  :type 'boolean
+  :group 'comint)
+
+(defvar pop-to-shell-primary-name "*shell*"
+  "Shell name to use for un-modified pop-to-shell buffer target.")
+
+(defun pop-to-shell (&optional arg)
+
+  "Navigate to or within a shell buffer.
+
+Use this command from within a shell subprocess buffer to get to
+the shell input point, or from outside a shell buffer to pop to a
+shell buffer, without displacing the current buffer.
+
+Specifically, like 'shell' command but:
+
+ - If the current buffer is associated with a subprocess (and one not
+   among those named on `non-interactive-process-buffers'), then focus
+   is moved to the process input point, else...
+ - Goes to a window that is already showing a shell buffer, if any.
+   In this case, the cursor is left in its prior position in the shell
+   buffer. (Repeating the command will then go to the process input
+   point, by the behavior mentioned just before this.) Else...
+ - Pops open a new shell buffer, if none is around.
+
+In any cases where the shell buffer already existed, the process
+is resumed if it was stopped.
+
+Further,
+
+ - With a universal argument, the user is prompted for the buffer name to
+   use (it will be bracketed by asterisks - a regrettable comint
+   requirement), defaulting to 'shell'.  This makes it easy to switch
+   between multiple process-associated buffers.
+ - A double universal argument will set the default target shell buffer name
+   to the provided one, making the target shell subsequently primary."
+
+  (interactive "P")
+
+  (if (not (boundp 'shell-buffer-name))
+      (setq shell-buffer-name "*shell*"))
+
+  (let* ((from (current-buffer))
+         (doublearg (equal arg '(16)))
+         (temp (if arg
+                   (read-shell-buffer-name-sans
+                    (format "Shell buffer name [%s]%s "
+                            pop-to-shell-primary-name
+                            (if doublearg " <==" ":"))
+                    pop-to-shell-primary-name)
+                 pop-to-shell-primary-name))
+         ;; Make sure it is bracketed with asterisks; silly.
+         (target-shell-buffer-name (if (string= temp "")
+                                       pop-to-shell-primary-name
+                                     (bracket-asterisks temp)))
+         (curr-buff-proc (or (get-buffer-process from)
+                             (and (fboundp 'rcirc-buffer-process)
+                                  (rcirc-buffer-process))
+                             (and (boundp 'erc-process)
+                                  erc-process)))
+         (buff (if (and curr-buff-proc
+                        (not (member (buffer-name from)
+                                     non-interactive-process-buffers)))
+                   from
+                 (get-buffer target-shell-buffer-name)))
+         (inwin nil)
+         (num 0)
+         already-there)
+    (when doublearg
+      (setq pop-to-shell-primary-name target-shell-buffer-name))
+    (if (and curr-buff-proc
+             (not arg)
+             (eq from buff)
+             (not (eq target-shell-buffer-name (buffer-name from))))
+        ;; We're in a buffer with a shell process, but not named shell
+        ;; - stick with it, but go to end:
+        (setq already-there t)
+      (cond
+                                        ; Already in the shell buffer:
+       ((string= (buffer-name) target-shell-buffer-name)
+        (setq already-there t))
+       ((or (not buff)
+            (not (catch 'got-a-vis
+                   (my-walk-windows
+                    (function (lambda (win)
+                                (if (and (eq (window-buffer win) buff)
+                                         (equal (frame-parameter
+                                                 (selected-frame) 'display)
+                                                (frame-parameter
+                                                 (window-frame win) 'display)))
+                                    (progn (setq inwin win)
+                                           (throw 'got-a-vis win))
+                                  (setq num (1+ num)))))
+                    nil 'visible t)
+                   nil)))
+        ;; No preexisting shell buffer, or not in a visible window:
+        (pop-to-buffer target-shell-buffer-name pop-up-windows))
+       ;; Buffer exists and already has a window - jump to it:
+       (t (if (and pop-to-shell-frame
+                   inwin
+                   (not (equal (window-frame (selected-window))
+                               (window-frame inwin))))
+              (select-frame-set-input-focus (window-frame inwin)))
+          (if (not (string= (buffer-name (current-buffer))
+                            target-shell-buffer-name))
+              (pop-to-buffer target-shell-buffer-name t))))
+      (condition-case err
+          (if (not (comint-check-proc (current-buffer)))
+              (start-shell-in-buffer (buffer-name (current-buffer))))
+        (file-error
+         ;; Whoops - can't get to the default directory, keep trying
+         ;; superior ones till we get somewhere that's acceptable:
+         (while (and (not (string= default-directory ""))
+                     (not (condition-case err (progn (shell) t)
+                            (file-error nil))))
+           (setq default-directory
+                 (file-name-directory
+                  (substring default-directory
+                             0
+                             (1- (length default-directory)))))))
+        ))
+    ;; If the destination buffer has a stopped process, resume it:
+    (let ((process (get-buffer-process (current-buffer))))
+      (if (and process (equal 'stop (process-status process)))
+          (continue-process process)))
+    (if (and (not already-there)
+             (not (equal (current-buffer) from)))
+        t
+      (goto-char (point-max))
+      (and (get-buffer-process from)
+           (goto-char (process-mark (get-buffer-process from)))))
+    )
+  )
+(defun my-walk-windows (func &optional minibuf all-frames selected)
+  (if (featurep 'xemacs)
+      (walk-windows func minibuf all-frames (selected-device))
+    (walk-windows func minibuf all-frames)))
+
+(defun my-set-mouse-position (window x y)
+  "Adapt for both xemacs and fsf emacs"
+  (if (string= (substring (emacs-version) 0 6) "XEmacs")
+      (set-mouse-position window x y)
+    (let ((frame (window-frame window)))
+      (select-frame-set-input-focus frame))))
+
+
+(defun read-shell-buffer-name-sans (prompt default)
+  "Obtain name without asterisks of shell buffer, adding the asterisks.
+
+Return indicated default on empty input."
+  (let ((got
+         (completing-read
+          prompt
+          (filter 'identity
+                  (mapcar (lambda (buffer)
+                            (let ((name (buffer-name buffer)))
+                              (if (with-current-buffer buffer
+                                         (eq major-mode 'shell-mode))
+                                  (if (> (length name) 2)
+                                      (substring name 1 (1- (length
+                                                             name)))
+                                    name))))
+                          (buffer-list))))))
+    (if (not (string= got "")) (bracket-asterisks got) default)))
+
+(defun bracket-asterisks (name)
+  "Return a copy of name, ensuring it has an asterisk at the beginning and end."
+  (if (not (string= (substring name 0 1) "*"))
+      (setq name (concat "*" name)))
+  (if (not (string= (substring name -1) "*"))
+      (setq name (concat name "*")))
+  name)
+(defun unbracket-asterisks (name)
+  "Return a copy of name, removing asterisks at beg and end, if any."
+  (if (string= (substring name 0 1) "*")
+      (setq name (substring name 1)))
+  (if (string= (substring name -1) "*")
+      (setq name (substring name 0 -1)))
+  name)
+(defun start-shell-in-buffer (buffer-name)
+  ;; Damn comint requires buffer name be bracketed by "*" asterisks.
+  (require 'comint)
+  (require 'shell)
+
+  (let* ((buffer buffer-name)
+         (prog (or explicit-shell-file-name
+                   (getenv "ESHELL")
+                   (getenv "SHELL")
+                   "/bin/sh"))
+         (name (file-name-nondirectory prog))
+         (startfile (concat "~/.emacs_" name))
+         (xargs-name (intern-soft (concat "explicit-" name "-args"))))
+    (setq buffer (set-buffer (apply 'make-comint
+                                    (unbracket-asterisks buffer-name)
+                                    prog
+                                    (if (file-exists-p startfile)
+                                        startfile)
+                                    (if (and xargs-name
+                                             (boundp xargs-name))
+                                        (symbol-value xargs-name)
+                                      '("-i")))))
+    (set-buffer buffer-name)
+    (shell-mode)))