Skip to content
jixiuf edited this page Nov 19, 2024 · 40 revisions

You will find documentation and adapter installation details in the README.org.

Ergonomic keybindings

Dape integrates with repeat-mode by default but if hydra is your preferred way the following might be useful:

(defun ccpp/post-init-hydra ()
  (defhydra dape-hydra (:hint nil)
    "
^Stepping^           ^Breakpoints^               ^Info
^^^^^^^^-----------------------------------------------------------
_n_: Next            _bb_: Toggle (add/remove)   _si_: Info
_i_/_o_: Step in/out   _bd_: Delete                _sm_: Memory
_<_/_>_: Stack up/down _bD_: Delete all            _ss_: Select Stack
_c_: Continue        _bl_: Set log message       _R_: Repl
_r_: Restart
            _d_: Init   _k_: Kill   _q_: Quit
"
    ("n" dape-next)
    ("i" dape-step-in)
    ("o" dape-step-out)
    ("<" dape-stack-select-down)
    (">" dape-stack-select-up)
    ("c" dape-continue)
    ("r" dape-restart)
    ("ba" dape-breakpoint-toggle)
    ("bb" dape-breakpoint-toggle)
    ("be" dape-breakpoint-expression)
    ("bd" dape-breakpoint-remove-at-point)
    ("bD" dape-breakpoint-remove-all)
    ("bl" dape-breakpoint-log)
    ("si" dape-info)
    ("sm" dape-read-memory)
    ("ss" dape-select-stack)
    ("R"  dape-repl)
    ("d" dape)
    ("k" dape-kill :color blue)
    ("q" dape-quit :color blue)))

Thanks to @vibrys for contributing.

Debug Configurations

On this page you will find useful dape configurations.

Feel free to add useful configurations!

Ruby - rdbg

Remote attach via docker-compose

NB: Once you have the configuration implemented as defined below, you can trigger dape from within the container within the context of a TRAMP connection. Use C-x C-f /docker:<container_name>:/path/to/project to enter the container, then run M-x dape.

Rails Server (with Puma)

Ensure your target ruby service is setup like so in your docker-compose.yml:

rails-app:
 image: rails-app:latest
 command: rdbg --port 5678 -c -- rails server -b 0.0.0.0
 environment:
   - RUBY_DEBUG_OPEN=true
   - RUBY_DEBUG_HOST=0.0.0.0
   # Allows rails to initialise, only stopping when a breakpoint is hit
   - RUBY_DEBUG_NONSTOP=true
   # Needed to stop puma making workers which all attempt to open rdbg 
   # on the same port
   - WEB_CONCURRENCY=0 
 ports:
   - "5678:5678" # For remote debugging RAILS with debug.rb

Use the following dape-config to attach to the process:

(add-to-list 'dape-configs
             `(rdbg-attach-local-source
               prefix-local "~/code/ruby-app/"
               prefix-remote "/usr/app/"
               port 5678
               :request "attach"
               :localfs t))

RSpec (with rspec-mode)

In order to setup rspec-mode correctly, it is necessary to run rdbg on a different port to the rails server instance. By using a custom wrapper function, we can add in the necessary environment variables, and spin up a fresh instance for the Rspec run:

(defun jjh/rspec--compose-default-wrapper (_compose compose-service command)
  "Function for wrapping a command for execution inside a compose
environment. By adding the port manually here, we keep it out of the
rails service - keeping it free for just rspec. We also name it so
it's easy to find."
  (format "docker-compose -f %s run -it --rm --name ruby-app-rspec -e 'RUBY_DEBUG_PORT=5680' -p 5680:5680 %s sh -c \"%s\""
          rspec-docker-file-name compose-service command))

(setq rspec-docker-wrapper-fn #'jjh/rspec--compose-default-wrapper)

It’s important to set the debug gem to require: false so we can control when the port is opened. Update the Gemfile:

gem "debug", require: false

In the spec_helper.rb, require the debug gem:

# For debugging
require "debug/open_nonstop"

You can then trigger the rspec tests with a custom dape-config entry:

(add-to-list 'dape-configs
             `(rdbg-attach-rspec
               prefix-local "~/code/app/"
               prefix-remote "/usr/app/"
               port 5680
               :request "attach"
               :localfs t))

