Klimi's new dotfiles with stow.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

837 rader
34 KiB

4 år sedan
  1. ;;; -*- coding: utf-8; lexical-binding: t -*-
  2. ;;;
  3. ;;; slime-trace-dialog.el -- a navigable dialog of inspectable trace entries
  4. ;;;
  5. ;;; TODO: implement better wrap interface for sbcl method, labels and such
  6. ;;; TODO: backtrace printing is very slow
  7. ;;;
  8. (require 'slime)
  9. (require 'slime-parse)
  10. (require 'slime-repl)
  11. (require 'cl-lib)
  12. (define-slime-contrib slime-trace-dialog
  13. "Provide an interfactive trace dialog buffer for managing and
  14. inspecting details of traced functions. Invoke this dialog with C-c T."
  15. (:authors "João Távora <joaotavora@gmail.com>")
  16. (:license "GPL")
  17. (:swank-dependencies swank-trace-dialog)
  18. (:on-load (add-hook 'slime-mode-hook 'slime-trace-dialog-enable)
  19. (add-hook 'slime-repl-mode-hook 'slime-trace-dialog-enable))
  20. (:on-unload (remove-hook 'slime-mode-hook 'slime-trace-dialog-enable)
  21. (remove-hook 'slime-repl-mode-hook 'slime-trace-dialog-enable)))
  22. ;;;; Variables
  23. ;;;
  24. (defvar slime-trace-dialog-flash t
  25. "Non-nil means flash the updated region of the SLIME Trace Dialog. ")
  26. (defvar slime-trace-dialog--specs-overlay nil)
  27. (defvar slime-trace-dialog--progress-overlay nil)
  28. (defvar slime-trace-dialog--tree-overlay nil)
  29. (defvar slime-trace-dialog--collapse-chars (cons "-" "+"))
  30. ;;;; Local trace entry model
  31. (defvar slime-trace-dialog--traces nil)
  32. (cl-defstruct (slime-trace-dialog--trace
  33. (:constructor slime-trace-dialog--make-trace))
  34. id
  35. parent
  36. spec
  37. args
  38. retlist
  39. depth
  40. beg
  41. end
  42. collapse-button-marker
  43. summary-beg
  44. children-end
  45. collapsed-p)
  46. (defun slime-trace-dialog--find-trace (id)
  47. (gethash id slime-trace-dialog--traces))
  48. ;;;; Modes and mode maps
  49. ;;;
  50. (defvar slime-trace-dialog-mode-map
  51. (let ((map (make-sparse-keymap))
  52. (remaps '((slime-inspector-operate-on-point . nil)
  53. (slime-inspector-operate-on-click . nil)
  54. (slime-inspector-reinspect
  55. . slime-trace-dialog-fetch-status)
  56. (slime-inspector-next-inspectable-object
  57. . slime-trace-dialog-next-button)
  58. (slime-inspector-previous-inspectable-object
  59. . slime-trace-dialog-prev-button))))
  60. (set-keymap-parent map slime-inspector-mode-map)
  61. (cl-loop for (old . new) in remaps
  62. do (substitute-key-definition old new map))
  63. (set-keymap-parent map slime-parent-map)
  64. (define-key map (kbd "G") 'slime-trace-dialog-fetch-traces)
  65. (define-key map (kbd "C-k") 'slime-trace-dialog-clear-fetched-traces)
  66. (define-key map (kbd "g") 'slime-trace-dialog-fetch-status)
  67. (define-key map (kbd "M-RET") 'slime-trace-dialog-copy-down-to-repl)
  68. (define-key map (kbd "q") 'quit-window)
  69. map))
  70. (define-derived-mode slime-trace-dialog-mode fundamental-mode
  71. "SLIME Trace Dialog" "Mode for controlling SLIME's Trace Dialog"
  72. (set-syntax-table lisp-mode-syntax-table)
  73. (read-only-mode 1)
  74. (add-to-list (make-local-variable 'slime-trace-dialog-after-toggle-hook)
  75. 'slime-trace-dialog-fetch-status))
  76. (define-derived-mode slime-trace-dialog--detail-mode slime-inspector-mode
  77. "SLIME Trace Detail"
  78. "Mode for viewing a particular trace from SLIME's Trace Dialog")
  79. (setq slime-trace-dialog--detail-mode-map
  80. (let ((map (make-sparse-keymap))
  81. (remaps '((slime-inspector-next-inspectable-object
  82. . slime-trace-dialog-next-button)
  83. (slime-inspector-previous-inspectable-object
  84. . slime-trace-dialog-prev-button))))
  85. (set-keymap-parent map slime-trace-dialog-mode-map)
  86. (cl-loop for (old . new) in remaps
  87. do (substitute-key-definition old new map))
  88. map))
  89. (defvar slime-trace-dialog-minor-mode-map
  90. (let ((map (make-sparse-keymap)))
  91. (define-key map (kbd "C-c T") 'slime-trace-dialog)
  92. (define-key map (kbd "C-c M-t") 'slime-trace-dialog-toggle-trace)
  93. map))
  94. (define-minor-mode slime-trace-dialog-minor-mode
  95. "Add keybindings for accessing SLIME's Trace Dialog.")
  96. (defun slime-trace-dialog-enable ()
  97. (slime-trace-dialog-minor-mode 1))
  98. (easy-menu-define slime-trace-dialog--menubar (list slime-trace-dialog-minor-mode-map
  99. slime-trace-dialog-mode-map)
  100. "A menu for accessing some features of SLIME's Trace Dialog"
  101. (let* ((in-dialog '(eq major-mode 'slime-trace-dialog-mode))
  102. (dialog-live `(and ,in-dialog
  103. (memq slime-buffer-connection slime-net-processes)))
  104. (connected '(slime-connected-p)))
  105. `("Trace"
  106. ["Toggle trace" slime-trace-dialog-toggle-trace ,connected]
  107. ["Trace complex spec" slime-trace-dialog-toggle-complex-trace ,connected]
  108. ["Open Trace dialog" slime-trace-dialog (and ,connected (not ,in-dialog))]
  109. "--"
  110. [ "Refresh traces and progress" slime-trace-dialog-fetch-status ,dialog-live]
  111. [ "Fetch next batch" slime-trace-dialog-fetch-traces ,dialog-live]
  112. [ "Clear all fetched traces" slime-trace-dialog-clear-fetched-traces ,dialog-live]
  113. [ "Toggle details" slime-trace-dialog-hide-details-mode ,in-dialog]
  114. [ "Toggle autofollow" slime-trace-dialog-autofollow-mode ,in-dialog])))
  115. (define-minor-mode slime-trace-dialog-hide-details-mode
  116. "Hide details in `slime-trace-dialog-mode'"
  117. nil " Brief"
  118. :group 'slime-trace-dialog
  119. (unless (derived-mode-p 'slime-trace-dialog-mode)
  120. (error "Not a SLIME Trace Dialog buffer"))
  121. (slime-trace-dialog--set-hide-details-mode))
  122. (define-minor-mode slime-trace-dialog-autofollow-mode
  123. "Automatically open buffers with trace details from `slime-trace-dialog-mode'"
  124. nil " Autofollow"
  125. :group 'slime-trace-dialog
  126. (unless (derived-mode-p 'slime-trace-dialog-mode)
  127. (error "Not a SLIME Trace Dialog buffer")))
  128. ;;;; Helper functions
  129. ;;;
  130. (defun slime-trace-dialog--call-refreshing (buffer
  131. overlay
  132. dont-erase
  133. recover-point-p
  134. fn)
  135. (with-current-buffer buffer
  136. (let ((inhibit-point-motion-hooks t)
  137. (inhibit-read-only t)
  138. (saved (point)))
  139. (save-restriction
  140. (when overlay
  141. (narrow-to-region (overlay-start overlay)
  142. (overlay-end overlay)))
  143. (unwind-protect
  144. (if dont-erase
  145. (goto-char (point-max))
  146. (delete-region (point-min) (point-max)))
  147. (funcall fn)
  148. (when recover-point-p
  149. (goto-char saved)))
  150. (when slime-trace-dialog-flash
  151. (slime-flash-region (point-min) (point-max)))))
  152. buffer))
  153. (cl-defmacro slime-trace-dialog--refresh ((&key
  154. overlay
  155. dont-erase
  156. recover-point-p
  157. buffer)
  158. &rest body)
  159. (declare (indent 1)
  160. (debug (sexp &rest form)))
  161. `(slime-trace-dialog--call-refreshing ,(or buffer
  162. `(current-buffer))
  163. ,overlay
  164. ,dont-erase
  165. ,recover-point-p
  166. #'(lambda () ,@body)))
  167. (defmacro slime-trace-dialog--insert-and-overlay (string overlay)
  168. `(save-restriction
  169. (let ((inhibit-read-only t))
  170. (narrow-to-region (point) (point))
  171. (insert ,string "\n")
  172. (set (make-local-variable ',overlay)
  173. (let ((overlay (make-overlay (point-min)
  174. (point-max)
  175. (current-buffer)
  176. nil
  177. t)))
  178. (move-overlay overlay (overlay-start overlay)
  179. (1- (overlay-end overlay)))
  180. ;; (overlay-put overlay 'face '(:background "darkslategrey"))
  181. overlay)))))
  182. (defun slime-trace-dialog--buffer-name ()
  183. (format "*traces for %s*"
  184. (slime-connection-name slime-default-connection)))
  185. (defun slime-trace-dialog--live-dialog (&optional buffer-or-name)
  186. (let ((buffer-or-name (or buffer-or-name
  187. (slime-trace-dialog--buffer-name))))
  188. (and (buffer-live-p (get-buffer buffer-or-name))
  189. (with-current-buffer buffer-or-name
  190. (memq slime-buffer-connection slime-net-processes))
  191. buffer-or-name)))
  192. (defun slime-trace-dialog--ensure-buffer ()
  193. (let ((name (slime-trace-dialog--buffer-name)))
  194. (or (slime-trace-dialog--live-dialog name)
  195. (with-current-buffer (get-buffer-create name)
  196. (let ((inhibit-read-only t))
  197. (erase-buffer))
  198. (slime-trace-dialog-mode)
  199. (save-excursion
  200. (buffer-disable-undo)
  201. (slime-trace-dialog--insert-and-overlay
  202. "[waiting for the traced specs to be available]"
  203. slime-trace-dialog--specs-overlay)
  204. (slime-trace-dialog--insert-and-overlay
  205. "[waiting for some info on trace download progress ]"
  206. slime-trace-dialog--progress-overlay)
  207. (slime-trace-dialog--insert-and-overlay
  208. "[waiting for the actual traces to be available]"
  209. slime-trace-dialog--tree-overlay)
  210. (current-buffer))
  211. (setq slime-buffer-connection slime-default-connection)
  212. (current-buffer)))))
  213. (defun slime-trace-dialog--make-autofollow-fn (id)
  214. (let ((requested nil))
  215. #'(lambda (_before after)
  216. (let ((inhibit-point-motion-hooks t)
  217. (id-after (get-text-property after 'slime-trace-dialog--id)))
  218. (when (and (= after (point))
  219. slime-trace-dialog-autofollow-mode
  220. id-after
  221. (= id-after id)
  222. (not requested))
  223. (setq requested t)
  224. (slime-eval-async `(swank-trace-dialog:report-trace-detail
  225. ,id-after)
  226. #'(lambda (detail)
  227. (setq requested nil)
  228. (when detail
  229. (let ((inhibit-point-motion-hooks t))
  230. (slime-trace-dialog--open-detail detail
  231. 'no-pop))))))))))
  232. (defun slime-trace-dialog--set-collapsed (collapsed-p trace button)
  233. (save-excursion
  234. (setf (slime-trace-dialog--trace-collapsed-p trace) collapsed-p)
  235. (slime-trace-dialog--go-replace-char-at
  236. button
  237. (if collapsed-p
  238. (cdr slime-trace-dialog--collapse-chars)
  239. (car slime-trace-dialog--collapse-chars)))
  240. (slime-trace-dialog--hide-unhide
  241. (slime-trace-dialog--trace-summary-beg trace)
  242. (slime-trace-dialog--trace-end trace)
  243. (if collapsed-p 1 -1))
  244. (slime-trace-dialog--hide-unhide
  245. (slime-trace-dialog--trace-end trace)
  246. (slime-trace-dialog--trace-children-end trace)
  247. (if collapsed-p 1 -1))))
  248. (defun slime-trace-dialog--hide-unhide (start-pos end-pos delta)
  249. (cl-loop with inhibit-read-only = t
  250. for pos = start-pos then next
  251. for next = (next-single-property-change
  252. pos
  253. 'slime-trace-dialog--hidden-level
  254. nil
  255. end-pos)
  256. for hidden-level = (+ (or (get-text-property
  257. pos
  258. 'slime-trace-dialog--hidden-level)
  259. 0)
  260. delta)
  261. do (add-text-properties pos next
  262. (list 'slime-trace-dialog--hidden-level
  263. hidden-level
  264. 'invisible
  265. (cl-plusp hidden-level)))
  266. while (< next end-pos)))
  267. (defun slime-trace-dialog--set-hide-details-mode ()
  268. (cl-loop for trace being the hash-values of slime-trace-dialog--traces
  269. do (slime-trace-dialog--hide-unhide
  270. (slime-trace-dialog--trace-summary-beg trace)
  271. (slime-trace-dialog--trace-end trace)
  272. (if slime-trace-dialog-hide-details-mode 1 -1))))
  273. (defun slime-trace-dialog--format-part (part-id part-text trace-id type)
  274. (slime-trace-dialog--button
  275. (format "%s" part-text)
  276. #'(lambda (_button)
  277. (slime-eval-async
  278. `(swank-trace-dialog:inspect-trace-part ,trace-id ,part-id ,type)
  279. #'slime-open-inspector))
  280. 'mouse-face 'highlight
  281. 'slime-trace-dialog--part-id part-id
  282. 'slime-trace-dialog--type type
  283. 'face 'slime-inspector-value-face))
  284. (defun slime-trace-dialog--format-trace-entry (id external)
  285. (slime-trace-dialog--button
  286. (format "%s" external)
  287. #'(lambda (_button)
  288. (slime-eval-async
  289. `(swank::inspect-object (swank-trace-dialog::find-trace ,id))
  290. #'slime-open-inspector))
  291. 'face 'slime-inspector-value-face))
  292. (defun slime-trace-dialog--format (fmt-string &rest args)
  293. (let* ((string (apply #'format fmt-string args))
  294. (indent (make-string (max 2
  295. (- 50 (length string))) ? )))
  296. (format "%s%s" string indent)))
  297. (defun slime-trace-dialog--button (title lambda &rest props)
  298. (let ((string (format "%s" title)))
  299. (apply #'make-text-button string nil
  300. 'action #'(lambda (button)
  301. (funcall lambda button))
  302. 'mouse-face 'highlight
  303. 'face 'slime-inspector-action-face
  304. props)
  305. string))
  306. (defun slime-trace-dialog--call-maintaining-properties (pos fn)
  307. (save-excursion
  308. (goto-char pos)
  309. (let* ((saved-props (text-properties-at pos))
  310. (saved-point (point))
  311. (inhibit-read-only t)
  312. (inhibit-point-motion-hooks t))
  313. (funcall fn)
  314. (add-text-properties saved-point (point) saved-props)
  315. (if (markerp pos) (set-marker pos saved-point)))))
  316. (cl-defmacro slime-trace-dialog--maintaining-properties (pos
  317. &body body)
  318. (declare (indent 1))
  319. `(slime-trace-dialog--call-maintaining-properties ,pos #'(lambda () ,@body)))
  320. (defun slime-trace-dialog--go-replace-char-at (pos char)
  321. (slime-trace-dialog--maintaining-properties pos
  322. (delete-char 1)
  323. (insert char)))
  324. ;;;; Handlers for the *trace-dialog* and *trace-detail* buffers
  325. ;;;
  326. (defun slime-trace-dialog--open-specs (traced-specs)
  327. (cl-labels ((make-report-spec-fn
  328. (&optional form)
  329. #'(lambda (_button)
  330. (slime-eval-async
  331. `(cl:progn
  332. ,form
  333. (swank-trace-dialog:report-specs))
  334. #'(lambda (results)
  335. (slime-trace-dialog--open-specs results))))))
  336. (slime-trace-dialog--refresh
  337. (:overlay slime-trace-dialog--specs-overlay
  338. :recover-point-p t)
  339. (insert
  340. (slime-trace-dialog--format "Traced specs (%s)" (length traced-specs))
  341. (slime-trace-dialog--button "[refresh]"
  342. (make-report-spec-fn))
  343. "\n" (make-string 50 ? )
  344. (slime-trace-dialog--button
  345. "[untrace all]"
  346. (make-report-spec-fn `(swank-trace-dialog:dialog-untrace-all)))
  347. "\n\n")
  348. (cl-loop for spec in traced-specs
  349. do (insert
  350. " "
  351. (slime-trace-dialog--button
  352. "[untrace]"
  353. (make-report-spec-fn
  354. `(swank-trace-dialog:dialog-untrace ',spec)))
  355. (format " %s" spec)
  356. "\n")))))
  357. (defvar slime-trace-dialog--fetch-key nil)
  358. (defvar slime-trace-dialog--stop-fetching nil)
  359. (defun slime-trace-dialog--update-progress (total &optional show-stop-p remaining-p)
  360. ;; `remaining-p' indicates `total' is the number of remaining traces.
  361. (slime-trace-dialog--refresh
  362. (:overlay slime-trace-dialog--progress-overlay
  363. :recover-point-p t)
  364. (let* ((done (hash-table-count slime-trace-dialog--traces))
  365. (total (if remaining-p (+ done total) total)))
  366. (insert
  367. (slime-trace-dialog--format "Trace collection status (%d/%s)"
  368. done
  369. (or total "0"))
  370. (slime-trace-dialog--button "[refresh]"
  371. #'(lambda (_button)
  372. (slime-trace-dialog-fetch-progress))))
  373. (when (and total (cl-plusp (- total done)))
  374. (insert "\n" (make-string 50 ? )
  375. (slime-trace-dialog--button
  376. "[fetch next batch]"
  377. #'(lambda (_button)
  378. (slime-trace-dialog-fetch-traces nil)))
  379. "\n" (make-string 50 ? )
  380. (slime-trace-dialog--button
  381. "[fetch all]"
  382. #'(lambda (_button)
  383. (slime-trace-dialog-fetch-traces t)))))
  384. (when total
  385. (insert "\n" (make-string 50 ? )
  386. (slime-trace-dialog--button
  387. "[clear]"
  388. #'(lambda (_button)
  389. (slime-trace-dialog-clear-fetched-traces)))))
  390. (when show-stop-p
  391. (insert "\n" (make-string 50 ? )
  392. (slime-trace-dialog--button
  393. "[stop]"
  394. #'(lambda (_button)
  395. (setq slime-trace-dialog--stop-fetching t)))))
  396. (insert "\n\n"))))
  397. (defun slime-trace-dialog--open-detail (trace-tuple &optional no-pop)
  398. (slime-with-popup-buffer ("*trace-detail*" :select (not no-pop)
  399. :mode 'slime-trace-dialog--detail-mode)
  400. (cl-destructuring-bind (id _parent-id _spec args retlist backtrace external)
  401. trace-tuple
  402. (let ((headline (slime-trace-dialog--format-trace-entry id external)))
  403. (setq headline (format "%s\n%s\n"
  404. headline
  405. (make-string (length headline) ?-)))
  406. (insert headline))
  407. (cl-loop for (type objects label)
  408. in `((:arg ,args "Called with args:")
  409. (:retval ,retlist "Returned values:"))
  410. do (insert (format "\n%s\n" label))
  411. (insert (cl-loop for object in objects
  412. for i from 0
  413. concat (format " %s: %s\n" i
  414. (slime-trace-dialog--format-part
  415. (cl-first object)
  416. (cl-second object)
  417. id
  418. type)))))
  419. (when backtrace
  420. (insert "\nBacktrace:\n"
  421. (cl-loop for (i spec) in backtrace
  422. concat (format " %s: %s\n" i spec)))))))
  423. ;;;; Rendering traces
  424. ;;;
  425. (defun slime-trace-dialog--draw-tree-lines (start offset direction)
  426. (save-excursion
  427. (let ((inhibit-point-motion-hooks t))
  428. (goto-char start)
  429. (cl-loop with replace-set = (if (eq direction 'down)
  430. '(? )
  431. '(? ?`))
  432. for line-beginning = (line-beginning-position
  433. (if (eq direction 'down)
  434. 2 0))
  435. for pos = (+ line-beginning offset)
  436. while (and (< (point-min) line-beginning)
  437. (< line-beginning (point-max))
  438. (memq (char-after pos) replace-set))
  439. do
  440. (slime-trace-dialog--go-replace-char-at pos "|")
  441. (goto-char pos)))))
  442. (defun slime-trace-dialog--make-indent (depth suffix)
  443. (concat (make-string (* 3 (max 0 (1- depth))) ? )
  444. (if (cl-plusp depth) suffix)))
  445. (defun slime-trace-dialog--make-collapse-button (trace)
  446. (slime-trace-dialog--button (if (slime-trace-dialog--trace-collapsed-p trace)
  447. (cdr slime-trace-dialog--collapse-chars)
  448. (car slime-trace-dialog--collapse-chars))
  449. #'(lambda (button)
  450. (slime-trace-dialog--set-collapsed
  451. (not (slime-trace-dialog--trace-collapsed-p
  452. trace))
  453. trace
  454. button))))
  455. (defun slime-trace-dialog--insert-trace (trace)
  456. (let* ((id (slime-trace-dialog--trace-id trace))
  457. (parent (slime-trace-dialog--trace-parent trace))
  458. (has-children-p (slime-trace-dialog--trace-children-end trace))
  459. (indent-spec (slime-trace-dialog--make-indent
  460. (slime-trace-dialog--trace-depth trace)
  461. "`--"))
  462. (indent-summary (slime-trace-dialog--make-indent
  463. (slime-trace-dialog--trace-depth trace)
  464. " "))
  465. (autofollow-fn (slime-trace-dialog--make-autofollow-fn id))
  466. (id-string (slime-trace-dialog--button
  467. (format "%4s" id)
  468. #'(lambda (_button)
  469. (slime-eval-async
  470. `(swank-trace-dialog:report-trace-detail
  471. ,id)
  472. #'slime-trace-dialog--open-detail))))
  473. (spec (slime-trace-dialog--trace-spec trace))
  474. (summary (cl-loop for (type objects marker) in
  475. `((:arg ,(slime-trace-dialog--trace-args trace)
  476. " > ")
  477. (:retval ,(slime-trace-dialog--trace-retlist trace)
  478. " < "))
  479. concat (cl-loop for object in objects
  480. concat " "
  481. concat indent-summary
  482. concat marker
  483. concat (slime-trace-dialog--format-part
  484. (cl-first object)
  485. (cl-second object)
  486. id
  487. type)
  488. concat "\n"))))
  489. (puthash id trace slime-trace-dialog--traces)
  490. ;; insert and propertize the text
  491. ;;
  492. (setf (slime-trace-dialog--trace-beg trace) (point-marker))
  493. (insert id-string " ")
  494. (insert indent-spec)
  495. (if has-children-p
  496. (insert (slime-trace-dialog--make-collapse-button trace))
  497. (setf (slime-trace-dialog--trace-collapse-button-marker trace)
  498. (point-marker))
  499. (insert "-"))
  500. (insert (format " %s\n" spec))
  501. (setf (slime-trace-dialog--trace-summary-beg trace) (point-marker))
  502. (insert summary)
  503. (setf (slime-trace-dialog--trace-end trace) (point-marker))
  504. (set-marker-insertion-type (slime-trace-dialog--trace-beg trace) t)
  505. (add-text-properties (slime-trace-dialog--trace-beg trace)
  506. (slime-trace-dialog--trace-end trace)
  507. (list 'slime-trace-dialog--id id
  508. 'point-entered autofollow-fn
  509. 'point-left autofollow-fn))
  510. ;; respect brief mode and collapsed state
  511. ;;
  512. (cl-loop for condition in (list slime-trace-dialog-hide-details-mode
  513. (slime-trace-dialog--trace-collapsed-p trace))
  514. when condition
  515. do (slime-trace-dialog--hide-unhide
  516. (slime-trace-dialog--trace-summary-beg
  517. trace)
  518. (slime-trace-dialog--trace-end trace)
  519. 1))
  520. (cl-loop for tr = trace then parent
  521. for parent = (slime-trace-dialog--trace-parent tr)
  522. while parent
  523. when (slime-trace-dialog--trace-collapsed-p parent)
  524. do (slime-trace-dialog--hide-unhide
  525. (slime-trace-dialog--trace-beg trace)
  526. (slime-trace-dialog--trace-end trace)
  527. (+ 1
  528. (or (get-text-property (slime-trace-dialog--trace-beg parent)
  529. 'slime-trace-dialog--hidden-level)
  530. 0)))
  531. (cl-return))
  532. ;; maybe add the collapse-button to the parent in case it didn't
  533. ;; have one already
  534. ;;
  535. (when (and parent
  536. (slime-trace-dialog--trace-collapse-button-marker parent))
  537. (slime-trace-dialog--maintaining-properties
  538. (slime-trace-dialog--trace-collapse-button-marker parent)
  539. (delete-char 1)
  540. (insert (slime-trace-dialog--make-collapse-button parent))
  541. (setf (slime-trace-dialog--trace-collapse-button-marker parent)
  542. nil)))
  543. ;; draw the tree lines
  544. ;;
  545. (when parent
  546. (slime-trace-dialog--draw-tree-lines (slime-trace-dialog--trace-beg trace)
  547. (+ 2 (length indent-spec))
  548. 'up))
  549. (when has-children-p
  550. (slime-trace-dialog--draw-tree-lines (slime-trace-dialog--trace-beg trace)
  551. (+ 5 (length indent-spec))
  552. 'down))
  553. ;; set the "children-end" slot
  554. ;;
  555. (unless (slime-trace-dialog--trace-children-end trace)
  556. (cl-loop for parent = trace
  557. then (slime-trace-dialog--trace-parent parent)
  558. while parent
  559. do
  560. (setf (slime-trace-dialog--trace-children-end parent)
  561. (slime-trace-dialog--trace-end trace))))))
  562. (defun slime-trace-dialog--render-trace (trace)
  563. ;; Render the trace entry in the appropriate place.
  564. ;;
  565. ;; A trace becomes a few lines of slightly propertized text in the
  566. ;; buffer, inserted by `slime-trace-dialog--insert-trace', bound by
  567. ;; point markers that we use here.
  568. ;;
  569. ;; The new trace might be replacing an existing one, or otherwise
  570. ;; must be placed under its existing parent which might or might not
  571. ;; be the last entry inserted.
  572. ;;
  573. (let ((existing (slime-trace-dialog--find-trace
  574. (slime-trace-dialog--trace-id trace)))
  575. (parent (slime-trace-dialog--trace-parent trace)))
  576. (cond (existing
  577. ;; Other traces might already reference `existing' and with
  578. ;; need to maintain that eqness. Best way to do that is
  579. ;; destructively modify `existing' with the new retlist...
  580. ;;
  581. (setf (slime-trace-dialog--trace-retlist existing)
  582. (slime-trace-dialog--trace-retlist trace))
  583. ;; Now, before deleting and re-inserting `existing' at an
  584. ;; arbitrary point in the tree, note that it's
  585. ;; "children-end" marker is already non-nil, and informs us
  586. ;; about its parenthood status. We want to 1. leave it
  587. ;; alone if it's already a parent, or 2. set it to nil if
  588. ;; it's a leaf, thus forcing the needed update of the
  589. ;; parents' "children-end" marker.
  590. ;;
  591. (when (= (slime-trace-dialog--trace-children-end existing)
  592. (slime-trace-dialog--trace-end existing))
  593. (setf (slime-trace-dialog--trace-children-end existing) nil))
  594. (delete-region (slime-trace-dialog--trace-beg existing)
  595. (slime-trace-dialog--trace-end existing))
  596. (goto-char (slime-trace-dialog--trace-end existing))
  597. ;; Remember to set `trace' to be `existing'
  598. ;;
  599. (setq trace existing))
  600. (parent
  601. (goto-char (1+ (slime-trace-dialog--trace-children-end parent))))
  602. (;; top level trace
  603. t
  604. (goto-char (point-max))))
  605. (goto-char (line-beginning-position))
  606. (slime-trace-dialog--insert-trace trace)))
  607. (defun slime-trace-dialog--update-tree (tuples)
  608. (save-excursion
  609. (slime-trace-dialog--refresh
  610. (:overlay slime-trace-dialog--tree-overlay
  611. :dont-erase t)
  612. (cl-loop for tuple in tuples
  613. for parent = (slime-trace-dialog--find-trace (cl-second tuple))
  614. for trace = (slime-trace-dialog--make-trace
  615. :id (cl-first tuple)
  616. :parent parent
  617. :spec (cl-third tuple)
  618. :args (cl-fourth tuple)
  619. :retlist (cl-fifth tuple)
  620. :depth (if parent
  621. (1+ (slime-trace-dialog--trace-depth
  622. parent))
  623. 0))
  624. do (slime-trace-dialog--render-trace trace)))))
  625. (defun slime-trace-dialog--clear-local-tree ()
  626. (set (make-local-variable 'slime-trace-dialog--fetch-key)
  627. (cl-gensym "slime-trace-dialog-fetch-key-"))
  628. (set (make-local-variable 'slime-trace-dialog--traces)
  629. (make-hash-table))
  630. (slime-trace-dialog--refresh
  631. (:overlay slime-trace-dialog--tree-overlay))
  632. (slime-trace-dialog--update-progress nil))
  633. (defun slime-trace-dialog--on-new-results (results &optional recurse)
  634. (cl-destructuring-bind (tuples remaining reply-key)
  635. results
  636. (cond ((and slime-trace-dialog--fetch-key
  637. (string= (symbol-name slime-trace-dialog--fetch-key)
  638. (symbol-name reply-key)))
  639. (slime-trace-dialog--update-tree tuples)
  640. (slime-trace-dialog--update-progress
  641. remaining
  642. (and recurse
  643. (cl-plusp remaining))
  644. t)
  645. (when (and recurse
  646. (not (prog1 slime-trace-dialog--stop-fetching
  647. (setq slime-trace-dialog--stop-fetching nil)))
  648. (cl-plusp remaining))
  649. (slime-eval-async `(swank-trace-dialog:report-partial-tree
  650. ',reply-key)
  651. #'(lambda (results) (slime-trace-dialog--on-new-results
  652. results
  653. recurse))))))))
  654. ;;;; Interactive functions
  655. ;;;
  656. (defun slime-trace-dialog-fetch-specs ()
  657. "Refresh just list of traced specs."
  658. (interactive)
  659. (slime-eval-async `(swank-trace-dialog:report-specs)
  660. #'slime-trace-dialog--open-specs))
  661. (defun slime-trace-dialog-fetch-progress ()
  662. (interactive)
  663. (slime-eval-async
  664. '(swank-trace-dialog:report-total)
  665. #'(lambda (total)
  666. (slime-trace-dialog--update-progress
  667. total))))
  668. (defun slime-trace-dialog-fetch-status ()
  669. "Refresh just the status part of the SLIME Trace Dialog"
  670. (interactive)
  671. (slime-trace-dialog-fetch-specs)
  672. (slime-trace-dialog-fetch-progress))
  673. (defun slime-trace-dialog-clear-fetched-traces (&optional interactive)
  674. "Clear local and remote traces collected so far"
  675. (interactive "p")
  676. (when (or (not interactive)
  677. (y-or-n-p "Clear all collected and fetched traces?"))
  678. (slime-eval-async
  679. '(swank-trace-dialog:clear-trace-tree)
  680. #'(lambda (_ignored)
  681. (slime-trace-dialog--clear-local-tree)))))
  682. (defun slime-trace-dialog-fetch-traces (&optional recurse)
  683. (interactive "P")
  684. (setq slime-trace-dialog--stop-fetching nil)
  685. (slime-eval-async `(swank-trace-dialog:report-partial-tree
  686. ',slime-trace-dialog--fetch-key)
  687. #'(lambda (results) (slime-trace-dialog--on-new-results results
  688. recurse))))
  689. (defun slime-trace-dialog-next-button (&optional goback)
  690. (interactive)
  691. (let ((finder (if goback
  692. #'previous-single-property-change
  693. #'next-single-property-change)))
  694. (cl-loop for pos = (funcall finder (point) 'action)
  695. while pos
  696. do (goto-char pos)
  697. until (get-text-property pos 'action))))
  698. (defun slime-trace-dialog-prev-button ()
  699. (interactive)
  700. (slime-trace-dialog-next-button 'goback))
  701. (defvar slime-trace-dialog-after-toggle-hook nil
  702. "Hooks run after toggling a dialog-trace")
  703. (defun slime-trace-dialog-toggle-trace (&optional using-context-p)
  704. "Toggle the dialog-trace of the spec at point.
  705. When USING-CONTEXT-P, attempt to decipher lambdas. methods and
  706. other complicated function specs."
  707. (interactive "P")
  708. ;; Notice the use of "spec strings" here as opposed to the
  709. ;; proper cons specs we use on the swank side.
  710. ;;
  711. ;; Notice the conditional use of `slime-trace-query' found in
  712. ;; swank-fancy-trace.el
  713. ;;
  714. (let* ((spec-string (if using-context-p
  715. (slime-extract-context)
  716. (slime-symbol-at-point)))
  717. (spec-string (if (fboundp 'slime-trace-query)
  718. (slime-trace-query spec-string)
  719. spec-string)))
  720. (message "%s" (slime-eval `(swank-trace-dialog:dialog-toggle-trace
  721. (swank::from-string ,spec-string))))
  722. (run-hooks 'slime-trace-dialog-after-toggle-hook)))
  723. (defun slime-trace-dialog--update-existing-dialog ()
  724. (let ((existing (slime-trace-dialog--live-dialog)))
  725. (when existing
  726. (with-current-buffer existing
  727. (slime-trace-dialog-fetch-status)))))
  728. (add-hook 'slime-trace-dialog-after-toggle-hook
  729. 'slime-trace-dialog--update-existing-dialog)
  730. (defun slime-trace-dialog-toggle-complex-trace ()
  731. "Toggle the dialog-trace of the complex spec at point.
  732. See `slime-trace-dialog-toggle-trace'."
  733. (interactive)
  734. (slime-trace-dialog-toggle-trace t))
  735. (defun slime-trace-dialog (&optional clear-and-fetch)
  736. "Show trace dialog and refresh trace collection status.
  737. With optional CLEAR-AND-FETCH prefix arg, clear the current tree
  738. and fetch a first batch of traces."
  739. (interactive "P")
  740. (with-current-buffer
  741. (pop-to-buffer (slime-trace-dialog--ensure-buffer))
  742. (slime-trace-dialog-fetch-status)
  743. (when (or clear-and-fetch
  744. (null slime-trace-dialog--fetch-key))
  745. (slime-trace-dialog--clear-local-tree))
  746. (when clear-and-fetch
  747. (slime-trace-dialog-fetch-traces nil))))
  748. (defun slime-trace-dialog-copy-down-to-repl (id part-id type)
  749. "Eval the Trace Dialog entry under point in the REPL (to set *)"
  750. (interactive (cl-loop for prop in '(slime-trace-dialog--id
  751. slime-trace-dialog--part-id
  752. slime-trace-dialog--type)
  753. collect (get-text-property (point) prop)))
  754. (unless (and id part-id type) (error "No trace part at point %s" (point)))
  755. (slime-repl-send-string
  756. (format "%s" `(nth-value 0
  757. (swank-trace-dialog::find-trace-part
  758. ,id ,part-id ,type))))
  759. (slime-repl))
  760. (provide 'slime-trace-dialog)