/
edb.el
890 lines (775 loc) · 27.5 KB
/
edb.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
;;; edb.el --- Erlang debugger front-end
(eval-when-compile (require 'cl))
(require 'erl)
(require 'erl-service)
(require 'erlang)
(require 'ewoc)
(eval-and-compile
(autoload 'erlang-extended-mode "distel"))
(when (featurep 'xemacs)
(require 'overlay))
;; Hack for XEmacs compatibility..
(unless (fboundp 'line-beginning-position)
(defalias 'line-beginning-position 'point-at-bol))
;; ----------------------------------------------------------------------
;; Configurables
(defcustom edb-popup-monitor-on-event t
"*Automatically popup the monitor on interesting events.
An interesting event is an unattached process reaching a breakpoint,
or an attached process exiting."
:type 'boolean
:group 'distel)
(defface edb-breakpoint-face
`((((type tty) (class color))
(:background "red" :foreground "black"))
(((type tty) (class mono))
(:inverse-video t))
(((class color) (background dark))
(:background "darkred" :foreground "white"))
(((class color) (background light))
(:background "tomato" :foreground "black"))
(t (:background "gray")))
"Face for marking a breakpoint definition."
:group 'distel)
(defface edb-breakpoint-stale-face
`((((type tty) (class color))
(:background "yellow" :foreground "black"))
(((type tty) (class mono))
(:inverse-video t))
(((class color) (background dark))
(:background "purple4"))
(((class color) (background light))
(:background "medium purple" :foreground "black"))
(t (:background "dark gray")))
"Face for marking a stale breakpoint definition."
:group 'distel)
;; ----------------------------------------------------------------------
;; Integration with erlang-extended-mode buffers.
(make-variable-buffer-local
(defvar edb-module-interpreted nil
"Non-nil means that the buffer's Erlang module is interpreted.
This variable is meaningful in erlang-extended-mode buffers.
The interpreted status refers to the node currently being monitored by
edb."))
(defun edb-setup-source-buffer ()
(make-local-variable 'kill-buffer-hook)
(add-hook 'kill-buffer-hook 'edb-delete-buffer-breakpoints)
(make-local-variable 'after-change-functions)
(add-to-list 'after-change-functions 'edb-make-breakpoints-stale)
(edb-update-interpreted-status)
(when edb-module-interpreted
(edb-create-buffer-breakpoints (edb-module))))
(add-hook 'erlang-extended-mode-hook
'edb-setup-source-buffer)
;; ----------------------------------------------------------------------
;; EDB minor mode for erlang-mode source files
(defun edb-toggle-interpret (node module file)
"Toggle debug-interpreting of the current buffer's module."
(interactive (list (erl-target-node)
(edb-module)
buffer-file-name))
(when (edb-ensure-monitoring node)
(erl-spawn
(erl-set-name "EDB RPC to toggle interpretation of %S on %S"
module node)
(erl-send-rpc node 'distel 'debug_toggle (list module file))
(erl-receive (module)
((['rex 'interpreted]
(message "Interpreting: %S" module))
(['rex 'uninterpreted]
(message "Stopped interpreting: %S" module))
(['rex ['badrpc reason]]
(message "Failed to interpret-toggle: %S" reason)))))))
(defun edb-module ()
(if (erlang-get-module)
(intern (erlang-get-module))
(error "Can't determine module for current buffer")))
(defun edb-toggle-breakpoint (node module line)
"Toggle a breakpoint on the current line."
(interactive (list (erl-target-node)
(edb-module)
(edb-line-number)))
(unless (edb-module-interpreted-p module)
(error "Module is not interpreted, can't set breakpoints."))
(if edb-buffer-breakpoints-stale
(edb-toggle-stale-breakpoint module line)
(edb-toggle-real-breakpoint node module line)))
(defun edb-toggle-stale-breakpoint (module line)
(let ((overlay (edb-first (lambda (ov) (overlay-get ov 'edb-breakpoint))
(overlays-in (line-beginning-position)
(1+ (line-end-position))))))
(if overlay
(delete-overlay overlay)
(edb-create-breakpoint module line))))
(defun edb-toggle-real-breakpoint (node module line)
(when (edb-ensure-monitoring node)
(erl-spawn
(erl-set-name "EDB RPC to toggle of breakpoint %S:%S on %S"
module line node)
(erl-send-rpc node 'distel 'break_toggle (list module line))
(erl-receive (module line)
((['rex 'enabled]
(message "Enabled breakpoint at %S:%S" module line))
(['rex 'disabled]
(message "Disabled breakpoint at %S:%S" module line)))))))
(defun edb-module-interpreted-p (module)
(assoc module edb-interpreted-modules))
(defun edb-line-number ()
"Current line number."
;; Taken from `count-lines' in gud.el
(save-restriction
(widen)
(+ (count-lines 1 (point))
(if (bolp) 1 0))))
(defun edb-save-dbg-state (node)
"Save debugger state (modules to interpret and breakpoints).
Use edb-restore-dbg-state to restore the state to the erlang node."
(interactive (list (erl-target-node)))
(let ((do-save nil))
(when (or (null edb-saved-interpreted-modules)
(y-or-n-p "You already have a saved debugger state, continue? "))
(setq edb-saved-interpreted-modules edb-interpreted-modules)
(edb-save-breakpoints node)
(message "Debugger state saved."))))
(defun edb-restore-dbg-state (node)
"Restore debugger state (modules to interpret and breakpoints)."
(interactive (list (erl-target-node)))
(if edb-saved-interpreted-modules
(when (edb-ensure-monitoring node)
(erl-spawn
(erl-set-name "EDB RPC to restore debugger state on %S" node)
(erl-send-rpc node 'distel 'debug_add
(list edb-saved-interpreted-modules))
(erl-receive (node)
((['rex 'ok]
(when (edb-restore-breakpoints
node
(lambda ()
(message "Debugger state restored.")))))))))
(message "No saved debugger state, aborting.")))
;; ----------------------------------------------------------------------
;; Monitor process
(defvar edb-monitor-buffer nil
"Monitor process/viewer buffer.")
(defvar edb-monitor-node nil
"Node we are debug-monitoring.")
(defvar edb-monitor-mode-map nil
"Keymap for Erlang debug monitor mode.")
(defvar edb-interpreted-modules '()
"Set of (module filename) being interpreted on the currently monitored node.")
(defvar edb-saved-interpreted-modules '()
"Set of (module filename) to interpret if edb-restore-dbg-state is called.")
(unless edb-monitor-mode-map
(setq edb-monitor-mode-map (make-sparse-keymap))
(define-key edb-monitor-mode-map [return] 'edb-attach-command)
(define-key edb-monitor-mode-map [(control m)] 'edb-attach-command)
(define-key edb-monitor-mode-map [?a] 'edb-attach-command)
(define-key edb-monitor-mode-map [?q] 'erl-bury-viewer)
(define-key edb-monitor-mode-map [?k] 'erl-quit-viewer))
(defvar edb-processes nil
"EWOC of processes running interpreted code.")
(defstruct (edb-process
(:constructor nil)
(:constructor make-edb-process (pid mfa status info)))
pid mfa status info)
(defun edb-monitor-mode ()
"Major mode for viewing debug'able processes.
Available commands:
\\[edb-attach-command] - Attach to the process at point.
\\[erl-bury-viewer] - Hide the monitor window.
\\[erl-quit-viewer] - Quit monitor."
(interactive)
(kill-all-local-variables)
(setq buffer-read-only t)
(setq erl-old-window-configuration (current-window-configuration))
(use-local-map edb-monitor-mode-map)
(setq mode-name "EDB Monitor")
(setq major-mode 'edb-monitor-mode))
(defun edb-monitor-insert-process (p)
(let ((buffer-read-only nil)
(text (edb-monitor-format (erl-pid-to-string (edb-process-pid p))
(edb-process-mfa p)
(edb-process-status p)
(edb-process-info p))))
(put-text-property 0 (length text) 'erl-pid (edb-process-pid p) text)
(insert text)))
(defun edb-monitor-format (pid mfa status info)
(format "%s %s %s %s"
(padcut pid 12)
(padcut mfa 21)
(padcut status 9)
(cut info 21)))
(defun padcut (s w)
(let ((len (length s)))
(cond ((= len w) s)
((< len w) (concat s (make-string (- w len) ? )))
((> len w) (substring s 0 w)))))
(defun cut (s w)
(if (> (length s) w)
(substring s 0 w)
s))
(defun edb-monitor-header ()
(edb-monitor-format "PID" "Initial Call" "Status" "Info"))
(defun edb-monitor (node)
(interactive (list (erl-target-node)))
(when (edb-ensure-monitoring node)
(unless (get-buffer-window edb-monitor-buffer)
;; Update the restorable window configuration
(with-current-buffer edb-monitor-buffer
(setq erl-old-window-configuration
(current-window-configuration))))
(pop-to-buffer edb-monitor-buffer)
(condition-case nil
(progn
(search-forward "break")
(move-beginning-of-line))
(error nil))))
;; (goto-char (point-max))
;; (forward-line -2)))
(defun edb-ensure-monitoring (node)
"Make sure the debug monitor is watching the node.
Returns NIL if this cannot be ensured."
(if (edb-monitor-node-change-p node)
(when (y-or-n-p (format "Attach debugger to %S instead of %S? "
node edb-monitor-node))
;; Kill existing edb then start again
(kill-buffer edb-monitor-buffer)
(edb-start-monitor node))
(if (edb-monitor-live-p)
t
(edb-start-monitor node))))
(defun edb-monitor-node-change-p (node)
"Do we have to detach/reattach to debug on NODE?"
(and (edb-monitor-live-p)
(not (equal node edb-monitor-node))))
(defun edb-monitor-live-p ()
"Are we actively debug-monitoring a node?"
(and edb-monitor-buffer
(buffer-live-p edb-monitor-buffer)))
(defun edb-monitor-buffer-name (node)
(format "*edb %S*" node))
(defun edb-start-monitor (node)
"Start debug-monitoring NODE."
(erl-spawn
(erl-set-name "EDB Monitor on %S" node)
(setq edb-monitor-node node)
(setq edb-monitor-buffer (current-buffer))
(rename-buffer (edb-monitor-buffer-name node))
(edb-monitor-mode)
(make-local-variable 'kill-buffer-hook)
(add-hook 'kill-buffer-hook 'edb-monitor-cleanup)
(erl-send-rpc node 'distel 'debug_subscribe (list erl-self))
(erl-receive (node)
((['rex [interpreted breaks snapshot]]
(setq edb-interpreted-modules interpreted)
(edb-init-breakpoints breaks)
(edb-update-source-buffers)
(setq edb-processes
(ewoc-create 'edb-monitor-insert-process
(edb-monitor-header)))
(mapc (lambda (item)
(mlet [pid mfa status info] item
(ewoc-enter-last edb-processes
(make-edb-process pid
mfa
status
info))))
snapshot)
(&edb-monitor-loop))))))
(defun &edb-monitor-loop ()
"Monitor process main loop.
Tracks events and state changes from the Erlang node."
(erl-receive ()
((['int ['new_status pid status info]]
(let ((proc (edb-monitor-lookup pid)))
(if (null proc)
(message "Unknown process: %s" (erl-pid-to-string pid))
(setf (edb-process-status proc) (symbol-name status))
(setf (edb-process-info proc) info)
(when (and edb-popup-monitor-on-event
(edb-interesting-event-p pid status info))
(display-buffer (current-buffer))))))
;;
(['int ['new_process (pid mfa status info)]]
(ewoc-enter-last edb-processes
(make-edb-process pid
mfa
(symbol-name status)
info)))
;;
(['int ['interpret mod file]]
(push (list mod file) edb-interpreted-modules)
(edb-update-source-buffers mod))
;;
(['int ['no_interpret mod]]
(setq edb-interpreted-modules
(assq-delete-all mod edb-interpreted-modules))
(edb-update-source-buffers mod))
;;
(['int ['no_break mod]]
(edb-delete-breakpoints mod))
;;
(['int ['new_break [[mod line] _info]]]
(edb-create-breakpoint mod line))
;;
(['int ['delete_break [mod line]]]
(edb-delete-breakpoint mod line)))
(ewoc-refresh edb-processes)
(&edb-monitor-loop)))
(defun edb-get-buffer (mod)
(edb-get-buffer2 mod (buffer-list)))
(defun edb-get-buffer2 (mod bufl)
(if (null bufl) nil
(with-current-buffer (car bufl)
(if (and erlang-extended-mode
(eq (edb-source-file-module-name) mod))
(car bufl)
(edb-get-buffer2 mod (cdr bufl))))))
(defun edb-interesting-event-p (pid status info)
(or (and (eq status 'exit)
(edb-attached-p pid))
(and (eq status 'break)
(not (edb-attached-p pid)))))
(defun edb-update-interpreted-status ()
"Update `edb-module-interpreted' for current buffer."
(when erlang-extended-mode
(let ((mod (edb-source-file-module-name)))
(if (and mod (assq mod edb-interpreted-modules))
(setq edb-module-interpreted t)
(setq edb-module-interpreted nil)
;; the erlang debugger automatically removes breakpoints when a
;; module becomes uninterpreted, so we match it here
(edb-delete-breakpoints (edb-source-file-module-name))))
(force-mode-line-update)))
(defun edb-update-source-buffers (&optional mod)
"Update the debugging state of all Erlang buffers.
When MOD is given, only update those visiting that module."
(mapc (lambda (buf)
(with-current-buffer buf
(when (and erlang-extended-mode
(or (null mod)
(eq (edb-source-file-module-name) mod)))
(edb-update-interpreted-status))))
(buffer-list)))
(defun edb-source-file-module-name ()
"Return the Erlang module of the current buffer as a symbol, or NIL."
(let ((name (erlang-get-module)))
(if name (intern name) nil)))
(defun edb-monitor-lookup (pid)
(car (ewoc-collect edb-processes
(lambda (p) (equal (edb-process-pid p) pid)))))
(defun edb-monitor-cleanup ()
"Cleanup state after the edb process exits."
(setq edb-interpreted-modules '())
(edb-delete-all-breakpoints)
(edb-update-source-buffers)
(setq edb-monitor-node nil))
;; ----------------------------------------------------------------------
;; Attach process
(make-variable-buffer-local
(defvar edb-pid nil
"Pid of attached process."))
(make-variable-buffer-local
(defvar edb-node nil
"Node of attached process."))
(make-variable-buffer-local
(defvar edb-module nil
"Current module source code in attach buffer."))
(make-variable-buffer-local
(defvar edb-variables-buffer nil
"Buffer showing variable bindings of attached process."))
(make-variable-buffer-local
(defvar edb-attach-buffer nil
"True if buffer is attach buffer."))
(defvar edb-attach-with-new-frame nil
"When true, attaching to a process opens a new frame.")
;; Attach setup
(defun edb-attach-command ()
(interactive)
(let ((pid (get-text-property (point) 'erl-pid)))
(if pid
(progn (when edb-attach-with-new-frame
(select-frame (make-frame)))
(edb-attach pid))
(error "No process at point."))))
(defun edb-attach (pid)
(let ((old-window-config (current-window-configuration)))
(delete-other-windows)
(switch-to-buffer (edb-attach-buffer pid))
(setq erl-old-window-configuration old-window-config)))
(defun edb-attach-buffer (pid)
(let ((bufname (edb-attach-buffer-name pid)))
(or (get-buffer bufname)
(edb-new-attach-buffer pid))))
(defun edb-new-attach-buffer (pid)
"Start a new attach process and returns its buffer."
(erl-pid->buffer
(erl-spawn
(erl-set-name "EDB Attach to process %S on %S"
(erl-pid-id pid)
(erl-pid-node pid))
(rename-buffer (edb-attach-buffer-name pid))
;; We must inhibit the erlang-new-file-hook, otherwise we trigger
;; it by entering erlang-mode in an empty buffer
(let ((erlang-new-file-hook nil))
(erlang-mode))
(erlang-extended-mode t)
(edb-attach-mode t)
(setq edb-attach-buffer t)
(message "Entered debugger. Press 'h' for help.")
(setq buffer-read-only t)
(erl-send-rpc (erl-pid-node pid)
'distel 'debug_attach (list erl-self pid))
(erl-receive ()
((['rex pid]
(assert (erl-pid-p pid))
(setq edb-pid pid)
(setq edb-node (erl-pid-node pid))
(save-excursion (edb-make-variables-window))))
(&edb-attach-loop)))))
;; Variables listing window
(defun edb-make-variables-window ()
"Make a window and buffer for viewing variable bindings.
The *Variables* buffer is killed with the current buffer."
(split-window-vertically (edb-variables-window-height))
(let ((vars-buf (edb-make-variables-buffer)))
(setq edb-variables-buffer vars-buf)
(make-local-variable 'kill-buffer-hook)
(add-hook 'kill-buffer-hook
(lambda () (kill-buffer edb-variables-buffer)))
(other-window 1)
(switch-to-buffer vars-buf)
(other-window -1)))
(defun edb-variables-window-height ()
(- (min (/ (window-height) 2) 12)))
(defun edb-make-variables-buffer ()
"Create the edb variable list buffer."
(let ((meta-pid edb-pid))
(with-current-buffer (generate-new-buffer "*Variables*")
(edb-variables-mode)
(setq edb-pid meta-pid)
(current-buffer))))
(defun edb-variables-mode ()
(kill-all-local-variables)
(setq major-mode 'edb-variables)
(setq mode-name "EDB Variables")
(setq buffer-read-only t)
(use-local-map edb-variables-mode-map))
(defvar edb-variables-mode-map nil
"Keymap for EDB variables viewing.")
(when (null edb-variables-mode-map)
(setq edb-variables-mode-map (make-sparse-keymap))
(define-key edb-variables-mode-map [?m] 'edb-show-variable)
(define-key edb-variables-mode-map [(control m)] 'edb-show-variable))
(defun edb-show-variable ()
"Pop a window showing the full value of the variable at point."
(interactive)
(let ((var (get-text-property (point) 'edb-variable-name)))
(if (null var)
(message "No variable at point")
(edb-attach-meta-cmd `[get_binding ,var]))))
;; Attach process states
(defun &edb-attach-loop ()
"Attached process loop."
(erl-receive ()
((['location mod line pos max]
(let ((msg (format "Location: %S:%S (Stack pos: %S/%S)"
mod line pos max)))
(setq header-line-format msg))
(&edb-attach-goto-source mod line))
(['status status]
(unless (memq status '(running idle))
(message "Unrecognised status: %S" status))
(setq header-line-format (format "Status: %S" status))
(setq overlay-arrow-position nil)
(&edb-attach-loop))
(['variables vars]
;; {variables, [{Name, String}]}
(when (buffer-live-p edb-variables-buffer)
(with-current-buffer edb-variables-buffer
(let ((buffer-read-only nil))
(erase-buffer)
(mapc (lambda (b)
(let ((name (tuple-elt b 1))
(string (tuple-elt b 2)))
(put-text-property 0 (length string)
'edb-variable-name name
string)
(insert string)))
vars))))
(&edb-attach-loop))
(['message msg]
(message msg)
(&edb-attach-loop))
(['show_variable value]
(save-excursion (display-message-or-view value "*Variable Value*"))
(&edb-attach-loop))
(other
(message "Other: %S" other)
(&edb-attach-loop)))))
(defun &edb-attach-goto-source (module line)
"Display MODULE:LINE in the attach buffer and reenter attach loop."
(if (eq edb-module module)
(progn (edb-attach-goto-line line)
(&edb-attach-loop))
(&edb-attach-find-source module line)))
(defun &edb-attach-find-source (module line)
"Load the source code for MODULE into current buffer at LINE.
Once loaded, reenters the attach loop."
(erl-send-rpc edb-node 'distel 'find_source (list module))
(erl-receive (module line)
((['rex ['ok path]]
(if (file-regular-p path)
(progn (setq edb-module module)
(let ((buffer-read-only nil))
(erase-buffer)
(insert-file-contents path))
(edb-delete-buffer-breakpoints)
(edb-create-buffer-breakpoints module)
(edb-attach-goto-line line))
(message "No such file: %s" path))))
(&edb-attach-loop)))
(defun edb-attach-goto-line (line)
(goto-line line)
(setq overlay-arrow-string "=>")
(setq overlay-arrow-position (copy-marker (point))))
(defun edb-attach-buffer-name (pid)
(format "*edbproc %s on %S*"
(erl-pid-to-string pid)
(erl-pid-node pid)))
(defun edb-attached-p (pid)
"Non-nil when we have an attach buffer viewing PID."
(buffer-live-p (get-buffer (edb-attach-buffer-name pid))))
;; ----------------------------------------------------------------------
;; Attach minor mode and commands
(define-minor-mode edb-attach-mode
"Minor mode for debugging an Erlang process.
Available commands:
\\<edb-attach-mode-map>
\\[edb-attach-help] - Popup this help text.
\\[erl-quit-viewer] - Quit the viewer (doesn't kill the process)
\\[edb-attach-step] - Step (into expression)
\\[edb-attach-next] - Next (over expression)
\\[edb-attach-up] - Up to the next stack frame
\\[edb-attach-down] - Down to the next stack frame
\\[edb-attach-continue] - Continue (until breakpoint)
\\[edb-toggle-breakpoint] - Toggle a breakpoint on the current line."
nil
" (attached)"
'(([? ] . edb-attach-step)
([?n] . edb-attach-next)
([?c] . edb-attach-continue)
([?u] . edb-attach-up)
([?d] . edb-attach-down)
([?q] . erl-quit-viewer)
([?h] . edb-attach-help)
([?b] . edb-toggle-breakpoint)))
(defun edb-attach-help ()
(interactive)
(describe-function 'edb-attach-mode))
(defun edb-attach-step ()
(interactive)
(edb-attach-meta-cmd 'step))
(defun edb-attach-next ()
(interactive)
(edb-attach-meta-cmd 'next))
(defun edb-attach-continue ()
(interactive)
(edb-attach-meta-cmd 'continue))
(defun edb-attach-up ()
(interactive)
(edb-attach-meta-cmd 'up))
(defun edb-attach-down ()
(interactive)
(edb-attach-meta-cmd 'down))
(defun edb-attach-meta-cmd (cmd)
(erl-send edb-pid `[emacs meta ,cmd]))
;; ----------------------------------------------------------------------
;; Breakpoints
(defvar edb-breakpoints '()
"List of all breakpoints on the currently monitored node.")
(defvar edb-saved-breakpoints '()
"List of breakpoints to set if edb-restore-dbg-state is called.")
(make-variable-buffer-local
(defvar edb-buffer-breakpoints nil
"List of active buffer breakpoints."))
(make-variable-buffer-local
(defvar edb-buffer-breakpoints-stale nil
"Nil if the breakpoints in the buffer are stale (out of synch)."))
;; breakpoints
(defun make-bp (mod line) (list mod line))
(defun bp-mod (bp) (car bp))
(defun bp-line (bp) (cadr bp))
;; buffer breakpoints
(defun make-bbp (mod line ov) (list mod line ov))
(defun bbp-mod (bbp) (car bbp))
(defun bbp-line (bbp) (cadr bbp))
(defun bbp-ov (bbp) (caddr bbp))
(defun edb-init-breakpoints (breaks)
(setq edb-breakpoints
(mapcar (lambda (pos)
(let ((mod (aref pos 0))
(line (aref pos 1)))
(make-bp mod line)))
breaks))
(mapc
(lambda (buf)
(with-current-buffer buf
(when erlang-extended-mode
(edb-create-buffer-breakpoints (edb-source-file-module-name)))))
(buffer-list)))
(defun edb-create-breakpoint (mod line)
"Updates all internal structures in all buffers with new breakpoint."
(push (make-bp mod line) edb-breakpoints)
(mapc
(lambda (buf)
(with-current-buffer buf
(if (and erlang-extended-mode
(eq (edb-source-file-module-name) mod))
(let ((bbp (make-bbp mod line (edb-make-breakpoint-overlay line))))
(push bbp edb-buffer-breakpoints)))))
(buffer-list)))
(defun edb-delete-all-breakpoints ()
"Updates all internal structures in all buffers."
(edb-del-breakpoints
(lambda (bp) t)
(lambda (bbp) t)))
(defun edb-delete-breakpoints (mod)
"Updates all internal structures in all buffers."
(edb-del-breakpoints
(lambda (bp) (eq (bp-mod bp) mod))
(lambda (bbp) (eq (bbp-mod bbp) mod))
mod))
(defun edb-delete-breakpoint (mod line)
"Updates all internal structures in all buffers."
(edb-del-breakpoints
(lambda (bp) (and (eq (bp-mod bp) mod)
(eq (bp-line bp) line)))
(lambda (bbp) (and (eq (bbp-mod bbp) mod)
(eq (bbp-line bbp) line)))
mod))
(defun edb-create-buffer-breakpoints (mod)
"Creates buffer breakpoints in the current buffer."
(when edb-buffer-breakpoints
;; remove old/stale breakpoints
(edb-delete-buffer-breakpoints))
(setq edb-buffer-breakpoints (edb-mk-bbps mod)))
(defun edb-delete-buffer-breakpoints ()
"Deletes all buffer breakpoints in the current buffer."
(setq edb-buffer-breakpoints
(edb-del-bbps edb-buffer-breakpoints (lambda (bbp) t))))
(defun edb-del-breakpoints (bp-f bbp-f &optional mod)
"Updates all internal structures in all buffers."
(setq edb-breakpoints (erl-remove-if bp-f edb-breakpoints))
(mapc
(lambda (buf)
(with-current-buffer buf
(if (and erlang-extended-mode
(or (not mod)
(eq (edb-source-file-module-name) mod)))
(setq edb-buffer-breakpoints
(edb-del-bbps edb-buffer-breakpoints bbp-f)))))
(buffer-list)))
(defun edb-synch-breakpoints (node module)
"Synchronizes the breakpoints in the current buffer to erlang.
I.e. deletes all old breakpoints, and re-applies them at the current line."
(interactive (list (erl-target-node)
(edb-module)))
(when (edb-ensure-monitoring node)
(let ((id (lambda (r) r)))
(mapc (lambda (new-bbp)
(let ((bbp (car new-bbp))
(new-line (cdr new-bbp)))
(erl-rpc id nil node 'distel 'break_delete
(list (bbp-mod bbp) (bbp-line bbp)))
(erl-rpc id nil node 'distel 'break_add
(list module new-line))))
(edb-new-bbps))
(setq edb-buffer-breakpoints-stale nil))))
(defun edb-make-breakpoints-stale (begin end length)
"Make breakpoints in the current buffer stale.
Has no effect if the buffer's module is not interpreted, or the
breakpoints are already marked as stale."
(when (and (not edb-attach-buffer)
(not edb-buffer-breakpoints-stale)
edb-module-interpreted)
(mapc (lambda (bbp)
(let ((ov (bbp-ov bbp)))
(overlay-put ov 'face 'edb-breakpoint-stale-face)))
edb-buffer-breakpoints)
(setq edb-buffer-breakpoints-stale t)))
(defun edb-save-breakpoints (node)
(let ((modules '()))
(setq edb-saved-breakpoints '())
(mapc
(lambda (buf)
(with-current-buffer buf
(if erlang-extended-mode
(let ((cur-mod (edb-source-file-module-name)))
(unless (member cur-mod modules)
(let ((new-lines (mapcar (lambda (new-bbp) (cdr new-bbp))
(edb-new-bbps))))
(push cur-mod modules)
(push (list cur-mod new-lines) edb-saved-breakpoints)))))))
(buffer-list))
(mapc (lambda (bp)
(unless (member (bp-mod bp) modules)
(push (list (bp-mod bp) (list (bp-line bp)))
edb-saved-breakpoints)))
edb-breakpoints)))
(defun edb-restore-breakpoints (node cont)
(erl-send-rpc node 'distel 'break_restore (list edb-saved-breakpoints))
(erl-receive (cont)
((['rex 'ok]
(funcall cont))
(['rex ['badrpc reason]]
(message "Failed to restore breakpoints: %S" reason)))))
(defun edb-new-bbps ()
(mapcar (lambda (bbp)
(let* ((new-pos (overlay-start (bbp-ov bbp)))
(new-line (+ (count-lines (point-min) new-pos) 1)))
(cons bbp new-line)))
edb-buffer-breakpoints))
(defun edb-mk-bbps (mod)
(zf
(lambda (bp)
(let ((bmod (bp-mod bp))
(line (bp-line bp)))
(if (eq bmod mod)
(let ((ov (edb-make-breakpoint-overlay line)))
(make-bbp bmod line ov))
nil)))
edb-breakpoints))
(defun edb-del-bbps (list pred)
(zf
(lambda (bbp)
(cond ((funcall pred bbp)
(delete-overlay (bbp-ov bbp))
nil)
(t bbp)))
list))
(defun edb-make-breakpoint-overlay (line)
"Creats an overlay at line"
(save-excursion
(goto-line line)
(let ((ov (make-overlay (line-beginning-position)
(line-beginning-position 2)
(current-buffer)
t
nil)))
(overlay-put ov 'edb-breakpoint t)
(if edb-buffer-breakpoints-stale
(overlay-put ov 'face 'edb-breakpoint-stale-face)
(overlay-put ov 'face 'edb-breakpoint-face))
ov)))
(defun zf (f l)
(let ((res nil))
(dolist (x l)
(let ((r (funcall f x)))
(if r (push r res))))
res))
(defun edb-first (pred list)
"Return the first element of LIST that satisfies PRED."
(loop for x in list
when (funcall pred x) return x))
(provide 'edb)