|
|
- ;;; -*- coding: utf-8; lexical-binding: t -*-
- ;;;
- ;;; slime-trace-dialog.el -- a navigable dialog of inspectable trace entries
- ;;;
- ;;; TODO: implement better wrap interface for sbcl method, labels and such
- ;;; TODO: backtrace printing is very slow
- ;;;
- (require 'slime)
- (require 'slime-parse)
- (require 'slime-repl)
- (require 'cl-lib)
-
- (define-slime-contrib slime-trace-dialog
- "Provide an interfactive trace dialog buffer for managing and
- inspecting details of traced functions. Invoke this dialog with C-c T."
- (:authors "João Távora <joaotavora@gmail.com>")
- (:license "GPL")
- (:swank-dependencies swank-trace-dialog)
- (:on-load (add-hook 'slime-mode-hook 'slime-trace-dialog-enable)
- (add-hook 'slime-repl-mode-hook 'slime-trace-dialog-enable))
- (:on-unload (remove-hook 'slime-mode-hook 'slime-trace-dialog-enable)
- (remove-hook 'slime-repl-mode-hook 'slime-trace-dialog-enable)))
-
- ;;;; Variables
- ;;;
- (defvar slime-trace-dialog-flash t
- "Non-nil means flash the updated region of the SLIME Trace Dialog. ")
-
- (defvar slime-trace-dialog--specs-overlay nil)
-
- (defvar slime-trace-dialog--progress-overlay nil)
-
- (defvar slime-trace-dialog--tree-overlay nil)
-
- (defvar slime-trace-dialog--collapse-chars (cons "-" "+"))
-
- ;;;; Local trace entry model
- (defvar slime-trace-dialog--traces nil)
-
- (cl-defstruct (slime-trace-dialog--trace
- (:constructor slime-trace-dialog--make-trace))
- id
- parent
- spec
- args
- retlist
- depth
- beg
- end
- collapse-button-marker
- summary-beg
- children-end
- collapsed-p)
-
- (defun slime-trace-dialog--find-trace (id)
- (gethash id slime-trace-dialog--traces))
-
- ;;;; Modes and mode maps
- ;;;
- (defvar slime-trace-dialog-mode-map
- (let ((map (make-sparse-keymap))
- (remaps '((slime-inspector-operate-on-point . nil)
- (slime-inspector-operate-on-click . nil)
- (slime-inspector-reinspect
- . slime-trace-dialog-fetch-status)
- (slime-inspector-next-inspectable-object
- . slime-trace-dialog-next-button)
- (slime-inspector-previous-inspectable-object
- . slime-trace-dialog-prev-button))))
- (set-keymap-parent map slime-inspector-mode-map)
- (cl-loop for (old . new) in remaps
- do (substitute-key-definition old new map))
- (set-keymap-parent map slime-parent-map)
- (define-key map (kbd "G") 'slime-trace-dialog-fetch-traces)
- (define-key map (kbd "C-k") 'slime-trace-dialog-clear-fetched-traces)
- (define-key map (kbd "g") 'slime-trace-dialog-fetch-status)
- (define-key map (kbd "M-RET") 'slime-trace-dialog-copy-down-to-repl)
- (define-key map (kbd "q") 'quit-window)
- map))
-
- (define-derived-mode slime-trace-dialog-mode fundamental-mode
- "SLIME Trace Dialog" "Mode for controlling SLIME's Trace Dialog"
- (set-syntax-table lisp-mode-syntax-table)
- (read-only-mode 1)
- (add-to-list (make-local-variable 'slime-trace-dialog-after-toggle-hook)
- 'slime-trace-dialog-fetch-status))
-
- (define-derived-mode slime-trace-dialog--detail-mode slime-inspector-mode
- "SLIME Trace Detail"
- "Mode for viewing a particular trace from SLIME's Trace Dialog")
-
- (setq slime-trace-dialog--detail-mode-map
- (let ((map (make-sparse-keymap))
- (remaps '((slime-inspector-next-inspectable-object
- . slime-trace-dialog-next-button)
- (slime-inspector-previous-inspectable-object
- . slime-trace-dialog-prev-button))))
- (set-keymap-parent map slime-trace-dialog-mode-map)
- (cl-loop for (old . new) in remaps
- do (substitute-key-definition old new map))
- map))
-
- (defvar slime-trace-dialog-minor-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "C-c T") 'slime-trace-dialog)
- (define-key map (kbd "C-c M-t") 'slime-trace-dialog-toggle-trace)
- map))
-
- (define-minor-mode slime-trace-dialog-minor-mode
- "Add keybindings for accessing SLIME's Trace Dialog.")
-
- (defun slime-trace-dialog-enable ()
- (slime-trace-dialog-minor-mode 1))
-
- (easy-menu-define slime-trace-dialog--menubar (list slime-trace-dialog-minor-mode-map
- slime-trace-dialog-mode-map)
- "A menu for accessing some features of SLIME's Trace Dialog"
- (let* ((in-dialog '(eq major-mode 'slime-trace-dialog-mode))
- (dialog-live `(and ,in-dialog
- (memq slime-buffer-connection slime-net-processes)))
- (connected '(slime-connected-p)))
- `("Trace"
- ["Toggle trace" slime-trace-dialog-toggle-trace ,connected]
- ["Trace complex spec" slime-trace-dialog-toggle-complex-trace ,connected]
- ["Open Trace dialog" slime-trace-dialog (and ,connected (not ,in-dialog))]
- "--"
- [ "Refresh traces and progress" slime-trace-dialog-fetch-status ,dialog-live]
- [ "Fetch next batch" slime-trace-dialog-fetch-traces ,dialog-live]
- [ "Clear all fetched traces" slime-trace-dialog-clear-fetched-traces ,dialog-live]
- [ "Toggle details" slime-trace-dialog-hide-details-mode ,in-dialog]
- [ "Toggle autofollow" slime-trace-dialog-autofollow-mode ,in-dialog])))
-
- (define-minor-mode slime-trace-dialog-hide-details-mode
- "Hide details in `slime-trace-dialog-mode'"
- nil " Brief"
- :group 'slime-trace-dialog
- (unless (derived-mode-p 'slime-trace-dialog-mode)
- (error "Not a SLIME Trace Dialog buffer"))
- (slime-trace-dialog--set-hide-details-mode))
-
- (define-minor-mode slime-trace-dialog-autofollow-mode
- "Automatically open buffers with trace details from `slime-trace-dialog-mode'"
- nil " Autofollow"
- :group 'slime-trace-dialog
- (unless (derived-mode-p 'slime-trace-dialog-mode)
- (error "Not a SLIME Trace Dialog buffer")))
-
- ;;;; Helper functions
- ;;;
- (defun slime-trace-dialog--call-refreshing (buffer
- overlay
- dont-erase
- recover-point-p
- fn)
- (with-current-buffer buffer
- (let ((inhibit-point-motion-hooks t)
- (inhibit-read-only t)
- (saved (point)))
- (save-restriction
- (when overlay
- (narrow-to-region (overlay-start overlay)
- (overlay-end overlay)))
- (unwind-protect
- (if dont-erase
- (goto-char (point-max))
- (delete-region (point-min) (point-max)))
- (funcall fn)
- (when recover-point-p
- (goto-char saved)))
- (when slime-trace-dialog-flash
- (slime-flash-region (point-min) (point-max)))))
- buffer))
-
- (cl-defmacro slime-trace-dialog--refresh ((&key
- overlay
- dont-erase
- recover-point-p
- buffer)
- &rest body)
- (declare (indent 1)
- (debug (sexp &rest form)))
- `(slime-trace-dialog--call-refreshing ,(or buffer
- `(current-buffer))
- ,overlay
- ,dont-erase
- ,recover-point-p
- #'(lambda () ,@body)))
-
- (defmacro slime-trace-dialog--insert-and-overlay (string overlay)
- `(save-restriction
- (let ((inhibit-read-only t))
- (narrow-to-region (point) (point))
- (insert ,string "\n")
- (set (make-local-variable ',overlay)
- (let ((overlay (make-overlay (point-min)
- (point-max)
- (current-buffer)
- nil
- t)))
- (move-overlay overlay (overlay-start overlay)
- (1- (overlay-end overlay)))
- ;; (overlay-put overlay 'face '(:background "darkslategrey"))
- overlay)))))
-
- (defun slime-trace-dialog--buffer-name ()
- (format "*traces for %s*"
- (slime-connection-name slime-default-connection)))
-
- (defun slime-trace-dialog--live-dialog (&optional buffer-or-name)
- (let ((buffer-or-name (or buffer-or-name
- (slime-trace-dialog--buffer-name))))
- (and (buffer-live-p (get-buffer buffer-or-name))
- (with-current-buffer buffer-or-name
- (memq slime-buffer-connection slime-net-processes))
- buffer-or-name)))
-
- (defun slime-trace-dialog--ensure-buffer ()
- (let ((name (slime-trace-dialog--buffer-name)))
- (or (slime-trace-dialog--live-dialog name)
- (with-current-buffer (get-buffer-create name)
- (let ((inhibit-read-only t))
- (erase-buffer))
- (slime-trace-dialog-mode)
- (save-excursion
- (buffer-disable-undo)
- (slime-trace-dialog--insert-and-overlay
- "[waiting for the traced specs to be available]"
- slime-trace-dialog--specs-overlay)
- (slime-trace-dialog--insert-and-overlay
- "[waiting for some info on trace download progress ]"
- slime-trace-dialog--progress-overlay)
- (slime-trace-dialog--insert-and-overlay
- "[waiting for the actual traces to be available]"
- slime-trace-dialog--tree-overlay)
- (current-buffer))
- (setq slime-buffer-connection slime-default-connection)
- (current-buffer)))))
-
- (defun slime-trace-dialog--make-autofollow-fn (id)
- (let ((requested nil))
- #'(lambda (_before after)
- (let ((inhibit-point-motion-hooks t)
- (id-after (get-text-property after 'slime-trace-dialog--id)))
- (when (and (= after (point))
- slime-trace-dialog-autofollow-mode
- id-after
- (= id-after id)
- (not requested))
- (setq requested t)
- (slime-eval-async `(swank-trace-dialog:report-trace-detail
- ,id-after)
- #'(lambda (detail)
- (setq requested nil)
- (when detail
- (let ((inhibit-point-motion-hooks t))
- (slime-trace-dialog--open-detail detail
- 'no-pop))))))))))
-
- (defun slime-trace-dialog--set-collapsed (collapsed-p trace button)
- (save-excursion
- (setf (slime-trace-dialog--trace-collapsed-p trace) collapsed-p)
- (slime-trace-dialog--go-replace-char-at
- button
- (if collapsed-p
- (cdr slime-trace-dialog--collapse-chars)
- (car slime-trace-dialog--collapse-chars)))
- (slime-trace-dialog--hide-unhide
- (slime-trace-dialog--trace-summary-beg trace)
- (slime-trace-dialog--trace-end trace)
- (if collapsed-p 1 -1))
- (slime-trace-dialog--hide-unhide
- (slime-trace-dialog--trace-end trace)
- (slime-trace-dialog--trace-children-end trace)
- (if collapsed-p 1 -1))))
-
- (defun slime-trace-dialog--hide-unhide (start-pos end-pos delta)
- (cl-loop with inhibit-read-only = t
- for pos = start-pos then next
- for next = (next-single-property-change
- pos
- 'slime-trace-dialog--hidden-level
- nil
- end-pos)
- for hidden-level = (+ (or (get-text-property
- pos
- 'slime-trace-dialog--hidden-level)
- 0)
- delta)
- do (add-text-properties pos next
- (list 'slime-trace-dialog--hidden-level
- hidden-level
- 'invisible
- (cl-plusp hidden-level)))
- while (< next end-pos)))
-
- (defun slime-trace-dialog--set-hide-details-mode ()
- (cl-loop for trace being the hash-values of slime-trace-dialog--traces
- do (slime-trace-dialog--hide-unhide
- (slime-trace-dialog--trace-summary-beg trace)
- (slime-trace-dialog--trace-end trace)
- (if slime-trace-dialog-hide-details-mode 1 -1))))
-
- (defun slime-trace-dialog--format-part (part-id part-text trace-id type)
- (slime-trace-dialog--button
- (format "%s" part-text)
- #'(lambda (_button)
- (slime-eval-async
- `(swank-trace-dialog:inspect-trace-part ,trace-id ,part-id ,type)
- #'slime-open-inspector))
- 'mouse-face 'highlight
- 'slime-trace-dialog--part-id part-id
- 'slime-trace-dialog--type type
- 'face 'slime-inspector-value-face))
-
- (defun slime-trace-dialog--format-trace-entry (id external)
- (slime-trace-dialog--button
- (format "%s" external)
- #'(lambda (_button)
- (slime-eval-async
- `(swank::inspect-object (swank-trace-dialog::find-trace ,id))
- #'slime-open-inspector))
- 'face 'slime-inspector-value-face))
-
- (defun slime-trace-dialog--format (fmt-string &rest args)
- (let* ((string (apply #'format fmt-string args))
- (indent (make-string (max 2
- (- 50 (length string))) ? )))
- (format "%s%s" string indent)))
-
- (defun slime-trace-dialog--button (title lambda &rest props)
- (let ((string (format "%s" title)))
- (apply #'make-text-button string nil
- 'action #'(lambda (button)
- (funcall lambda button))
- 'mouse-face 'highlight
- 'face 'slime-inspector-action-face
- props)
- string))
-
- (defun slime-trace-dialog--call-maintaining-properties (pos fn)
- (save-excursion
- (goto-char pos)
- (let* ((saved-props (text-properties-at pos))
- (saved-point (point))
- (inhibit-read-only t)
- (inhibit-point-motion-hooks t))
- (funcall fn)
- (add-text-properties saved-point (point) saved-props)
- (if (markerp pos) (set-marker pos saved-point)))))
-
- (cl-defmacro slime-trace-dialog--maintaining-properties (pos
- &body body)
- (declare (indent 1))
- `(slime-trace-dialog--call-maintaining-properties ,pos #'(lambda () ,@body)))
-
- (defun slime-trace-dialog--go-replace-char-at (pos char)
- (slime-trace-dialog--maintaining-properties pos
- (delete-char 1)
- (insert char)))
-
- ;;;; Handlers for the *trace-dialog* and *trace-detail* buffers
- ;;;
- (defun slime-trace-dialog--open-specs (traced-specs)
- (cl-labels ((make-report-spec-fn
- (&optional form)
- #'(lambda (_button)
- (slime-eval-async
- `(cl:progn
- ,form
- (swank-trace-dialog:report-specs))
- #'(lambda (results)
- (slime-trace-dialog--open-specs results))))))
- (slime-trace-dialog--refresh
- (:overlay slime-trace-dialog--specs-overlay
- :recover-point-p t)
- (insert
- (slime-trace-dialog--format "Traced specs (%s)" (length traced-specs))
- (slime-trace-dialog--button "[refresh]"
- (make-report-spec-fn))
- "\n" (make-string 50 ? )
- (slime-trace-dialog--button
- "[untrace all]"
- (make-report-spec-fn `(swank-trace-dialog:dialog-untrace-all)))
- "\n\n")
- (cl-loop for spec in traced-specs
- do (insert
- " "
- (slime-trace-dialog--button
- "[untrace]"
- (make-report-spec-fn
- `(swank-trace-dialog:dialog-untrace ',spec)))
- (format " %s" spec)
- "\n")))))
-
- (defvar slime-trace-dialog--fetch-key nil)
-
- (defvar slime-trace-dialog--stop-fetching nil)
-
- (defun slime-trace-dialog--update-progress (total &optional show-stop-p remaining-p)
- ;; `remaining-p' indicates `total' is the number of remaining traces.
- (slime-trace-dialog--refresh
- (:overlay slime-trace-dialog--progress-overlay
- :recover-point-p t)
- (let* ((done (hash-table-count slime-trace-dialog--traces))
- (total (if remaining-p (+ done total) total)))
- (insert
- (slime-trace-dialog--format "Trace collection status (%d/%s)"
- done
- (or total "0"))
- (slime-trace-dialog--button "[refresh]"
- #'(lambda (_button)
- (slime-trace-dialog-fetch-progress))))
-
- (when (and total (cl-plusp (- total done)))
- (insert "\n" (make-string 50 ? )
- (slime-trace-dialog--button
- "[fetch next batch]"
- #'(lambda (_button)
- (slime-trace-dialog-fetch-traces nil)))
- "\n" (make-string 50 ? )
- (slime-trace-dialog--button
- "[fetch all]"
- #'(lambda (_button)
- (slime-trace-dialog-fetch-traces t)))))
- (when total
- (insert "\n" (make-string 50 ? )
- (slime-trace-dialog--button
- "[clear]"
- #'(lambda (_button)
- (slime-trace-dialog-clear-fetched-traces)))))
- (when show-stop-p
- (insert "\n" (make-string 50 ? )
- (slime-trace-dialog--button
- "[stop]"
- #'(lambda (_button)
- (setq slime-trace-dialog--stop-fetching t)))))
- (insert "\n\n"))))
-
- (defun slime-trace-dialog--open-detail (trace-tuple &optional no-pop)
- (slime-with-popup-buffer ("*trace-detail*" :select (not no-pop)
- :mode 'slime-trace-dialog--detail-mode)
- (cl-destructuring-bind (id _parent-id _spec args retlist backtrace external)
- trace-tuple
- (let ((headline (slime-trace-dialog--format-trace-entry id external)))
- (setq headline (format "%s\n%s\n"
- headline
- (make-string (length headline) ?-)))
- (insert headline))
- (cl-loop for (type objects label)
- in `((:arg ,args "Called with args:")
- (:retval ,retlist "Returned values:"))
- do (insert (format "\n%s\n" label))
- (insert (cl-loop for object in objects
- for i from 0
- concat (format " %s: %s\n" i
- (slime-trace-dialog--format-part
- (cl-first object)
- (cl-second object)
- id
- type)))))
- (when backtrace
- (insert "\nBacktrace:\n"
- (cl-loop for (i spec) in backtrace
- concat (format " %s: %s\n" i spec)))))))
-
- ;;;; Rendering traces
- ;;;
- (defun slime-trace-dialog--draw-tree-lines (start offset direction)
- (save-excursion
- (let ((inhibit-point-motion-hooks t))
- (goto-char start)
- (cl-loop with replace-set = (if (eq direction 'down)
- '(? )
- '(? ?`))
- for line-beginning = (line-beginning-position
- (if (eq direction 'down)
- 2 0))
- for pos = (+ line-beginning offset)
- while (and (< (point-min) line-beginning)
- (< line-beginning (point-max))
- (memq (char-after pos) replace-set))
- do
- (slime-trace-dialog--go-replace-char-at pos "|")
- (goto-char pos)))))
-
- (defun slime-trace-dialog--make-indent (depth suffix)
- (concat (make-string (* 3 (max 0 (1- depth))) ? )
- (if (cl-plusp depth) suffix)))
-
- (defun slime-trace-dialog--make-collapse-button (trace)
- (slime-trace-dialog--button (if (slime-trace-dialog--trace-collapsed-p trace)
- (cdr slime-trace-dialog--collapse-chars)
- (car slime-trace-dialog--collapse-chars))
- #'(lambda (button)
- (slime-trace-dialog--set-collapsed
- (not (slime-trace-dialog--trace-collapsed-p
- trace))
- trace
- button))))
-
-
- (defun slime-trace-dialog--insert-trace (trace)
- (let* ((id (slime-trace-dialog--trace-id trace))
- (parent (slime-trace-dialog--trace-parent trace))
- (has-children-p (slime-trace-dialog--trace-children-end trace))
- (indent-spec (slime-trace-dialog--make-indent
- (slime-trace-dialog--trace-depth trace)
- "`--"))
- (indent-summary (slime-trace-dialog--make-indent
- (slime-trace-dialog--trace-depth trace)
- " "))
- (autofollow-fn (slime-trace-dialog--make-autofollow-fn id))
- (id-string (slime-trace-dialog--button
- (format "%4s" id)
- #'(lambda (_button)
- (slime-eval-async
- `(swank-trace-dialog:report-trace-detail
- ,id)
- #'slime-trace-dialog--open-detail))))
- (spec (slime-trace-dialog--trace-spec trace))
- (summary (cl-loop for (type objects marker) in
- `((:arg ,(slime-trace-dialog--trace-args trace)
- " > ")
- (:retval ,(slime-trace-dialog--trace-retlist trace)
- " < "))
- concat (cl-loop for object in objects
- concat " "
- concat indent-summary
- concat marker
- concat (slime-trace-dialog--format-part
- (cl-first object)
- (cl-second object)
- id
- type)
- concat "\n"))))
- (puthash id trace slime-trace-dialog--traces)
- ;; insert and propertize the text
- ;;
- (setf (slime-trace-dialog--trace-beg trace) (point-marker))
- (insert id-string " ")
- (insert indent-spec)
- (if has-children-p
- (insert (slime-trace-dialog--make-collapse-button trace))
- (setf (slime-trace-dialog--trace-collapse-button-marker trace)
- (point-marker))
- (insert "-"))
- (insert (format " %s\n" spec))
- (setf (slime-trace-dialog--trace-summary-beg trace) (point-marker))
- (insert summary)
- (setf (slime-trace-dialog--trace-end trace) (point-marker))
- (set-marker-insertion-type (slime-trace-dialog--trace-beg trace) t)
-
- (add-text-properties (slime-trace-dialog--trace-beg trace)
- (slime-trace-dialog--trace-end trace)
- (list 'slime-trace-dialog--id id
- 'point-entered autofollow-fn
- 'point-left autofollow-fn))
- ;; respect brief mode and collapsed state
- ;;
- (cl-loop for condition in (list slime-trace-dialog-hide-details-mode
- (slime-trace-dialog--trace-collapsed-p trace))
- when condition
- do (slime-trace-dialog--hide-unhide
- (slime-trace-dialog--trace-summary-beg
- trace)
- (slime-trace-dialog--trace-end trace)
- 1))
- (cl-loop for tr = trace then parent
- for parent = (slime-trace-dialog--trace-parent tr)
- while parent
- when (slime-trace-dialog--trace-collapsed-p parent)
- do (slime-trace-dialog--hide-unhide
- (slime-trace-dialog--trace-beg trace)
- (slime-trace-dialog--trace-end trace)
- (+ 1
- (or (get-text-property (slime-trace-dialog--trace-beg parent)
- 'slime-trace-dialog--hidden-level)
- 0)))
- (cl-return))
- ;; maybe add the collapse-button to the parent in case it didn't
- ;; have one already
- ;;
- (when (and parent
- (slime-trace-dialog--trace-collapse-button-marker parent))
- (slime-trace-dialog--maintaining-properties
- (slime-trace-dialog--trace-collapse-button-marker parent)
- (delete-char 1)
- (insert (slime-trace-dialog--make-collapse-button parent))
- (setf (slime-trace-dialog--trace-collapse-button-marker parent)
- nil)))
- ;; draw the tree lines
- ;;
- (when parent
- (slime-trace-dialog--draw-tree-lines (slime-trace-dialog--trace-beg trace)
- (+ 2 (length indent-spec))
- 'up))
- (when has-children-p
- (slime-trace-dialog--draw-tree-lines (slime-trace-dialog--trace-beg trace)
- (+ 5 (length indent-spec))
- 'down))
- ;; set the "children-end" slot
- ;;
- (unless (slime-trace-dialog--trace-children-end trace)
- (cl-loop for parent = trace
- then (slime-trace-dialog--trace-parent parent)
- while parent
- do
- (setf (slime-trace-dialog--trace-children-end parent)
- (slime-trace-dialog--trace-end trace))))))
-
- (defun slime-trace-dialog--render-trace (trace)
- ;; Render the trace entry in the appropriate place.
- ;;
- ;; A trace becomes a few lines of slightly propertized text in the
- ;; buffer, inserted by `slime-trace-dialog--insert-trace', bound by
- ;; point markers that we use here.
- ;;
- ;; The new trace might be replacing an existing one, or otherwise
- ;; must be placed under its existing parent which might or might not
- ;; be the last entry inserted.
- ;;
- (let ((existing (slime-trace-dialog--find-trace
- (slime-trace-dialog--trace-id trace)))
- (parent (slime-trace-dialog--trace-parent trace)))
- (cond (existing
- ;; Other traces might already reference `existing' and with
- ;; need to maintain that eqness. Best way to do that is
- ;; destructively modify `existing' with the new retlist...
- ;;
- (setf (slime-trace-dialog--trace-retlist existing)
- (slime-trace-dialog--trace-retlist trace))
- ;; Now, before deleting and re-inserting `existing' at an
- ;; arbitrary point in the tree, note that it's
- ;; "children-end" marker is already non-nil, and informs us
- ;; about its parenthood status. We want to 1. leave it
- ;; alone if it's already a parent, or 2. set it to nil if
- ;; it's a leaf, thus forcing the needed update of the
- ;; parents' "children-end" marker.
- ;;
- (when (= (slime-trace-dialog--trace-children-end existing)
- (slime-trace-dialog--trace-end existing))
- (setf (slime-trace-dialog--trace-children-end existing) nil))
- (delete-region (slime-trace-dialog--trace-beg existing)
- (slime-trace-dialog--trace-end existing))
- (goto-char (slime-trace-dialog--trace-end existing))
- ;; Remember to set `trace' to be `existing'
- ;;
- (setq trace existing))
- (parent
- (goto-char (1+ (slime-trace-dialog--trace-children-end parent))))
- (;; top level trace
- t
- (goto-char (point-max))))
- (goto-char (line-beginning-position))
- (slime-trace-dialog--insert-trace trace)))
-
- (defun slime-trace-dialog--update-tree (tuples)
- (save-excursion
- (slime-trace-dialog--refresh
- (:overlay slime-trace-dialog--tree-overlay
- :dont-erase t)
- (cl-loop for tuple in tuples
- for parent = (slime-trace-dialog--find-trace (cl-second tuple))
- for trace = (slime-trace-dialog--make-trace
- :id (cl-first tuple)
- :parent parent
- :spec (cl-third tuple)
- :args (cl-fourth tuple)
- :retlist (cl-fifth tuple)
- :depth (if parent
- (1+ (slime-trace-dialog--trace-depth
- parent))
- 0))
- do (slime-trace-dialog--render-trace trace)))))
-
- (defun slime-trace-dialog--clear-local-tree ()
- (set (make-local-variable 'slime-trace-dialog--fetch-key)
- (cl-gensym "slime-trace-dialog-fetch-key-"))
- (set (make-local-variable 'slime-trace-dialog--traces)
- (make-hash-table))
- (slime-trace-dialog--refresh
- (:overlay slime-trace-dialog--tree-overlay))
- (slime-trace-dialog--update-progress nil))
-
- (defun slime-trace-dialog--on-new-results (results &optional recurse)
- (cl-destructuring-bind (tuples remaining reply-key)
- results
- (cond ((and slime-trace-dialog--fetch-key
- (string= (symbol-name slime-trace-dialog--fetch-key)
- (symbol-name reply-key)))
- (slime-trace-dialog--update-tree tuples)
- (slime-trace-dialog--update-progress
- remaining
- (and recurse
- (cl-plusp remaining))
- t)
- (when (and recurse
- (not (prog1 slime-trace-dialog--stop-fetching
- (setq slime-trace-dialog--stop-fetching nil)))
- (cl-plusp remaining))
- (slime-eval-async `(swank-trace-dialog:report-partial-tree
- ',reply-key)
- #'(lambda (results) (slime-trace-dialog--on-new-results
- results
- recurse))))))))
-
- ;;;; Interactive functions
- ;;;
- (defun slime-trace-dialog-fetch-specs ()
- "Refresh just list of traced specs."
- (interactive)
- (slime-eval-async `(swank-trace-dialog:report-specs)
- #'slime-trace-dialog--open-specs))
-
- (defun slime-trace-dialog-fetch-progress ()
- (interactive)
- (slime-eval-async
- '(swank-trace-dialog:report-total)
- #'(lambda (total)
- (slime-trace-dialog--update-progress
- total))))
-
- (defun slime-trace-dialog-fetch-status ()
- "Refresh just the status part of the SLIME Trace Dialog"
- (interactive)
- (slime-trace-dialog-fetch-specs)
- (slime-trace-dialog-fetch-progress))
-
- (defun slime-trace-dialog-clear-fetched-traces (&optional interactive)
- "Clear local and remote traces collected so far"
- (interactive "p")
- (when (or (not interactive)
- (y-or-n-p "Clear all collected and fetched traces?"))
- (slime-eval-async
- '(swank-trace-dialog:clear-trace-tree)
- #'(lambda (_ignored)
- (slime-trace-dialog--clear-local-tree)))))
-
- (defun slime-trace-dialog-fetch-traces (&optional recurse)
- (interactive "P")
- (setq slime-trace-dialog--stop-fetching nil)
- (slime-eval-async `(swank-trace-dialog:report-partial-tree
- ',slime-trace-dialog--fetch-key)
- #'(lambda (results) (slime-trace-dialog--on-new-results results
- recurse))))
-
- (defun slime-trace-dialog-next-button (&optional goback)
- (interactive)
- (let ((finder (if goback
- #'previous-single-property-change
- #'next-single-property-change)))
- (cl-loop for pos = (funcall finder (point) 'action)
- while pos
- do (goto-char pos)
- until (get-text-property pos 'action))))
-
- (defun slime-trace-dialog-prev-button ()
- (interactive)
- (slime-trace-dialog-next-button 'goback))
-
- (defvar slime-trace-dialog-after-toggle-hook nil
- "Hooks run after toggling a dialog-trace")
-
- (defun slime-trace-dialog-toggle-trace (&optional using-context-p)
- "Toggle the dialog-trace of the spec at point.
-
- When USING-CONTEXT-P, attempt to decipher lambdas. methods and
- other complicated function specs."
- (interactive "P")
- ;; Notice the use of "spec strings" here as opposed to the
- ;; proper cons specs we use on the swank side.
- ;;
- ;; Notice the conditional use of `slime-trace-query' found in
- ;; swank-fancy-trace.el
- ;;
- (let* ((spec-string (if using-context-p
- (slime-extract-context)
- (slime-symbol-at-point)))
- (spec-string (if (fboundp 'slime-trace-query)
- (slime-trace-query spec-string)
- spec-string)))
- (message "%s" (slime-eval `(swank-trace-dialog:dialog-toggle-trace
- (swank::from-string ,spec-string))))
- (run-hooks 'slime-trace-dialog-after-toggle-hook)))
-
- (defun slime-trace-dialog--update-existing-dialog ()
- (let ((existing (slime-trace-dialog--live-dialog)))
- (when existing
- (with-current-buffer existing
- (slime-trace-dialog-fetch-status)))))
-
- (add-hook 'slime-trace-dialog-after-toggle-hook
- 'slime-trace-dialog--update-existing-dialog)
-
- (defun slime-trace-dialog-toggle-complex-trace ()
- "Toggle the dialog-trace of the complex spec at point.
-
- See `slime-trace-dialog-toggle-trace'."
- (interactive)
- (slime-trace-dialog-toggle-trace t))
-
- (defun slime-trace-dialog (&optional clear-and-fetch)
- "Show trace dialog and refresh trace collection status.
-
- With optional CLEAR-AND-FETCH prefix arg, clear the current tree
- and fetch a first batch of traces."
- (interactive "P")
- (with-current-buffer
- (pop-to-buffer (slime-trace-dialog--ensure-buffer))
- (slime-trace-dialog-fetch-status)
- (when (or clear-and-fetch
- (null slime-trace-dialog--fetch-key))
- (slime-trace-dialog--clear-local-tree))
- (when clear-and-fetch
- (slime-trace-dialog-fetch-traces nil))))
-
- (defun slime-trace-dialog-copy-down-to-repl (id part-id type)
- "Eval the Trace Dialog entry under point in the REPL (to set *)"
- (interactive (cl-loop for prop in '(slime-trace-dialog--id
- slime-trace-dialog--part-id
- slime-trace-dialog--type)
- collect (get-text-property (point) prop)))
- (unless (and id part-id type) (error "No trace part at point %s" (point)))
- (slime-repl-send-string
- (format "%s" `(nth-value 0
- (swank-trace-dialog::find-trace-part
- ,id ,part-id ,type))))
- (slime-repl))
-
- (provide 'slime-trace-dialog)
|