;;; -*- 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 ") (: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)