Python - debugpy

Remote attach

Make sure that remote application imports and starts debugpy.

import debugpy
# Port does not matter just needs to match port in config
# dape debugpy-remote-attach
debugpy.listen(("0.0.0.0", 5678))
(add-to-list 'dape-configs
 `(debugpy-remote-attach
   modes (python-mode python-ts-mode)
   host (lambda () (read-string "Host: " "localhost"))
   port (lambda () (read-number "Port: "))
   :request "attach"
   :type "python"
   :pathMappings [(:localRoot (lambda ()
                                (read-directory-name "Local source directory: "
                                                     (funcall dape-cwd-fn)))
                   :remoteRoot (lambda ()
                                 (read-string "Remote source directory: ")))]
   :justMyCode nil
   :showReturnValue t))

Thanks to @Gavinok for contributing.

Pytest

Debug single test.

debugpy-module :module "pytest" :args ["test_file_relative_path_to_base_project::test_method_name"]

Contributed by @Peter-Chou

Go - dlv

Unit test at point

(add-to-list 'dape-configs
             `(delve
               modes (go-mode go-ts-mode)
               ensure dape-ensure-command
               fn (dape-config-autoport dape-config-tramp)
               command "dlv"
               command-insert-stderr t
               command-args ("dap" "--listen" "127.0.0.1::autoport")
               command-cwd (lambda()(if (string-suffix-p "_test.go" (buffer-name))
                                     default-directory (dape-cwd)))
               port :autoport
               :type "debug"
               :request "launch"
               :mode (lambda() (if (string-suffix-p "_test.go" (buffer-name)) "test" "debug"))
               :program "."
               :cwd "."
               :args (lambda()
                       (require 'which-func)
                       (if (string-suffix-p "_test.go" (buffer-name))
                           (when-let* ((test-name (which-function))
                                       (test-regexp (concat "^" test-name "$")))
                             (if test-name `["-test.run" ,test-regexp]
                               (error "No test selected")))
                         []))))

The point can be anywhere in function TestDemo, then call dape and “Run adapter: delve-unit-test” it will execute go test -test.run TestDemo.

func TestDemo(t *testing.T) {
   t.Error("hello")
}

Thanks to @jixiuf for contributing.

Java jdtls

dape already support working with JDTLS + Java Debug Server but it doesn’t support junit.

Unit test at point for java

need eglot-java

with dape-dwim it would auto select the unit test mthod or main to debug

(add-to-list 'dape-configs
               `(junit
                 modes (java-mode java-ts-mode)
                 ensure (lambda (config)
                          (save-excursion
                            (unless (eglot-current-server)
                              (user-error "No eglot instance active in buffer %s" (current-buffer)))
                            (when (equal ':json-false
                                         (eglot-execute-command
                                          (eglot-java--find-server)
                                          "java.project.isTestFile"
                                          (vector (eglot--path-to-uri (buffer-file-name)))))
                              (user-error "Not in a java test file"))
                            t))
                 fn (lambda (config)
                      (let ((file (expand-file-name (plist-get config :program)
                                                    (project-root (project-current)))))
                        (with-current-buffer (find-file-noselect file)
                          (save-excursion (eglot-java-run-test t))
                          (thread-first
                            config
                            (plist-put 'hostname "localhost")
                            (plist-put 'port (eglot-execute-command (eglot-current-server)
                                                                    "vscode.java.startDebugSession" nil))
                            (plist-put :projectName (project-name (project-current)))))))
                 :program dape-buffer-default
                 :request "attach"
                 :hostname "localhost"
                 :port 8000))

eglot-java config

  (setq eglot-java-user-init-opts-fn 'custom-eglot-java-init-opts)
(defun custom-eglot-java-init-opts ( &optional server eglot-java-eclipse-jdt)
  "Custom options that will be merged with any default settings."
  ;; :bundles ["/home/me/.emacs.d/lsp-bundles/com.microsoft.java.debug.plugin-0.50.0.jar"]
  `(:bundles [,(download-java-debug-plugin)]))

(defun download-java-debug-plugin ()
  (let ((cache-dir (expand-file-name "~/.cache/emacs/"))
        (url "https://repo1.maven.org/maven2/com/microsoft/java/com.microsoft.java.debug.plugin/maven-metadata.xml")
        (version nil)
        (jar-file-name nil)
        (jar-file nil))
    (unless (file-directory-p cache-dir)
      (make-directory cache-dir t))
    (setq jar-file (car (directory-files cache-dir nil "com\\.microsoft\\.java\\.debug\\.plugin-\\([0-9]+\\.[0-9]+\\.[0-9]+\\)\\.jar" t)))
    (if jar-file
        (expand-file-name jar-file cache-dir)  
      (with-temp-buffer
        (url-insert-file-contents url) 
        (when (re-search-forward "<latest>\\(.*?\\)</latest>" nil t)
          (setq version (match-string 1))
          (setq jar-file-name (format "com.microsoft.java.debug.plugin-%s.jar" version))
          (setq jar-file (expand-file-name jar-file-name cache-dir))
          (unless (file-exists-p jar-file)
            (setq url (format "https://repo1.maven.org/maven2/com/microsoft/java/com.microsoft.java.debug.plugin/%s/%s"
                              version jar-file-name))
            (message url)
            (url-copy-file url jar-file)) 
          jar-file))))) 

Swift IOS

Setup

Follow the steps in the README.org on how to install codelldb in section “C, C++ and Rust - codelldb”

Add the following configuration to your init.el:

(add-to-list 'dape-configs
             `(ios
               modes (swift-mode)
               command-cwd dape-command-cwd
               command ,(file-name-concat dape-adapter-dir
                                          "codelldb"
                                          "extension"
                                          "adapter"
                                          "codelldb")
               command-args ("--port" :autoport
                             "--settings" "{\"sourceLanguages\":[\"swift\"]}"
                             "--liblldb" "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/LLDB")
               port :autoport
               simulator-id "iPhone 15 Pro"
               app-bundle-id "com.yourcompany.ProductName"
               fn (dape-config-autoport
                   ,(lambda (config)
                      (with-temp-buffer
                        (let* ((command
                                (format "xcrun simctl launch --wait-for-debugger --terminate-running-process %S %S"
                                        (plist-get config 'simulator-id)
                                        (plist-get config 'app-bundle-id)))
                               (code (call-process-shell-command command nil (current-buffer))))
                          (dape--message (format "* Running: %S *" command))
                          (dape--message (buffer-string))
                          (save-match-data
                            (if (and (zerop code)
                                     (progn (goto-char (point-min))
                                            (search-forward-regexp "[[:digit:]]+" nil t)))
                                (plist-put config :pid (string-to-number (match-string 0)))
                              (dape--message (format "* Running: %S *" command))
                              (dape--message (format "Failed to start simulator:\n%s" (buffer-string)))
                              (user-error "Failed to start simulator")))))
                      config))
               :type "lldb"
               :request "attach"
               :cwd "."))

Debug

Start simulator:

open -n "Simulator"

Compile program, see xcodebuild (could use compile flag in dape)

Debug Jest unit test

(contributed by @timcharper)

The following configuration can be used to run an entire file of jest tests (works as of dape 0.5.0).

(defun dape-jest/find-file-buffer-default ()
  "Read filename at project root, defaulting to current buffer. Return vector of jest args to run said file"
  (let ((file (dape-buffer-default)))
    (if file
        `["--runInBand" "--no-coverage" ,file]
      (user-error "No file found"))))

(defun dape-jest/ensure (config)
  "Ensure node is available, jest is installed, that the dapDebugServer is installed"

  (dape-ensure-command config)
  (let ((cwd (dape-cwd))
        (js-debug-file (expand-file-name
                        (dape--config-eval-value (car (plist-get config 'command-args)))
                        (dape--config-eval-value (plist-get config 'command-cwd))))
        (node-jest-file (expand-file-name
                        (dape--config-eval-value (plist-get config :program))
                        (dape--config-eval-value (plist-get config :cwd)))))
    (unless (file-exists-p js-debug-file)
      (user-error "Debug server file %S does not exist" js-debug-file))
    (unless (file-exists-p node-jest-file)
      (user-error "Jest executable not found at %S" node-jest-file))))


(add-to-list 'dape-configs
             `(jest
               modes (js-mode js-ts-mode typescript-mode)
               ensure dape-jest/ensure
               command "node"
               command-cwd dape-command-cwd
               command-args (,(expand-file-name
                               (file-name-concat dape-adapter-dir
                                                 "js-debug"
                                                 "src"
                                                 "dapDebugServer.js"))
                             :autoport)
               port :autoport
               fn dape-config-autoport
               :type "pwa-node"
               :cwd dape-cwd
               :env (:VAR1 "some value" :VAR2 "another value")
               :program "node_modules/.bin/jest"
               :args dape-jest/find-file-buffer-default
               :outputCapture "console"
               :sourceMapRenames t
               :pauseForSourceMap nil
               :autoAttachChildProcesses t
               :console "internalConsole"
               :outputCapture "std"
               :killBehavior "forceful"))

Embedded jlink debugging with cortex-debug

https://github.com/svaante/dape-cortex-debug

Misc

dape-dwim

Very specialized function contributed by @jixiuf

(defun compare-vectors-prefix (vec1 vec2)
  (let ((min-length (min (length vec1) (length vec2))))
    (cl-loop for i below min-length
             always (equal (aref vec1 i) (aref vec2 i)))))


;;;###autoload
(defun dape-dwim()
  "If a DAP (Debug Adapter Protocol) session is active, terminate the session.
If there's no active DAP session, start a new session with default configuration.
When prefix argument is given, invoke `dape' interactively instead.

This function uses `dape' related functions to manage debug sessions for Emacs.
It also handles session configuration by looking up the appropriate settings
based on the current context and previous history."
  (interactive)
  (require 'dape)
  (if (dape--live-connection 'parent t)
      (progn
        (call-interactively #'dape-quit)
        (message "dape quit now!"))
    (if current-prefix-arg
        (call-interactively #'dape)
      (let* ((cfg (car (cl-loop for (key . config) in dape-configs
                                when (and (dape--config-mode-p config)
                                          (dape--config-ensure config))
                                collect (dape--config-eval key config))))
             (suggested-configs
              (cl-loop for (key . config) in dape-configs
                       when (and (dape--config-mode-p config)
                                 (dape--config-ensure config))
                       collect (dape--config-to-string key nil)))
             (hist (seq-find (lambda (str)
                               (ignore-errors
                                 (member (thread-first (dape--config-from-string str)
                                                       (car)
                                                       (dape--config-to-string nil))
                                         suggested-configs)))
                             dape-history)))
        (when hist
          (setq hist (nth 1 (dape--config-from-string hist)))
          (when (and
                 (equal (plist-get  hist 'command-cwd) (plist-get  cfg 'command-cwd))
                 (equal (plist-get  hist 'command) (plist-get  cfg 'command))
                 (compare-vectors-prefix (plist-get  hist ':args) (plist-get  cfg ':args))
                 (equal (plist-get  hist ':program) (plist-get  cfg ':program))
                 (equal (plist-get  hist ':mainClass) (plist-get  cfg ':mainClass))
                 )
            (setq cfg hist))
          )
        (when (plist-get  cfg 'command-cwd)
          (call-process "find" nil nil nil (plist-get  cfg 'command-cwd)
                        "-maxdepth" "1" "-type" "f" "-name" "__debug_bin*" "-exec" "rm" "{}" ";"))
        (when cfg (dape cfg)))
      )))