Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

491 lines
16 KiB

  1. ;;; Backtrace buffer
  2. ;; Author: Rocky Bernstein <rocky@gnu.org>
  3. ;; Copyright (C) 2015-2017, 2019 Free Software Foundation, Inc
  4. ;; This program is free software: you can redistribute it and/or
  5. ;; modify it under the terms of the GNU General Public License as
  6. ;; published by the Free Software Foundation, either version 3 of the
  7. ;; License, or (at your option) any later version.
  8. ;; This program is distributed in the hope that it will be useful, but
  9. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. ;; General Public License for more details.
  12. ;; You should have received a copy of the GNU General Public License
  13. ;; along with this program. If not, see
  14. ;; <http://www.gnu.org/licenses/>.
  15. (require 'ansi-color)
  16. (require 'ring)
  17. (require 'load-relative)
  18. (eval-when-compile (require 'cl-lib))
  19. (require-relative-list
  20. '("../key" "helper" "../follow" "../loc") "realgud-")
  21. (require-relative-list '("command") "realgud-buffer-")
  22. (declare-function realgud-cmdbuf-debugger-name 'realgud-buffer-command)
  23. (declare-function realgud-cmdbuf? 'realgud-buffer-command)
  24. (declare-function realgud-cmdbuf-info-bt-buf= 'realgud-buffer-command)
  25. (declare-function realgud-cmdbuf-info-divert-output?= 'realgud-buffer-command)
  26. (declare-function realgud-backtrace-mode (cmdbuf))
  27. (declare-function realgud:cmd-backtrace (arg))
  28. (declare-function realgud-cmdbuf-pat(key))
  29. (declare-function realgud-cmdbuf-info-in-srcbuf?= (arg))
  30. (declare-function realgud-get-cmdbuf 'realgud-buffer-helper)
  31. (declare-function realgud:file-loc-from-line 'realgud-file)
  32. (declare-function buffer-killed? 'realgud-helper)
  33. (declare-function realgud:loc-describe 'realgud-loc)
  34. (cl-defstruct realgud-backtrace-info
  35. "debugger object/structure specific to a (top-level) program to be debugged."
  36. (cmdbuf nil) ;; buffer of the associated debugger process
  37. (cur-pos 0) ;; Frame we are at
  38. frame-ring ;; ring of marks in buffer of frame numbers. The
  39. ;; text at that marker has additional properties about the
  40. ;; frame
  41. )
  42. (declare-function realgud:cmd-frame 'realgud-buffer-command)
  43. (declare-function realgud-get-cmdbuf(&optional opt-buffer))
  44. (declare-function realgud-command 'realgud-send)
  45. (make-variable-buffer-local (defvar realgud-backtrace-info))
  46. ;: FIXME: not picked up from track. Why?
  47. (defvar realgud-track-divert-string nil)
  48. (defvar realgud-goto-entry-acc "")
  49. (defun realgud-get-buffer-base-name(string)
  50. "Leading and ending * in string. For example:
  51. *shell<2>* -> shell<2>
  52. *foo shell* -> foo
  53. buffer.c -> buffer.c"
  54. (if (string-match "^[*]?\\([^*]+\\)[*]?$" string)
  55. (let ((string-sans-stars (match-string 1 string)))
  56. (if (string-match "\\(.+\\) shell" string-sans-stars)
  57. (match-string 1 string-sans-stars)
  58. string-sans-stars)
  59. )
  60. string
  61. )
  62. )
  63. (defun realgud:backtrace-describe (&optional buffer)
  64. (interactive "")
  65. (unless buffer (setq buffer (current-buffer)))
  66. (with-current-buffer buffer
  67. (let ((frames (realgud-backtrace-info-frame-ring realgud-backtrace-info))
  68. (frame)
  69. (loc)
  70. (i 0))
  71. (switch-to-buffer (get-buffer-create "*Describe Backtrace*"))
  72. (while (and (< i (ring-length frames)) (setq frame (ring-ref frames i)))
  73. (insert (format "*** %d\n" i))
  74. (insert (format "%s\n" frame))
  75. (when (markerp frame)
  76. (with-current-buffer (marker-buffer frame)
  77. (goto-char frame)
  78. (setq loc (get-text-property (point) 'loc))
  79. )
  80. (when loc (realgud:loc-describe loc)))
  81. (setq i (1+ i))
  82. )
  83. )
  84. ))
  85. ;; FIXME: create this in a new frame.
  86. (defun realgud:backtrace-init ()
  87. (interactive)
  88. (let ((buffer (current-buffer))
  89. (cmdbuf (realgud-get-cmdbuf))
  90. (process)
  91. )
  92. (with-current-buffer-safe cmdbuf
  93. (let ((backtrace-pat (realgud-cmdbuf-pat "debugger-backtrace"))
  94. (indicator-re (or (realgud-cmdbuf-pat "selected-frame-indicator")
  95. "->"))
  96. (selected-frame-num)
  97. (frame-pos-ring)
  98. (sleep-count 0)
  99. )
  100. (unless backtrace-pat
  101. (error "No 'debugger-backtrace' regular expression recorded for debugger %s"
  102. (realgud-cmdbuf-debugger-name)))
  103. (setq process (get-buffer-process (current-buffer)))
  104. (realgud-cmdbuf-info-in-srcbuf?= (not (realgud-cmdbuf? buffer)))
  105. (realgud-cmdbuf-info-divert-output?= t)
  106. (setq realgud-track-divert-string nil)
  107. (realgud:cmd-backtrace 0)
  108. (while (and (eq 'run (process-status process))
  109. (null realgud-track-divert-string)
  110. (> 1000 (setq sleep-count (1+ sleep-count))))
  111. (sleep-for 0.001)
  112. )
  113. (if (>= sleep-count 1000)
  114. (message "Timeout on running debugger command")
  115. ;; else
  116. ;; (message "+++4 %s" realgud-track-divert-string)
  117. (let ((bt-buffer (get-buffer-create
  118. (format "*Backtrace %s*"
  119. (realgud-get-buffer-base-name
  120. (buffer-name)))))
  121. (divert-string realgud-track-divert-string)
  122. )
  123. (realgud-cmdbuf-info-bt-buf= bt-buffer)
  124. (with-current-buffer bt-buffer
  125. (setq buffer-read-only nil)
  126. (delete-region (point-min) (point-max))
  127. (if divert-string
  128. (let* ((triple
  129. (realgud:backtrace-add-text-properties
  130. backtrace-pat cmdbuf divert-string indicator-re))
  131. (string-with-props
  132. (ansi-color-filter-apply (car triple)))
  133. (frame-num-pos-list (cl-caddr triple))
  134. )
  135. (setq selected-frame-num (cadr triple))
  136. (insert string-with-props)
  137. ;; add marks for each position
  138. (realgud-backtrace-mode cmdbuf)
  139. (setq frame-pos-ring
  140. (make-ring (length frame-num-pos-list)))
  141. (dolist (pos frame-num-pos-list)
  142. (goto-char (1+ pos))
  143. (ring-insert-at-beginning frame-pos-ring (point-marker))
  144. )
  145. )
  146. )
  147. ;; realgud-backtrace-mode kills all local variables so
  148. ;; we set this after. Alternatively change realgud-backtrace-mode.
  149. (set (make-local-variable 'realgud-backtrace-info)
  150. (make-realgud-backtrace-info
  151. :cmdbuf cmdbuf
  152. :frame-ring frame-pos-ring
  153. ))
  154. (if selected-frame-num
  155. (realgud-backtrace-moveto-frame selected-frame-num))
  156. )
  157. )
  158. )
  159. )
  160. )
  161. (unless cmdbuf
  162. (message "Unable to find debugger command buffer for %s" buffer))
  163. )
  164. )
  165. (defun realgud-backtrace? ( &optional buffer)
  166. "Return true if BUFFER is a debugger command buffer."
  167. (with-current-buffer-safe
  168. (or buffer (current-buffer))
  169. (realgud-backtrace-info-set?)))
  170. (defalias 'realgud-backtrace-info? 'realgud-backtrace-info-p)
  171. (defun realgud-backtrace-info-set? ()
  172. "Return true if realgud-backtrace-info is set."
  173. (and (boundp 'realgud-backtrace-info)
  174. realgud-backtrace-info
  175. (realgud-backtrace-info? realgud-backtrace-info)))
  176. (defun realgud-backtrace-moveto-frame-selected ()
  177. "Set point to the selected frame."
  178. (interactive)
  179. (if (realgud-backtrace?)
  180. (let* ((cur-pos (realgud-sget 'backtrace-info 'cur-pos))
  181. (ring-size (ring-size (realgud-sget 'backtrace-info 'frame-ring)))
  182. )
  183. (if (and cur-pos (> ring-size 0))
  184. (realgud-backtrace-moveto-frame cur-pos)
  185. ;else
  186. (message "No frame information recorded")
  187. )
  188. )
  189. )
  190. )
  191. (defun realgud-backtrace-moveto-frame (num &optional _opt-buffer)
  192. (if (integerp num)
  193. (if (realgud-backtrace?)
  194. (let* ((ring (realgud-sget 'backtrace-info 'frame-ring))
  195. (marker (ring-ref ring num)))
  196. (setf (realgud-backtrace-info-cur-pos realgud-backtrace-info) num)
  197. (goto-char marker)
  198. )
  199. )
  200. ; else
  201. (message "frame number %s is not an integer" num)
  202. )
  203. )
  204. (defun realgud-backtrace-moveto-frame-next ()
  205. "Set point to the next frame. If we are at the end, wrap to the
  206. beginning. Note that we are just moving in the backtrace buffer,
  207. not updating the frame stack."
  208. (interactive)
  209. (if (realgud-backtrace?)
  210. (let* ((cur-pos (realgud-sget 'backtrace-info 'cur-pos))
  211. (ring-size (ring-size (realgud-sget 'backtrace-info 'frame-ring)))
  212. )
  213. (if (and cur-pos (> ring-size 0))
  214. (realgud-backtrace-moveto-frame (ring-plus1 cur-pos ring-size))
  215. ;else
  216. (message "No frame information recorded")
  217. )
  218. )
  219. )
  220. )
  221. (defun realgud-backtrace-moveto-frame-prev ()
  222. "Set point to the next frame. If we are at the beginning, wrap to the
  223. end. Note that we are just moving in the backtrace buffer,
  224. not updating the frame stack."
  225. (interactive)
  226. (if (realgud-backtrace?)
  227. (let* ((cur-pos (realgud-sget 'backtrace-info 'cur-pos))
  228. (ring-size (ring-size (realgud-sget 'backtrace-info 'frame-ring)))
  229. )
  230. (if (and cur-pos (> ring-size 0))
  231. (realgud-backtrace-moveto-frame (ring-minus1 cur-pos ring-size))
  232. ;else
  233. (message "No frame information recorded")
  234. )
  235. )
  236. )
  237. )
  238. (defun realgud-goto-frame-n-internal (keys)
  239. (if (and (stringp keys)
  240. (= (length keys) 1))
  241. (progn
  242. (setq realgud-goto-entry-acc (concat realgud-goto-entry-acc keys))
  243. ;; Try to find the longest suffix.
  244. (let ((acc realgud-goto-entry-acc))
  245. (while (not (string= acc ""))
  246. (if (not (realgud-goto-entry-try acc))
  247. (setq acc (substring acc 1))
  248. (realgud:cmd-frame (string-to-number acc))
  249. ;; Break loop.
  250. (setq acc "")))))
  251. (message "`realgud-goto-frame-n' must be bound to a number key")))
  252. ;; FIXME: replace with ring.
  253. (defun realgud-goto-entry-try (str)
  254. "See if there is an entry with number STR. If not return nil."
  255. (goto-char (point-min))
  256. (if (re-search-forward (concat "^[^0-9]*\\(" str "\\)[^0-9]") nil t)
  257. (progn
  258. (goto-char (match-end 1))
  259. t)
  260. nil))
  261. ;; The following is split in two to facilitate debugging.
  262. (defun realgud-goto-entry-n-internal (keys)
  263. (if (and (stringp keys)
  264. (= (length keys) 1))
  265. (progn
  266. (setq realgud-goto-entry-acc (concat realgud-goto-entry-acc keys))
  267. ;; Try to find the longest suffix.
  268. (let ((acc realgud-goto-entry-acc)
  269. (p (point)))
  270. (while (not (string= acc ""))
  271. (if (not (realgud-goto-entry-try acc))
  272. (setq acc (substring acc 1))
  273. (setq p (point))
  274. ;; Break loop.
  275. (setq acc "")))
  276. (goto-char p)))
  277. (message "`realgud-goto-entry-n' must be bound to a number key")))
  278. (defun realgud-goto-entry-n ()
  279. "Go to an entry number.
  280. Breakpoints, Display expressions and Stack Frames all have
  281. numbers associated with them which are distinct from line
  282. numbers. In a secondary buffer, this function is usually bound to
  283. a numeric key which will position you at that entry number. To
  284. go to an entry above 9, just keep entering the number. For
  285. example, if you press 1 and then 9, you should jump to entry
  286. 1 (if it exists) and then 19 (if that exists). Entering any
  287. non-digit will start entry number from the beginning again."
  288. (interactive)
  289. (if (not (eq last-command 'realgud-goto-entry-n))
  290. (setq realgud-goto-entry-acc ""))
  291. (realgud-goto-entry-n-internal (this-command-keys)))
  292. (defun realgud-goto-frame ()
  293. "Go to the frame number. We get the frame number from the
  294. 'frame-num property"
  295. (interactive)
  296. (if (realgud-backtrace?)
  297. (let ((frame-num (get-text-property (point) 'frame-num)))
  298. (if frame-num
  299. (realgud:cmd-frame frame-num)
  300. (message "No frame property found at this point")
  301. )
  302. )
  303. )
  304. )
  305. (defun realgud-goto-frame-1 ()
  306. "Go to the frame 1"
  307. (interactive)
  308. (if (realgud-backtrace?)
  309. (realgud:cmd-frame 1)
  310. )
  311. )
  312. (defun realgud-goto-frame-2 ()
  313. "Go to the frame 2"
  314. (interactive)
  315. (if (realgud-backtrace?)
  316. (realgud:cmd-frame 2)
  317. )
  318. )
  319. (defun realgud-goto-frame-3 ()
  320. "Go to the frame 3"
  321. (interactive)
  322. (if (realgud-backtrace?)
  323. (realgud:cmd-frame 3)
  324. )
  325. )
  326. (defun realgud-goto-frame-mouse (event)
  327. (interactive "e")
  328. (let* ((pos (posn-point (event-end event)))
  329. (frame-num (get-text-property pos 'frame-num)))
  330. (if (realgud-backtrace?)
  331. (if frame-num
  332. (realgud:cmd-frame frame-num)
  333. (message "No frame property found at this point")
  334. )
  335. )
  336. )
  337. )
  338. (defun realgud-goto-frame-n ()
  339. "Go to the frame number indicated by the accumulated numeric keys just entered.
  340. This function is usually bound to a numeric key in a 'frame'
  341. secondary buffer. To go to an entry above 9, just keep entering
  342. the number. For example, if you press 1 and then 9, frame 1 is selected
  343. \(if it exists) and then frame 19 (if that exists). Entering any
  344. non-digit will start entry number from the beginning again."
  345. (interactive)
  346. (if (not (eq last-command 'realgud-goto-frame-n))
  347. (setq realgud-goto-entry-acc ""))
  348. (realgud-goto-frame-n-internal (this-command-keys)))
  349. (defun realgud:backtrace-add-text-properties(backtrace-pat cmdbuf &optional opt-string
  350. frame-indicator-re)
  351. "Parse OPT-STRING or the current buffer and add frame properties: frame number,
  352. filename, line number, whether the frame is selected as text properties."
  353. (let* ((string (or opt-string
  354. (buffer-substring (point-min) (point-max))
  355. ))
  356. (stripped-string (ansi-color-filter-apply string))
  357. (frame-regexp (realgud-loc-pat-regexp backtrace-pat))
  358. (frame-group-pat (realgud-loc-pat-num backtrace-pat))
  359. (file-group-pat (realgud-loc-pat-file-group backtrace-pat))
  360. (line-group-pat (realgud-loc-pat-line-group backtrace-pat))
  361. (alt-frame-num -1)
  362. (last-pos 0)
  363. (selected-frame-num nil)
  364. (frame-num-pos-list '())
  365. )
  366. (while (string-match frame-regexp stripped-string last-pos)
  367. (let ((frame-num-str) (frame-num) (line-num) (filename)
  368. ;; FIXME: Remove hack that group 1 is always the frame indicator.
  369. (frame-indicator
  370. (substring stripped-string (match-beginning 1) (match-end 1)))
  371. ;; From https://github.com/realgud/realgud/pull/192
  372. ;; Each frame of backtrace is searched via string-match
  373. ;; invocation and a position of the current frame is
  374. ;; updated via (setq last-pos (match-end 0)) in the end of
  375. ;; the loop. But somewhere in the body of the loop (I do
  376. ;; not know exactly where), there is another call to
  377. ;; string-match and it messes up all positions.
  378. (whole-match-begin (match-beginning 0))
  379. (whole-match-end (match-end 0))
  380. (frame-num-pos)
  381. )
  382. (if frame-group-pat
  383. (progn
  384. (setq frame-num-str
  385. (substring stripped-string
  386. (match-beginning frame-group-pat)
  387. (match-end frame-group-pat)))
  388. (setq frame-num (string-to-number frame-num-str))
  389. (setq frame-num-pos (match-beginning frame-group-pat))
  390. (cl-pushnew frame-num-pos frame-num-pos-list)
  391. (add-text-properties (match-beginning frame-group-pat)
  392. (match-end frame-group-pat)
  393. (list 'mouse-face 'highlight
  394. 'help-echo "mouse-2: goto this frame"
  395. 'frame frame-num)
  396. string)
  397. )
  398. ; else
  399. (progn
  400. (setq frame-num-str
  401. (substring stripped-string (match-beginning 0)
  402. (match-end 0)))
  403. (setq frame-num (cl-incf alt-frame-num))
  404. (setq frame-num-pos (match-beginning 0))
  405. (cl-pushnew frame-num-pos frame-num-pos-list)
  406. (add-text-properties (match-beginning 0) (match-end 0)
  407. (list 'mouse-face 'highlight
  408. 'help-echo "mouse-2: goto this frame"
  409. 'frame frame-num)
  410. string)
  411. )
  412. )
  413. (when file-group-pat
  414. (setq filename (substring stripped-string
  415. (match-beginning file-group-pat)
  416. (match-end file-group-pat)))
  417. (add-text-properties (match-beginning file-group-pat)
  418. (match-end file-group-pat)
  419. (list 'mouse-face 'highlight
  420. 'help-echo "mouse-2: goto this file"
  421. 'action 'realgud:follow-event
  422. 'file filename)
  423. string)
  424. )
  425. (when line-group-pat
  426. (let ((line-num-str (substring stripped-string
  427. (match-beginning line-group-pat)
  428. (match-end line-group-pat))))
  429. (setq line-num (string-to-number (or line-num-str "1")))
  430. ))
  431. (when (and (stringp filename) (numberp line-num))
  432. (let ((loc (realgud:file-loc-from-line filename line-num cmdbuf)))
  433. (put-text-property whole-match-begin whole-match-end
  434. 'loc loc string)
  435. ))
  436. (put-text-property whole-match-begin whole-match-end
  437. 'frame-num frame-num string)
  438. (setq last-pos whole-match-end)
  439. (if (string-match frame-indicator-re frame-indicator)
  440. (setq selected-frame-num frame-num))
  441. ))
  442. (list string selected-frame-num (nreverse frame-num-pos-list))
  443. )
  444. )
  445. (provide-me "realgud-buffer-")