From c54dda1be8ff75572abf9dab1cb56d8e5a1cd8df Mon Sep 17 00:00:00 2001 From: Ivan Necas Date: Fri, 8 Nov 2024 15:19:39 +0100 Subject: [PATCH] Debug extension Use DebugAdapter.jl + dape to provide debugging capabilities from repl. Requires https://github.com/julia-vscode/DebugAdapter.jl/pull/95 to be merged first. --- README.md | 14 +++++- extensions/debug/Debug.jl | 80 +++++++++++++++++++++++++++++++++++ extensions/debug/Project.toml | 2 + extensions/debug/debug.el | 11 +++++ 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 extensions/debug/Debug.jl create mode 100644 extensions/debug/Project.toml create mode 100644 extensions/debug/debug.el diff --git a/README.md b/README.md index 1984f17..de13fd2 100644 --- a/README.md +++ b/README.md @@ -463,11 +463,23 @@ Customization variables: - `julia-snail/ob-julia-capture-io t` : If true, all intermediate printing during evaluation will be captured by ob-julia and printed into your org notebook - `julia-snail/ob-julia-resource-directory "./.ob-julia-snail/"`: Directory used to store automatically generated image files for display in org buffers. By default this is a local hidden directory, but it can be changed to e.g. `/tmp/` if you don't want to keep the image files around. +### Debug + +This extension uses [DebugAdapter.jl](https://github.com/julia-vscode/DebugAdapter.jl) and [dape](https://github.com/svaante/dape) to allow debugging inside the REPL. + +Use one of the following macros to initiate the debug session from the REPL: +- `@run` pause on first breakpoint. +- `@enter` pause on entry. + +```julia +using .JuliaSnail.Extensions.Debug + +@enter(println("hello world")) +``` ## Future improvements - The `libvterm` dependency forces the use of recent Emacs releases, forces Emacs to be build with module support, complicates support for Windows, and is generally quite gnarly. The Eat alternative requires Emacs 28. It would be much better to re-implement the REPL in Elisp and make sure it works on older Emacs versions. - Completion does not pick up local variables. - A real eldoc implementation would be great, but difficult to do with Julia’s generic functions. -- A debugger would be great. - A real test suite which fully drives both Julia and Emacs and runs in a CI environment (like GitHub Actions) wouldn’t hurt, either. diff --git a/extensions/debug/Debug.jl b/extensions/debug/Debug.jl new file mode 100644 index 0000000..8ec2398 --- /dev/null +++ b/extensions/debug/Debug.jl @@ -0,0 +1,80 @@ +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. + +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. + +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . + +module Debug + +export @enter, @run + +Main.JuliaSnail.@with_pkg_env (@__DIR__) begin + import DebugAdapter + import Sockets + import Logging +end + +port = 12124 +conn = missing +session = missing +server = missing +ready = Channel{Bool}(1) + +function init() + # Logging.global_logger(Logging.SimpleLogger(Logging.Debug)) +end + +function start() + global server = Sockets.listen(port) + Threads.@spawn begin + while true + @debug "Listening on port $port" + global conn = Sockets.accept(server) + @debug "Accepted connection" + global session = DebugAdapter.DebugSession(conn) + # When using the attach request, the terminate request does not work. + session.capabilities.supportsTerminateRequest = false + @debug "Starting debug session" + put!(ready, true) + DebugAdapter.run(session) + end + end +end + +function run(mod, code, filepath; stop_on_entry=false) + if ismissing(server) + start() + end + soe = ":json-false" + if stop_on_entry + soe = "t" + end + Main.JuliaSnail.send_to_client(""" + (dape `(:request "attach" + host "localhost" + port $port + :type "julia" + :stopOnEntry $soe)) +""") + # Wait for the session to be ready. + take!(ready) + DebugAdapter.debug_code(session, mod, code, filepath) +end + +macro enter(command) + Base.remove_linenums!(command) + :(run(Main, $(string(command)), $(string(__source__.file)), stop_on_entry=true)) +end + +macro run(command) + Base.remove_linenums!(command) + :(run(Main, $(string(command)), $(string(__source__.file)), stop_on_entry=false)) +end +end diff --git a/extensions/debug/Project.toml b/extensions/debug/Project.toml new file mode 100644 index 0000000..f4cc02d --- /dev/null +++ b/extensions/debug/Project.toml @@ -0,0 +1,2 @@ +[deps] +DebugAdapter = "17994d07-08fe-42cc-bc1b-7af499b1ea47" diff --git a/extensions/debug/debug.el b/extensions/debug/debug.el new file mode 100644 index 0000000..e3d422a --- /dev/null +++ b/extensions/debug/debug.el @@ -0,0 +1,11 @@ +;;; debug.el --- Julia Snail -*- lexical-binding: t -*- + +(defun julia-snail/debug-init (repl-buf) + (julia-snail--send-to-server + '("JuliaSnail" "Extensions") + "load([\"debug\" \"Debug.jl\"]); Debug.init()" + :repl-buf repl-buf + :async nil + :async-poll-maximum 120000)) + +(provide 'julia-snail/debug)