Skip to content

Commit 369a05a

Browse files
authored
add support for watchers (#49)
* add watchers
1 parent cbbb690 commit 369a05a

File tree

8 files changed

+579
-3
lines changed

8 files changed

+579
-3
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ Basic Commands:
4141
- ``` `stuff ```: run `stuff` in the current function's context
4242
- `fr [v::Int]`: show all variables in the current function, `v` defaults to `1`
4343
- `f [n::Int]`: go to the `n`-th function in the call stack
44+
- `w`
45+
- `w add expr`: add an expression to the watch list
46+
- `w`: show all watch expressions evaluated in the current function's context
47+
- `w rm [i::Int]`: remove all or the `i`:th watch expression
4448
- `q`: quit the debugger, returning `nothing`
4549

4650
Advanced commands:

src/Debugger.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,23 @@ export @bp, @breakpoint, breakpoint
3131
include("LineNumbers.jl")
3232
using .LineNumbers: SourceFile, compute_line
3333

34+
# We make WATCH_LIST a global since it is likely useful to keep
35+
# watch expressions between invocations of the debugger interface
36+
const WATCH_LIST = []
37+
3438
mutable struct DebuggerState
3539
frame::Union{Nothing, Frame}
3640
level::Int
3741
broke_on_error::Bool
42+
watch_list::Vector
3843
repl
3944
terminal
4045
main_mode
4146
julia_prompt::Ref{LineEdit.Prompt}
4247
standard_keymap
4348
overall_result
4449
end
45-
DebuggerState(stack, repl, terminal) = DebuggerState(stack, 1, false, repl, terminal, nothing, Ref{LineEdit.Prompt}(), nothing, nothing)
50+
DebuggerState(stack, repl, terminal) = DebuggerState(stack, 1, false, WATCH_LIST, repl, terminal, nothing, Ref{LineEdit.Prompt}(), nothing, nothing)
4651
DebuggerState(stack, repl) = DebuggerState(stack, repl, nothing)
4752

4853
function active_frame(state)
@@ -60,6 +65,7 @@ include("locationinfo.jl")
6065
include("repl.jl")
6166
include("commands.jl")
6267
include("printing.jl")
68+
include("watch.jl")
6369

6470
function _make_frame(mod, arg)
6571
args = try

src/commands.jl

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,34 @@ function execute_command(state::DebuggerState, ::Union{Val{:f}, Val{:fr}}, cmd)
9898
end
9999
end
100100

101+
function execute_command(state::DebuggerState, ::Val{:w}, cmd::AbstractString)
102+
# TODO show some info messages?
103+
cmds = split(cmd)
104+
if length(cmds) == 1
105+
io = Base.pipe_writer(state.terminal)
106+
show_watch_list(io, state)
107+
return false
108+
elseif length(cmds) >= 2
109+
if cmds[2] == "rm"
110+
if length(cmds) == 2
111+
clear_watch_list!(state)
112+
return false
113+
elseif length(cmds) == 3
114+
i = parse(Int, cmds[3])
115+
clear_watch_list!(state, i)
116+
return false
117+
end
118+
end
119+
if cmds[2] == "add"
120+
if add_watch_entry!(state, join(cmds[3:end]))
121+
end
122+
return false
123+
end
124+
end
125+
# Error
126+
return execute_command(state, Val(:_), cmd)
127+
end
128+
101129
function execute_command(state::DebuggerState, _, cmd)
102130
println("Unknown command `$cmd`. Executing `?` to obtain help.")
103131
execute_command(state, Val{Symbol("?")}(), "?")
@@ -116,6 +144,10 @@ function execute_command(state::DebuggerState, ::Val{:?}, cmd::AbstractString)
116144
- ``` `stuff ```: run `stuff` in the current function's context\\
117145
- `fr [v::Int]`: show all variables in the current frame, `v` defaults to `1`\\
118146
- `f [n::Int]`: go to the `n`-th frame\\
147+
- `w`\\
148+
- `w add expr`: add an expression to the watch list\\
149+
- `w`: show all watch expressions evaluated in the current function's context\\
150+
- `w rm [i::Int]`: remove all or the `i`:th watch expression\\
119151
- `q`: quit the debugger, returning `nothing`\\
120152
Advanced commands:\\
121153
- `nc`: step to the next call\\

src/printing.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function print_next_expr(io::IO, frame::Frame)
4848
end
4949
if isexpr(expr, :call) || isexpr(expr, :return)
5050
for i in 1:length(expr.args)
51-
val = try
51+
val = try
5252
@lookup(frame, expr.args[i])
5353
catch err
5454
err isa UndefVarError || rethrow(err)

src/repl.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ function RunDebugger(frame, repl = Base.active_repl, terminal = Base.active_repl
7373

7474
state.standard_keymap = Dict{Any,Any}[skeymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults]
7575
panel.keymap_dict = LineEdit.keymap([repl_switch;state.standard_keymap])
76-
76+
7777
if initial_continue
7878
execute_command(state, Val(:c), "c")
7979
state.frame === nothing && return state.overall_result

src/watch.jl

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
function add_watch_entry!(state::DebuggerState, cmd::AbstractString)
2+
expr = Base.parse_input_line(cmd)
3+
if isexpr(expr, :error) || isexpr(expr, :incomplete)
4+
printstyled(stderr, "ERROR: syntax: ", expr.args[1], "\n", color=Base.error_color())
5+
return false
6+
end
7+
isexpr(expr, :toplevel) && (expr = expr.args[end])
8+
push!(state.watch_list, expr)
9+
return true
10+
end
11+
12+
function show_watch_list(io, state::DebuggerState)
13+
frame = active_frame(state)
14+
outbuf = IOContext(IOBuffer(), io)
15+
16+
for (i, expr) in enumerate(state.watch_list)
17+
vars = filter(v -> v.name != Symbol(""), JuliaInterpreter.locals(frame))
18+
eval_expr = Expr(:let,
19+
Expr(:block, map(x->Expr(:(=), x...), [(v.name, v.value) for v in vars])...),
20+
expr)
21+
errored = false
22+
res = try
23+
Core.eval(moduleof(frame), eval_expr)
24+
catch err
25+
errored = true
26+
err
27+
end
28+
expr_str = highlight_code(string(expr); context=io) # Should maybe use repr here in some cases (strings)
29+
if errored
30+
res_str = sprint((io, args...) -> printstyled(io, args...; color=Base.error_color()), res; context=io)
31+
else
32+
res_str = highlight_code(repr(res), context=io)
33+
end
34+
print(outbuf, "$i] $(expr_str): $(res_str)\n")
35+
end
36+
print(io, String(take!(outbuf.io)))
37+
println(io)
38+
end
39+
40+
clear_watch_list!(state::DebuggerState) = empty!(state.watch_list)
41+
function clear_watch_list!(state::DebuggerState, i::Int)
42+
if !checkbounds(Bool, state.watch_list, i)
43+
printstyled(stderr, "ERROR: watch entry $i does not exist\n\n"; color=Base.error_color())
44+
return
45+
end
46+
deleteat!(state.watch_list, i)
47+
end

test/ui.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ end
6868

6969
run_terminal_test(@make_frame(my_gcd(10, 20)),
7070
["n\n","`", "my_gc\t\n", "a\n", UP_ARROW, UP_ARROW, UP_ARROW, CTRL_C,
71+
"w add a\n", "w add sin(a)\n", "w add b\n", "w\n", "w rm 1\n", "w\n",
7172
"bt\n", "st\n", "c\n", "c\n"],
7273
"ui/history_gcd.multiout")
7374

0 commit comments

Comments
 (0)