aboutsummaryrefslogtreecommitdiffstats
path: root/lib/debugger/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/debugger/src')
-rw-r--r--lib/debugger/src/Makefile136
-rw-r--r--lib/debugger/src/dbg_debugged.erl118
-rw-r--r--lib/debugger/src/dbg_icmd.erl482
-rw-r--r--lib/debugger/src/dbg_idb.erl54
-rw-r--r--lib/debugger/src/dbg_ieval.erl1733
-rw-r--r--lib/debugger/src/dbg_ieval.hrl26
-rw-r--r--lib/debugger/src/dbg_iload.erl669
-rw-r--r--lib/debugger/src/dbg_iserver.erl606
-rw-r--r--lib/debugger/src/dbg_ui_break.erl98
-rw-r--r--lib/debugger/src/dbg_ui_break_win.erl307
-rw-r--r--lib/debugger/src/dbg_ui_edit.erl91
-rw-r--r--lib/debugger/src/dbg_ui_edit_win.erl122
-rw-r--r--lib/debugger/src/dbg_ui_filedialog_win.erl338
-rw-r--r--lib/debugger/src/dbg_ui_interpret.erl161
-rw-r--r--lib/debugger/src/dbg_ui_mon.erl744
-rw-r--r--lib/debugger/src/dbg_ui_mon_win.erl564
-rw-r--r--lib/debugger/src/dbg_ui_settings.erl162
-rw-r--r--lib/debugger/src/dbg_ui_trace.erl814
-rw-r--r--lib/debugger/src/dbg_ui_trace_win.erl1597
-rw-r--r--lib/debugger/src/dbg_ui_view.erl256
-rw-r--r--lib/debugger/src/dbg_ui_win.erl277
-rw-r--r--lib/debugger/src/dbg_ui_winman.erl177
-rw-r--r--lib/debugger/src/dbg_wx_break.erl102
-rw-r--r--lib/debugger/src/dbg_wx_break_win.erl272
-rw-r--r--lib/debugger/src/dbg_wx_code.erl163
-rw-r--r--lib/debugger/src/dbg_wx_filedialog_win.erl572
-rw-r--r--lib/debugger/src/dbg_wx_filedialog_win.hrl180
-rw-r--r--lib/debugger/src/dbg_wx_interpret.erl131
-rw-r--r--lib/debugger/src/dbg_wx_mon.erl759
-rw-r--r--lib/debugger/src/dbg_wx_mon_win.erl586
-rw-r--r--lib/debugger/src/dbg_wx_settings.erl110
-rw-r--r--lib/debugger/src/dbg_wx_src_view.erl66
-rw-r--r--lib/debugger/src/dbg_wx_trace.erl839
-rwxr-xr-xlib/debugger/src/dbg_wx_trace_win.erl1029
-rw-r--r--lib/debugger/src/dbg_wx_view.erl270
-rw-r--r--lib/debugger/src/dbg_wx_win.erl332
-rwxr-xr-xlib/debugger/src/dbg_wx_winman.erl175
-rw-r--r--lib/debugger/src/debugger.app.src62
-rw-r--r--lib/debugger/src/debugger.appup.src19
-rw-r--r--lib/debugger/src/debugger.erl118
-rw-r--r--lib/debugger/src/i.erl376
-rw-r--r--lib/debugger/src/int.erl736
42 files changed, 16429 insertions, 0 deletions
diff --git a/lib/debugger/src/Makefile b/lib/debugger/src/Makefile
new file mode 100644
index 0000000000..8551fe887d
--- /dev/null
+++ b/lib/debugger/src/Makefile
@@ -0,0 +1,136 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1997-2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../vsn.mk
+VSN=$(DEBUGGER_VSN)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/debugger-$(VSN)
+
+# ----------------------------------------------------
+# Common Macros
+# ----------------------------------------------------
+
+MODULES= \
+ debugger \
+ i \
+ int \
+ dbg_debugged \
+ dbg_icmd \
+ dbg_idb \
+ dbg_ieval \
+ dbg_iload \
+ dbg_iserver \
+ dbg_ui_break \
+ dbg_ui_break_win \
+ dbg_ui_edit \
+ dbg_ui_edit_win \
+ dbg_ui_filedialog_win \
+ dbg_ui_interpret \
+ dbg_ui_mon \
+ dbg_ui_mon_win \
+ dbg_ui_settings \
+ dbg_ui_trace \
+ dbg_ui_trace_win \
+ dbg_ui_view \
+ dbg_ui_win \
+ dbg_ui_winman \
+ dbg_wx_break \
+ dbg_wx_break_win \
+ dbg_wx_code \
+ dbg_wx_filedialog_win \
+ dbg_wx_interpret \
+ dbg_wx_mon \
+ dbg_wx_mon_win \
+ dbg_wx_settings \
+ dbg_wx_src_view \
+ dbg_wx_trace \
+ dbg_wx_trace_win \
+ dbg_wx_view \
+ dbg_wx_win \
+ dbg_wx_winman
+
+
+HRL_FILES=
+
+INTERNAL_HRL_FILES= dbg_ieval.hrl
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET)
+
+#TOOLBOX_FILES= debugger.tool debugger.gif
+#TARGET_TOOLBOX_FILES= $(EBIN)/debugger.tool $(EBIN)/debugger.gif
+
+APP_FILE = debugger.app
+APPUP_FILE = debugger.appup
+
+APP_SRC = $(APP_FILE).src
+APPUP_SRC = $(APPUP_FILE).src
+
+APP_TARGET = $(EBIN)/$(APP_FILE)
+APPUP_TARGET = $(EBIN)/$(APPUP_FILE)
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+ERL_COMPILE_FLAGS += +warn_obsolete_guard
+
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt: $(TARGET_FILES) $(TARGET_TOOLBOX_FILES)
+
+clean:
+ rm -f $(TARGET_FILES) $(TARGET_TOOLBOX_FILES)
+ rm -f errs core *~
+
+$(APP_TARGET): $(APP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+docs:
+
+# ----------------------------------------------------
+# Special Targets
+# ----------------------------------------------------
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DATA) $(ERL_FILES) $(INTERNAL_HRL_FILES) $(TOOLBOX_FILES) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DATA) $(TARGET_FILES) $(TARGET_TOOLBOX_FILES) $(RELSYSDIR)/ebin
+
+release_docs_spec:
diff --git a/lib/debugger/src/dbg_debugged.erl b/lib/debugger/src/dbg_debugged.erl
new file mode 100644
index 0000000000..b56ebef14a
--- /dev/null
+++ b/lib/debugger/src/dbg_debugged.erl
@@ -0,0 +1,118 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_debugged).
+
+%% External exports
+-export([eval/3]).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% eval(Mod, Func, Args) -> Value
+%% Main entry point from external (non-interpreted) code.
+%% Called via the error handler.
+%%--------------------------------------------------------------------
+eval(Mod, Func, Args) ->
+ SaveStacktrace = erlang:get_stacktrace(),
+ Meta = dbg_ieval:eval(Mod, Func, Args),
+ Mref = erlang:monitor(process, Meta),
+ msg_loop(Meta, Mref, SaveStacktrace).
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+msg_loop(Meta, Mref, SaveStacktrace) ->
+ receive
+
+ %% Evaluated function has returned a value
+ {sys, Meta, {ready, Val}} ->
+ demonitor(Mref),
+
+ %% Restore original stacktrace and return the value
+ try erlang:raise(throw, stack, SaveStacktrace)
+ catch
+ throw:stack ->
+ case Val of
+ {dbg_apply,M,F,A} ->
+ apply(M, F, A);
+ _ ->
+ Val
+ end
+ end;
+
+ %% Evaluated function raised an (uncaught) exception
+ {sys, Meta, {exception,{Class,Reason,Stacktrace}}} ->
+ demonitor(Mref),
+
+ %% ...raise the same exception
+ erlang:error(erlang:raise(Class, Reason, Stacktrace),
+ [Class,Reason,Stacktrace]);
+
+ %% Meta is evaluating a receive, must be done within context
+ %% of real (=this) process
+ {sys, Meta, {'receive',Msg}} ->
+ receive Msg -> Meta ! {self(), rec_acked} end,
+ msg_loop(Meta, Mref, SaveStacktrace);
+
+ %% Meta needs something evaluated within context of real process
+ {sys, Meta, {command, Command, Stacktrace}} ->
+ Reply = handle_command(Command, Stacktrace),
+ Meta ! {sys, self(), Reply},
+ msg_loop(Meta, Mref, SaveStacktrace);
+
+ %% Meta has terminated
+ %% Must be due to int:stop() (or -heaven forbid- a debugger bug)
+ {'DOWN', Mref, _, _, Reason} ->
+
+ %% Restore original stacktrace and return a dummy value
+ try erlang:raise(throw, stack, SaveStacktrace)
+ catch
+ throw:stack ->
+ {interpreter_terminated, Reason}
+ end
+ end.
+
+handle_command(Command, Stacktrace) ->
+ try reply(Command)
+ catch Class:Reason ->
+ Stacktrace2 = stacktrace_f(erlang:get_stacktrace()),
+ {exception, {Class,Reason,Stacktrace2++Stacktrace}}
+ end.
+
+reply({apply,M,F,As}) ->
+ {value, erlang:apply(M,F,As)};
+reply({eval,Expr,Bs}) ->
+ erl_eval:expr(Expr, Bs). % {value, Value, Bs2}
+
+%% Demonitor and delete message from inbox
+%%
+demonitor(Mref) ->
+ erlang:demonitor(Mref),
+ receive {'DOWN',Mref,_,_,_} -> ok
+ after 0 -> ok
+ end.
+
+%% Fix stacktrace - keep all above call to this module.
+%%
+stacktrace_f([]) -> [];
+stacktrace_f([{?MODULE,_,_}|_]) -> [];
+stacktrace_f([F|S]) -> [F|stacktrace_f(S)].
diff --git a/lib/debugger/src/dbg_icmd.erl b/lib/debugger/src/dbg_icmd.erl
new file mode 100644
index 0000000000..7ccb9793a3
--- /dev/null
+++ b/lib/debugger/src/dbg_icmd.erl
@@ -0,0 +1,482 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_icmd).
+
+%% Internal command receiver/handler
+-export([cmd/3]).
+
+%% User control of process execution and settings
+-export([step/1, next/1, continue/1, finish/1, skip/1, timeout/1,
+ stop/1]).
+-export([eval/2]).
+-export([set/3, get/3]).
+-export([handle_msg/4]).
+
+%% Library functions for attached process handling
+-export([tell_attached/1]).
+
+%% get_binding/2
+-export([get_binding/2]).
+
+-include("dbg_ieval.hrl").
+
+%%====================================================================
+%% Internal command receiver/handler
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% cmd(Expr, Bs, Ieval) -> {skip, Bs} | Bs
+%% This function is called from dbg_ieval before evaluating any
+%% expression to give the user the chance to inspect variables etc.
+%% get(next_break) => break | running
+%% | Le
+%% specifies if the process should break.
+%%--------------------------------------------------------------------
+
+%% Common Test adaptation
+cmd({call_remote,0,ct_line,line,_As}, Bs, _Ieval) ->
+ Bs;
+
+cmd(Expr, Bs, Ieval) ->
+ cmd(Expr, Bs, get(next_break), Ieval).
+
+%% Evaluation should break
+cmd(Expr, Bs, break, Ieval) ->
+ break(Expr, Bs, Ieval);
+%% Evaluation should continue, unless there is a breakpoint at
+%% the current line
+cmd(Expr, Bs, running, #ieval{level=Le,module=M}=Ieval) ->
+ Line = element(2, Expr),
+ case break_p(M, Line, Le, Bs) of
+ true ->
+ put(next_break, break),
+ break(Expr, Bs, Ieval);
+ false ->
+ handle_cmd(Bs, running, Ieval)
+ end;
+%% Evaluation should continue for now (until we've returned to
+%% call level Next)
+cmd(Expr, Bs, Next, #ieval{level=Le}=Ieval) when is_integer(Next),
+ Next<Le ->
+ Line = element(2, Expr),
+ handle_cmd(Bs, Next, Ieval#ieval{line=Line});
+%% Evaluation has returned to call level Next, break
+cmd(Expr, Bs, Next, #ieval{level=Le}=Ieval) when is_integer(Next),
+ Next>=Le ->
+ put(next_break, break),
+ break(Expr, Bs, Ieval).
+
+%% break_p(Mod, Line, Le, Bs) -> true | false
+%% Checks if there is a breakpoint at Line in Mod.
+%% As a side effect, disables or deletes breakpoint as specified
+break_p(Mod, Line, Le, Bs) ->
+ case lists:keysearch({Mod, Line}, 1, get(breakpoints)) of
+ {value, {_Point, [active, Action, _, Cond]}} ->
+ case get(user_eval) of
+ [{Line, Le}|_] -> false;
+ _ ->
+ Bool = case Cond of
+ null -> true;
+ {CM, CN} ->
+ try apply(CM, CN, [Bs]) of
+ true -> true;
+ false -> false;
+ _Term -> false
+ catch
+ _C:_R -> false
+ end
+ end,
+ if
+ Bool ->
+ case Action of
+ enable -> ignore;
+ disable ->
+ dbg_iserver:cast(get(int),
+ {break_option,
+ {Mod, Line},
+ status,
+ inactive});
+ delete ->
+ dbg_iserver:cast(get(int),
+ {delete_break,
+ {Mod, Line}})
+ end;
+ true -> ignore
+ end,
+ Bool
+ end;
+ _Other -> % {value, {_Point, [inactive|_]}} | false
+ false
+ end.
+
+%% Called whenever evaluation enters break mode, informs attached
+%% process and dbg_iserver
+break(Expr, Bs, #ieval{level=Le,module=M}=Ieval) ->
+ Line = element(2, Expr),
+ dbg_iserver:cast(get(int), {set_status,self(),break,{M,Line}}),
+ tell_attached({break_at,M,Line,Le}),
+ handle_cmd(Bs, break, Ieval#ieval{line=Line}).
+
+%%--------------------------------------------------------------------
+%% handle_cmd(Bs0, Status, Ieval) -> Bs1 | {skip, Bs1}
+%% Status = break | running | Le
+%% In break mode, loop waiting for user commands (and handle other
+%% messages meanwhile).
+%% In other modes, handle other messages, if any.
+%%--------------------------------------------------------------------
+handle_cmd(Bs, break, #ieval{level=Le}=Ieval) ->
+ receive
+ {user, {cmd, Cmd}} ->
+ dbg_iserver:cast(get(int), {set_status,self(),running,{}}),
+ tell_attached(running),
+ case Cmd of
+ step -> Bs;
+ next -> put(next_break, Le), Bs;
+ continue -> put(next_break, running), Bs;
+ finish -> put(next_break, Le-1), Bs;
+ skip -> {skip, Bs}
+ end;
+ {user, {eval, Cmd}} ->
+ Bs1 = eval_nonrestricted(Cmd, Bs, Ieval),
+ handle_cmd(Bs1, break, Ieval);
+ Msg ->
+ dbg_ieval:check_exit_msg(Msg, Bs, Ieval),
+ handle_msg(Msg, break, Bs, Ieval),
+ handle_cmd(Bs, break, Ieval)
+ end;
+handle_cmd(Bs, Status, Ieval) ->
+ receive
+ Msg ->
+ dbg_ieval:check_exit_msg(Msg, Bs, Ieval),
+ handle_msg(Msg, Status, Bs, Ieval),
+ handle_cmd(Bs, Status, Ieval)
+ after 0 ->
+ Bs
+ end.
+
+%%====================================================================
+%% User control of process execution and settings
+%%====================================================================
+
+step(Meta) -> Meta ! {user, {cmd, step}}.
+next(Meta) -> Meta ! {user, {cmd, next}}.
+continue(Meta) -> Meta ! {user, {cmd, continue}}.
+finish(Meta) -> Meta ! {user, {cmd, finish}}.
+skip(Meta) -> Meta ! {user, {cmd, skip}}.
+
+timeout(Meta) -> Meta ! {user, timeout}.
+
+stop(Meta) -> Meta ! {user, {cmd, stop}}.
+
+eval(Meta, {Mod, Cmd}) ->
+ eval(Meta, {Mod, Cmd, nostack});
+eval(Meta, {Mod, Cmd, SP}) ->
+ Cmd2 = case lists:reverse(Cmd) of % Commands must end with ".\n"
+ [10,$.|_] -> Cmd;
+ [10|T] -> lists:reverse([10,$.|T]);
+ [$.|T] -> lists:reverse([10,$.|T]);
+ T -> lists:reverse([10,$.|T])
+ end,
+ Meta ! {user, {eval, {self(), Mod, Cmd2, SP}}}.
+
+%% Tag Args
+%% --- ----
+%% trace Bool
+%% stack_trace Flag
+set(Meta, Tag, Args) ->
+ Meta ! {user, {set, Tag, Args}}.
+
+%% Tag Args Reply
+%% --- ---- -----
+%% bindings nostack | SP [{Var,Val}] (Var=atom(), Val=term())
+%% stack_frame {up|down, SP} [{Le,Where,Bs}] | top | bottom
+%% (Where = {Mod,Li}
+%% messages null [Msg] (Msg=term())
+%% backtrace all | N [{Le,MFA}] (MFA={M,F,Args}|{Fun,Args})
+get(Meta, Tag, Args) ->
+ Meta ! {user, {get, Tag, self(), Args}},
+ receive
+ {Meta, Tag, Reply} -> Reply
+ end.
+
+%%--------------------------------------------------------------------
+%% handle_msg({int, Msg} | {user, Msg}, Status, Bs, Ieval)
+%% Status = idle | exit_at | wait_at | wait_after_at
+%% | break | running | Le | {Le,MFA}
+%%--------------------------------------------------------------------
+handle_msg({int, Msg}, Status, Bs, Ieval) ->
+ handle_int_msg(Msg, Status, Bs, Ieval);
+handle_msg({user, Msg}, Status, Bs, Ieval) ->
+ handle_user_msg(Msg, Status, Bs, Ieval);
+handle_msg(Msg, Status, Bs, Ieval) ->
+ io:format("***WARNING*** Unexp msg ~p, info ~p~n",
+ [Msg,{Status,Bs,Ieval}]).
+
+%% handle_int_msg(Msg, Status, Bs, Ieval)
+%% Msg = {attached, AttPid} | {detached, AttPid}
+%% | {old_code, Mod}
+%% | {new_break, Break} | {delete_break, Break}
+%% | {break_options, {Break, Options}}
+%% | no_break | {no_break, Mod}
+%% | stop (only when Status==exit_at, means AttPid has terminated)
+%% Interpreter internal messages (from dbg_iserver)
+handle_int_msg({attached, AttPid}, Status, _Bs,
+ #ieval{level=Le,module=M,line=Line}) ->
+
+ %% Update process dictionary
+ put(attached, AttPid),
+ put(next_break, break),
+
+ %% Tell attached process in which module evalution is located
+ if
+ Le==1 ->
+ tell_attached({attached, undefined, -1, get(trace)});
+ true ->
+ tell_attached({attached, M, Line, get(trace)}),
+
+ %% Give info about status and call level as well
+ %% In this case, Status can not be exit_at
+ Msg = case Status of
+ idle -> {func_at,M,Line,Le};
+ break -> {break_at,M,Line,Le};
+ wait_at -> {wait_at,M,Line,Le};
+ wait_after_at -> {wait_after_at,M,Line,Le};
+ _ -> running % running | Le | {Le,MFA}
+ end,
+ tell_attached(Msg)
+ end;
+handle_int_msg(detached, _Status, _Bs, _Ieval) ->
+ %% Update process dictionary
+ put(attached, undefined),
+ put(next_break, running),
+ put(trace, false); % no need for tracing if there is no AttPid
+handle_int_msg({old_code,Mod}, Status, Bs,
+ #ieval{level=Le,module=M}=Ieval) ->
+ if
+ Status==idle, Le==1 ->
+ erase([Mod|db]),
+ put(cache, []);
+ true ->
+ case dbg_ieval:in_use_p(Mod, M) of
+ true ->
+ %% A call to Mod is on the stack (or might be),
+ %% so we must terminate.
+ exit(get(self), kill),
+ dbg_ieval:exception(exit, old_code, Bs, Ieval);
+ false ->
+ erase([Mod|db]),
+ put(cache, [])
+ end
+ end;
+handle_int_msg({new_break, Break}, _Status, _Bs, _Ieval) ->
+ put(breakpoints, [Break | get(breakpoints)]);
+handle_int_msg({delete_break, Point}, _Status, _Bs, _Ieval) ->
+ put(breakpoints, lists:keydelete(Point, 1, get(breakpoints)));
+handle_int_msg({break_options, Break}, _Status, _Bs, _Ieval) ->
+ {Point, _Options} = Break,
+ put(breakpoints, lists:keyreplace(Point,1,get(breakpoints), Break));
+handle_int_msg(no_break, _Status, _Bs, _Ieval) ->
+ put(breakpoints, []);
+handle_int_msg({no_break,M}, _Status, _Bs, _Ieval) ->
+ put(breakpoints, [ML || {Mod,_L}=ML <- get(breakpoints), Mod=/=M]);
+handle_int_msg(stop, exit_at, _Bs, _Ieval) ->
+ erlang:exit(normal).
+
+%% handle_user_msg(Msg, Status, Bs, Ieval)
+%% Msg = {cmd, Cmd}, Cmd = step | next | continue | finish| skip| stop
+%% | timeout
+%% | {eval, {Pid, Mod, Str, SP}}
+%% | {set, Tag, Args} | {get, Tag, Pid, Args}
+%% Messages from the attached process
+%% Msg = {cmd, Cmd}, Cmd /= stop, can only be received in break mode,
+%% handled in handle_cmd/3
+%% Msg = timeout is handled when needed (when evaluating receive..after)
+%% in dbg_ieval:do_receive/5 when Status==wait_after_at
+%% For all other Status, it should be ignored
+handle_user_msg({cmd, stop}, Status, _Bs, _Ieval) ->
+ case lists:member(Status, [running, wait_at, wait_after_at]) of
+ true ->
+ put(next_break, break);
+ false when is_integer(Status); is_tuple(Status) ->
+ put(next_break, break);
+ false -> % idle | exit_at (| break)
+ ignore
+ end;
+handle_user_msg({cmd, continue}, Status, _Bs, _Ieval) ->
+ %% Allow leaving break mode when waiting in a receive
+ case lists:member(Status, [wait_at, wait_after_at]) of
+ true ->
+ put(next_break, running);
+ false ->
+ ignore
+ end;
+handle_user_msg({cmd, _Cmd}, _Status, _Bs, _Ieval) ->
+ ignore;
+handle_user_msg(timeout, _Status, _Bs, _Ieval) ->
+ ignore;
+handle_user_msg({eval,Cmd}, wait_at, Bs, _Ieval) ->
+ eval_restricted(Cmd, Bs);
+handle_user_msg({eval,Cmd}, wait_after_at, Bs, _Ieval) ->
+ eval_restricted(Cmd, Bs);
+handle_user_msg({set,trace,Bool}, _Status, _Bs, _Ieval) ->
+ put(trace, Bool),
+ tell_attached({trace, Bool});
+handle_user_msg({set,stack_trace,Flag}, _Status, _Bs, _Ieval) ->
+ set_stack_trace(Flag);
+handle_user_msg({get,bindings,From,SP}, _Status, Bs, _Ieval) ->
+ reply(From, bindings, bindings(Bs, SP));
+handle_user_msg({get,stack_frame,From,{Dir,SP}}, _Status, _Bs,_Ieval) ->
+ reply(From, stack_frame, dbg_ieval:stack_frame(Dir, SP));
+handle_user_msg({get,messages,From,_}, _Status, _Bs, _Ieval) ->
+ reply(From, messages, messages());
+handle_user_msg({get,backtrace,From,N}, _Status, _Bs, _Ieval) ->
+ reply(From, backtrace, dbg_ieval:backtrace(N)).
+
+set_stack_trace(true) ->
+ set_stack_trace(all);
+set_stack_trace(Flag) ->
+ if
+ Flag==false ->
+ put(stack, []);
+ Flag==no_tail; Flag==all ->
+ ignore
+ end,
+ put(trace_stack, Flag),
+ tell_attached({stack_trace, Flag}).
+
+reply(From, Tag, Reply) ->
+ From ! {self(), Tag, Reply}.
+
+bindings(Bs, nostack) ->
+ Bs;
+bindings(Bs, SP) ->
+ case dbg_ieval:stack_level() of
+ Le when SP>Le ->
+ Bs;
+ _ ->
+ dbg_ieval:bindings(SP)
+ end.
+
+messages() ->
+ {messages, Msgs} = erlang:process_info(get(self), messages),
+ Msgs.
+
+
+%%====================================================================
+%% Evaluating expressions within process context
+%%====================================================================
+
+eval_restricted({From,_Mod,Cmd,SP}, Bs) ->
+ case catch parse_cmd(Cmd, 1) of
+ {'EXIT', _Reason} ->
+ From ! {self(), {eval_rsp, 'Parse error'}};
+ [{var,_,Var}] ->
+ Bs2 = bindings(Bs, SP),
+ Res = case get_binding(Var, Bs2) of
+ {value, Value} -> Value;
+ unbound -> unbound
+ end,
+ From ! {self(), {eval_rsp, Res}};
+ _Forms ->
+ Rsp = 'Only possible to inspect variables',
+ From ! {self(), {eval_rsp, Rsp}}
+ end.
+
+eval_nonrestricted({From,Mod,Cmd,SP}, Bs, #ieval{level=Le}) when SP<Le->
+ %% Evaluate in stack
+ eval_restricted({From, Mod, Cmd, SP}, Bs),
+ Bs;
+eval_nonrestricted({From, _Mod, Cmd, _SP}, Bs,
+ #ieval{level=Le,module=M,line=Line}=Ieval) ->
+ case catch parse_cmd(Cmd, Line) of
+ {'EXIT', _Reason} ->
+ From ! {self(), {eval_rsp, 'Parse error'}},
+ Bs;
+ Forms ->
+ mark_running(Line, Le),
+ {Res, Bs2} =
+ lists:foldl(fun(Expr, {_Res, Bs0}) ->
+ eval_nonrestricted_1(Expr,Bs0,Ieval)
+ end,
+ {null, Bs},
+ Forms),
+ mark_break(M, Line, Le),
+ From ! {self(), {eval_rsp, Res}},
+ Bs2
+ end.
+
+eval_nonrestricted_1({match,_,{var,_,Var},Expr}, Bs, Ieval) ->
+ {value,Res,Bs2} =
+ dbg_ieval:eval_expr(Expr, Bs, Ieval#ieval{last_call=false}),
+ Bs3 = case lists:keysearch(Var, 1, Bs) of
+ {value, {Var,_Value}} ->
+ lists:keyreplace(Var, 1, Bs2, {Var,Res});
+ false -> [{Var,Res} | Bs2]
+ end,
+ {Res,Bs3};
+eval_nonrestricted_1({var,_,Var}, Bs, _Ieval) ->
+ Res = case lists:keysearch(Var, 1, Bs) of
+ {value, {Var, Value}} -> Value;
+ false -> unbound
+ end,
+ {Res,Bs};
+eval_nonrestricted_1(Expr, Bs, Ieval) ->
+ {value,Res,Bs2} =
+ dbg_ieval:eval_expr(Expr, Bs, Ieval#ieval{last_call=false}),
+ {Res,Bs2}.
+
+mark_running(LineNo, Le) ->
+ put(next_break, running),
+ put(user_eval, [{LineNo, Le} | get(user_eval)]),
+ dbg_iserver:cast(get(int), {set_status, self(), running, {}}),
+ tell_attached(running).
+
+mark_break(Cm, LineNo, Le) ->
+ put(next_break, break),
+ put(user_eval, tl(get(user_eval))),
+ tell_attached({break_at, Cm, LineNo, Le}),
+ dbg_iserver:cast(get(int), {set_status,self(),break,{Cm,LineNo}}).
+
+parse_cmd(Cmd, LineNo) ->
+ {ok,Tokens,_} = erl_scan:string(Cmd, LineNo),
+ {ok,Forms} = erl_parse:parse_exprs(Tokens),
+ Forms.
+
+
+%%====================================================================
+%% Library functions for attached process handling
+%%====================================================================
+
+tell_attached(Msg) ->
+ case get(attached) of
+ undefined -> ignore;
+ AttPid ->
+ AttPid ! {self(), Msg}
+ end.
+
+
+%%====================================================================
+%% get_binding/2
+%%====================================================================
+
+get_binding(Var, Bs) ->
+ case lists:keysearch(Var, 1, Bs) of
+ {value, {Var, Value}} -> {value, Value};
+ false -> unbound
+ end.
diff --git a/lib/debugger/src/dbg_idb.erl b/lib/debugger/src/dbg_idb.erl
new file mode 100644
index 0000000000..50ed2bb7ce
--- /dev/null
+++ b/lib/debugger/src/dbg_idb.erl
@@ -0,0 +1,54 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_idb).
+
+%% External exports
+-export([insert/3, lookup/2, match_object/2]).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+insert(DbRef, Key, Value) ->
+ case DbRef of
+ {Node, ModDb} ->
+ rpc:block_call(Node, ets, insert, [ModDb, {Key, Value}]);
+ ModDb ->
+ ets:insert(ModDb, {Key, Value})
+ end.
+
+lookup(DbRef, Key) ->
+ Res = case DbRef of
+ {Node, ModDb} ->
+ rpc:block_call(Node, ets, lookup, [ModDb, Key]);
+ ModDb ->
+ ets:lookup(ModDb, Key)
+ end,
+ case Res of
+ [{Key, Value}] -> {ok, Value};
+ _ -> not_found
+ end.
+
+match_object(DbRef, Key) ->
+ case DbRef of
+ {Node, ModDb} ->
+ rpc:block_call(Node, ets, match_object, [ModDb, Key]);
+ ModDb ->
+ ets:match_object(ModDb, Key)
+ end.
diff --git a/lib/debugger/src/dbg_ieval.erl b/lib/debugger/src/dbg_ieval.erl
new file mode 100644
index 0000000000..47d4ecaaf8
--- /dev/null
+++ b/lib/debugger/src/dbg_ieval.erl
@@ -0,0 +1,1733 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ieval).
+
+-export([eval/3,exit_info/5]).
+-export([eval_expr/3]).
+-export([check_exit_msg/3,exception/4,in_use_p/2]).
+-export([stack_level/0, bindings/1, stack_frame/2, backtrace/1]).
+
+-include("dbg_ieval.hrl").
+
+-import(lists, [foldl/3,flatmap/2]).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% eval(Mod, Func, Args) -> Meta
+%% Mod = Func = atom()
+%% Args = [term()]
+%% MFA = {Mod,Func,Args} | {Mod,Func,Arity} | {Fun,Args}
+%% Arity = integer()
+%% Meta = pid()
+%% Entry point from debugged process (dbg_debugged).
+%% Immediately returns the pid for the meta process.
+%% The evaluated value will later be sent as a message to
+%% the calling process.
+%%--------------------------------------------------------------------
+eval(Mod, Func, Args) ->
+ Debugged = self(),
+ Int = dbg_iserver:find(),
+ case dbg_iserver:call(Int, {get_meta,Debugged}) of
+ {ok,Meta} ->
+ Meta ! {re_entry, Debugged, {eval,{Mod,Func,Args}}},
+ Meta;
+ {error,not_interpreted} ->
+ spawn(fun() ->
+ meta(Int, Debugged, Mod, Func, Args)
+ end)
+ end.
+
+%%--------------------------------------------------------------------
+%% exit_info(Int, AttPid, OrigPid, Reason, ExitInfo)
+%% Int = AttPid = OrigPid = pid()
+%% Reason = term()
+%% ExitInfo = {{Mod,Line}, Bs, Stack} | {}
+%% Meta process started when attaching to a terminated process.
+%% Spawned (by dbg_iserver) in response to user request.
+%%--------------------------------------------------------------------
+exit_info(Int, AttPid, OrigPid, Reason, ExitInfo) ->
+ put(int, Int),
+ put(attached, AttPid),
+ put(breakpoints, dbg_iserver:call(Int, all_breaks)),
+ put(self, OrigPid),
+ put(exit_info, ExitInfo),
+
+ case ExitInfo of
+ {{Mod,Line},Bs,S} ->
+ Stack = binary_to_term(S),
+ put(stack, Stack),
+ Le = stack_level(Stack),
+ dbg_icmd:tell_attached({exit_at, {Mod, Line}, Reason, Le}),
+ exit_loop(OrigPid, Reason, Bs,#ieval{module=Mod,line=Line});
+ {} ->
+ put(stack, []),
+ dbg_icmd:tell_attached({exit_at, null, Reason, 1}),
+ exit_loop(OrigPid, Reason, erl_eval:new_bindings(),#ieval{})
+ end.
+
+%%--------------------------------------------------------------------
+%% eval_expr(Expr, Bs, Ieval) -> {value, Value, Bs}
+%%
+%% Evalute a shell expression in the real process.
+%% Called (dbg_icmd) in response to a user request.
+%%--------------------------------------------------------------------
+eval_expr(Expr, Bs, Ieval) ->
+
+ %% Save current exit info
+ ExitInfo = get(exit_info),
+ Stacktrace = get(stacktrace),
+
+ %% Emulate a surrounding catch
+ try debugged_cmd({eval,Expr,Bs}, Bs, Ieval)
+ catch
+ Class:Reason ->
+ Result = case Class of
+ throw -> Reason;
+ _ -> {'EXIT', Reason}
+ end,
+
+ %% Reset exit info
+ put(exit_info, ExitInfo),
+ put(stacktrace, Stacktrace),
+
+ {value, Result, Bs}
+ end.
+
+%%--------------------------------------------------------------------
+%% check_exit_msg(Msg, Bs, Ieval)
+%% Msg = term()
+%% Check if Msg is an 'EXIT' msg from the iserver or a 'DOWN' msg
+%% from the debugged process. If so exit with correct reason.
+%%--------------------------------------------------------------------
+check_exit_msg({'EXIT', Int, Reason}, _Bs, #ieval{level=Le}) ->
+ %% This *must* be interpreter which has terminated,
+ %% we are not linked to anyone else
+ if
+ Le==1 ->
+ exit(Reason);
+ Le>1 ->
+ exit({Int, Reason})
+ end;
+check_exit_msg({'DOWN',_,_,_,Reason}, Bs,
+ #ieval{level=Le, module=Mod, line=Li}) ->
+ %% This *must* be Debugged which has terminated,
+ %% we are not monitoring anyone else
+
+ %% Inform Int about current position, bindings and stack
+ ExitInfo =
+ case get(exit_info) of
+
+ %% Debugged has been terminated by someone
+ %% - really the position, bindings and stack are of no
+ %% importance in this case
+ %% If we don't save them, however, post-mortem analysis
+ %% of the process isn't possible
+ undefined when Le==1 -> % died outside interpreted code
+ {};
+ undefined when Le>1 ->
+ StackBin = term_to_binary(get(stack)),
+ {{Mod, Li}, Bs, StackBin};
+
+ %% Debugged has terminated due to an exception
+ ExitInfo0 ->
+ ExitInfo0
+ end,
+ dbg_iserver:cast(get(int), {set_exit_info,self(),ExitInfo}),
+
+ if
+ Le==1 ->
+ exit(Reason);
+ Le>1 ->
+ exit({get(self), Reason})
+ end;
+check_exit_msg(_Msg, _Bs, _Ieval) ->
+ ignore.
+
+%%--------------------------------------------------------------------
+%% exception(Class, Reason, Bs, Ieval)
+%% Class = error | exit | throw
+%% Reason = term()
+%% Bs = bindings()
+%% Ieval = #ieval{}
+%% Store information about where in the code the error is located
+%% and then raise the exception.
+%%--------------------------------------------------------------------
+exception(Class, Reason, Bs, Ieval) ->
+ exception(Class, Reason, fix_stacktrace(1), Bs, Ieval).
+
+exception(Class, Reason, Stacktrace, Bs, #ieval{module=M, line=Line}) ->
+ ExitInfo = {{M,Line}, Bs, term_to_binary(get(stack))},
+ put(exit_info, ExitInfo),
+ put(stacktrace, Stacktrace),
+ erlang:Class(Reason).
+
+%%--------------------------------------------------------------------
+%% in_use_p(Mod, Cm) -> boolean()
+%% Mod = Cm = atom()
+%% Returns true if Mod is found on the stack, otherwise false.
+%%--------------------------------------------------------------------
+in_use_p(Mod, Mod) -> true;
+in_use_p(Mod, _Cm) ->
+ case get(trace_stack) of
+ false -> true;
+ _ -> % all | no_tail
+ lists:any(fun({_,{M,_,_,_}}) when M =:= Mod -> true;
+ (_) -> false
+ end,
+ get(stack))
+ end.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+%%--Loops-------------------------------------------------------------
+
+%% Entry point for first-time initialization of meta process
+meta(Int, Debugged, M, F, As) ->
+ process_flag(trap_exit, true),
+ erlang:monitor(process, Debugged),
+
+ %% Inform dbg_iserver, get the initial status in return
+ Pargs = case {M, F} of
+ %% If it's a fun we're evaluating, show a text
+ %% representation of the fun and its arguments,
+ %% not dbg_ieval:eval_fun(...)
+ {dbg_ieval, eval_fun} ->
+ {Mx, Fx} = lists:last(As),
+ {Mx, Fx, lists:nth(2, As)};
+ _ ->
+ {M, F, As}
+ end,
+ Status = dbg_iserver:call(Int, {new_process,Debugged,self(),Pargs}),
+
+ %% Initiate process dictionary
+ put(int, Int), % pid() dbg_iserver
+ put(attached, undefined),% pid() attached process
+ put(breakpoints, dbg_iserver:call(Int, all_breaks)),
+ put(cache, []),
+ put(next_break, Status), % break | running (other values later)
+ put(self, Debugged), % pid() interpreted process
+ put(stack, []),
+ put(stacktrace, []),
+ put(trace_stack, dbg_iserver:call(Int, get_stack_trace)),
+ put(trace, false), % bool() Trace on/off
+ put(user_eval, []),
+
+
+ %% Send the result of the meta process
+ Ieval = #ieval{},
+ Debugged ! {sys, self(), eval_mfa(Debugged,M,F,As,Ieval)},
+
+ dbg_iserver:cast(Int, {set_status, self(), idle, {}}),
+ dbg_icmd:tell_attached(idle),
+
+ meta_loop(Debugged, erl_eval:new_bindings(), Ieval).
+
+debugged_cmd(Cmd, Bs, Ieval) ->
+ Debugged = get(self),
+ Stacktrace = fix_stacktrace(2),
+ Debugged ! {sys, self(), {command,Cmd,Stacktrace}},
+ meta_loop(Debugged, Bs, Ieval).
+
+meta_loop(Debugged, Bs, #ieval{level=Le} = Ieval) ->
+ receive
+
+ %% The following messages can only be received when Meta is
+ %% waiting for Debugged to evaluate non-interpreted code
+ %% or a Bif. Le>1
+ {sys, Debugged, {value,Val}} ->
+ {value, Val, Bs};
+ {sys, Debugged, {value,Val,Bs2}} ->
+ {value, Val, Bs2};
+ {sys, Debugged, {exception,{Class,Reason,Stacktrace}}} ->
+ case get(exit_info) of
+
+ %% Error occured outside interpreted code
+ undefined ->
+ exception(Class,Reason,Stacktrace,Bs,Ieval);
+
+ %% Error must have occured within a re-entry to
+ %% interpreted code, simply raise the exception
+ _ ->
+ erlang:Class(Reason)
+ end;
+
+ %% Re-entry to Meta from non-interpreted code
+ {re_entry, Debugged, {eval,{M,F,As}}} when Le==1 ->
+ %% Reset process dictionary
+ %% This is really only necessary if the process left
+ %% interpreted code at a call level > 1
+ put(stack, []),
+ put(stacktrace, []),
+ put(exit_info, undefined),
+
+ dbg_iserver:cast(get(int), {set_status,self(),running,{}}),
+ dbg_icmd:tell_attached(running),
+
+ %% Tell attached process(es) to update source code.
+ dbg_icmd:tell_attached({re_entry,M,F}),
+
+ %% Send the result of the meta process
+ Debugged ! {sys,self(),eval_mfa(Debugged,M,F,As,Ieval)},
+
+ dbg_iserver:cast(get(int), {set_status,self(),idle,{}}),
+ dbg_icmd:tell_attached(idle),
+ meta_loop(Debugged, Bs, Ieval);
+
+ %% Evaluation in Debugged results in call to interpreted
+ %% function (probably? a fun)
+ {re_entry, Debugged, {eval,{M,F,As}}} when Le>1 ->
+ Ieval2 = Ieval#ieval{module=undefined, line=-1},
+ Debugged ! {sys,self(),eval_mfa(Debugged,M,F,As,Ieval2)},
+ meta_loop(Debugged, Bs, Ieval);
+
+ Msg ->
+ check_exit_msg(Msg, Bs, Ieval),
+ dbg_icmd:handle_msg(Msg, idle, Bs, Ieval),
+ meta_loop(Debugged, Bs, Ieval)
+ end.
+
+exit_loop(OrigPid, Reason, Bs, Ieval) ->
+ receive
+ Msg ->
+ check_exit_msg(Msg, Bs, Ieval),
+ dbg_icmd:handle_msg(Msg, exit_at, Bs, Ieval),
+ exit_loop(OrigPid, Reason, Bs, Ieval)
+ end.
+
+%%--Stack emulation---------------------------------------------------
+
+%% We keep track of a call stack that is used for
+%% 1) saving stack frames that can be inspected from an Attached
+%% Process GUI (using dbg_icmd:get(Meta, stack_frame, {Dir, SP})
+%% 2) generate an approximation of regular stacktrace -- sent to
+%% Debugged when it should raise an exception or evaluate a
+%% function (since it might possible raise an exception)
+%%
+%% Stack = [Entry]
+%% Entry = {Le, {MFA, Where, Bs}}
+%% Le = int() % current call level
+%% MFA = {M,F,Args} % called function (or fun)
+%% | {Fun,Args} %
+%% Where = {M,Li} % from where (module+line) function is called
+%% Bs = bindings() % current variable bindings
+%%
+%% How to push depends on the "Stack Trace" option (value saved in
+%% process dictionary item 'trace_stack').
+%% all - everything is pushed
+%% no_tail - tail recursive push
+%% false - nothing is pushed
+%% Whenever a function returns, the corresponding call frame is popped.
+
+push(MFA, Bs, #ieval{level=Le,module=Cm,line=Li,last_call=Lc}) ->
+ Entry = {Le, {MFA, {Cm,Li}, Bs}},
+ case get(trace_stack) of
+ false -> ignore;
+ no_tail when Lc ->
+ case get(stack) of
+ [] -> put(stack, [Entry]);
+ [_Entry|Entries] -> put(stack, [Entry|Entries])
+ end;
+ _ -> % all | no_tail when Lc==false
+ put(stack, [Entry|get(stack)])
+ end.
+
+pop() ->
+ case get(trace_stack) of
+ false -> ignore;
+ _ -> % all � no_tail
+ case get(stack) of
+ [_Entry|Entries] ->
+ put(stack, Entries);
+ [] ->
+ ignore
+ end
+ end.
+
+pop(Le) ->
+ case get(trace_stack) of
+ false -> ignore;
+ _ -> % all | no_tail
+ put(stack, pop(Le, get(stack)))
+ end.
+
+pop(Level, [{Le, _}|Stack]) when Level=<Le ->
+ pop(Level, Stack);
+pop(_Level, Stack) ->
+ Stack.
+
+
+%% stack_level() -> Le
+%% stack_level(Stack) -> Le
+%% Top call level
+stack_level() ->
+ stack_level(get(stack)).
+
+stack_level([]) -> 1;
+stack_level([{Le,_}|_]) -> Le.
+
+%% fix_stacktrace(Start) -> Stacktrace
+%% Start = 1|2
+%% Stacktrace = [{M,F,Args|Arity} | {Fun,Args}]
+%% Convert internal stack format to imitation of regular stacktrace.
+%% Max three elements, no repeated (recursive) calls to the same
+%% function and convert argument lists to arity for all but topmost
+%% entry (and funs).
+%% 'Start' indicates where at get(stack) to start. This somewhat ugly
+%% solution is because fix_stacktrace has two uses: 1) to imitate
+%% the stacktrace in the case of an exception in the interpreted code,
+%% in which case the current call (top of the stack = first of the list)
+%% should be included, and 2) to send a current stacktrace to Debugged
+%% when evaluation passes into non-interpreted code, in which case
+%% the current call should NOT be included (as it is Debugged which
+%% will make the actual function call).
+fix_stacktrace(Start) ->
+ case fix_stacktrace2(sublist(get(stack), Start, 3)) of
+ [] ->
+ [];
+ [H|T] ->
+ [H|args2arity(T)]
+ end.
+
+sublist([], _Start, _Length) ->
+ []; % workaround, lists:sublist([],2,3) fails
+sublist(L, Start, Length) ->
+ lists:sublist(L, Start, Length).
+
+fix_stacktrace2([{_,{{M,F,As1},_,_}}, {_,{{M,F,As2},_,_}}|_])
+ when length(As1)==length(As2) ->
+ [{M,F,As1}];
+fix_stacktrace2([{_,{{Fun,As1},_,_}}, {_,{{Fun,As2},_,_}}|_])
+ when length(As1)==length(As2) ->
+ [{Fun,As1}];
+fix_stacktrace2([{_,{MFA,_,_}}|Entries]) ->
+ [MFA|fix_stacktrace2(Entries)];
+fix_stacktrace2([]) ->
+ [].
+
+args2arity([{M,F,As}|Entries]) when is_list(As) ->
+ [{M,F,length(As)}|args2arity(Entries)];
+args2arity([Entry|Entries]) ->
+ [Entry|args2arity(Entries)];
+args2arity([]) ->
+ [].
+
+%% bindings(SP) -> Bs
+%% SP = Le % stack pointer
+%% Return the bindings for the specified call level
+bindings(SP) ->
+ bindings(SP, get(stack)).
+
+bindings(SP, [{SP,{_MFA,_Wh,Bs}}|_]) ->
+ Bs;
+bindings(SP, [_Entry|Entries]) ->
+ bindings(SP, Entries);
+bindings(_SP, []) ->
+ erl_eval:new_bindings().
+
+%% stack_frame(Dir, SP) -> {Le, Where, Bs} | top | bottom
+%% Dir = up | down
+%% Where = {Cm, Li}
+%% Cm = Module | undefined % module
+%% Li = int() | -1 % line number
+%% Bs = bindings()
+%% Return stack frame info one step up/down from given stack pointer
+%% up = to lower call levels
+%% down = to higher call levels
+stack_frame(up, SP) ->
+ stack_frame(SP, up, get(stack));
+stack_frame(down, SP) ->
+ stack_frame(SP, down, lists:reverse(get(stack))).
+
+stack_frame(SP, up, [{Le, {_MFA,Where,Bs}}|_]) when Le<SP ->
+ {Le, Where, Bs};
+stack_frame(SP, down, [{Le, {_MFA,Where,Bs}}|_]) when Le>SP ->
+ {Le, Where, Bs};
+stack_frame(SP, Dir, [{SP, _}|Stack]) ->
+ case Stack of
+ [{Le, {_MFA,Where,Bs}}|_] ->
+ {Le, Where, Bs};
+ [] when Dir==up ->
+ top;
+ [] when Dir==down ->
+ bottom
+ end;
+stack_frame(SP, Dir, [_Entry|Stack]) ->
+ stack_frame(SP, Dir, Stack).
+
+%% backtrace(HowMany) -> Backtrace
+%% HowMany = all | int()
+%% Backtrace = {Le, MFA}
+%% Return all/the last N called functions, in reversed call order
+backtrace(HowMany) ->
+ Stack = case HowMany of
+ all -> get(stack);
+ N -> lists:sublist(get(stack), N)
+ end,
+ [{Le, MFA} || {Le,{MFA,_Wh,_Bs}} <- Stack].
+
+%%--Trace function----------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% trace(What, Args)
+%% What = send | receivex | received | call | return | bif
+%% Args depends on What, see the code.
+%%--------------------------------------------------------------------
+trace(What, Args) ->
+ trace(What, Args, get(trace)).
+
+trace(return, {_Le,{dbg_apply,_,_,_}}, _Bool) ->
+ ignore;
+trace(What, Args, true) ->
+ Str = case What of
+ send ->
+ {To,Msg} = Args,
+ io_lib:format("==> ~w : ~p~n", [To, Msg]);
+ receivex ->
+ {Le, TimeoutP} = Args,
+ Tail = case TimeoutP of
+ true -> "with timeout~n";
+ false -> "~n"
+ end,
+ io_lib:format(" (~w) receive " ++ Tail, [Le]);
+
+ received when Args==null ->
+ io_lib:format("~n", []);
+ received -> % Args=Msg
+ io_lib:format("~n<== ~p~n", [Args]);
+
+ call ->
+ {Called, {Le,Li,M,F,As}} = Args,
+ case Called of
+ extern ->
+ io_lib:format("++ (~w) <~w> ~w:~w~s~n",
+ [Le,Li,M,F,format_args(As)]);
+ local ->
+ io_lib:format("++ (~w) <~w> ~w~s~n",
+ [Le,Li,F,format_args(As)])
+ end;
+ call_fun ->
+ {Le,Li,F,As} = Args,
+ io_lib:format("++ (~w) <~w> ~w~s~n",
+ [Le, Li, F, format_args(As)]);
+ return ->
+ {Le,Val} = Args,
+ io_lib:format("-- (~w) ~p~n", [Le, Val]);
+
+
+ bif ->
+ {Le,Li,M,F,As} = Args,
+ io_lib:format("++ (~w) <~w> ~w:~w~s~n",
+ [Le, Li, M, F, format_args(As)])
+ end,
+ dbg_icmd:tell_attached({trace_output, Str});
+trace(_What, _Args, false) ->
+ ignore.
+
+format_args(As) when is_list(As) ->
+ [$(,format_args1(As),$)];
+format_args(A) ->
+ [$/,io_lib:format("~p", [A])].
+
+format_args1([A]) ->
+ [io_lib:format("~p", [A])];
+format_args1([A|As]) ->
+ [io_lib:format("~p", [A]),$,|format_args1(As)];
+format_args1([]) ->
+ [].
+
+%%--Other useful functions--------------------------------------------
+
+%% Mimic catch behaviour
+catch_value(error, Reason) ->
+ {'EXIT',{Reason,get(stacktrace)}};
+catch_value(exit, Reason) ->
+ {'EXIT',Reason};
+catch_value(throw, Reason) ->
+ Reason.
+
+%%--Code interpretation-----------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Top level function of meta evaluator.
+%% Return message to be replied to the target process.
+%%--------------------------------------------------------------------
+eval_mfa(Debugged, M, F, As, Ieval) ->
+ Int = get(int),
+ Bs = erl_eval:new_bindings(),
+ try eval_function(M,F,As,Bs,extern,Ieval#ieval{last_call=true}) of
+ {value, Val, _Bs} ->
+ {ready, Val}
+ catch
+ exit:{Debugged, Reason} ->
+ exit(Reason);
+ exit:{Int, Reason} ->
+ exit(Reason);
+ Class:Reason ->
+ {exception, {Class, Reason, get(stacktrace)}}
+ end.
+
+eval_function(Mod, Fun, As0, Bs0, _Called, Ieval) when is_function(Fun);
+ Mod==?MODULE,
+ Fun==eval_fun ->
+ #ieval{level=Le, line=Li, last_call=Lc} = Ieval,
+ case lambda(Fun, As0) of
+ {Cs,Module,Name,As,Bs} ->
+ push({Module,Name,As}, Bs0, Ieval),
+ trace(call_fun, {Le,Li,Name,As}),
+ {value, Val, _Bs} =
+ fnk_clauses(Cs, Module, Name, As, Bs,
+ Ieval#ieval{level=Le+1}),
+ pop(),
+ trace(return, {Le,Val}),
+ {value, Val, Bs0};
+
+ not_interpreted when Lc -> % We are leaving interpreted code
+ trace(call_fun, {Le,Li,Fun,As0}),
+ {value, {dbg_apply,erlang,apply,[Fun,As0]}, Bs0};
+ not_interpreted ->
+ push({Fun,As0}, Bs0, Ieval),
+ trace(call_fun, {Le,Li,Fun,As0}),
+ {value, Val, _Bs} =
+ debugged_cmd({apply,erlang,apply,[Fun,As0]},Bs0,
+ Ieval#ieval{level=Le+1}),
+ pop(),
+ trace(return, {Le,Val}),
+ {value, Val, Bs0};
+
+ {error,Reason} ->
+ %% It's ok not to push anything in this case, the error
+ %% reason contains information about the culprit
+ %% ({badarity,{{Mod,Name},As}})
+ exception(error, Reason, Bs0, Ieval)
+ end;
+
+%% Common Test adaptation
+eval_function(ct_line, line, As, Bs, extern, #ieval{level=Le}=Ieval) ->
+ debugged_cmd({apply,ct_line,line,As}, Bs, Ieval#ieval{level=Le+1}),
+ {value, ignore, Bs};
+
+eval_function(Mod, Name, As0, Bs0, Called, Ieval) ->
+ #ieval{level=Le, line=Li, last_call=Lc} = Ieval,
+
+ push({Mod,Name,As0}, Bs0, Ieval),
+ trace(call, {Called, {Le,Li,Mod,Name,As0}}),
+
+ case get_function(Mod, Name, As0, Called) of
+ Cs when is_list(Cs) ->
+ {value, Val, _Bs} =
+ fnk_clauses(Cs, Mod, Name, As0, erl_eval:new_bindings(),
+ Ieval#ieval{level=Le+1}),
+ pop(),
+ trace(return, {Le,Val}),
+ {value, Val, Bs0};
+
+ not_interpreted when Lc -> % We are leaving interpreted code
+ {value, {dbg_apply,Mod,Name,As0}, Bs0};
+ not_interpreted ->
+ {value, Val, _Bs} =
+ debugged_cmd({apply,Mod,Name,As0}, Bs0,
+ Ieval#ieval{level=Le+1}),
+ pop(),
+ trace(return, {Le,Val}),
+ {value, Val, Bs0};
+
+ undef ->
+ exception(error, undef, Bs0, Ieval)
+ end.
+
+lambda(eval_fun, [Cs,As,Bs,{Mod,Name}=F]) ->
+ %% Fun defined in interpreted code, called from outside
+ if
+ length(element(3,hd(Cs))) == length(As) ->
+ db_ref(Mod), %% Adds ref between module and process
+ {Cs,Mod,Name,As,Bs};
+ true ->
+ {error,{badarity,{F,As}}}
+ end;
+lambda(Fun, As) when is_function(Fun) ->
+ %% Fun called from within interpreted code...
+ case erlang:fun_info(Fun, module) of
+
+ %% ... and the fun was defined in interpreted code
+ {module, ?MODULE} ->
+ {env, [{Mod,Name},Bs,Cs]} = erlang:fun_info(Fun, env),
+ {arity, Arity} = erlang:fun_info(Fun, arity),
+ if
+ length(As) == Arity ->
+ db_ref(Mod), %% Adds ref between module and process
+ {Cs,Mod,Name,As,Bs};
+ true ->
+ {error,{badarity,{Fun,As}}}
+ end;
+
+ %% ... and the fun was defined outside interpreted code
+ _ ->
+ not_interpreted
+ end.
+
+get_function(Mod, Name, Args, local) ->
+ Arity = length(Args),
+ Key = {Mod,Name,Arity},
+ case cached(Key) of
+ false ->
+ DbRef = db_ref(Mod),
+ case dbg_idb:match_object(DbRef, {{Mod,Name,Arity,'_'},'_'}) of
+ [{{Mod,Name,Arity,Exp},Clauses}] ->
+ cache(Key, {Exp,Clauses}),
+ Clauses;
+ _ -> undef
+ end;
+ {_Exp,Cs} -> Cs
+ end;
+get_function(Mod, Name, Args, extern) ->
+ Arity = length(Args),
+ Key = {Mod,Name,Arity},
+ case cached(Key) of
+ false ->
+ case db_ref(Mod) of
+ not_found -> not_interpreted;
+ DbRef ->
+ case dbg_idb:lookup(DbRef, {Mod,Name,Arity,true}) of
+ {ok,Data} ->
+ cache(Key, {true,Data}),
+ Data;
+ not_found ->
+ case dbg_idb:lookup(DbRef, module) of
+ {ok,_} -> undef;
+ not_found -> not_interpreted
+ end
+ end
+ end;
+ {true,Cs} -> Cs;
+ {false,_} -> undef
+ end.
+
+db_ref(Mod) ->
+ case get([Mod|db]) of
+ undefined ->
+ case dbg_iserver:call(get(int),
+ {get_module_db, Mod, get(self)}) of
+ not_found ->
+ not_found;
+ ModDb ->
+ Node = node(get(int)),
+ DbRef = if
+ Node/=node() -> {Node,ModDb};
+ true -> ModDb
+ end,
+ put([Mod|db], DbRef),
+ DbRef
+ end;
+ DbRef ->
+ DbRef
+ end.
+
+
+cache(Key, Data) ->
+ put(cache, lists:sublist([{Key,Data}|get(cache)], 5)).
+
+cached(Key) ->
+ case lists:keysearch(Key, 1, get(cache)) of
+ {value,{Key,Data}} -> Data;
+ false -> false
+ end.
+
+%% Try to find a matching function clause
+%% #ieval.level is set, the other fields must be set in this function
+fnk_clauses([{clause,Line,Pars,Gs,Body}|Cs], M, F, As, Bs0, Ieval) ->
+ case head_match(Pars, As, [], Bs0) of
+ {match,Bs1} ->
+ Bs = add_bindings(Bs1, Bs0),
+ case guard(Gs, Bs) of
+ true ->
+ seq(Body, Bs,
+ Ieval#ieval{line=Line,
+ module=M,function=F,arguments=As});
+ false ->
+ fnk_clauses(Cs, M, F, As, Bs0, Ieval)
+ end;
+ nomatch ->
+ fnk_clauses(Cs, M, F, As, Bs0, Ieval)
+ end;
+fnk_clauses([], _M, _F, _As, Bs, Ieval) ->
+ exception(error, function_clause, Bs, Ieval).
+
+seq([E], Bs0, Ieval) ->
+ case dbg_icmd:cmd(E, Bs0, Ieval) of
+ {skip,Bs} ->
+ {value,skipped,Bs};
+ Bs ->
+ expr(E, Bs, Ieval)
+ end;
+seq([E|Es], Bs0, Ieval) ->
+ case dbg_icmd:cmd(E, Bs0, Ieval) of
+ {skip,Bs} ->
+ seq(Es, Bs, Ieval);
+ Bs1 ->
+ {value,_,Bs} = expr(E, Bs1, Ieval#ieval{last_call=false}),
+ seq(Es, Bs, Ieval)
+ end;
+seq([], Bs, _) ->
+ {value,true,Bs}.
+
+%% Variable
+expr({var,Line,V}, Bs, Ieval) ->
+ case binding(V, Bs) of
+ {value,Val} ->
+ {value,Val,Bs};
+ unbound ->
+ exception(error, {unbound,V}, Bs, Ieval#ieval{line=Line})
+ end;
+
+expr({value,_,Val}, Bs, _Ieval) ->
+ {value,Val,Bs};
+expr({value,Val}, Bs, _Ieval) -> % Special case straight values
+ {value,Val,Bs};
+
+%% List
+expr({cons,Line,H0,T0}, Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ Ieval1 = Ieval#ieval{last_call=false},
+ {value,H,Bs1} = expr(H0,Bs0,Ieval1),
+ {value,T,Bs2} = expr(T0,Bs0,Ieval1),
+ {value,[H|T],merge_bindings(Bs2, Bs1, Ieval)};
+
+%% Tuple
+expr({tuple,Line,Es0}, Bs0, Ieval) ->
+ {Vs,Bs} = eval_list(Es0, Bs0, Ieval#ieval{line=Line}),
+ {value,list_to_tuple(Vs),Bs};
+
+%% A block of statements
+expr({block,Line,Es},Bs,Ieval) ->
+ seq(Es, Bs, Ieval#ieval{line=Line});
+
+%% Catch statement
+expr({'catch',Line,Expr}, Bs0, Ieval) ->
+ try expr(Expr, Bs0, Ieval#ieval{line=Line, last_call=false})
+ catch
+ Class:Reason ->
+ %% Exception caught, reset exit info
+ put(exit_info, undefined),
+ pop(Ieval#ieval.level),
+ Value = catch_value(Class, Reason),
+ trace(return, {Ieval#ieval.level,Value}),
+ {value, Value, Bs0}
+ end;
+
+%% Try-catch statement
+expr({'try',Line,Es,CaseCs,CatchCs,[]}, Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ try seq(Es, Bs0, Ieval#ieval{last_call=false}) of
+ {value,Val,Bs} = Value ->
+ case CaseCs of
+ [] -> Value;
+ _ ->
+ case_clauses(Val, CaseCs, Bs, try_clause, Ieval)
+ end
+ catch
+ Class:Reason when CatchCs=/=[] ->
+ catch_clauses({Class,Reason,[]}, CatchCs, Bs0, Ieval)
+ end;
+expr({'try',Line,Es,CaseCs,CatchCs,As}, Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ try seq(Es, Bs0, Ieval#ieval{last_call=false}) of
+ {value,Val,Bs} = Value ->
+ case CaseCs of
+ [] -> Value;
+ _ ->
+ case_clauses(Val, CaseCs, Bs, try_clause, Ieval)
+ end
+ catch
+ Class:Reason when CatchCs =/= [] ->
+ catch_clauses({Class,Reason,[]}, CatchCs, Bs0, Ieval)
+ after
+ seq(As, Bs0, Ieval#ieval{last_call=false})
+ end;
+
+%% Case statement
+expr({'case',Line,E,Cs}, Bs0, Ieval) ->
+ {value,Val,Bs} =
+ expr(E, Bs0, Ieval#ieval{line=Line, last_call=false}),
+ case_clauses(Val, Cs, Bs, case_clause, Ieval#ieval{line=Line});
+
+%% If statement
+expr({'if',Line,Cs}, Bs, Ieval) ->
+ if_clauses(Cs, Bs, Ieval#ieval{line=Line});
+
+%% Andalso/orelse
+expr({'andalso',Line,E1,E2}, Bs, Ieval) ->
+ case expr(E1, Bs, Ieval#ieval{line=Line, last_call=false}) of
+ {value,false,_}=Res ->
+ Res;
+ {value,true,_} ->
+ expr(E2, Bs, Ieval#ieval{line=Line, last_call=false});
+ {value,Val,Bs} ->
+ exception(error, {badarg,Val}, Bs, Ieval)
+ end;
+expr({'orelse',Line,E1,E2}, Bs, Ieval) ->
+ case expr(E1, Bs, Ieval#ieval{line=Line, last_call=false}) of
+ {value,true,_}=Res ->
+ Res;
+ {value,false,_} ->
+ expr(E2, Bs, Ieval#ieval{line=Line, last_call=false});
+ {value,Val,_} ->
+ exception(error, {badarg,Val}, Bs, Ieval)
+ end;
+
+%% Matching expression
+expr({match,Line,Lhs,Rhs0}, Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {value,Rhs,Bs1} = expr(Rhs0, Bs0, Ieval#ieval{last_call=false}),
+ case match(Lhs, Rhs, Bs1) of
+ {match,Bs} ->
+ {value,Rhs,Bs};
+ nomatch ->
+ exception(error, {badmatch,Rhs}, Bs1, Ieval)
+ end;
+
+%% Construct a fun
+expr({make_fun,Line,Name,Cs}, Bs, #ieval{module=Module}=Ieval) ->
+ Arity = length(element(3,hd(Cs))),
+ Info = {Module,Name},
+ Fun =
+ case Arity of
+ 0 -> fun() -> eval_fun(Cs, [], Bs, Info) end;
+ 1 -> fun(A) -> eval_fun(Cs, [A], Bs,Info) end;
+ 2 -> fun(A,B) -> eval_fun(Cs, [A,B], Bs,Info) end;
+ 3 -> fun(A,B,C) -> eval_fun(Cs, [A,B,C], Bs,Info) end;
+ 4 -> fun(A,B,C,D) -> eval_fun(Cs, [A,B,C,D], Bs,Info) end;
+ 5 -> fun(A,B,C,D,E) -> eval_fun(Cs, [A,B,C,D,E], Bs,Info) end;
+ 6 -> fun(A,B,C,D,E,F) -> eval_fun(Cs, [A,B,C,D,E,F], Bs,Info) end;
+ 7 -> fun(A,B,C,D,E,F,G) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G], Bs,Info) end;
+ 8 -> fun(A,B,C,D,E,F,G,H) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H], Bs,Info) end;
+ 9 -> fun(A,B,C,D,E,F,G,H,I) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I], Bs,Info) end;
+ 10 -> fun(A,B,C,D,E,F,G,H,I,J) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J], Bs,Info) end;
+ 11 -> fun(A,B,C,D,E,F,G,H,I,J,K) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K], Bs,Info) end;
+ 12 -> fun(A,B,C,D,E,F,G,H,I,J,K,L) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L], Bs,Info) end;
+ 13 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M], Bs,Info) end;
+ 14 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N], Bs,Info) end;
+ 15 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O], Bs,Info) end;
+ 16 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P], Bs,Info) end;
+ 17 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q], Bs,Info) end;
+ 18 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R], Bs,Info) end;
+ 19 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S],Bs,Info) end;
+ 20 -> fun(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T) ->
+ eval_fun(Cs, [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T],Bs,Info) end;
+ _Other ->
+ exception(error, {'argument_limit',{'fun',Cs}}, Bs,
+ Ieval#ieval{line=Line})
+ end,
+ {value,Fun,Bs};
+
+%% Common test adaptation
+expr({call_remote,0,ct_line,line,As0}, Bs0, Ieval0) ->
+ {As,_Bs} = eval_list(As0, Bs0, Ieval0),
+ eval_function(ct_line, line, As, Bs0, extern, Ieval0);
+
+%% Local function call
+expr({local_call,Line,F,As0}, Bs0, #ieval{module=M} = Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {As,Bs} = eval_list(As0, Bs0, Ieval),
+ eval_function(M, F, As, Bs, local, Ieval);
+
+%% Remote function call
+expr({call_remote,Line,M,F,As0}, Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {As,Bs} = eval_list(As0, Bs0, Ieval),
+ eval_function(M, F, As, Bs, extern, Ieval);
+
+%% Emulated semantics of some BIFs
+expr({dbg,Line,self,[]}, Bs, #ieval{level=Le}) ->
+ trace(bif, {Le,Line,erlang,self,[]}),
+ Self = get(self),
+ trace(return, {Le,Self}),
+ {value,Self,Bs};
+expr({dbg,Line,get_stacktrace,[]}, Bs, #ieval{level=Le}) ->
+ trace(bif, {Le,Line,erlang,get_stacktrace,[]}),
+ Stacktrace = get(stacktrace),
+ trace(return, {Le,Stacktrace}),
+ {value,Stacktrace,Bs};
+expr({dbg,Line,throw,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {[Term],Bs} = eval_list(As0, Bs0, Ieval),
+ trace(bif, {Le,Line,erlang,throw,[Term]}),
+ exception(throw, Term, Bs, Ieval);
+expr({dbg,Line,error,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {[Term],Bs} = eval_list(As0, Bs0, Ieval),
+ trace(bif, {Le,Line,erlang,error,[Term]}),
+ exception(error, Term, Bs, Ieval);
+expr({dbg,Line,fault,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {[Term],Bs} = eval_list(As0, Bs0, Ieval),
+ trace(bif, {Le,Line,erlang,fault,[Term]}),
+ exception(fault, Term, Bs, Ieval);
+expr({dbg,Line,exit,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {[Term],Bs} = eval_list(As0, Bs0, Ieval),
+ trace(bif, {Le,Line,erlang,exit,[Term]}),
+ exception(exit, Term, Bs, Ieval);
+
+%% Call to "safe" BIF, ie a BIF that can be executed in Meta process
+expr({safe_bif,Line,M,F,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {As,Bs} = eval_list(As0, Bs0, Ieval),
+ trace(bif, {Le,Line,M,F,As}),
+ push({M,F,As}, Bs0, Ieval),
+ {_,Value,_} = Res = safe_bif(M, F, As, Bs, Ieval),
+ trace(return, {Le,Value}),
+ pop(),
+ Res;
+
+%% Call to a BIF that must be evaluated in the correct process
+expr({bif,Line,M,F,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {As,Bs} = eval_list(As0, Bs0, Ieval),
+ trace(bif, {Le,Line,M,F,As}),
+ push({M,F,As}, Bs0, Ieval),
+ {_,Value,_} =
+ Res = debugged_cmd({apply,M,F,As}, Bs, Ieval#ieval{level=Le+1}),
+ trace(return, {Le,Value}),
+ pop(),
+ Res;
+
+%% Call to a BIF that spawns a new process
+expr({spawn_bif,Line,M,F,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {As,Bs} = eval_list(As0, Bs0, Ieval),
+ trace(bif, {Le,Line,M,F,As}),
+ push({M,F,As}, Bs0, Ieval),
+ Res = debugged_cmd({apply,M,F,As}, Bs,Ieval#ieval{level=Le+1}),
+ trace(return, {Le,Res}),
+ pop(),
+ Res;
+
+%% Call to an operation
+expr({op,Line,Op,As0}, Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {As,Bs} = eval_list(As0, Bs0, Ieval),
+ try apply(erlang,Op,As) of
+ Value ->
+ {value,Value,Bs}
+ catch
+ Class:Reason ->
+ exception(Class, Reason, Bs, Ieval)
+ end;
+
+%% apply/2 (fun)
+expr({apply_fun,Line,Fun0,As0}, Bs0, #ieval{level=Le}=Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ FunValue = case expr(Fun0, Bs0, Ieval) of
+ {value,{dbg_apply,Mx,Fx,Asx},Bsx} ->
+ debugged_cmd({apply,Mx,Fx,Asx},
+ Bsx, Ieval#ieval{level=Le+1});
+ OtherFunValue ->
+ OtherFunValue
+ end,
+ case FunValue of
+ {value,Fun,Bs1} when is_function(Fun) ->
+ {As,Bs} = eval_list(As0, Bs1, Ieval),
+ eval_function(undefined, Fun, As, Bs, extern, Ieval);
+ {value,{M,F},Bs1} when is_atom(M), is_atom(F) ->
+ {As,Bs} = eval_list(As0, Bs1, Ieval),
+ eval_function(M, F, As, Bs, extern, Ieval);
+ {value,BadFun,Bs1} ->
+ exception(error, {badfun,BadFun}, Bs1, Ieval)
+ end;
+
+%% apply/3
+expr({apply,Line,As0}, Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {[M,F,As],Bs} = eval_list(As0, Bs0, Ieval),
+ eval_function(M, F, As, Bs, extern, Ieval);
+
+%% Mod:module_info/0,1
+expr({module_info_0,_,Mod}, Bs, _Ieval) ->
+ {value,[{compile,module_info(Mod,compile)},
+ {attributes,module_info(Mod,attributes)},
+ {imports,module_info(Mod,imports)},
+ {exports,module_info(Mod,exports)}],Bs};
+expr({module_info_1,Line,Mod,[As0]}, Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {value,What,Bs} = expr(As0, Bs0, Ieval),
+ {value,module_info(Mod, What),Bs};
+
+%% Receive statement
+expr({'receive',Line,Cs}, Bs0, #ieval{level=Le}=Ieval) ->
+ trace(receivex, {Le,false}),
+ eval_receive(get(self), Cs, Bs0, Ieval#ieval{line=Line});
+
+%% Receive..after statement
+expr({'receive',Line,Cs,To,ToExprs}, Bs0, #ieval{level=Le}=Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {value,ToVal,ToBs} = expr(To, Bs0, Ieval#ieval{last_call=false}),
+ trace(receivex, {Le,true}),
+ check_timeoutvalue(ToVal, ToBs, To, Ieval),
+ {Stamp,_} = statistics(wall_clock),
+ eval_receive(get(self), Cs, ToVal, ToExprs, ToBs, Bs0,
+ 0, Stamp, Ieval);
+
+%% Send (!)
+expr({send,Line,To0,Msg0}, Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ Ieval1 = Ieval#ieval{last_call=false},
+ {value,To,Bs1} = expr(To0, Bs0, Ieval1),
+ {value,Msg,Bs2} = expr(Msg0, Bs0, Ieval1),
+ Bs = merge_bindings(Bs2, Bs1, Ieval),
+ eval_send(To, Msg, Bs, Ieval);
+
+%% Binary
+expr({bin,Line,Fs}, Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ eval_bits:expr_grp(Fs, Bs0,
+ fun (E, B) -> expr(E, B, Ieval) end,
+ [],
+ false);
+
+%% List comprehension
+expr({lc,_Line,E,Qs}, Bs, Ieval) ->
+ eval_lc(E, Qs, Bs, Ieval);
+expr({bc,_Line,E,Qs}, Bs, Ieval) ->
+ eval_bc(E, Qs, Bs, Ieval);
+
+%% Brutal exit on unknown expressions/clauses/values/etc.
+expr(E, _Bs, _Ieval) ->
+ erlang:error({'NYI',E}).
+
+%% Interpreted fun() called from uninterpreted module, recurse
+eval_fun(Cs, As, Bs, Info) ->
+ dbg_debugged:eval(?MODULE, eval_fun, [Cs,As,Bs,Info]).
+
+%% eval_lc(Expr,[Qualifier],Bindings,IevalState) ->
+%% {value,Value,Bindings}.
+%% This is evaluating list comprehensions "straight out of the book".
+%% Copied from rv's implementation in erl_eval.
+eval_lc(E, Qs, Bs, Ieval) ->
+ {value,eval_lc1(E, Qs, Bs, Ieval),Bs}.
+
+eval_lc1(E, [{generate,Line,P,L0}|Qs], Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {value,L1,Bs1} = expr(L0, Bs0, Ieval#ieval{last_call=false}),
+ flatmap(fun (V) ->
+ case catch match1(P, V, [], Bs0) of
+ {match,Bsn} ->
+ Bs2 = add_bindings(Bsn, Bs1),
+ eval_lc1(E, Qs, Bs2, Ieval);
+ nomatch -> []
+ end end,L1);
+eval_lc1(E, [{b_generate,Line,P,L0}|Qs], Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {value,Bin,_} = expr(L0, Bs0, Ieval#ieval{last_call=false}),
+ CompFun = fun(NewBs) -> eval_lc1(E, Qs, NewBs, Ieval) end,
+ eval_b_generate(Bin, P, Bs0, CompFun);
+eval_lc1(E, [{guard,Q}|Qs], Bs0, Ieval) ->
+ case guard(Q, Bs0) of
+ true -> eval_lc1(E, Qs, Bs0, Ieval);
+ false -> []
+ end;
+eval_lc1(E, [Q|Qs], Bs0, Ieval) ->
+ case expr(Q, Bs0, Ieval#ieval{last_call=false}) of
+ {value,true,Bs} -> eval_lc1(E, Qs, Bs, Ieval);
+ _ -> []
+ end;
+eval_lc1(E, [], Bs, Ieval) ->
+ {value,V,_} = expr(E, Bs, Ieval#ieval{last_call=false}),
+ [V].
+
+%% eval_bc(Expr,[Qualifier],Bindings,IevalState) ->
+%% {value,Value,Bindings}.
+%% This is evaluating list comprehensions "straight out of the book".
+%% Copied from rv's implementation in erl_eval.
+eval_bc(E, Qs, Bs, Ieval) ->
+ Val = erlang:list_to_bitstring(eval_bc1(E, Qs, Bs, Ieval)),
+ {value,Val,Bs}.
+
+eval_bc1(E, [{generate,Line,P,L0}|Qs], Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {value,L1,Bs1} = expr(L0, Bs0, Ieval#ieval{last_call=false}),
+ flatmap(fun (V) ->
+ case catch match1(P, V, [], Bs0) of
+ {match,Bsn} ->
+ Bs2 = add_bindings(Bsn, Bs1),
+ eval_bc1(E, Qs, Bs2, Ieval);
+ nomatch -> []
+ end end, L1);
+eval_bc1(E, [{b_generate,Line,P,L0}|Qs], Bs0, Ieval0) ->
+ Ieval = Ieval0#ieval{line=Line},
+ {value,Bin,_} = expr(L0, Bs0, Ieval#ieval{last_call=false}),
+ CompFun = fun(NewBs) -> eval_bc1(E, Qs, NewBs, Ieval) end,
+ eval_b_generate(Bin, P, Bs0, CompFun);
+eval_bc1(E, [{guard,Q}|Qs], Bs0, Ieval) ->
+ case guard(Q, Bs0) of
+ true -> eval_bc1(E, Qs, Bs0, Ieval);
+ false -> []
+ end;
+eval_bc1(E, [Q|Qs], Bs0, Ieval) ->
+ case expr(Q, Bs0, Ieval#ieval{last_call=false}) of
+ {value,true,Bs} -> eval_bc1(E, Qs, Bs, Ieval);
+ _ -> []
+ end;
+eval_bc1(E, [], Bs, Ieval) ->
+ {value,V,_} = expr(E, Bs, Ieval#ieval{last_call=false}),
+ [V].
+
+eval_b_generate(<<_/bitstring>>=Bin, P, Bs0, CompFun) ->
+ Mfun = fun(L, R, Bs) -> match1(L, R, Bs, Bs0) end,
+ Efun = fun(Exp, Bs) -> expr(Exp, Bs, #ieval{}) end,
+ case eval_bits:bin_gen(P, Bin, erl_eval:new_bindings(), Bs0, Mfun, Efun) of
+ {match,Rest,Bs1} ->
+ Bs2 = add_bindings(Bs1, Bs0),
+ CompFun(Bs2) ++ eval_b_generate(Rest, P, Bs0, CompFun);
+ {nomatch,Rest} ->
+ eval_b_generate(Rest, P, Bs0, CompFun);
+ done ->
+ []
+ end.
+
+module_info(Mod, module) -> Mod;
+module_info(_Mod, compile) -> [];
+module_info(Mod, attributes) ->
+ {ok, Attr} = dbg_iserver:call(get(int), {lookup, Mod, attributes}),
+ Attr;
+module_info(_Mod, imports) -> [];
+module_info(Mod, exports) ->
+ {ok, Exp} = dbg_iserver:call(get(int), {lookup, Mod, exports}),
+ Exp;
+module_info(_Mod, functions) -> [].
+
+safe_bif(M, F, As, Bs, Ieval) ->
+ try apply(M, F, As) of
+ Value ->
+ {value,Value,Bs}
+ catch
+ Class:Reason ->
+ exception(Class, Reason, Bs, Ieval)
+ end.
+
+eval_send(To, Msg, Bs, Ieval) ->
+ try To ! Msg of
+ Msg ->
+ trace(send, {To,Msg}),
+ {value,Msg,Bs}
+ catch
+ Class:Reason ->
+ exception(Class, Reason, Bs, Ieval)
+ end.
+
+%% Start tracing of messages before fetching current messages in
+%% the queue to make sure that no messages are lost.
+eval_receive(Debugged, Cs, Bs0,
+ #ieval{module=M,line=Line,level=Le}=Ieval) ->
+ %% To avoid private message passing protocol between META
+ %% and interpreted process.
+ erlang:trace(Debugged,true,['receive']),
+ {_,Msgs} = erlang:process_info(Debugged,messages),
+ case receive_clauses(Cs, Bs0, Msgs) of
+ nomatch ->
+ dbg_iserver:cast(get(int), {set_status, self(),waiting,{}}),
+ dbg_icmd:tell_attached({wait_at,M,Line,Le}),
+ eval_receive1(Debugged, Cs, Bs0, Ieval);
+ {eval,B,Bs,Msg} ->
+ rec_mess(Debugged, Msg, Bs, Ieval),
+ seq(B, Bs, Ieval)
+ end.
+
+eval_receive1(Debugged, Cs, Bs0, Ieval) ->
+ Msgs = do_receive(Debugged, Bs0, Ieval),
+ case receive_clauses(Cs, Bs0, Msgs) of
+ nomatch ->
+ eval_receive1(Debugged, Cs, Bs0, Ieval);
+ {eval,B,Bs,Msg} ->
+ rec_mess(Debugged, Msg, Bs0, Ieval),
+ dbg_iserver:cast(get(int), {set_status, self(),running,{}}),
+ dbg_icmd:tell_attached(running),
+ seq(B, Bs, Ieval)
+ end.
+
+check_timeoutvalue(ToVal,_,_,_) when is_integer(ToVal), ToVal>=0 -> true;
+check_timeoutvalue(infinity,_,_,_) -> true;
+check_timeoutvalue(_ToVal, ToBs, To, Ieval) ->
+ Line = element(2, To),
+ exception(error, timeout_value, ToBs, Ieval#ieval{line=Line}).
+
+eval_receive(Debugged, Cs, 0, ToExprs, ToBs, Bs0, 0, _Stamp, Ieval) ->
+ {_,Msgs} = erlang:process_info(Debugged,messages),
+ case receive_clauses(Cs, Bs0, Msgs) of
+ nomatch ->
+ trace(received,null),
+ seq(ToExprs, ToBs, Ieval);
+ {eval,B,Bs,Msg} ->
+ rec_mess_no_trace(Debugged, Msg, Bs0, Ieval),
+ seq(B, Bs, Ieval)
+ end;
+eval_receive(Debugged, Cs, ToVal, ToExprs, ToBs, Bs0,
+ 0, Stamp, #ieval{module=M,line=Line,level=Le}=Ieval)->
+ erlang:trace(Debugged,true,['receive']),
+ {_,Msgs} = erlang:process_info(Debugged,messages),
+ case receive_clauses(Cs, Bs0, Msgs) of
+ nomatch ->
+ {Stamp1,Time1} = newtime(Stamp,ToVal),
+ dbg_iserver:cast(get(int), {set_status, self(),waiting,{}}),
+ dbg_icmd:tell_attached({wait_after_at,M,Line,Le}),
+ eval_receive(Debugged, Cs, Time1, ToExprs, ToBs, Bs0,
+ infinity,Stamp1, Ieval);
+ {eval,B,Bs,Msg} ->
+ rec_mess(Debugged, Msg, Bs0, Ieval),
+ seq(B, Bs, Ieval)
+ end;
+eval_receive(Debugged, Cs, ToVal, ToExprs, ToBs, Bs0,
+ _, Stamp, Ieval) ->
+ case do_receive(Debugged, ToVal, Stamp, Bs0, Ieval) of
+ timeout ->
+ trace(received,null),
+ rec_mess(Debugged),
+ dbg_iserver:cast(get(int), {set_status, self(),running,{}}),
+ dbg_icmd:tell_attached(running),
+ seq(ToExprs, ToBs, Ieval);
+ Msgs ->
+ case receive_clauses(Cs, Bs0, Msgs) of
+ nomatch ->
+ {Stamp1,Time1} = newtime(Stamp,ToVal),
+ eval_receive(Debugged, Cs, Time1, ToExprs, ToBs,
+ Bs0, infinity,Stamp1, Ieval);
+ {eval,B,Bs,Msg} ->
+ rec_mess(Debugged, Msg, Bs0, Ieval),
+ dbg_iserver:cast(get(int),
+ {set_status, self(), running, {}}),
+ dbg_icmd:tell_attached(running),
+ seq(B, Bs, Ieval)
+ end
+ end.
+
+do_receive(Debugged, Bs, Ieval) ->
+ receive
+ {trace,Debugged,'receive',Msg} ->
+ [Msg];
+ Msg ->
+ check_exit_msg(Msg, Bs, Ieval),
+ dbg_icmd:handle_msg(Msg, wait_at, Bs, Ieval),
+ do_receive(Debugged, Bs, Ieval)
+ end.
+
+do_receive(Debugged, Time, Stamp, Bs, Ieval) ->
+ receive
+ {trace,Debugged,'receive',Msg} ->
+ [Msg];
+ {user, timeout} ->
+ timeout;
+ Msg ->
+ check_exit_msg(Msg, Bs, Ieval),
+ dbg_icmd:handle_msg(Msg, wait_after_at, Bs, Ieval),
+ {Stamp1,Time1} = newtime(Stamp,Time),
+ do_receive(Debugged, Time1, Stamp1, Bs, Ieval)
+ after Time ->
+ timeout
+ end.
+
+newtime(Stamp,infinity) ->
+ {Stamp,infinity};
+newtime(Stamp,Time) ->
+ {Stamp1,_} = statistics(wall_clock),
+ case Time - (Stamp1 - Stamp) of
+ NewTime when NewTime > 0 ->
+ {Stamp1,NewTime};
+ _ ->
+ {Stamp1,0}
+ end.
+
+rec_mess(Debugged, Msg, Bs, Ieval) ->
+ erlang:trace(Debugged, false, ['receive']),
+ flush_traces(Debugged),
+ Debugged ! {sys,self(),{'receive',Msg}},
+ rec_ack(Debugged, Bs, Ieval).
+
+rec_mess(Debugged) ->
+ erlang:trace(Debugged, false, ['receive']),
+ flush_traces(Debugged).
+
+rec_mess_no_trace(Debugged, Msg, Bs, Ieval) ->
+ Debugged ! {sys,self(),{'receive',Msg}},
+ rec_ack(Debugged, Bs, Ieval).
+
+rec_ack(Debugged, Bs, Ieval) ->
+ receive
+ {Debugged,rec_acked} ->
+ true;
+ Msg ->
+ check_exit_msg(Msg, Bs, Ieval),
+ io:format("***WARNING*** Unexp msg ~p, ieval ~p~n",
+ [Msg, Ieval])
+ end.
+
+flush_traces(Debugged) ->
+ receive
+ {trace,Debugged,'receive',_} ->
+ flush_traces(Debugged)
+ after 0 ->
+ true
+ end.
+
+%% eval_list(ExpressionList, Bindings, Ieval)
+%% Evaluate a list of expressions "in parallel" at the same level.
+eval_list(Es, Bs, Ieval) ->
+ eval_list(Es, [], Bs, Bs, Ieval).
+
+eval_list([E|Es], Vs, BsOrig, Bs0, Ieval) ->
+ {value,V,Bs1} = expr(E, BsOrig, Ieval#ieval{last_call=false}),
+ eval_list(Es, [V|Vs], BsOrig, merge_bindings(Bs1,Bs0,Ieval), Ieval);
+eval_list([], Vs, _, Bs, _Ieval) ->
+ {lists:reverse(Vs,[]),Bs}.
+
+%% if_clauses(Clauses, Bindings, Ieval)
+if_clauses([{clause,_,[],G,B}|Cs], Bs, Ieval) ->
+ case guard(G, Bs) of
+ true ->
+ seq(B, Bs, Ieval);
+ false ->
+ if_clauses(Cs, Bs, Ieval)
+ end;
+if_clauses([], Bs, Ieval) ->
+ exception(error, if_clause, Bs, Ieval).
+
+%% case_clauses(Value, Clauses, Bindings, Error, Ieval)
+%% Error = try_clause � case_clause
+case_clauses(Val, [{clause,_,[P],G,B}|Cs], Bs0, Error, Ieval) ->
+ case match(P, Val, Bs0) of
+ {match,Bs} ->
+ case guard(G, Bs) of
+ true ->
+ seq(B, Bs, Ieval);
+ false ->
+ case_clauses(Val, Cs, Bs0, Error, Ieval)
+ end;
+ nomatch ->
+ case_clauses(Val, Cs, Bs0, Error, Ieval)
+ end;
+case_clauses(Val,[], Bs, Error, Ieval) ->
+ exception(error, {Error,Val}, Bs, Ieval).
+
+%% catch_clauses(Exception, Clauses, Bindings, Ieval)
+%% Exception = {Class,Reason,[]}
+catch_clauses(Exception, [{clause,_,[P],G,B}|CatchCs], Bs0, Ieval) ->
+ case match(P, Exception, Bs0) of
+ {match,Bs} ->
+ case guard(G, Bs) of
+ true ->
+ %% Exception caught, reset exit info
+ put(exit_info, undefined),
+ pop(Ieval#ieval.level),
+ seq(B, Bs, Ieval);
+ false ->
+ catch_clauses(Exception, CatchCs, Bs0, Ieval)
+ end;
+ nomatch ->
+ catch_clauses(Exception, CatchCs, Bs0, Ieval)
+ end;
+catch_clauses({Class,Reason,[]}, [], _Bs, _Ieval) ->
+ erlang:Class(Reason).
+
+receive_clauses(Cs, Bs0, [Msg|Msgs]) ->
+ case rec_clauses(Cs, Bs0, Msg) of
+ nomatch ->
+ receive_clauses(Cs, Bs0, Msgs);
+ {eval,B,Bs} ->
+ {eval,B,Bs,Msg}
+ end;
+receive_clauses(_, _, []) ->
+ nomatch.
+
+rec_clauses([{clause,_,Pars,G,B}|Cs], Bs0, Msg) ->
+ case rec_match(Pars, Msg, Bs0) of
+ {match,Bs} ->
+ case guard(G, Bs) of
+ true ->
+ trace(received, Msg),
+ {eval,B,Bs};
+ false ->
+ rec_clauses(Cs, Bs0, Msg)
+ end;
+ nomatch ->
+ rec_clauses(Cs, Bs0, Msg)
+ end;
+rec_clauses([], _, _) ->
+ nomatch.
+
+%% guard(GuardTests,Bindings)
+%% Evaluate a list of guards.
+guard([], _) -> true;
+guard(Gs, Bs) -> or_guard(Gs, Bs).
+
+or_guard([G|Gs], Bs) ->
+ %% Short-circuit OR.
+ case and_guard(G, Bs) of
+ true -> true;
+ false -> or_guard(Gs, Bs)
+ end;
+or_guard([], _) -> false.
+
+and_guard([G|Gs], Bs) ->
+ %% Short-circuit AND.
+ case catch guard_expr(G, Bs) of
+ {value,true} -> and_guard(Gs, Bs);
+ _ -> false
+ end;
+and_guard([],_) -> true.
+
+guard_exprs([A0|As0], Bs) ->
+ {value,A} = guard_expr(A0, Bs),
+ {values,As} = guard_exprs(As0, Bs),
+ {values,[A|As]};
+guard_exprs([], _) ->
+ {values,[]}.
+
+guard_expr({'andalso',_,E1,E2}, Bs) ->
+ case guard_expr(E1, Bs) of
+ {value,false}=Res -> Res;
+ {value,true} ->
+ case guard_expr(E2, Bs) of
+ {value,Bool}=Res when is_boolean(Bool) -> Res
+ end
+ end;
+guard_expr({'orelse',_,E1,E2}, Bs) ->
+ case guard_expr(E1, Bs) of
+ {value,true}=Res -> Res;
+ {value,false} ->
+ case guard_expr(E2, Bs) of
+ {value,Bool}=Res when is_boolean(Bool) -> Res
+ end
+ end;
+guard_expr({dbg,_,self,[]}, _) ->
+ {value,get(self)};
+guard_expr({safe_bif,_,erlang,'not',As0}, Bs) ->
+ {values,As} = guard_exprs(As0, Bs),
+ {value,apply(erlang, 'not', As)};
+guard_expr({safe_bif,_,Mod,Func,As0}, Bs) ->
+ {values,As} = guard_exprs(As0, Bs),
+ {value,apply(Mod, Func, As)};
+guard_expr({var,_,V}, Bs) ->
+ {value,_} = binding(V, Bs);
+guard_expr({value,_,Val}, _Bs) ->
+ {value,Val};
+guard_expr({cons,_,H0,T0}, Bs) ->
+ {value,H} = guard_expr(H0, Bs),
+ {value,T} = guard_expr(T0, Bs),
+ {value,[H|T]};
+guard_expr({tuple,_,Es0}, Bs) ->
+ {values,Es} = guard_exprs(Es0, Bs),
+ {value,list_to_tuple(Es)};
+guard_expr({bin,_,Flds}, Bs) ->
+ {value,V,_Bs} =
+ eval_bits:expr_grp(Flds, Bs,
+ fun(E,B) ->
+ {value,V} = guard_expr(E,B),
+ {value,V,B}
+ end, [], false),
+ {value,V}.
+
+%% match(Pattern,Term,Bs) -> {match,Bs} | nomatch
+match(Pat, Term, Bs) ->
+ try match1(Pat, Term, Bs, Bs)
+ catch
+ Result -> Result
+ end.
+
+match1({value,_,V}, V, Bs,_BBs) ->
+ {match,Bs};
+match1({var,_,'_'}, Term, Bs,_BBs) -> % Anonymous variable matches
+ {match,add_anon(Term, Bs)}; % everything,save it anyway
+match1({var,_,Name}, Term, Bs, _BBs) ->
+ case binding(Name, Bs) of
+ {value,Term} ->
+ {match,Bs};
+ {value,_} ->
+ throw(nomatch);
+ unbound ->
+ {match,[{Name,Term}|Bs]} % Add the new binding
+ end;
+match1({match,_,Pat1,Pat2}, Term, Bs0, BBs) ->
+ {match,Bs1} = match1(Pat1, Term, Bs0, BBs),
+ match1(Pat2, Term, Bs1, BBs);
+match1({cons,_,H,T}, [H1|T1], Bs0, BBs) ->
+ {match,Bs} = match1(H, H1, Bs0, BBs),
+ match1(T, T1, Bs, BBs);
+match1({tuple,_,Elts}, Tuple, Bs, BBs)
+ when length(Elts) =:= tuple_size(Tuple) ->
+ match_tuple(Elts, Tuple, 1, Bs, BBs);
+match1({bin,_,Fs}, B, Bs0, BBs0) when is_bitstring(B) ->
+ Bs1 = lists:sort(Bs0), %Kludge.
+ BBs = lists:sort(BBs0),
+ try eval_bits:match_bits(Fs, B, Bs1, BBs,
+ fun(L, R, Bs) -> match1(L, R, Bs, BBs) end,
+ fun(E, Bs) -> expr(E, Bs, #ieval{}) end,
+ false) of
+ Match -> Match
+ catch
+ _:_ -> throw(nomatch)
+ end;
+match1(_,_,_,_) ->
+ throw(nomatch).
+
+match_tuple([E|Es], Tuple, I, Bs0, BBs) ->
+ {match,Bs} = match1(E, element(I, Tuple), Bs0, BBs),
+ match_tuple(Es, Tuple, I+1, Bs, BBs);
+match_tuple([], _, _, Bs, _BBs) ->
+ {match,Bs}.
+
+head_match([Par|Pars], [Arg|Args], Bs0, BBs) ->
+ try match1(Par, Arg, Bs0, BBs) of
+ {match,Bs} -> head_match(Pars, Args, Bs, BBs)
+ catch
+ Result -> Result
+ end;
+head_match([],[],Bs,_) -> {match,Bs}.
+
+rec_match([Par],Msg,Bs0) ->
+ match(Par,Msg,Bs0).
+
+binding(Name,[{Name,Val}|_]) ->
+ {value,Val};
+binding(Name,[_,{Name,Val}|_]) ->
+ {value,Val};
+binding(Name,[_,_,{Name,Val}|_]) ->
+ {value,Val};
+binding(Name,[_,_,_,{Name,Val}|_]) ->
+ {value,Val};
+binding(Name,[_,_,_,_,{Name,Val}|_]) ->
+ {value,Val};
+binding(Name,[_,_,_,_,_,{Name,Val}|_]) ->
+ {value,Val};
+binding(Name,[_,_,_,_,_,_|Bs]) ->
+ binding(Name,Bs);
+binding(Name,[_,_,_,_,_|Bs]) ->
+ binding(Name,Bs);
+binding(Name,[_,_,_,_|Bs]) ->
+ binding(Name,Bs);
+binding(Name,[_,_,_|Bs]) ->
+ binding(Name,Bs);
+binding(Name,[_,_|Bs]) ->
+ binding(Name,Bs);
+binding(Name,[_|Bs]) ->
+ binding(Name,Bs);
+binding(_,[]) ->
+ unbound.
+
+add_anon(Val,[{'_',_}|Bs]) ->
+ [{'_',Val}|Bs];
+add_anon(Val,[B1,{'_',_}|Bs]) ->
+ [B1,{'_',Val}|Bs];
+add_anon(Val,[B1,B2,{'_',_}|Bs]) ->
+ [B1,B2,{'_',Val}|Bs];
+add_anon(Val,[B1,B2,B3,{'_',_}|Bs]) ->
+ [B1,B2,B3,{'_',Val}|Bs];
+add_anon(Val,[B1,B2,B3,B4,{'_',_}|Bs]) ->
+ [B1,B2,B3,B4,{'_',Val}|Bs];
+add_anon(Val,[B1,B2,B3,B4,B5,{'_',_}|Bs]) ->
+ [B1,B2,B3,B4,B5,{'_',Val}|Bs];
+add_anon(Val,[B1,B2,B3,B4,B5,B6|Bs]) ->
+ [B1,B2,B3,B4,B5,B6|add_anon(Val,Bs)];
+add_anon(Val,[B1,B2,B3,B4,B5|Bs]) ->
+ [B1,B2,B3,B4,B5|add_anon(Val,Bs)];
+add_anon(Val,[B1,B2,B3,B4|Bs]) ->
+ [B1,B2,B3,B4|add_anon(Val,Bs)];
+add_anon(Val,[B1,B2,B3|Bs]) ->
+ [B1,B2,B3|add_anon(Val,Bs)];
+add_anon(Val,[B1,B2|Bs]) ->
+ [B1,B2|add_anon(Val,Bs)];
+add_anon(Val,[B1|Bs]) ->
+ [B1|add_anon(Val,Bs)];
+add_anon(Val,[]) ->
+ [{'_',Val}].
+
+%% merge_bindings(Bindings1, Bindings2, Ieval)
+%% Merge bindings detecting bad matches.
+%% Special case '_',save the new one !!!
+%% Bindings1 is the newest bindings.
+merge_bindings(Bs, Bs, _Ieval) ->
+ Bs; % Identical bindings
+merge_bindings([{Name,V}|B1s], B2s, Ieval) ->
+ case binding(Name, B2s) of
+ {value,V} -> % Already there, and the same
+ merge_bindings(B1s, B2s, Ieval);
+ {value,_} when Name=='_' -> % Already there, but anonymous
+ B2s1 = lists:keydelete('_', 1, B2s),
+ [{Name,V}|merge_bindings(B1s, B2s1, Ieval)];
+ {value,_} -> % Already there, but different => badmatch
+ exception(error, {badmatch,V}, B2s, Ieval);
+ unbound -> % Not there,add it
+ [{Name,V}|merge_bindings(B1s, B2s, Ieval)]
+ end;
+merge_bindings([], B2s, _Ieval) ->
+ B2s.
+
+%% add_bindings(Bindings1,Bindings2)
+%% Add Bindings1 to Bindings2. Bindings in
+%% Bindings1 hides bindings in Bindings2.
+%% Used in list comprehensions (and funs).
+add_bindings(Bs1,[]) ->
+ Bs1;
+add_bindings([{Name,V}|Bs],ToBs0) ->
+ ToBs = add_binding(Name,V,ToBs0),
+ add_bindings(Bs,ToBs);
+add_bindings([],ToBs) ->
+ ToBs.
+
+add_binding(N,Val,[{N,_}|Bs]) ->
+ [{N,Val}|Bs];
+add_binding(N,Val,[B1,{N,_}|Bs]) ->
+ [B1,{N,Val}|Bs];
+add_binding(N,Val,[B1,B2,{N,_}|Bs]) ->
+ [B1,B2,{N,Val}|Bs];
+add_binding(N,Val,[B1,B2,B3,{N,_}|Bs]) ->
+ [B1,B2,B3,{N,Val}|Bs];
+add_binding(N,Val,[B1,B2,B3,B4,{N,_}|Bs]) ->
+ [B1,B2,B3,B4,{N,Val}|Bs];
+add_binding(N,Val,[B1,B2,B3,B4,B5,{N,_}|Bs]) ->
+ [B1,B2,B3,B4,B5,{N,Val}|Bs];
+add_binding(N,Val,[B1,B2,B3,B4,B5,B6|Bs]) ->
+ [B1,B2,B3,B4,B5,B6|add_binding(N,Val,Bs)];
+add_binding(N,Val,[B1,B2,B3,B4,B5|Bs]) ->
+ [B1,B2,B3,B4,B5|add_binding(N,Val,Bs)];
+add_binding(N,Val,[B1,B2,B3,B4|Bs]) ->
+ [B1,B2,B3,B4|add_binding(N,Val,Bs)];
+add_binding(N,Val,[B1,B2,B3|Bs]) ->
+ [B1,B2,B3|add_binding(N,Val,Bs)];
+add_binding(N,Val,[B1,B2|Bs]) ->
+ [B1,B2|add_binding(N,Val,Bs)];
+add_binding(N,Val,[B1|Bs]) ->
+ [B1|add_binding(N,Val,Bs)];
+add_binding(N,Val,[]) ->
+ [{N,Val}].
diff --git a/lib/debugger/src/dbg_ieval.hrl b/lib/debugger/src/dbg_ieval.hrl
new file mode 100644
index 0000000000..a344748f48
--- /dev/null
+++ b/lib/debugger/src/dbg_ieval.hrl
@@ -0,0 +1,26 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-record(ieval, {level = 1, % Current call level
+ line = -1, % Current source code line (of module)
+ module, % MFA which called the currently
+ function, % interpreted function
+ arguments, %
+ last_call = false % True if current expression is
+ }). % the VERY last to be evaluated
+ % (ie at all, not only in a clause)
diff --git a/lib/debugger/src/dbg_iload.erl b/lib/debugger/src/dbg_iload.erl
new file mode 100644
index 0000000000..1216338006
--- /dev/null
+++ b/lib/debugger/src/dbg_iload.erl
@@ -0,0 +1,669 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_iload).
+
+%% External exports
+-export([load_mod/4]).
+
+%% Internal exports
+-export([load_mod1/4]).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% load_mod(Mod, File, Binary, Db) -> {ok, Mod}
+%% Mod = atom()
+%% File = string() Source file (including path)
+%% Binary = binary()
+%% Db = ETS identifier
+%% Load a new module into the database.
+%%
+%% We want the loading of a module to be syncronous so no other
+%% process tries to interpret code in a module not being completely
+%% loaded. This is achieved as this function is called from
+%% dbg_iserver. We are suspended until the module has been loaded.
+%%--------------------------------------------------------------------
+load_mod(Mod, File, Binary, Db) ->
+ Flag = process_flag(trap_exit, true),
+ Pid = spawn_link(?MODULE, load_mod1, [Mod, File, Binary, Db]),
+ receive
+ {'EXIT', Pid, What} ->
+ process_flag(trap_exit, Flag),
+ What
+ end.
+
+%%====================================================================
+%% Internal exports
+%%====================================================================
+
+load_mod1(Mod, File, Binary, Db) ->
+ store_module(Mod, File, Binary, Db),
+ exit({ok, Mod}).
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+store_module(Mod, File, Binary, Db) ->
+ {interpreter_module, Exp, Abst, Src, MD5} = binary_to_term(Binary),
+ Forms = case abstr(Abst) of
+ {abstract_v1,Forms0} -> Forms0;
+ {abstract_v2,Forms0} -> Forms0;
+ {raw_abstract_v1,Code0} ->
+ Code = interpret_file_attribute(Code0),
+ {_,_,Forms0,_} = sys_pre_expand:module(Code, []),
+ Forms0
+ end,
+ dbg_idb:insert(Db, mod_file, File),
+ dbg_idb:insert(Db, exports, Exp),
+ dbg_idb:insert(Db, defs, []),
+
+ put(vcount, 0),
+ put(fun_count, 0),
+ put(funs, []),
+ put(mod_md5, MD5),
+ Attr = store_forms(Forms, Mod, Db, Exp, []),
+ erase(mod_md5),
+ erase(current_function),
+% store_funs(Db, Mod),
+ erase(vcount),
+ erase(funs),
+ erase(fun_count),
+
+ dbg_idb:insert(Db, attributes, Attr),
+ NewBinary = store_mod_line_no(Mod, Db, binary_to_list(Src)),
+ dbg_idb:insert(Db, mod_bin, NewBinary),
+ dbg_idb:insert(Db, mod_raw, <<Src/binary,0:8>>), %% Add eos
+ dbg_idb:insert(Db, module, Mod).
+%% Adjust line numbers using the file/2 attribute.
+%% Also take the absolute value of line numbers.
+%% This simple fix will make the marker point at the correct line
+%% (assuming the file attributes are correct) in the source; it will
+%% not point at code in included files.
+interpret_file_attribute(Code) ->
+ epp:interpret_file_attribute(Code).
+
+
+abstr(Bin) when is_binary(Bin) -> binary_to_term(Bin);
+abstr(Term) -> Term.
+
+% store_funs(Db, Mod) ->
+% store_funs_1(get(funs), Db, Mod).
+
+% store_funs_1([{Name,Index,Uniq,_,_,Arity,Cs}|Fs], Db, Mod) ->
+% dbg_idb:insert(Db, {Mod,Name,Arity,false}, Cs),
+% dbg_idb:insert(Db, {'fun',Mod,Index,Uniq}, {Name,Arity,Cs}),
+% store_funs_1(Fs, Db, Mod);
+% store_funs_1([], _, _) -> ok.
+
+store_forms([{function,_,module_info,0,_}|Fs], Mod, Db, Exp, Attr) ->
+ Cs = [{clause,0,[],[], [{module_info_0,0,Mod}]}],
+ dbg_idb:insert(Db, {Mod,module_info,0,true}, Cs),
+ store_forms(Fs, Mod, Db, Exp, Attr);
+store_forms([{function,_,module_info,1,_}|Fs], Mod, Db, Exp, Attr) ->
+ Cs = [{clause,0,[{var,0,'What'}],[], [{module_info_1,0,Mod,[{var,0,'What'}]}]}],
+ dbg_idb:insert(Db, {Mod,module_info,1,true}, Cs),
+ store_forms(Fs, Mod, Db, Exp, Attr);
+store_forms([{function,_,Name,Arity,Cs0}|Fs], Mod, Db, Exp, Attr) ->
+ FA = {Name,Arity},
+ put(current_function, FA),
+ Cs = clauses(Cs0),
+ Exported = lists:member(FA, Exp),
+ dbg_idb:insert(Db, {Mod,Name,Arity,Exported}, Cs),
+ store_forms(Fs, Mod, Db, Exp, Attr);
+store_forms([{attribute,_,Name,Val}|Fs], Mod, Db, Exp, Attr) ->
+ store_forms(Fs, Mod, Db, Exp, [{Name,Val}|Attr]);
+store_forms([F|_], _Mod, _Db, _Exp, _Attr) ->
+ exit({unknown_form,F});
+store_forms([], _, _, _, Attr) ->
+ lists:reverse(Attr).
+
+store_mod_line_no(Mod, Db, Contents) ->
+ store_mod_line_no(Mod, Db, Contents, 1, 0, []).
+
+store_mod_line_no(_, _, [], _, _, NewCont) ->
+ list_to_binary(lists:reverse(NewCont));
+store_mod_line_no(Mod, Db, Contents, LineNo, Pos, NewCont) when is_integer(LineNo) ->
+ {ContTail,Pos1,NewCont1} = store_line(Mod, Db, Contents, LineNo, Pos, NewCont),
+ store_mod_line_no(Mod, Db, ContTail, LineNo+1, Pos1, NewCont1).
+
+store_line(_, Db, Contents, LineNo, Pos, NewCont) ->
+ {ContHead,ContTail,PosNL} = get_nl(Contents,Pos+8,[]),
+ dbg_idb:insert(Db,LineNo,{Pos+8,PosNL}),
+ {ContTail,PosNL+1,[make_lineno(LineNo, 8, ContHead)|NewCont]}.
+
+make_lineno(N, P, Acc) ->
+ S = integer_to_list(N),
+ S ++ [$:|spaces(P-length(S)-1, Acc)].
+
+spaces(P, Acc) when P > 0 ->
+ spaces(P-1, [$\s|Acc]);
+spaces(_, Acc) -> Acc.
+
+get_nl([10|T],Pos,Head) -> {lists:reverse([10|Head]),T,Pos};
+get_nl([H|T],Pos,Head) ->
+ get_nl(T,Pos+1,[H|Head]);
+get_nl([],Pos,Head) -> {lists:reverse(Head),[],Pos}.
+
+%%% Rewrite the abstract syntax tree to that it will be easier (== faster)
+%%% to interpret.
+
+clauses([C0|Cs]) ->
+ C1 = clause(C0),
+ [C1|clauses(Cs)];
+clauses([]) -> [].
+
+clause({clause,Line,H0,G0,B0}) ->
+ H1 = head(H0),
+ G1 = guard(G0),
+ B1 = exprs(B0),
+ {clause,Line,H1,G1,B1}.
+
+head(Ps) -> patterns(Ps).
+
+%% These patterns are processed "sequentially" for purposes of variable
+%% definition etc.
+
+patterns([P0|Ps]) ->
+ P1 = pattern(P0),
+ [P1|patterns(Ps)];
+patterns([]) -> [].
+
+%% N.B. Only valid patterns are included here.
+
+pattern({var,Line,V}) -> {var,Line,V};
+pattern({char,Line,I}) -> {value,Line,I};
+pattern({integer,Line,I}) -> {value,Line,I};
+pattern({match,Line,Pat1,Pat2}) ->
+ {match,Line,pattern(Pat1),pattern(Pat2)};
+pattern({float,Line,F}) -> {value,Line,F};
+pattern({atom,Line,A}) -> {value,Line,A};
+pattern({string,Line,S}) -> {value,Line,S};
+pattern({nil,Line}) -> {value,Line,[]};
+pattern({cons,Line,H0,T0}) ->
+ H1 = pattern(H0),
+ T1 = pattern(T0),
+ {cons,Line,H1,T1};
+pattern({tuple,Line,Ps0}) ->
+ Ps1 = pattern_list(Ps0),
+ {tuple,Line,Ps1};
+pattern({op,_,'-',{integer,Line,I}}) ->
+ {value,Line,-I};
+pattern({op,_,'+',{integer,Line,I}}) ->
+ {value,Line,I};
+pattern({op,_,'-',{char,Line,I}}) ->
+ {value,Line,-I};
+pattern({op,_,'+',{char,Line,I}}) ->
+ {value,Line,I};
+pattern({op,_,'-',{float,Line,I}}) ->
+ {value,Line,-I};
+pattern({op,_,'+',{float,Line,I}}) ->
+ {value,Line,I};
+pattern({bin,Line,Grp}) ->
+ Grp1 = pattern_list(Grp),
+ {bin,Line,Grp1};
+pattern({bin_element,Line,Expr,Size,Type}) ->
+ Expr1 = pattern(Expr),
+ Size1 = expr(Size),
+ {bin_element,Line,Expr1,Size1,Type}.
+
+%% These patterns are processed "in parallel" for purposes of variable
+%% definition etc.
+
+pattern_list([P0|Ps]) ->
+ P1 = pattern(P0),
+ [P1|pattern_list(Ps)];
+pattern_list([]) -> [].
+
+guard([G0|Gs]) ->
+ G1 = and_guard(G0),
+ [G1|guard(Gs)];
+guard([]) -> [].
+
+and_guard([{atom,_,true}|Gs]) ->
+ and_guard(Gs);
+and_guard([G0|Gs]) ->
+ G1 = guard_test(G0),
+ [G1|and_guard(Gs)];
+and_guard([]) -> [].
+
+guard_test({call,Line,{remote,_,{atom,_,erlang},{atom,_,F}},As0}) ->
+ As = gexpr_list(As0),
+ case map_guard_bif(F, length(As0)) of
+ {ok,Name} ->
+ {safe_bif,Line,erlang,Name,As};
+ error ->
+ {safe_bif,Line,erlang,F,As}
+ end;
+guard_test({op,Line,Op,L0}) ->
+ true = erl_internal:arith_op(Op, 1) orelse %Assertion.
+ erl_internal:bool_op(Op, 1),
+ L1 = gexpr(L0),
+ {safe_bif,Line,erlang,Op,[L1]};
+guard_test({op,Line,Op,L0,R0}) when Op =:= 'andalso'; Op =:= 'orelse' ->
+ L1 = gexpr(L0),
+ R1 = gexpr(R0), %They see the same variables
+ {Op,Line,L1,R1};
+guard_test({op,Line,Op,L0,R0}) ->
+ true = erl_internal:comp_op(Op, 2) orelse %Assertion.
+ erl_internal:bool_op(Op, 2) orelse
+ erl_internal:arith_op(Op, 2),
+ L1 = gexpr(L0),
+ R1 = gexpr(R0), %They see the same variables
+ {safe_bif,Line,erlang,Op,[L1,R1]};
+guard_test({integer,_,_}=I) -> I;
+guard_test({char,_,_}=C) -> C;
+guard_test({float,_,_}=F) -> F;
+guard_test({atom,_,_}=A) -> A;
+guard_test({nil,_}=N) -> N;
+guard_test({var,_,_}=V) ->V. % Boolean var
+
+map_guard_bif(integer, 1) -> {ok,is_integer};
+map_guard_bif(float, 1) -> {ok,is_float};
+map_guard_bif(number, 1) -> {ok,is_number};
+map_guard_bif(atom, 1) -> {ok,is_atom};
+map_guard_bif(list, 1) -> {ok,is_list};
+map_guard_bif(tuple, 1) -> {ok,is_tuple};
+map_guard_bif(pid, 1) -> {ok,is_pid};
+map_guard_bif(reference, 1) -> {ok,is_reference};
+map_guard_bif(port, 1) -> {ok,is_port};
+map_guard_bif(binary, 1) -> {ok,is_binary};
+map_guard_bif(function, 1) -> {ok,is_function};
+map_guard_bif(_, _) -> error.
+
+gexpr({var,Line,V}) -> {var,Line,V};
+gexpr({integer,Line,I}) -> {value,Line,I};
+gexpr({char,Line,I}) -> {value,Line,I};
+gexpr({float,Line,F}) -> {value,Line,F};
+gexpr({atom,Line,A}) -> {value,Line,A};
+gexpr({string,Line,S}) -> {value,Line,S};
+gexpr({nil,Line}) -> {value,Line,[]};
+gexpr({cons,Line,H0,T0}) ->
+ case {gexpr(H0),gexpr(T0)} of
+ {{value,Line,H1},{value,Line,T1}} -> {value,Line,[H1|T1]};
+ {H1,T1} -> {cons,Line,H1,T1}
+ end;
+gexpr({tuple,Line,Es0}) ->
+ Es1 = gexpr_list(Es0),
+ {tuple,Line,Es1};
+gexpr({bin,Line,Flds0}) ->
+ Flds = gexpr_list(Flds0),
+ {bin,Line,Flds};
+gexpr({bin_element,Line,Expr0,Size0,Type}) ->
+ Expr = gexpr(Expr0),
+ Size = gexpr(Size0),
+ {bin_element,Line,Expr,Size,Type};
+%%% The previous passes have added the module name 'erlang' to
+%%% all BIF calls, even in guards.
+gexpr({call,Line,{remote,_,{atom,_,erlang},{atom,_,self}},[]}) ->
+ {dbg, Line, self, []};
+gexpr({call,Line,{remote,_,{atom,_,erlang},{atom,_,F}},As0}) ->
+ As = gexpr_list(As0),
+ {safe_bif,Line,erlang,F,As};
+gexpr({op,Line,Op,A0}) ->
+ erl_internal:arith_op(Op, 1),
+ A1 = gexpr(A0),
+ {safe_bif,Line,erlang,Op,[A1]};
+gexpr({op,Line,Op,L0,R0}) when Op =:= 'andalso'; Op =:= 'orelse' ->
+ L1 = gexpr(L0),
+ R1 = gexpr(R0), %They see the same variables
+ {Op,Line,L1,R1};
+gexpr({op,Line,Op,L0,R0}) ->
+ true = erl_internal:arith_op(Op, 2) orelse erl_internal:comp_op(Op, 2)
+ orelse erl_internal:bool_op(Op, 2),
+ L1 = gexpr(L0),
+ R1 = gexpr(R0), %They see the same variables
+ {safe_bif,Line,erlang,Op,[L1,R1]}.
+
+%% These expressions are processed "in parallel" for purposes of variable
+%% definition etc.
+
+gexpr_list([E0|Es]) ->
+ E1 = gexpr(E0),
+ [E1|gexpr_list(Es)];
+gexpr_list([]) -> [].
+
+%% These expressions are processed "sequentially" for purposes of variable
+%% definition etc.
+
+exprs([E0|Es]) ->
+ E1 = expr(E0),
+ [E1|exprs(Es)];
+exprs([]) -> [].
+
+expr({var,Line,V}) -> {var,Line,V};
+expr({integer,Line,I}) -> {value,Line,I};
+expr({char,Line,I}) -> {value,Line,I};
+expr({float,Line,F}) -> {value,Line,F};
+expr({atom,Line,A}) -> {value,Line,A};
+expr({string,Line,S}) -> {value,Line,S};
+expr({nil,Line}) -> {value,Line,[]};
+expr({cons,Line,H0,T0}) ->
+ case {expr(H0),expr(T0)} of
+ {{value,Line,H1},{value,Line,T1}} -> {value,Line,[H1|T1]};
+ {H1,T1} -> {cons,Line,H1,T1}
+ end;
+expr({tuple,Line,Es0}) ->
+ Es1 = expr_list(Es0),
+ {tuple,Line,Es1};
+expr({block,Line,Es0}) ->
+ %% Unfold block into a sequence.
+ Es1 = exprs(Es0),
+ {block,Line,Es1};
+expr({'if',Line,Cs0}) ->
+ Cs1 = icr_clauses(Cs0),
+ {'if',Line,Cs1};
+expr({'case',Line,E0,Cs0}) ->
+ E1 = expr(E0),
+ Cs1 = icr_clauses(Cs0),
+ {'case',Line,E1,Cs1};
+expr({'receive',Line,Cs0}) ->
+ Cs1 = icr_clauses(Cs0),
+ {'receive',Line,Cs1};
+expr({'receive',Line,Cs0,To0,ToEs0}) ->
+ To1 = expr(To0),
+ ToEs1 = exprs(ToEs0),
+ Cs1 = icr_clauses(Cs0),
+ {'receive',Line,Cs1,To1,ToEs1};
+expr({'fun',Line,{clauses,Cs0},{_,_,Name}}) when is_atom(Name) ->
+ %% New R10B-2 format (abstract_v2).
+ Cs = fun_clauses(Cs0),
+ {make_fun,Line,Name,Cs};
+expr({'fun',Line,{clauses,Cs0},{_,_,_,_,Name}}) when is_atom(Name) ->
+ %% New R8 format (abstract_v2).
+ Cs = fun_clauses(Cs0),
+ {make_fun,Line,Name,Cs};
+expr({'fun',Line,{function,F,A},{_Index,_OldUniq,Name}}) ->
+ %% New R8 format (abstract_v2).
+ As = new_vars(A, Line),
+ Cs = [{clause,Line,As,[],[{local_call,Line,F,As}]}],
+ {make_fun,Line,Name,Cs};
+expr({'fun',_,{clauses,_},{_OldUniq,_Hvss,_Free}}) ->
+ %% Old format (abstract_v1).
+ exit({?MODULE,old_funs});
+expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,self}},[]}) ->
+ {dbg,Line,self,[]};
+expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,get_stacktrace}},[]}) ->
+ {dbg,Line,get_stacktrace,[]};
+expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,throw}},[_]=As}) ->
+ {dbg,Line,throw,expr_list(As)};
+expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,error}},[_]=As}) ->
+ {dbg,Line,error,expr_list(As)};
+expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,fault}},[_]=As}) ->
+ {dbg,Line,fault,expr_list(As)};
+expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,exit}},[_]=As}) ->
+ {dbg,Line,exit,expr_list(As)};
+expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,apply}},As0})
+ when length(As0) == 3 ->
+ As = expr_list(As0),
+ {apply,Line,As};
+expr({call,Line,{remote,_,{atom,_,Mod},{atom,_,Func}},As0}) ->
+ As = expr_list(As0),
+ case erlang:is_builtin(Mod, Func, length(As)) of
+ false ->
+ {call_remote,Line,Mod,Func,As};
+ true ->
+ case bif_type(Mod, Func) of
+ safe -> {safe_bif,Line,Mod,Func,As};
+ spawn -> {spawn_bif,Line,Mod,Func,As};
+ unsafe ->{bif,Line,Mod,Func,As}
+ end
+ end;
+expr({call,Line,{remote,_,Mod0,Func0},As0}) ->
+ %% New R8 format (abstract_v2).
+ Mod = expr(Mod0),
+ Func = expr(Func0),
+ As = consify(expr_list(As0)),
+ {apply,Line,[Mod,Func,As]};
+expr({call,Line,{atom,_,Func},As0}) ->
+ As = expr_list(As0),
+ {local_call,Line,Func,As};
+expr({call,Line,Fun0,As0}) ->
+ Fun = expr(Fun0),
+ As = expr_list(As0),
+ {apply_fun,Line,Fun,As};
+expr({'catch',Line,E0}) ->
+ %% No new variables added.
+ E1 = expr(E0),
+ {'catch',Line,E1};
+expr({'try',Line,Es0,CaseCs0,CatchCs0,As0}) ->
+ %% No new variables added.
+ Es = expr_list(Es0),
+ CaseCs = icr_clauses(CaseCs0),
+ CatchCs = icr_clauses(CatchCs0),
+ As = expr_list(As0),
+ {'try',Line,Es,CaseCs,CatchCs,As};
+expr({'query', Line, E0}) ->
+ E = expr(E0),
+ {'query', Line, E};
+expr({lc,Line,E0,Gs0}) -> %R8.
+ Gs = lists:map(fun ({generate,L,P0,Qs}) ->
+ {generate,L,expr(P0),expr(Qs)};
+ ({b_generate,L,P0,Qs}) -> %R12.
+ {b_generate,L,expr(P0),expr(Qs)};
+ (Expr) ->
+ case is_guard_test(Expr) of
+ true -> {guard,[[guard_test(Expr)]]};
+ false -> expr(Expr)
+ end
+ end, Gs0),
+ {lc,Line,expr(E0),Gs};
+expr({bc,Line,E0,Gs0}) -> %R12.
+ Gs = lists:map(fun ({generate,L,P0,Qs}) ->
+ {generate,L,expr(P0),expr(Qs)};
+ ({b_generate,L,P0,Qs}) -> %R12.
+ {b_generate,L,expr(P0),expr(Qs)};
+ (Expr) ->
+ case is_guard_test(Expr) of
+ true -> {guard,[[guard_test(Expr)]]};
+ false -> expr(Expr)
+ end
+ end, Gs0),
+ {bc,Line,expr(E0),Gs};
+expr({match,Line,P0,E0}) ->
+ E1 = expr(E0),
+ P1 = pattern(P0),
+ {match,Line,P1,E1};
+expr({op,Line,Op,A0}) ->
+ A1 = expr(A0),
+ {op,Line,Op,[A1]};
+expr({op,Line,'++',L0,R0}) ->
+ L1 = expr(L0),
+ R1 = expr(R0), %They see the same variables
+ {op,Line,append,[L1,R1]};
+expr({op,Line,'--',L0,R0}) ->
+ L1 = expr(L0),
+ R1 = expr(R0), %They see the same variables
+ {op,Line,subtract,[L1,R1]};
+expr({op,Line,'!',L0,R0}) ->
+ L1 = expr(L0),
+ R1 = expr(R0), %They see the same variables
+ {send,Line,L1,R1};
+expr({op,Line,Op,L0,R0}) when Op =:= 'andalso'; Op =:= 'orelse' ->
+ L1 = expr(L0),
+ R1 = expr(R0), %They see the same variables
+ {Op,Line,L1,R1};
+expr({op,Line,Op,L0,R0}) ->
+ L1 = expr(L0),
+ R1 = expr(R0), %They see the same variables
+ {op,Line,Op,[L1,R1]};
+expr({bin,Line,Grp}) ->
+ Grp1 = expr_list(Grp),
+ {bin,Line,Grp1};
+expr({bin_element,Line,Expr,Size,Type}) ->
+ Expr1 = expr(Expr),
+ Size1 = expr(Size),
+ {bin_element,Line,Expr1,Size1,Type};
+expr(Other) ->
+ exit({?MODULE,{unknown_expr,Other}}).
+
+%% is_guard_test(Expression) -> true | false.
+%% Test if a general expression is a guard test. Cannot use erl_lint
+%% here as sys_pre_expand has transformed source.
+
+is_guard_test({op,_,Op,L,R}) ->
+ case erl_internal:comp_op(Op, 2) of
+ true -> is_gexpr_list([L,R]);
+ false -> false
+ end;
+is_guard_test({call,_,{remote,_,{atom,_,erlang},{atom,_,Test}},As}) ->
+ case erl_internal:type_test(Test, length(As)) of
+ true -> is_gexpr_list(As);
+ false -> false
+ end;
+is_guard_test({atom,_,true}) -> true;
+is_guard_test(_) -> false.
+
+is_gexpr({var,_,_}) -> true;
+is_gexpr({atom,_,_}) -> true;
+is_gexpr({integer,_,_}) -> true;
+is_gexpr({char,_,_}) -> true;
+is_gexpr({float,_,_}) -> true;
+is_gexpr({string,_,_}) -> true;
+is_gexpr({nil,_}) -> true;
+is_gexpr({cons,_,H,T}) -> is_gexpr_list([H,T]);
+is_gexpr({tuple,_,Es}) -> is_gexpr_list(Es);
+is_gexpr({call,_,{remote,_,{atom,_,erlang},{atom,_,F}},As}) ->
+ Ar = length(As),
+ case erl_internal:guard_bif(F, Ar) of
+ true -> is_gexpr_list(As);
+ false ->
+ case erl_internal:arith_op(F, Ar) of
+ true -> is_gexpr_list(As);
+ false -> false
+ end
+ end;
+is_gexpr({op,_,Op,A}) ->
+ case erl_internal:arith_op(Op, 1) of
+ true -> is_gexpr(A);
+ false -> false
+ end;
+is_gexpr({op,_,Op,A1,A2}) ->
+ case erl_internal:arith_op(Op, 2) of
+ true -> is_gexpr_list([A1,A2]);
+ false -> false
+ end;
+is_gexpr(_) -> false.
+
+is_gexpr_list(Es) -> lists:all(fun (E) -> is_gexpr(E) end, Es).
+
+consify([A|As]) ->
+ {cons,0,A,consify(As)};
+consify([]) -> {value,0,[]}.
+
+
+%% -type expr_list([Expression]) -> [Expression].
+%% These expressions are processed "in parallel" for purposes of variable
+%% definition etc.
+
+expr_list([E0|Es]) ->
+ E1 = expr(E0),
+ [E1|expr_list(Es)];
+expr_list([]) -> [].
+
+icr_clauses([C0|Cs]) ->
+ C1 = clause(C0),
+ [C1|icr_clauses(Cs)];
+icr_clauses([]) -> [].
+
+fun_clauses([{clause,L,H,G,B}|Cs]) ->
+ [{clause,L,head(H),guard(G),exprs(B)}|fun_clauses(Cs)];
+fun_clauses([]) -> [].
+
+%% new_var_name() -> VarName.
+
+new_var_name() ->
+ C = get(vcount),
+ put(vcount, C+1),
+ list_to_atom("%" ++ integer_to_list(C)).
+
+%% new_vars(Count, Line) -> [Var].
+%% Make Count new variables.
+
+new_vars(N, L) -> new_vars(N, L, []).
+
+new_vars(N, L, Vs) when N > 0 ->
+ V = {var,L,new_var_name()},
+ new_vars(N-1, L, [V|Vs]);
+new_vars(0, _, Vs) -> Vs.
+
+bif_type(erlang, Name) -> bif_type(Name);
+bif_type(_, _) -> unsafe.
+
+bif_type(register) -> safe;
+bif_type(unregister) -> safe;
+bif_type(whereis) -> safe;
+bif_type(registered) -> safe;
+bif_type(abs) -> safe;
+bif_type(float) -> safe;
+bif_type(trunc) -> safe;
+bif_type(round) -> safe;
+bif_type(math) -> safe;
+bif_type(node) -> safe;
+bif_type(length) -> safe;
+bif_type(hd) -> safe;
+bif_type(tl) -> safe;
+bif_type(size) -> safe;
+bif_type(element) -> safe;
+bif_type(setelement) -> safe;
+bif_type(atom_to_list) -> safe;
+bif_type(list_to_atom) -> safe;
+bif_type(integer_to_list) -> safe;
+bif_type(list_to_integer) -> safe;
+bif_type(float_to_list) -> safe;
+bif_type(list_to_float) -> safe;
+bif_type(tuple_to_list) -> safe;
+bif_type(list_to_tuple) -> safe;
+bif_type(make_ref) -> safe;
+bif_type(time) -> safe;
+bif_type(date) -> safe;
+bif_type(processes) -> safe;
+bif_type(process_info) -> safe;
+bif_type(load_module) -> safe;
+bif_type(delete_module) -> safe;
+bif_type(halt) -> safe;
+bif_type(check_process_code) -> safe;
+bif_type(purge_module) -> safe;
+bif_type(pid_to_list) -> safe;
+bif_type(list_to_pid) -> safe;
+bif_type(module_loaded) -> safe;
+bif_type(binary_to_term) -> safe;
+bif_type(term_to_binary) -> safe;
+bif_type(alive) -> safe;
+bif_type(notalive) -> safe;
+bif_type(nodes) -> safe;
+bif_type(is_alive) -> safe;
+bif_type(disconnect_node) -> safe;
+bif_type(binary_to_list) -> safe;
+bif_type(list_to_binary) -> safe;
+bif_type(split_binary) -> safe;
+bif_type(concat_binary) -> safe;
+bif_type(term_to_atom) -> safe;
+bif_type(hash) -> safe;
+bif_type(pre_loaded) -> safe;
+bif_type(info) -> safe;
+bif_type(set_cookie) -> safe;
+bif_type(get_cookie) -> safe;
+bif_type(spawn) -> spawn;
+bif_type(spawn_link) -> spawn;
+bif_type(spawn_opt) -> spawn;
+bif_type(_) -> unsafe.
diff --git a/lib/debugger/src/dbg_iserver.erl b/lib/debugger/src/dbg_iserver.erl
new file mode 100644
index 0000000000..4c1e9ccb7b
--- /dev/null
+++ b/lib/debugger/src/dbg_iserver.erl
@@ -0,0 +1,606 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_iserver).
+-behaviour(gen_server).
+
+%% External exports
+-export([start/0, stop/0, find/0,
+ call/1, call/2, cast/1, cast/2, safe_call/1, safe_cast/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(proc, {pid, % pid() Debugged process
+ meta, % pid() Meta process
+ attpid, % pid() | undefined Attached process
+ status, % running | exit | idle | waiting
+ info = {}, % {} | term()
+ exit_info= {}, % {} | {{Mod,Line}, Bs, Stack}
+ function % {Mod,Func,Args} Initial function call
+ }).
+
+-record(state, {db, % ETS table
+ procs = [], % [#proc{}]
+ breaks = [], % [{{M,L},Options} Breakpoints
+ auto, % Auto attach settings
+ stack, % Stack trace settings
+ subs = [] % [pid()] Subscribers (Debugger)
+ }).
+
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+start() ->
+ gen_server:start({local, ?MODULE}, ?MODULE, [], []).
+
+stop() ->
+ gen_server:cast(?MODULE, stop).
+
+find() ->
+ global:whereis_name(?MODULE).
+
+call(Request) ->
+ gen_server:call(?MODULE, Request, infinity).
+
+call(Int, Request) ->
+ gen_server:call(Int, Request, infinity).
+
+cast(Request) ->
+ gen_server:cast(?MODULE, Request).
+
+cast(Int, Request) ->
+ gen_server:cast(Int, Request).
+
+safe_call(Request) ->
+ ensure_started(),
+ call(Request).
+
+safe_cast(Request) ->
+ ensure_started(),
+ cast(Request).
+
+ensure_started() ->
+ case whereis(?MODULE) of
+ undefined -> start();
+ _Pid -> ignore
+ end.
+
+%%--Module database---------------------------------------------------
+%% This server creates an ETS table, where the following information
+%% is saved:
+%%
+%% Key Value
+%% --- -----
+%% {Mod, refs} [ModDb]
+%% ModDb [pid()]
+%%
+%% In each ModDb, the following information is saved by dbg_iload:
+%%
+%% Key Value
+%% --- -----
+%% attributes Attr
+%% exports Exp
+%% defs []
+%% mod_bin Binary
+%% mod_raw Raw Binary
+%% mod_file File
+%% module Mod
+%% {Mod,Name,Arity,Exported} Cs
+%% {'fun',Mod,Index,Uniq} {Name,Arity,Cs}
+%% Line {Pos,PosNL}
+
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+init([]) ->
+ process_flag(trap_exit, true),
+ global:register_name(?MODULE, self()),
+ Db = ets:new(?MODULE, [ordered_set, protected]),
+ {ok, #state{db=Db, auto=false, stack=all}}.
+
+%% Attaching to a process
+handle_call({attached, AttPid, Pid}, _From, State) ->
+ {true, Proc} = get_proc({pid, Pid}, State#state.procs),
+ case Proc#proc.attpid of
+ undefined ->
+ link(AttPid),
+ case Proc#proc.status of
+ exit ->
+ Args = [self(),
+ AttPid,Pid,Proc#proc.info,
+ Proc#proc.exit_info],
+ Meta = spawn_link(dbg_ieval, exit_info, Args),
+ Proc2 = Proc#proc{meta=Meta, attpid=AttPid},
+ Procs = lists:keyreplace(Pid, #proc.pid,
+ State#state.procs, Proc2),
+ {reply, {ok,Meta}, State#state{procs=Procs}};
+ _Status ->
+ Meta = Proc#proc.meta,
+ send(Meta, {attached, AttPid}),
+ Procs = lists:keyreplace(Pid, #proc.pid,
+ State#state.procs,
+ Proc#proc{attpid=AttPid}),
+ {reply, {ok, Meta}, State#state{procs=Procs}}
+ end;
+ _AttPid -> % there is already an attached process
+ {reply, error, State}
+ end;
+
+%% Getting and setting options
+handle_call(get_auto_attach, _From, State) ->
+ {reply, State#state.auto, State};
+handle_call(get_stack_trace, _From, State) ->
+ {reply, State#state.stack, State};
+
+%% Retrieving information
+handle_call(snapshot, _From, State) ->
+ Reply = lists:map(fun(Proc) ->
+ {Proc#proc.pid, Proc#proc.function,
+ Proc#proc.status, Proc#proc.info}
+ end,
+ State#state.procs),
+ {reply, Reply, State};
+handle_call({get_meta, Pid}, _From, State) ->
+ Reply = case get_proc({pid, Pid}, State#state.procs) of
+ {true, Proc} ->
+ {ok, Proc#proc.meta};
+ false ->
+ {error, not_interpreted}
+ end,
+ {reply, Reply, State};
+handle_call({get_attpid, Pid}, _From, State) ->
+ Reply = case get_proc({pid, Pid}, State#state.procs) of
+ {true, Proc} ->
+ {ok, Proc#proc.attpid};
+ false ->
+ {error, not_interpreted}
+ end,
+ {reply, Reply, State};
+
+
+%% Breakpoint handling
+handle_call({new_break, Point, Options}, _From, State) ->
+ case lists:keysearch(Point, 1, State#state.breaks) of
+ false ->
+ Break = {Point, Options},
+ send_all([subscriber, meta, attached],
+ {new_break, Break}, State),
+ Breaks = keyinsert(Break, 1, State#state.breaks),
+ {reply, ok, State#state{breaks=Breaks}};
+ {value, _Break} ->
+ {reply, {error, break_exists}, State}
+ end;
+handle_call(all_breaks, _From, State) ->
+ {reply, State#state.breaks, State};
+handle_call({all_breaks, Mod}, _From, State) ->
+ Reply = lists:filter(fun({{M,_L}, _Options}) ->
+ if M==Mod -> true; true -> false end
+ end,
+ State#state.breaks),
+ {reply, Reply, State};
+
+%% From Meta process
+handle_call({new_process, Pid, Meta, Function}, _From, State) ->
+ link(Meta),
+
+ %% A new, debugged process has been started. Return its status,
+ %% ie running (running as usual) or break (stop)
+ %% The status depends on if the process is automatically attached to
+ %% or not.
+ Reply = case auto_attach(init, State#state.auto, Pid) of
+ AttPid when is_pid(AttPid) -> break;
+ ignore -> running
+ end,
+
+ %% Do not add AttPid, it should call attached/2 when started instead
+ Proc = #proc{pid=Pid, meta=Meta, status=running, function=Function},
+ send_all(subscriber,
+ {new_process, {Pid,Function,running,{}}}, State),
+
+ {reply, Reply, State#state{procs=State#state.procs++[Proc]}};
+
+%% Code loading
+handle_call({load, Mod, Src, Bin}, _From, State) ->
+
+ %% Create an ETS table for storing information about the module
+ Db = State#state.db,
+ ModDb = ets:new(Mod, [ordered_set, public]),
+ ModDbs = case ets:lookup(Db, {Mod, refs}) of
+ [] -> [];
+ [{{Mod, refs}, ModDbs1}] -> ModDbs1
+ end,
+ ets:insert(Db, {{Mod, refs}, [ModDb|ModDbs]}),
+ ets:insert(Db, {ModDb, []}),
+
+ %% Load the code
+ {ok, Mod} = dbg_iload:load_mod(Mod, Src, Bin, ModDb),
+
+ %% Inform all subscribers and attached processes
+ send_all([subscriber, attached], {interpret, Mod}, State),
+
+ {reply, {module, Mod}, State};
+
+%% Module database
+handle_call({get_module_db, Mod, Pid}, _From, State) ->
+ Db = State#state.db,
+ Reply = case ets:lookup(Db, {Mod, refs}) of
+ [] -> not_found;
+ [{{Mod, refs}, [ModDb|_ModDbs]}] ->
+ [{ModDb, Pids}] = ets:lookup(Db, ModDb),
+ ets:insert(Db, {ModDb, [Pid|Pids]}),
+ ModDb
+ end,
+ {reply, Reply, State};
+handle_call({lookup, Mod, Key}, _From, State) ->
+ Db = State#state.db,
+ Reply = case ets:lookup(Db, {Mod, refs}) of
+ [] -> not_found;
+ [{{Mod, refs}, [ModDb|_ModDbs]}] ->
+ case ets:lookup(ModDb, Key) of
+ [] -> not_found;
+ [{Key, Value}] -> {ok, Value}
+ end
+ end,
+ {reply, Reply, State};
+handle_call({functions, Mod}, _From, State) ->
+ Db = State#state.db,
+ Reply = case ets:lookup(Db, {Mod, refs}) of
+ [] -> [];
+ [{{Mod, refs}, [ModDb|_ModDbs]}] ->
+ Pattern = {{Mod,'$1','$2','_'}, '_'},
+ ets:match(ModDb, Pattern)
+ end,
+ {reply, Reply, State};
+handle_call({contents, Mod, Pid}, _From, State) ->
+ Db = State#state.db,
+ [{{Mod, refs}, ModDbs}] = ets:lookup(Db, {Mod, refs}),
+ ModDb = if
+ Pid==any -> hd(ModDbs);
+ true ->
+ lists:foldl(fun(T, not_found) ->
+ [{T, Pids}] = ets:lookup(Db, T),
+ case lists:member(Pid, Pids) of
+ true -> T;
+ false -> not_found
+ end;
+ (_T, T) -> T
+ end,
+ not_found,
+ ModDbs)
+ end,
+ [{mod_bin, Bin}] = ets:lookup(ModDb, mod_bin),
+ {reply, {ok, Bin}, State};
+handle_call({raw_contents, Mod, Pid}, _From, State) ->
+ Db = State#state.db,
+ [{{Mod, refs}, ModDbs}] = ets:lookup(Db, {Mod, refs}),
+ ModDb = if
+ Pid==any -> hd(ModDbs);
+ true ->
+ lists:foldl(fun(T, not_found) ->
+ [{T, Pids}] = ets:lookup(Db, T),
+ case lists:member(Pid, Pids) of
+ true -> T;
+ false -> not_found
+ end;
+ (_T, T) -> T
+ end,
+ not_found,
+ ModDbs)
+ end,
+ [{mod_raw, Bin}] = ets:lookup(ModDb, mod_raw),
+ {reply, {ok, Bin}, State};
+handle_call({is_interpreted, Mod, Name, Arity}, _From, State) ->
+ Db = State#state.db,
+ Reply = case ets:lookup(Db, {Mod, refs}) of
+ [] -> false;
+ [{{Mod, refs}, [ModDb|_ModDbs]}] ->
+ Pattern = {{Mod,Name,Arity,'_'}, '_'},
+ case ets:match_object(ModDb, Pattern) of
+ [{_Key, Clauses}] -> {true, Clauses};
+ [] -> false
+ end
+ end,
+ {reply, Reply, State};
+handle_call(all_interpreted, _From, State) ->
+ Db = State#state.db,
+ Mods = ets:select(Db, [{{{'$1',refs},'_'},[],['$1']}]),
+ {reply, Mods, State};
+handle_call({file, Mod}, From, State) ->
+ {reply, Res, _} = handle_call({lookup, Mod, mod_file}, From, State),
+ Reply = case Res of
+ {ok, File} -> File;
+ not_found -> {error, not_loaded}
+ end,
+ {reply, Reply, State}.
+
+
+handle_cast(stop, State) ->
+ {stop, shutdown, State};
+handle_cast({subscribe, Sub}, State) ->
+ {noreply, State#state{subs=[Sub|State#state.subs]}};
+
+%% Attaching to a process
+handle_cast({attach, Pid, {Mod, Func, Args}}, State) ->
+ %% Simply spawn process, which should call int:attached(Pid)
+ spawn(Mod, Func, [Pid | Args]),
+ {noreply, State};
+
+%% Getting and setting options
+handle_cast({set_auto_attach, false}, State) ->
+ send_all(subscriber, {auto_attach, false}, State),
+ {noreply, State#state{auto=false}};
+handle_cast({set_auto_attach, Flags, Function}, State) ->
+ send_all(subscriber, {auto_attach, {Flags, Function}}, State),
+ {noreply, State#state{auto={Flags, Function}}};
+handle_cast({set_stack_trace, Flag}, State) ->
+ send_all(subscriber, {stack_trace, Flag}, State),
+ {noreply, State#state{stack=Flag}};
+
+%% Retrieving information
+handle_cast(clear, State) ->
+ Procs = lists:filter(fun(#proc{status=Status}) ->
+ if Status==exit -> false; true -> true end
+ end,
+ State#state.procs),
+ {noreply, State#state{procs=Procs}};
+
+%% Breakpoint handling
+handle_cast({delete_break, Point}, State) ->
+ case lists:keysearch(Point, 1, State#state.breaks) of
+ {value, _Break} ->
+ send_all([subscriber, meta, attached],
+ {delete_break, Point}, State),
+ Breaks = lists:keydelete(Point, 1, State#state.breaks),
+ {noreply, State#state{breaks=Breaks}};
+ false ->
+ {noreply, State}
+ end;
+handle_cast({break_option, Point, Option, Value}, State) ->
+ case lists:keysearch(Point, 1, State#state.breaks) of
+ {value, {Point, Options}} ->
+ N = case Option of
+ status -> 1;
+ action -> 2;
+ condition -> 4
+ end,
+ Options2 = list_setelement(N, Options, Value),
+ send_all([subscriber, meta, attached],
+ {break_options, {Point, Options2}}, State),
+ Breaks = lists:keyreplace(Point, 1, State#state.breaks,
+ {Point, Options2}),
+ {noreply, State#state{breaks=Breaks}};
+ false ->
+ {noreply, State}
+ end;
+handle_cast(no_break, State) ->
+ send_all([subscriber, meta, attached], no_break, State),
+ {noreply, State#state{breaks=[]}};
+handle_cast({no_break, Mod}, State) ->
+ send_all([subscriber, meta, attached], {no_break, Mod}, State),
+ Breaks = lists:filter(fun({{M, _L}, _O}) ->
+ if M==Mod -> false; true -> true end
+ end,
+ State#state.breaks),
+ {noreply, State#state{breaks=Breaks}};
+
+%% From Meta process
+handle_cast({set_status, Meta, Status, Info}, State) ->
+ {true, Proc} = get_proc({meta, Meta}, State#state.procs),
+ send_all(subscriber, {new_status, Proc#proc.pid, Status, Info}, State),
+ if
+ Status==break ->
+ auto_attach(break, State#state.auto, Proc);
+ true -> ignore
+ end,
+ Proc2 = Proc#proc{status=Status, info=Info},
+ {noreply, State#state{procs=lists:keyreplace(Meta, #proc.meta,
+ State#state.procs, Proc2)}};
+handle_cast({set_exit_info, Meta, ExitInfo}, State) ->
+ {true, Proc} = get_proc({meta, Meta}, State#state.procs),
+ Procs = lists:keyreplace(Meta, #proc.meta, State#state.procs,
+ Proc#proc{exit_info=ExitInfo}),
+ {noreply,State#state{procs=Procs}};
+
+%% Code loading
+handle_cast({delete, Mod}, State) ->
+
+ %% Remove the ETS table with information about the module
+ Db = State#state.db,
+ case ets:lookup(Db, {Mod, refs}) of
+ [] -> % Mod is not interpreted
+ {noreply, State};
+ [{{Mod, refs}, ModDbs}] ->
+ ets:delete(Db, {Mod, refs}),
+ AllPids = lists:foldl(
+ fun(ModDb, PidsAcc) ->
+ [{ModDb, Pids}] = ets:lookup(Db, ModDb),
+ ets:delete(Db, ModDb),
+ ets:delete(ModDb),
+ PidsAcc++Pids
+ end,
+ [],
+ ModDbs),
+ lists:foreach(fun(Pid) ->
+ case get_proc({pid, Pid},
+ State#state.procs) of
+ {true, Proc} ->
+ send(Proc#proc.meta,
+ {old_code, Mod});
+ false -> ignore % pid may have exited
+ end
+ end,
+ AllPids),
+
+ send_all([subscriber,attached], {no_interpret, Mod}, State),
+
+ %% Remove all breakpoints for Mod
+ handle_cast({no_break, Mod}, State)
+ end.
+
+%% Process exits
+handle_info({'EXIT',Who,Why}, State) ->
+ case get_proc({meta, Who}, State#state.procs) of
+
+ %% Exited process is a meta process for exit_info
+ {true,#proc{status=exit}} ->
+ {noreply,State};
+
+ %% Exited process is a meta process
+ {true,Proc} ->
+ Pid = Proc#proc.pid,
+ ExitInfo = Proc#proc.exit_info,
+ %% Check if someone is attached to the debugged process,
+ %% if so a new meta process should be started
+ Meta = case Proc#proc.attpid of
+ AttPid when is_pid(AttPid) ->
+ spawn_link(dbg_ieval, exit_info,
+ [self(),AttPid,Pid,Why,ExitInfo]);
+ undefined ->
+ %% Otherwise, auto attach if necessary
+ auto_attach(exit, State#state.auto, Pid),
+ Who
+ end,
+ send_all(subscriber, {new_status,Pid,exit,Why}, State),
+ Procs = lists:keyreplace(Who, #proc.meta, State#state.procs,
+ Proc#proc{meta=Meta,
+ status=exit,
+ info=Why}),
+ {noreply,State#state{procs=Procs}};
+
+ false ->
+ case get_proc({attpid, Who}, State#state.procs) of
+
+ %% Exited process is an attached process
+ {true, Proc} ->
+ %% If status==exit, then the meta process is a
+ %% simple meta for a terminated process and can be
+ %% terminated as well (it is only needed by
+ %% the attached process)
+ case Proc#proc.status of
+ exit -> send(Proc#proc.meta, stop);
+ _Status -> send(Proc#proc.meta, detached)
+ end,
+ Procs = lists:keyreplace(Proc#proc.pid, #proc.pid,
+ State#state.procs,
+ Proc#proc{attpid=undefined}),
+ {noreply, State#state{procs=Procs}};
+
+ %% Otherwise exited process must be a subscriber
+ false ->
+ Subs = lists:delete(Who, State#state.subs),
+ {noreply, State#state{subs=Subs}}
+ end
+ end.
+
+terminate(_Reason, _State) ->
+ EbinDir = filename:join(code:lib_dir(debugger), "ebin"),
+ code:unstick_dir(EbinDir),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+auto_attach(Why, Auto, Proc) when is_record(Proc, proc) ->
+ case Proc#proc.attpid of
+ AttPid when is_pid(AttPid) -> ignore;
+ undefined ->
+ auto_attach(Why, Auto, Proc#proc.pid)
+ end;
+auto_attach(Why, Auto, Pid) when is_pid(Pid) ->
+ case Auto of
+ false -> ignore;
+ {Flags, {Mod, Func, Args}} ->
+ case lists:member(Why, Flags) of
+ true ->
+ spawn(Mod, Func, [Pid | Args]);
+ false -> ignore
+ end
+ end.
+
+keyinsert(Tuple1, N, [Tuple2|Tuples]) ->
+ if
+ element(N, Tuple1)<element(N, Tuple2) ->
+ [Tuple1, Tuple2|Tuples];
+ true ->
+ [Tuple2 | keyinsert(Tuple1, N, Tuples)]
+ end;
+keyinsert(Tuple, _N, []) ->
+ [Tuple].
+
+list_setelement(N, L, E) -> list_setelement(1, N, L, E).
+
+list_setelement(I, I, [_|T], E) ->
+ [E|T];
+list_setelement(I, N, [H|T], E) ->
+ [H|list_setelement(I+1, N, T, E)].
+
+mapfilter(Fun, [H|T]) ->
+ case Fun(H) of
+ ignore -> mapfilter(Fun, T);
+ H2 -> [H2|mapfilter(Fun, T)]
+ end;
+mapfilter(_Fun, []) ->
+ [].
+
+send_all([Type|Types], Msg, State) ->
+ send_all(Type, Msg, State),
+ send_all(Types, Msg, State);
+send_all([], _Msg, _State) -> ok;
+
+send_all(subscriber, Msg, State) ->
+ send_all(State#state.subs, Msg);
+send_all(meta, Msg, State) ->
+ Metas = lists:map(fun(Proc) -> Proc#proc.meta end, State#state.procs),
+ send_all(Metas, Msg);
+send_all(attached, Msg, State) ->
+ AttPids= mapfilter(fun(Proc) ->
+ case Proc#proc.attpid of
+ Pid when is_pid(Pid) -> Pid;
+ undefined -> ignore
+ end
+ end,
+ State#state.procs),
+ send_all(AttPids, Msg).
+
+send_all(Pids, Msg) ->
+ lists:foreach(fun(Pid) -> send(Pid, Msg) end, Pids).
+
+send(Pid, Msg) ->
+ Pid ! {int, Msg}.
+
+get_proc({Type, Pid}, Procs) ->
+ Index = case Type of
+ pid -> #proc.pid;
+ meta -> #proc.meta;
+ attpid -> #proc.attpid
+ end,
+ case lists:keysearch(Pid, Index, Procs) of
+ {value, Proc} -> {true, Proc};
+ false -> false
+ end.
diff --git a/lib/debugger/src/dbg_ui_break.erl b/lib/debugger/src/dbg_ui_break.erl
new file mode 100644
index 0000000000..8b9a236ce7
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_break.erl
@@ -0,0 +1,98 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_break).
+
+%% External exports
+-export([start/3, start/4, start/5]).
+
+%% Internal exports
+-export([init/5]).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start(GS, Pos, Type)
+%% start(GS, Pos, Type, Module, Line)
+%% GS = graphics system identifier
+%% Pos = {X, Y}
+%% X = Y = integer()
+%% Type = line | conditional | function
+%% Module = atom()
+%% Line = integer()
+%%--------------------------------------------------------------------
+start(GS, Pos, Type) ->
+ start(GS, Pos, Type, "", "").
+start(GS, Pos, Type, Mod) ->
+ start(GS, Pos, Type, Mod, "").
+start(GS, Pos, Type, Mod, Line) ->
+ spawn_link(?MODULE, init, [GS, Pos, Type, Mod, Line]).
+
+
+%%====================================================================
+%% Internal exports
+%%====================================================================
+
+init(GS, Pos, Type, Mod, Line) ->
+ Win = dbg_ui_break_win:create_win(GS, Pos, Type, Mod, Line),
+ if
+ Type==function, is_atom(Mod) ->
+ Win2 = gui_cmd({module, Mod}, Win),
+ loop(Win2);
+ true ->
+ loop(Win)
+ end.
+
+loop(Win) ->
+ receive
+
+ %% From the GUI
+ GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs ->
+ Cmd = dbg_ui_break_win:handle_event(GuiEvent, Win),
+ Win2 = gui_cmd(Cmd, Win),
+ loop(Win2)
+ end.
+
+gui_cmd(ignore, Win) ->
+ Win;
+gui_cmd(stopped, _Win) ->
+ exit(normal);
+gui_cmd({win, Win2}, _Win) ->
+ Win2;
+gui_cmd({module, Mod}, Win) ->
+ Funcs = int:functions(Mod),
+ dbg_ui_break_win:update_functions(Win, Funcs);
+gui_cmd({break, DataL, Action}, _Win) ->
+ Fun =
+ fun(Data) ->
+ case Data of
+ [Mod, Line] ->
+ int:break(Mod, Line),
+ int:action_at_break(Mod, Line, Action);
+ [Mod, Line, CMod, CFunc] ->
+ int:break(Mod, Line),
+ int:test_at_break(Mod, Line, {CMod, CFunc}),
+ int:action_at_break(Mod, Line, Action);
+ [Mod, Func, Arity] ->
+ int:break_in(Mod, Func, Arity)
+ end
+ end,
+ lists:foreach(Fun, DataL),
+ exit(normal).
diff --git a/lib/debugger/src/dbg_ui_break_win.erl b/lib/debugger/src/dbg_ui_break_win.erl
new file mode 100644
index 0000000000..a56fe36828
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_break_win.erl
@@ -0,0 +1,307 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_break_win).
+
+%% External exports
+-export([create_win/5,
+ update_functions/2,
+ handle_event/2]).
+
+-record(winInfo, {type, % line | conditional | function
+ win, % gsobj()
+ packer, % gsobj() | undefined
+ entries, % [{atom|integer, GSobj()}]
+ trigger, % enable | disable | delete
+ ok, % gsobj()
+ cancel, % gsobj()
+ listbox, % gsobj()
+ funcs=[] % [[Name, Arity]]
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% create_win(GS, Pos, Type, Mod, Line) -> #winInfo{}
+%% GS = graphics system identifier
+%% Pos = {X, Y}
+%% X = Y = integer()
+%% Type = line | conditional | function
+%% Mod = atom() | ""
+%% Line = integer() | ""
+%%--------------------------------------------------------------------
+create_win(GS, {X, Y}, function, Mod, _Line) ->
+ Pad = 8,
+ W = 230,
+
+ Font = dbg_ui_win:font(normal),
+
+ %% Window
+ Win = gs:window(GS, [{title, "Function Break"}, {x, X}, {y, Y},
+ {destroy, true}, {configure, true},
+ {keypress, true}, {data, window}]),
+
+ %% Frame
+ Frm = gs:frame(Win, [{x, 0}, {y, 0}, {width, W}, {height, 190},
+ {packer_x, [{fixed, 70}, {stretch, 1, W-80},
+ {fixed, 10}]},
+ {packer_y, [{fixed, 10}, {fixed, 30},
+ {stretch, 1, 100}, {fixed, 40}]}]),
+
+ %% Create input field (label+entry)
+ gs:label(Frm, [{label, {text,"Module:"}}, {font, Font}, {align, e},
+ {pack_x, 1}, {pack_y, 2}]),
+ Ent = gs:entry(Frm, [{text, Mod},
+ {pack_x, 2}, {pack_y, 2},
+ {keypress, true}, {setfocus, true},
+ {buttonpress, true}]),
+ Entries = [{Ent, atom}],
+
+ %% Create a listbox containing the functions of the module
+ gs:label(Frm, [{label, {text,"Function:"}}, {font, Font}, {align, ne},
+ {pack_x, 1}, {pack_y, 3}]),
+ Lb = gs:listbox(Frm, [{bw, 2}, {relief, ridge}, {vscroll, right},
+ {pack_x, 2}, {pack_y, 3},
+ {selectmode, multiple}]),
+
+ %% Add Ok and Cancel buttons
+ {Wbtn, Hbtn} = dbg_ui_win:min_size(["Ok","Cancel"], 70, 30),
+ Bot = gs:frame(Frm, [{pack_x, {1, 3}}, {pack_y, 4}]),
+ Ok = gs:button(Bot, [{x, Pad}, {y, Pad},
+ {width, Wbtn}, {height, Hbtn},
+ {label, {text,"Ok"}}, {font, Font}]),
+ Cancel = gs:button(Bot, [{x, W-Pad-Wbtn}, {y, Pad},
+ {width, Wbtn}, {height, Hbtn},
+ {label, {text,"Cancel"}}, {font, Font}]),
+
+ Wfrm = gs:read(Frm, width), Hfrm = gs:read(Frm, height),
+ gs:config(Win, [{width, Wfrm}, {height, Hfrm}, {map, true}]),
+ #winInfo{type=function, win=Win,
+ packer=Frm, entries=Entries, trigger=enable,
+ ok=Ok, cancel=Cancel, listbox=Lb, funcs=[]};
+create_win(GS, {X, Y}, Type, Mod, Line) ->
+ Pad = 8,
+ W = 230,
+
+ Font = dbg_ui_win:font(normal),
+
+ %% Window
+ Title = case Type of
+ line -> "Line Break";
+ conditional -> "Conditional Break"
+ end,
+ Win = gs:window(GS, [{title, Title}, {x, X}, {y, Y},
+ {destroy, true}]),
+
+ %% Create input fields (label+entry)
+ {Wlbl, Hlbl} = dbg_ui_win:min_size(["C-Function:"], 10, 30),
+ Went = W-Wlbl-2*Pad,
+ Labels = case Type of
+ line ->
+ [{atom,"Module:",Mod}, {integer,"Line:",Line}];
+ conditional ->
+ [{atom,"Module:",Mod}, {integer,"Line:",Line},
+ {atom,"C-Module:",""}, {atom,"C-Function:",""}]
+ end,
+ Fun = fun({DataType, Label, Default}, Yin) ->
+ gs:create(label, Win, [{x, Pad}, {y, Yin},
+ {width,Wlbl}, {height,Hlbl},
+ {label, {text,Label}},
+ {font, Font}, {align, e}]),
+ Ent = gs:create(entry, Win, [{x, Pad+Wlbl}, {y, Yin},
+ {width, Went},
+ {height, Hlbl},
+ {text, Default},
+ {keypress, true}]),
+ {{Ent, DataType}, Yin+Hlbl}
+ end,
+ {Entries, Yacc} = lists:mapfoldl(Fun, Pad, Labels),
+ {First, _DataType} = hd(Entries),
+ gs:config(First, [{buttonpress, true}, {setfocus, true}]),
+
+ %% Add 'trigger action' buttons
+ {Wlbl2, Hlbl2} = dbg_ui_win:min_size(["Trigger Action"], 100, 20),
+ Wfrm = Wlbl2+8, Hfrm = Hlbl2*4+4,
+ Grp = erlang:now(),
+ Frm = gs:frame(Win, [{x, W/2-Wfrm/2-2}, {y, Yacc+Pad-2},
+ {width, Wfrm}, {height, Hfrm}, {bw, 2}]),
+ gs:label(Frm, [{label, {text, "Trigger Action"}}, {font, Font},
+ {x, 2}, {y, 0}, {width, Wlbl2}, {height, Hlbl2}]),
+ gs:radiobutton(Frm, [{label, {text, "Enable"}}, {font, Font},
+ {x, 10}, {y, Hlbl2},
+ {width, Wlbl2-10}, {height, Hlbl2},
+ {align, w}, {group, Grp},
+ {data, {trigger, enable}},
+ {select, true}]),
+ gs:radiobutton(Frm, [{label, {text, "Disable"}}, {font, Font},
+ {x, 10}, {y, Hlbl2*2},
+ {width, Wlbl2-10}, {height, Hlbl2},
+ {align, w}, {group, Grp},
+ {data, {trigger, disable}}]),
+ gs:radiobutton(Frm, [{label, {text, "Delete"}}, {font, Font},
+ {x, 10}, {y, Hlbl2*3},
+ {width, Wlbl2-10}, {height, Hlbl2},
+ {align, w}, {group, Grp},
+ {data, {trigger, delete}}]),
+
+ %% Add Ok and Cancel buttons
+ {Wbtn, Hbtn} = dbg_ui_win:min_size(["Ok","Cancel"], 70, 30),
+ Ybtn = Yacc + Pad + Hfrm + Pad,
+ Ok = gs:button(Win, [{x, Pad}, {y, Ybtn},
+ {width, Wbtn}, {height, Hbtn},
+ {label, {text,"Ok"}}, {font, Font}]),
+ gs:button(Win, [{x, W-Pad-Wbtn}, {y, Ybtn},
+ {width, Wbtn}, {height, Hbtn},
+ {label, {text,"Cancel"}}, {font, Font}]),
+
+ Hwin = Ybtn + Hbtn + Pad,
+ gs:config(Win, [{width, W}, {height, Hwin}, {map, true}]),
+
+ #winInfo{type=Type, win=Win,
+ entries=Entries, trigger=enable, ok=Ok}.
+
+%%--------------------------------------------------------------------
+%% update_functions(WinInfo, Funcs) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Funcs = [{Name, Arity}]
+%% Name = atom()
+%% Arity = integer()
+%%--------------------------------------------------------------------
+update_functions(WinInfo, Funcs) ->
+ Items = lists:map(fun([N, A]) -> io_lib:format("~p/~p", [N, A]) end,
+ Funcs),
+ gs:config(WinInfo#winInfo.listbox, [{items, Items},
+ {setfocus, true}]),
+ WinInfo#winInfo{funcs=Funcs}.
+
+%%--------------------------------------------------------------------
+%% handle_event(GSEvent, WinInfo) -> Command
+%% GSEvent = {gs, Id, Event, Data, Arg}
+%% WinInfo = #winInfo{}
+%% Command = ignore
+%% | stopped
+%% | {win, WinInfo}
+%% | {module, Mod}
+%% | {break, [[Mod, Line]], Action}
+%% | {break, [[Mod, Line, CMod, CFunc]], Action}
+%% | {break, [[Mod, Func, Arity]], Action}
+%%--------------------------------------------------------------------
+handle_event({gs, _Id, destroy, _Data, _Arg}, _WinInfo) ->
+ stopped;
+handle_event({gs, _Id, configure, _Data, [W, H|_]}, WinInfo) ->
+ gs:config(WinInfo#winInfo.packer, [{width, W-10}, {height, H-10}]),
+ gs:config(WinInfo#winInfo.cancel, [{x, W-80}]),
+ ignore;
+handle_event({gs, Ent, buttonpress, _,[N,X0,Y0|_]}, WinInfo) when N>1 ->
+ %% Right (middle) mouse button click in module entry, display a
+ %% menu containing all interpreted modules
+ Mods = int:interpreted(),
+ X = gs:read(Ent, x) + X0,
+ Y = gs:read(Ent, y) + Y0,
+ Menu = gs:menu(WinInfo#winInfo.win, [{post_at,{X,Y}}]),
+ lists:foreach(fun(Mod) ->
+ gs:menuitem(Menu, [{label,{text,Mod}},
+ {data,{module,Mod}}])
+ end,
+ Mods),
+ ignore;
+handle_event({gs, LB, keypress, window, [Key|_]}, WinInfo) ->
+ %% Used for functional break window, since listboxes for some
+ %% reason doesn't generate keypress events
+ if
+ Key/='Tab', Key/='Return' ->
+ ignore;
+ true ->
+ handle_event({gs, LB, click, listbox, ["Ok"]}, WinInfo)
+ end;
+handle_event({gs, Ent, keypress, Data, [Key|_]}, WinInfo) ->
+ case WinInfo#winInfo.type of
+ function when Key/='Tab', Key/='Return' ->
+ case gs:read(WinInfo#winInfo.listbox, items) of
+ [] -> ignore;
+ _Items ->
+ gs:config(WinInfo#winInfo.listbox, clear),
+ {win, WinInfo#winInfo{funcs=[]}}
+ end;
+ function -> % 'Return' | 'Tab' pressed in Module entry
+ case check_input(WinInfo#winInfo.entries) of
+ error -> ignore;
+ [Mod] -> {module, Mod}
+ end;
+ _Type when Key=='Tab'; Key=='Return' ->
+ case next_entry(Ent, WinInfo#winInfo.entries) of
+ last ->
+ gs:config(WinInfo#winInfo.ok, flash),
+ handle_event({gs, Ent, click, Data, ["Ok"]}, WinInfo);
+ Next ->
+ gs:config(Next, {setfocus, true}),
+ ignore
+ end;
+ _Type -> ignore
+ end;
+handle_event({gs, _Id, click, _Data, ["Ok"|_]}, WinInfo) ->
+ case check_input(WinInfo#winInfo.entries) of
+ error -> ignore;
+ Data when WinInfo#winInfo.type/=function ->
+ {break, [Data], WinInfo#winInfo.trigger};
+ [Mod] -> % Function break window
+ case gs:read(WinInfo#winInfo.listbox, selection) of
+ [] ->
+ {module, Mod};
+ IndexL ->
+ Funcs = WinInfo#winInfo.funcs,
+ Breaks =
+ lists:map(fun(Index) ->
+ Func = lists:nth(Index+1,
+ Funcs),
+ [Mod | Func]
+ end,
+ IndexL),
+ {break, Breaks, enable}
+ end
+ end;
+handle_event({gs, _Id, click, _Data, ["Cancel"|_]}, _WinInfo) ->
+ stopped;
+handle_event({gs, _Id, click, {trigger,Trigger}, _Arg}, WinInfo) ->
+ {win, WinInfo#winInfo{trigger=Trigger}};
+handle_event({gs, _Id, click, {module, Mod}, _Arg}, WinInfo) ->
+ {Ent, _DataType} = hd(WinInfo#winInfo.entries),
+ gs:config(Ent, {insert,{0,Mod}}),
+ ignore;
+handle_event(_GSEvent, _WinInfo) ->
+ ignore.
+
+check_input(Entries) ->
+ check_input(Entries, []).
+check_input([{Entry, Type} | Entries], Data) ->
+ Str = gs:read(Entry, text),
+ case erl_scan:string(Str) of
+ {ok, [{Type, _Line, Val}], _EndLine} ->
+ check_input(Entries, [Val|Data]);
+ _Error -> error
+ end;
+check_input([], Data) -> lists:reverse(Data).
+
+next_entry(Entry, [{Entry, _Type}]) ->
+ last;
+next_entry(Entry, [{Entry, _Type1}, {Next, _Type2}|_]) ->
+ Next;
+next_entry(Entry, [_|Entries]) ->
+ next_entry(Entry, Entries).
diff --git a/lib/debugger/src/dbg_ui_edit.erl b/lib/debugger/src/dbg_ui_edit.erl
new file mode 100644
index 0000000000..390e6acdb4
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_edit.erl
@@ -0,0 +1,91 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_edit).
+
+%% External exports
+-export([start/5]).
+
+%% Internal exports
+-export([init/6]).
+
+-record(state, {win, % term() Edit dialog window data
+ pid, % pid() Parent
+ prompt % atom()
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start(GS, Pos, Title, Prompt, {Type, Value})
+%% GS = graphics system identifier
+%% Pos = {X, Y}
+%% X = Y = integer()
+%% Title = string()
+%% Prompt = atom()
+%% Type = term | atom | float | integer | string
+%% Value = term()
+%%--------------------------------------------------------------------
+start(GS, Pos, Title, Prompt, Edit) ->
+ case dbg_ui_winman:is_started(Title) of
+ true -> ignore;
+ false ->
+ spawn(?MODULE, init, [self(), GS, Pos, Title, Prompt, Edit])
+ end.
+
+
+%%====================================================================
+%% Internal exports
+%%====================================================================
+
+init(Pid, GS, Pos, Title, Prompt, Edit) ->
+
+ %% Create edit dialog window
+ Win = dbg_ui_edit_win:create_win(GS, Pos, Title, Prompt, Edit),
+ Window = dbg_ui_edit_win:get_window(Win),
+ dbg_ui_winman:insert(Title, Window),
+ State = #state{win=Win, pid=Pid, prompt=Prompt},
+
+ loop(State).
+
+loop(State) ->
+ receive
+
+ %% From the GUI
+ GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs ->
+ Cmd = dbg_ui_edit_win:handle_event(GuiEvent,
+ State#state.win),
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the dbg_ui_winman process (Debugger window manager)
+ {dbg_ui_winman, update_windows_menu, _Data} ->
+ loop(State);
+ {dbg_ui_winman, destroy} ->
+ exit(normal)
+ end.
+
+gui_cmd(ignore, State) ->
+ State;
+gui_cmd(stopped, _State) ->
+ exit(normal);
+gui_cmd({edit, Value}, State) ->
+ State#state.pid ! {dbg_ui_edit, State#state.prompt, Value},
+ exit(normal).
diff --git a/lib/debugger/src/dbg_ui_edit_win.erl b/lib/debugger/src/dbg_ui_edit_win.erl
new file mode 100644
index 0000000000..badaf4bef4
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_edit_win.erl
@@ -0,0 +1,122 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_edit_win).
+
+%% External exports
+-export([create_win/5, get_window/1,
+ handle_event/2]).
+
+-record(winInfo, {window, % gsobj()
+ entry, % gsobj()
+ button, % gsobj()
+ type % atom()
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% create_win(GS, Pos, Title, Prompt, {Type, Value}) -> #winInfo{}
+%% GS = graphics system identifier
+%% Pos = {X, Y}
+%% X = Y = integer()
+%% Title = string()
+%% Prompt = atom()
+%% Type = term | atom | float | integer | string
+%% Value = term()
+%%--------------------------------------------------------------------
+create_win(GS, {X, Y}, Title, Prompt, {Type, Value}) ->
+ Pad=8,
+
+ Font = dbg_ui_win:font(normal),
+
+ %% Window
+ Win = gs:window(GS, [{title, Title}, {x, X}, {y, Y},
+ {destroy, true}]),
+
+ %% Label
+ {Wlbl, Hlbl} = dbg_ui_win:min_size([Prompt], 50, 30),
+ gs:label(Win, [{x, Pad}, {y, Pad}, {width, Wlbl}, {height, Hlbl},
+ {align, e}, {label, {text, Prompt}}, {font, Font}]),
+
+
+ %% Entry
+ {Went, _Hent} = dbg_ui_win:min_size([Value], 100, 20),
+ Ent = gs:entry(Win, [{x, Pad+Wlbl}, {y, Pad},
+ {width, Went}, {height, Hlbl},
+ {text, Value},
+ {keypress, true}]),
+
+ %% Ok and Cancel buttons
+ W = Pad + Wlbl + Went + Pad,
+ {Wbtn, Hbtn} = dbg_ui_win:min_size(["Cancel"], 70, 30),
+ Ybtn = Pad + Hlbl + Pad,
+ Btn = gs:button(Win, [{x, Pad}, {y, Ybtn},
+ {width, Wbtn}, {height, Hbtn},
+ {label, {text,"Ok"}}, {font, Font}]),
+ gs:button(Win, [{x, W-Pad-Wbtn}, {y, Ybtn},
+ {width, Wbtn}, {height, Hbtn},
+ {label, {text,"Cancel"}}, {font, Font}]),
+
+ H = Ybtn + Hbtn + Pad,
+ gs:config(Win, [{width, W}, {height, H}, {map, true}]),
+
+ #winInfo{window=Win, entry=Ent, button=Btn, type=Type}.
+
+%%--------------------------------------------------------------------
+%% get_window(WinInfo) -> Window
+%% WinInfo = #winInfo{}
+%% Window = gsobj()
+%%--------------------------------------------------------------------
+get_window(WinInfo) ->
+ WinInfo#winInfo.window.
+
+%%--------------------------------------------------------------------
+%% handle_event(GSEvent, WinInfo) -> Command
+%% GSEvent = {gs, Id, Event, Data, Arg}
+%% WinInfo = #winInfo{}
+%% Command = ignore
+%% | stopped
+%% | {edit, Value}
+%%--------------------------------------------------------------------
+handle_event({gs, _Id, destroy, _Data, _Arg}, _WinInfo) ->
+ stopped;
+handle_event({gs, Id, keypress, Data, ['Return'|_]}, WinInfo) ->
+ gs:config(WinInfo#winInfo.button, flash),
+ handle_event({gs, Id, click, Data, ["Ok"]}, WinInfo);
+handle_event({gs, _Id, click, _Data, ["Ok"|_]}, WinInfo) ->
+ Ent = WinInfo#winInfo.entry,
+ Str = gs:read(Ent, text),
+ Type = WinInfo#winInfo.type,
+ case erl_scan:string(Str) of
+ {ok, Tokens, _EndLine} when Type==term ->
+ case erl_parse:parse_term(Tokens++[{dot, 1}]) of
+ {ok, Value} -> {edit, Value};
+ _Error -> ignore
+ end;
+ {ok, [{Type, _Line, Value}], _EndLine} when Type/=term ->
+ {edit, Value};
+ _ ->
+ ignore
+ end;
+handle_event({gs, _Id, click, _Data, ["Cancel"|_]}, _WinInfo) ->
+ stopped;
+handle_event(_GSEvent, _WinInfo) ->
+ ignore.
diff --git a/lib/debugger/src/dbg_ui_filedialog_win.erl b/lib/debugger/src/dbg_ui_filedialog_win.erl
new file mode 100644
index 0000000000..f7d76076a5
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_filedialog_win.erl
@@ -0,0 +1,338 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(dbg_ui_filedialog_win).
+
+%% External exports
+-export([create_win/6, create_win/7, get_window/1,
+ tag/2,
+ handle_event/2]).
+
+-record(winInfo, {window, % gsobj()
+ extra, % fun()
+ cwd, % string()
+ pattern % string()
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% create_win(GS, Title, Pos, Mode, Filter, Extra)
+%% create_win(GS, Title, Pos, Mode, Filter, Extra, FileName) -> #winInfo{}
+%% GS = term()
+%% Title = string()
+%% Pos = {X,Y}
+%% Mode = normal | multiselect
+%% Filter = string() File name that may include * symbols.
+%% Extra = fun(File) -> {true, tag} | true | {error, term()}
+%% FileName = string() Suggested file name when saving
+%%--------------------------------------------------------------------
+create_win(GS, Title, {X,Y}, Mode, Filter, Extra) ->
+ create_win(GS, Title, {X,Y}, Mode, Filter, Extra, null).
+create_win(GS, Title, {X,Y}, Mode, Filter, Extra, FileName) ->
+ Pad = 8,
+ Wlb = 480, Hlb = 130,
+
+ Font = dbg_ui_win:font(normal),
+
+ {Wlbl, Hlbl} = dbg_ui_win:min_size(["Directories"], 80, 20),
+ {Wbtn, Hbtn} = dbg_ui_win:min_size(["Filter","Cancel"], 70, 30),
+
+ %% Window
+ Win = gs:window(GS, [{title,Title}, {x, X}, {y,Y}, {destroy,true}]),
+
+ %% 'Filter' label and entry (for selecting directory)
+ gs:label(Win, [{label, {text,"Filter"}}, {font, Font}, {align, sw},
+ {x, Pad+2}, {y, Pad}, {width,Wlbl}, {height,Hlbl}]),
+ gs:entry('Filter', Win, [{x, Pad}, {y, Pad+Hlbl},
+ {width, Wlb}, {height, Hbtn},
+ {keypress, true}]),
+
+ %% Listboxes (showing directories and files)
+ Xmid = Pad + Wlb/2,
+ Y2 = Pad + Hlbl + Hbtn + Pad,
+ gs:label(Win, [{label, {text,"Directories"}},
+ {font, Font}, {align, sw},
+ {x, Pad+2}, {y, Y2},
+ {width, Wlbl}, {height, Hlbl}]),
+ gs:label(Win, [{label, {text,"Files"}},
+ {font, Font}, {align, sw},
+ {x, Xmid+Pad/2+2}, {y, Y2},
+ {width, Wlbl}, {height, Hlbl}]),
+ gs:listbox('Dirs', Win, [{x, Pad}, {y, Y2+Hlbl},
+ {width, Wlb/2-Pad/2}, {height, Hlb},
+ {vscroll, right},
+ {click, true}, {doubleclick, true}]),
+ gs:listbox('Files', Win, [{x, Xmid+Pad/2}, {y, Y2+Hlbl},
+ {width, Wlb/2-Pad/2}, {height, Hlb},
+ {vscroll, right},
+ {click, true}, {doubleclick, true}]),
+
+ %% 'Selection' label and entry (for selecting file)
+ Y3 = Y2 + Hlbl + Hlb,
+ gs:label(Win, [{label, {text,"Selection"}}, {font,Font}, {align,sw},
+ {x, Pad+2}, {y, Y3}, {width, Wlbl}, {height, Hlbl}]),
+ gs:entry('Selection', Win, [{x, Pad}, {y, Y3+Hlbl},
+ {width, Wlb}, {height, Hbtn},
+ {keypress, true}]),
+
+ %% Buttons
+ Y4 = Y3 + Hlbl + Hbtn + Pad,
+ Wb = Wlb - Wbtn,
+ Opts = [{y, Y4}, {width, Wbtn}, {height, Hbtn}, {font, Font}],
+ case Mode of
+ normal ->
+ gs:button(Win, [{label, {text,"Ok"}}, {x, Pad},
+ {data, select} | Opts]),
+ gs:button(Win, [{label, {text,"Filter"}}, {x, Wlb/2-Wbtn/2},
+ {data, filter} | Opts]),
+ gs:button(Win, [{label, {text,"Cancel"}}, {x, Pad+Wb},
+ {data, done} | Opts]);
+ multiselect ->
+ gs:button(Win, [{label, {text,"Choose"}}, {x, Pad},
+ {data, select} | Opts]),
+ gs:button(Win, [{label, {text,"All"}}, {x, Pad+Wb/3},
+ {data, multiselect} | Opts]),
+ gs:button(Win, [{label, {text,"Filter"}}, {x, Pad+2*Wb/3},
+ {data, filter} | Opts]),
+ gs:button(Win, [{label, {text,"Done"}}, {x, Pad+Wb},
+ {data, done} | Opts])
+ end,
+
+ %% Insert contents
+ {ok, Home} = file:get_cwd(),
+ {Cwd, Pattern} = update_win(Filter, Extra, Home),
+ if
+ is_list(FileName) ->
+ gs:config('Selection', {text, filename:join(Cwd,FileName)});
+ true -> ignore
+ end,
+
+ Wwin = Pad + Wlb + Pad,
+ Hwin = Y4 + Hbtn + Pad,
+ gs:config(Win, [{width, Wwin}, {height, Hwin}, {map, true}]),
+
+ #winInfo{window=Win, extra=Extra, cwd=Cwd, pattern=Pattern}.
+
+%%--------------------------------------------------------------------
+%% get_window(WinInfo) -> Window
+%% WinInfo = #winInfo{}
+%% Window = gsobj()
+%%--------------------------------------------------------------------
+get_window(WinInfo) ->
+ WinInfo#winInfo.window.
+
+%%--------------------------------------------------------------------
+%% tag(WinInfo, File)
+%% WinInfo = #winInfo{}
+%% File = string()
+%%--------------------------------------------------------------------
+tag(WinInfo, File0) ->
+ File = relfile(WinInfo#winInfo.cwd, File0),
+ case member(File, gs:read('Files', items)) of
+ {true, Index} -> gs:config('Files', {change, {Index, tag(File)}});
+ false -> ignore
+ end.
+
+tag(Str) -> [$*|Str].
+untag([$*|Str]) -> Str;
+untag([$(|Str]) -> [$)|Rts] = lists:reverse(Str),lists:reverse(Rts);
+untag(Str) -> Str.
+
+member(E, L) -> member(E, L, 0).
+member(E, [E|_], I) -> {true, I};
+member(E, [_|T], I) -> member(E, T, I+1);
+member(_E, [], _I) -> false.
+
+%%--------------------------------------------------------------------
+%% handle_event(GSEvent, WinInfo) -> Command
+%% GSEvent = {gs, Id, Event, Data, Arg}
+%% WinInfo = #winInfo{}
+%% Command = ignore
+%% | {stopped, Dir}
+%% | {win, WinInfo}
+%% | {select, File} | {multiselect, Dir, FileNames}
+%%--------------------------------------------------------------------
+handle_event({gs, _Id, destroy, _Data, _Args}, WinInfo) ->
+ {stopped, WinInfo#winInfo.cwd};
+
+handle_event({gs, 'Filter', keypress, _Data, ['Return'|_]}, WinInfo) ->
+ handle_event({gs, null, click, filter, null}, WinInfo);
+handle_event({gs, 'Selection', keypress, _Data, ['Return'|_]}, WinInfo) ->
+ handle_event({gs, null, click, select, null}, WinInfo);
+
+handle_event({gs, 'Dirs', click, _Data, [0,"..",true|_]}, WinInfo) ->
+ Filter = filename:join(filename:dirname(WinInfo#winInfo.cwd),
+ WinInfo#winInfo.pattern),
+ gs:config('Filter', {text, Filter}),
+ ignore;
+handle_event({gs, 'Dirs', click, _Data, [_Index,Str,true|_]}, WinInfo) ->
+ Filter = filename:join([WinInfo#winInfo.cwd, Str,
+ WinInfo#winInfo.pattern]),
+ gs:config('Filter', {text, Filter}),
+ ignore;
+handle_event({gs, 'Dirs', doubleclick, _Data, _Arg}, WinInfo) ->
+ handle_event({gs, null, click, filter, null}, WinInfo);
+
+handle_event({gs, 'Files', click, _Data, [_Index,Str,true|_]}, WinInfo) ->
+ Selection = filename:join(WinInfo#winInfo.cwd, untag(Str)),
+ gs:config('Selection', {text, Selection}),
+ ignore;
+handle_event({gs, 'Files', doubleclick, _Data, _Arg}, WinInfo) ->
+ handle_event({gs, null, click, select, null}, WinInfo);
+
+handle_event({gs, _Id, click, select, _Arg}, _WinInfo) ->
+ {select, gs:read('Selection', text)};
+handle_event({gs, _Id, click, multiselect, _Arg}, WinInfo) ->
+ Files = lists:map(fun(File) -> untag(File) end,
+ gs:read('Files', items)),
+ {multiselect, WinInfo#winInfo.cwd, Files};
+handle_event({gs, _Id, click, filter, _Arg}, WinInfo) ->
+ {Cwd, Pattern} = update_win(gs:read('Filter', text),
+ WinInfo#winInfo.extra,
+ WinInfo#winInfo.cwd),
+ {win, WinInfo#winInfo{cwd=Cwd, pattern=Pattern}};
+handle_event({gs, _Id, click, done, _Arg}, WinInfo) ->
+ {stopped, WinInfo#winInfo.cwd};
+
+handle_event(_GSEvent, _WinInfo) ->
+ ignore.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+update_win(Filter, ExtraFilter, Prev) ->
+ {Res, {Filter2, Cwd, FilePattern}} = check_filter(Filter, Prev),
+
+ Dirs = [".." | get_subdirs(Cwd)],
+
+ gs:config('Filter', {text, Filter2}),
+ gs:config('Dirs', {items, Dirs}),
+ gs:config('Selection', {text, Cwd}),
+
+ case Res of
+ ok ->
+ Matching = lists:sort(filelib:wildcard(Filter2, erl_prim_loader)),
+ Files = extra_filter(Matching, Cwd, ExtraFilter),
+ gs:config('Files', {items, Files});
+ error ->
+ gs:config('Files', beep)
+ end,
+
+ {Cwd, FilePattern}.
+
+%% check_filter(Filter, Prev) -> {ok, Res} | {error, Res}
+%% Res = {Filter, Cwd, FilePattern}
+%% Filter = Prev = Cwd = FilePattern = string()
+check_filter(Filter0, Prev) ->
+ Filter = case filename:pathtype(Filter0) of
+ absolute -> Filter0;
+ _Relative -> filename:absname(Filter0, Prev)
+ end,
+ Comps = filename:split(Filter),
+ Last = lists:last(Comps),
+ FilePattern = case is_pattern(Last) of
+ true -> Last;
+ false -> "*"
+ end,
+ {Cwd, Rest} = max_existing(Comps),
+ case Rest of
+ [] ->
+ %% Filter = existing file or directory
+ Res = case filelib:is_dir(Filter, erl_prim_loader) of
+ true -> {filename:join(Filter, "*"), Filter, "*"};
+ false -> {Filter, filename:dirname(Filter),
+ filename:basename(Filter)}
+ end,
+ {ok, Res};
+ [FilePattern] ->
+ %% Filter = existing dir and valid pattern
+ {ok, {Filter, Cwd, FilePattern}};
+ Comps ->
+ %% Filter = garbage
+ {error, {Prev, Prev, "*"}};
+ [Name|_Names] ->
+ %% Filter = existing dir ++ pattern or non-existing file/dir
+ case is_pattern(Name) of
+ true -> {ok, {Filter, Cwd, FilePattern}};
+ false -> {error, {Cwd, Cwd, ""}}
+ end
+ end.
+
+max_existing([Name | Names]) ->
+ case filelib:is_file(Name, erl_prim_loader) of
+ true -> max_existing(Name, Names);
+ false -> {[], [Name | Names]}
+ end.
+max_existing(Dir, [Name | Names]) ->
+ Dir2 = filename:join(Dir, Name),
+ case filelib:is_file(Dir2, erl_prim_loader) of
+ true when Names==[] -> {Dir2, []};
+ true -> max_existing(Dir2, Names);
+ false -> {Dir, [Name | Names]}
+ end.
+
+is_pattern(Str) ->
+ lists:member($*, Str).
+
+extra_filter([File|Files], Dir, Fun) ->
+ case Fun(File) of
+ true ->
+ [relfile(Dir, File) | extra_filter(Files, Dir, Fun)];
+ {true,tag} ->
+ [[$*|relfile(Dir,File)] | extra_filter(Files, Dir, Fun)];
+ {true,disable} ->
+ [[$(|relfile(Dir,File)]++[$)] | extra_filter(Files, Dir, Fun)];
+ {error, _Reason} -> extra_filter(Files, Dir, Fun)
+ end;
+extra_filter([], _Dir, _Fun) -> [].
+
+get_subdirs(Dir) ->
+ case erl_prim_loader:list_dir(Dir) of
+ {ok, FileNames} ->
+ X = lists:filter(fun(FileName) ->
+ File = filename:join(Dir, FileName),
+ filelib:is_dir(File, erl_prim_loader)
+ end,
+ FileNames),
+ lists:sort(X);
+ _Error ->
+ []
+ end.
+
+%% Return the "remainder" of a file name relative a dir name, examples:
+%% relfile("/home/gunilla", "/home/gunilla/m.erl") -> "m.erl"
+%% relfile("/home/gunilla/dir", "/home/gunilla/dir/m.erl") -> "dir/m.erl"
+%% relfile("/home/gunilla", "/home/arne/m.erl") -> "/home/arne/m.erl"
+relfile(Dir, File) ->
+ case compare(Dir, File) of
+ error -> File;
+ RelFile -> RelFile
+ end.
+
+compare([_|Dir], [_|File]) ->
+ compare(Dir, File);
+compare([], [$/|File]) ->
+ File;
+compare(_, _) ->
+ error.
+
diff --git a/lib/debugger/src/dbg_ui_interpret.erl b/lib/debugger/src/dbg_ui_interpret.erl
new file mode 100644
index 0000000000..952e73b537
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_interpret.erl
@@ -0,0 +1,161 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_interpret).
+
+-include_lib("kernel/include/file.hrl").
+
+%% External exports
+-export([start/4]).
+
+%% Internal exports
+-export([init/6]).
+
+-record(state, {gs, % term() Graphics system id
+ win, % term() Interpret dialog window data
+ monitor, % pid() Monitor pid
+ mode % local | global
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start(GS, Pos, Dir, Mode)
+%% GS = Graphics system id
+%% Dir = string()
+%% Pos = {X,Y}
+%% Mode = local | global
+%%--------------------------------------------------------------------
+start(GS, Pos, Dir, Mode) ->
+ Title = "Interpret Dialog",
+ case dbg_ui_winman:is_started(Title) of
+ true -> ignore;
+ false ->
+ spawn(?MODULE, init, [self(), GS, Pos, Title, Dir, Mode])
+ end.
+
+%%====================================================================
+%% Internal exports
+%%====================================================================
+
+init(Monitor, GS, Pos, Title, Dir, Mode) ->
+ Filter = filename:join(Dir, "*.erl"),
+ Extra = fun(File) ->
+ case int:interpretable(File) of
+ true ->
+ ModS = filename:basename(File, ".erl"),
+ Mod = list_to_atom(ModS),
+ case int:file(Mod) of
+ File -> {true, tag};
+ _ -> true % {error,not_loaded} | File2
+ end;
+ _Error -> {true,disable}
+ end
+ end,
+
+ %% Create interpret dialog window
+ Win = dbg_ui_filedialog_win:create_win(GS, Title, Pos, multiselect,
+ Filter, Extra),
+ Window = dbg_ui_filedialog_win:get_window(Win),
+ dbg_ui_winman:insert(Title, Window),
+
+ State = #state{gs=GS, win=Win, monitor=Monitor, mode=Mode},
+ loop(State).
+
+
+%%====================================================================
+%% Main loop and message handling
+%%====================================================================
+
+loop(State) ->
+ receive
+
+ %% From the GUI
+ GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs ->
+ Cmd = dbg_ui_filedialog_win:handle_event(GuiEvent,
+ State#state.win),
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the dbg_ui_winman process (Debugger window manager)
+ {dbg_ui_winman, update_windows_menu, _Data} ->
+ loop(State);
+ {dbg_ui_winman, destroy} ->
+ exit(normal)
+ end.
+
+gui_cmd(ignore, State) ->
+ State;
+gui_cmd({stopped, Dir}, State) ->
+ State#state.monitor ! {dbg_ui_interpret, Dir},
+ exit(normal);
+gui_cmd({win, Win}, State) ->
+ State#state{win=Win};
+gui_cmd({select, File}, State) ->
+ Res = case State#state.mode of
+ local -> int:i(File);
+ global -> int:ni(File)
+ end,
+
+ case Res of
+ %% Interpretation succeeded, tag the file name
+ {module, _Mod} ->
+ dbg_ui_filedialog_win:tag(State#state.win, File);
+
+ %% Interpretation failed
+ error ->
+ Error = format_error(int:interpretable(File)),
+ Msg = ["Error when interpreting:", File, Error],
+ Window = dbg_ui_filedialog_win:get_window(State#state.win),
+ tool_utils:notify(Window, Msg)
+ end,
+ State;
+gui_cmd({multiselect, Dir, FileNames}, State) ->
+ interpret_all(State, Dir, FileNames),
+ State.
+
+interpret_all(State, Dir, [File0|Files]) ->
+ File = filename:join(Dir, File0),
+ Res = case State#state.mode of
+ local -> int:i(File);
+ global -> int:ni(File)
+ end,
+ case Res of
+ {module, _Mod} ->
+ dbg_ui_filedialog_win:tag(State#state.win, File),
+ interpret_all(State, Dir, Files);
+ error ->
+ Window = dbg_ui_filedialog_win:get_window(State#state.win),
+ Error = format_error(int:interpretable(File)),
+ Msg = ["Error when interpreting:", File, Error,
+ "Ok to continue?"],
+ case tool_utils:confirm(Window, Msg) of
+ ok -> interpret_all(State, Dir, Files);
+ cancel -> true
+ end
+ end;
+interpret_all(_State, _Dir, []) ->
+ true.
+
+format_error({error,no_beam}) -> "No BEAM file";
+format_error({error,no_debug_info}) -> "No debug_info in BEAM file";
+format_error({error,badarg}) -> "File does not exist";
+format_error({error,{app,App}}) ->
+ "Cannot interpret "++atom_to_list(App)++" modules".
diff --git a/lib/debugger/src/dbg_ui_mon.erl b/lib/debugger/src/dbg_ui_mon.erl
new file mode 100644
index 0000000000..63cc9b66d1
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_mon.erl
@@ -0,0 +1,744 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_mon).
+
+-include_lib("kernel/include/file.hrl").
+
+%% External exports
+-export([start/2, stop/0]).
+
+-define(TRACEWIN, ['Button Area', 'Evaluator Area', 'Bindings Area']).
+-define(BACKTRACE, 100).
+
+-record(pinfo, {pid, % pid()
+ status % break | exit | idle | running | waiting
+ }).
+
+-record(state, {mode, % local | global
+ starter, % bool() 'true' if int was started by me
+
+ gs, % term() Graphics system id
+ win, % term() Monitor window data
+ focus, % undefined | #pinfo{} Process in focus
+ coords, % {X,Y} Mouse pointer position
+
+ intdir, % string() Default dir
+ pinfos, % [#pinfo{}] Debugged processes
+
+ tracewin, % [Area] Areas shown in trace window
+ backtrace, % integer() Number of call frames to fetch
+
+ attach, % false | {Flags, Function}
+
+ sfile, % default | string() Settings file
+ changed % boolean() Settings have been changed
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start(Mode, SFile) -> {ok, Pid} | {error, Reason}
+%% Mode = local | global
+%% SFile = string() | default Settings file
+%% Pid = pid()
+%% Reason = {already_started,Pid} | term()
+%%--------------------------------------------------------------------
+start(Mode, SFile) ->
+ case whereis(?MODULE) of
+ undefined ->
+ CallingPid = self(),
+ Pid = spawn(fun () -> init(CallingPid, Mode, SFile) end),
+ receive
+ {initialization_complete, Pid} ->
+ {ok, Pid};
+ Error ->
+ Error
+ end;
+
+ Pid ->
+ {error, {already_started,Pid}}
+ end.
+
+%%--------------------------------------------------------------------
+%% stop() -> ok
+%%--------------------------------------------------------------------
+stop() ->
+ case whereis(?MODULE) of
+ undefined ->
+ ok;
+ Pid ->
+ Flag = process_flag(trap_exit, true),
+ link(Pid),
+ Pid ! stop,
+ receive
+ {'EXIT', Pid, stop} ->
+ process_flag(trap_exit, Flag),
+ ok
+ end
+ end.
+
+
+%%====================================================================
+%% Initialization
+%%====================================================================
+
+init(CallingPid, Mode, SFile) ->
+ register(?MODULE, self()),
+
+ %% Graphics system
+ case catch dbg_ui_mon_win:init() of
+ {'EXIT', Reason} ->
+ CallingPid ! {error, Reason};
+ GS ->
+ init2(CallingPid, Mode, SFile, GS)
+ end.
+
+init2(CallingPid, Mode, SFile, GS) ->
+
+ %% Start Int if necessary and subscribe to information from it
+ Bool = case int:start() of
+ {ok, _Int} -> true;
+ {error, {already_started, _Int}} -> false
+ end,
+ int:subscribe(),
+
+ %% Start other necessary stuff
+ dbg_ui_winman:start(), % Debugger window manager
+
+ %% Create monitor window
+ Title = "Monitor",
+ Win = dbg_ui_mon_win:create_win(GS, Title, menus()),
+ Window = dbg_ui_mon_win:get_window(Win),
+ dbg_ui_winman:insert(Title, Window),
+
+ %% Initial process state
+ State1 = #state{mode = Mode,
+ starter = Bool,
+
+ gs = GS,
+ win = Win,
+ focus = undefined,
+ coords = {0,0},
+
+ intdir = element(2, file:get_cwd()),
+ pinfos = [],
+
+ sfile = SFile,
+ changed = false
+ },
+
+ State2 = init_options(?TRACEWIN, % Trace Window
+ int:auto_attach(), % Auto Attach
+ int:stack_trace(), % Stack Trace
+ ?BACKTRACE, % Back Trace Size
+ State1),
+
+ State3 = init_contents(int:interpreted(), % Modules
+ int:all_breaks(), % Breakpoints
+ int:snapshot(), % Processes
+ State2),
+
+ %% Disable/enable functionality according to process in focus (none)
+ gui_enable_functions(State3#state.focus),
+
+ CallingPid ! {initialization_complete, self()},
+
+ if
+ SFile==default ->
+ loop(State3);
+ true ->
+ loop(load_settings(SFile, State3))
+ end.
+
+init_options(TraceWin, AutoAttach, StackTrace, BackTrace, State) ->
+ lists:foreach(fun(Area) ->
+ dbg_ui_mon_win:select(Area, true)
+ end,
+ TraceWin),
+
+ case AutoAttach of
+ false -> ignore;
+ {Flags, _Function} ->
+ dbg_ui_mon_win:show_option(State#state.win,
+ auto_attach, Flags),
+ lists:foreach(fun(Flag) ->
+ dbg_ui_mon_win:select(map(Flag), true)
+ end,
+ Flags)
+ end,
+
+ dbg_ui_mon_win:show_option(State#state.win,
+ stack_trace, StackTrace),
+ dbg_ui_mon_win:select(map(StackTrace), true),
+
+ dbg_ui_mon_win:show_option(State#state.win, back_trace, BackTrace),
+
+ State#state{tracewin=TraceWin, backtrace=BackTrace}.
+
+init_contents(Mods, Breaks, Processes, State) ->
+ Win2 =
+ lists:foldl(fun(Mod, Win) ->
+ dbg_ui_mon_win:add_module(Win,'Module',Mod)
+ end,
+ State#state.win,
+ Mods),
+
+ Win3 =
+ lists:foldl(fun(Break, Win) ->
+ dbg_ui_mon_win:add_break(Win,'Break',Break)
+ end,
+ Win2,
+ Breaks),
+
+ lists:foldl(fun(PidTuple, State0) ->
+ int_cmd({new_process, PidTuple}, State0)
+ end,
+ State#state{win=Win3},
+ Processes).
+
+
+%%====================================================================
+%% Main loop and message handling
+%%====================================================================
+
+loop(State) ->
+ receive
+
+ stop ->
+ gui_cmd(stopped, State);
+
+ %% From the GUI
+ GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs ->
+ Cmd = dbg_ui_mon_win:handle_event(GuiEvent,State#state.win),
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the interpreter process
+ {int, Cmd} ->
+ State2 = int_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the dbg_ui_interpret process
+ {dbg_ui_interpret, Dir} ->
+ loop(State#state{intdir=Dir});
+
+ %% From the dbg_ui_edit process
+ {dbg_ui_edit, 'Backtrace:', BackTrace} ->
+ dbg_ui_mon_win:show_option(State#state.win,
+ back_trace, BackTrace),
+ loop(State#state{backtrace=BackTrace});
+
+ %% From the dbg_ui_settings process
+ {dbg_ui_settings, SFile, Action} ->
+ State2 = case Action of
+ load -> load_settings(SFile, State);
+ save -> save_settings(SFile, State)
+ end,
+ loop(State2);
+
+ %% From the dbg_ui_winman process (Debugger window manager)
+ {dbg_ui_winman, update_windows_menu, Data} ->
+ dbg_ui_winman:update_windows_menu(Data),
+ loop(State)
+ end.
+
+%%--Commands from the GUI---------------------------------------------
+%% Act upon a command from the GUI. In most cases, it is only necessary
+%% to call a relevant int-function. int will then report when the action
+%% has been taken.
+
+gui_cmd(ignore, State) ->
+ State;
+gui_cmd(stopped, State) ->
+ if
+ State#state.starter==true -> int:stop();
+ true -> int:auto_attach(false)
+ end,
+ exit(stop);
+gui_cmd({coords, Coords}, State) ->
+ State#state{coords=Coords};
+
+gui_cmd({shortcut, Key}, State) ->
+ case shortcut(Key) of
+ {always, Cmd} -> gui_cmd(Cmd, State);
+ {if_enabled, Cmd} ->
+ case dbg_ui_mon_win:is_enabled(Cmd) of
+ true -> gui_cmd(Cmd, State);
+ false -> State
+ end;
+ false -> State
+ end;
+
+%% File Menu
+gui_cmd('Load Settings...', State) ->
+ Window = dbg_ui_mon_win:get_window(State#state.win),
+ dbg_ui_settings:start(Window, State#state.coords,
+ load, State#state.sfile),
+ State;
+gui_cmd('Save Settings...', State) ->
+ Window = dbg_ui_mon_win:get_window(State#state.win),
+ dbg_ui_settings:start(Window, State#state.coords,
+ save, State#state.sfile),
+ State;
+gui_cmd('Exit', State) ->
+ gui_cmd(stopped, State);
+
+%% Edit Menu
+gui_cmd('Refresh', State) ->
+ int:clear(),
+ Win = dbg_ui_mon_win:clear_processes(State#state.win),
+ gui_enable_functions(undefined),
+ State2 = State#state{win=Win, focus=undefined, pinfos=[]},
+ lists:foldl(fun(PidTuple, S) ->
+ int_cmd({new_process,PidTuple}, S)
+ end,
+ State2,
+ int:snapshot());
+gui_cmd('Kill All', State) ->
+ lists:foreach(fun(PInfo) ->
+ case PInfo#pinfo.status of
+ exit -> ignore;
+ _Status -> exit(PInfo#pinfo.pid, kill)
+ end
+ end,
+ State#state.pinfos),
+ State;
+
+%% Module Menu
+gui_cmd('Interpret...', State) ->
+ dbg_ui_interpret:start(State#state.gs, State#state.coords,
+ State#state.intdir, State#state.mode),
+ State;
+gui_cmd('Delete All Modules', State) ->
+ lists:foreach(fun(Mod) -> int:nn(Mod) end, int:interpreted()),
+ State;
+gui_cmd({module, Mod, What}, State) ->
+ case What of
+ delete -> int:nn(Mod);
+ view -> dbg_ui_view:start(State#state.gs, Mod)
+ end,
+ State;
+
+%% Process Menu
+gui_cmd('Step', State) ->
+ int:step((State#state.focus)#pinfo.pid),
+ State;
+gui_cmd('Next', State) ->
+ int:next((State#state.focus)#pinfo.pid),
+ State;
+gui_cmd('Continue', State) ->
+ int:continue((State#state.focus)#pinfo.pid),
+ State;
+gui_cmd('Finish ', State) ->
+ int:finish((State#state.focus)#pinfo.pid),
+ State;
+gui_cmd('Attach', State) ->
+ Pid = (State#state.focus)#pinfo.pid,
+ case dbg_ui_winman:is_started(dbg_ui_trace:title(Pid)) of
+ true -> ignore;
+ false -> int:attach(Pid, trace_function(State))
+ end,
+ State;
+gui_cmd('Kill', State) ->
+ exit((State#state.focus)#pinfo.pid, kill),
+ State;
+
+%% Break Menu
+gui_cmd('Line Break...', State) ->
+ dbg_ui_break:start(State#state.gs, State#state.coords, line),
+ State;
+gui_cmd('Conditional Break...', State) ->
+ dbg_ui_break:start(State#state.gs, State#state.coords, conditional),
+ State;
+gui_cmd('Function Break...', State) ->
+ dbg_ui_break:start(State#state.gs, State#state.coords, function),
+ State;
+gui_cmd('Enable All', State) ->
+ Breaks = int:all_breaks(),
+ lists:foreach(fun ({{Mod, Line}, _Options}) ->
+ int:enable_break(Mod, Line)
+ end,
+ Breaks),
+ State;
+gui_cmd('Disable All', State) ->
+ Breaks = int:all_breaks(),
+ lists:foreach(fun ({{Mod, Line}, _Options}) ->
+ int:disable_break(Mod, Line)
+ end,
+ Breaks),
+ State;
+gui_cmd('Delete All', State) ->
+ int:no_break(),
+ State;
+gui_cmd({break, {Mod, Line}, What}, State) ->
+ case What of
+ delete -> int:delete_break(Mod, Line);
+ {status, inactive} -> int:disable_break(Mod, Line);
+ {status, active} -> int:enable_break(Mod, Line);
+ {trigger, Action} -> int:action_at_break(Mod, Line, Action)
+ end,
+ State;
+
+%% Options Commands
+gui_cmd({'Trace Window', TraceWin}, State) ->
+ State2 = State#state{tracewin=TraceWin},
+ case State#state.attach of
+ false -> ignore;
+ {Flags, {dbg_ui_trace, start, StartFlags}} ->
+ case trace_function(State2) of
+ {_, _, StartFlags} -> ignore;
+ NewFunction -> % {_, _, NewStartFlags}
+ int:auto_attach(Flags, NewFunction)
+ end;
+ _AutoAttach -> ignore
+ end,
+ State2;
+gui_cmd({'Auto Attach', When}, State) ->
+ if
+ When==[] -> int:auto_attach(false);
+ true ->
+ Flags = lists:map(fun(Name) -> map(Name) end, When),
+ int:auto_attach(Flags, trace_function(State))
+ end,
+ State;
+gui_cmd({'Stack Trace', [Name]}, State) ->
+ int:stack_trace(map(Name)),
+ State;
+gui_cmd('Back Trace Size...', State) ->
+ dbg_ui_edit:start(State#state.gs, State#state.coords, "Backtrace",
+ 'Backtrace:', {integer, State#state.backtrace}),
+ State;
+
+%% Help Menu
+gui_cmd('Debugger', State) ->
+ HelpFile = filename:join([code:lib_dir(debugger),
+ "doc", "html", "part_frame.html"]),
+ Window = dbg_ui_mon_win:get_window(State#state.win),
+ tool_utils:open_help(Window, HelpFile),
+ State;
+
+gui_cmd({focus, Pid, Win}, State) ->
+ {value, PInfo} =
+ lists:keysearch(Pid, #pinfo.pid, State#state.pinfos),
+ gui_enable_functions(PInfo),
+ State#state{win=Win, focus=PInfo};
+gui_cmd(default, State) ->
+ case lists:member('Attach', menus(enabled, State#state.focus)) of
+ true -> gui_cmd('Attach', State);
+ false -> State
+ end.
+
+%%--Commands from the interpreter-------------------------------------
+
+int_cmd({interpret, Mod}, State) ->
+ Win = dbg_ui_mon_win:add_module(State#state.win, 'Module', Mod),
+ State#state{win=Win};
+int_cmd({no_interpret, Mod}, State) ->
+ Win = dbg_ui_mon_win:delete_module(State#state.win, Mod),
+ State#state{win=Win};
+
+int_cmd({new_process, {Pid, Function, Status, Info}}, State) ->
+
+ %% Create record with information about the process
+ Name = registered_name(Pid),
+ PInfo = #pinfo{pid=Pid, status=Status},
+
+ %% Update window
+ Win = dbg_ui_mon_win:add_process(State#state.win,
+ Pid, Name, Function, Status, Info),
+
+ %% Store process information
+ PInfos = State#state.pinfos ++ [PInfo],
+ State#state{win=Win, pinfos=PInfos};
+int_cmd({new_status, Pid, Status, Info}, State) ->
+
+ %% Find stored information about the process
+ PInfos = State#state.pinfos,
+ {value, PInfo} = lists:keysearch(Pid, #pinfo.pid, PInfos),
+
+ %% Update process information
+ PInfo2 = PInfo#pinfo{status=Status},
+ PInfos2 = lists:keyreplace(Pid, #pinfo.pid, PInfos, PInfo2),
+ State2 = State#state{pinfos=PInfos2},
+
+ %% Update window
+ dbg_ui_mon_win:update_process(State2#state.win, Pid, Status, Info),
+ case State2#state.focus of
+ #pinfo{pid=Pid} ->
+ gui_enable_functions(PInfo2),
+ State2#state{focus=PInfo2};
+ _ ->
+ State2
+ end;
+
+int_cmd({new_break, Break}, State) ->
+ Win = dbg_ui_mon_win:add_break(State#state.win, 'Break', Break),
+ State#state{win=Win};
+int_cmd({delete_break, Point}, State) ->
+ Win = dbg_ui_mon_win:delete_break(State#state.win, Point),
+ State#state{win=Win};
+int_cmd({break_options, Break}, State) ->
+ dbg_ui_mon_win:update_break(State#state.win, Break),
+ State;
+int_cmd(no_break, State) ->
+ Win = dbg_ui_mon_win:clear_breaks(State#state.win),
+ State#state{win=Win};
+int_cmd({no_break, Mod}, State) ->
+ Win = dbg_ui_mon_win:clear_breaks(State#state.win, Mod),
+ State#state{win=Win};
+
+int_cmd({auto_attach, AutoAttach}, State) ->
+ OnFlags = case AutoAttach of
+ false -> [];
+ {Flags, _Function} -> Flags
+ end,
+ OffFlags = [init, exit, break] -- OnFlags,
+ dbg_ui_mon_win:show_option(State#state.win, auto_attach, OnFlags),
+ lists:foreach(fun(Flag) ->
+ dbg_ui_mon_win:select(map(Flag), true)
+ end,
+ OnFlags),
+ lists:foreach(fun(Flag) ->
+ dbg_ui_mon_win:select(map(Flag), false)
+ end,
+ OffFlags),
+ State#state{attach=AutoAttach};
+int_cmd({stack_trace, Flag}, State) ->
+ dbg_ui_mon_win:show_option(State#state.win, stack_trace, Flag),
+ dbg_ui_mon_win:select(map(Flag), true),
+ State.
+
+
+%%====================================================================
+%% GUI auxiliary functions
+%%====================================================================
+
+menus() ->
+ [{'File', [{'Load Settings...', 0},
+ {'Save Settings...', 2},
+ separator,
+ {'Exit', 0}]},
+ {'Edit', [{'Refresh', no},
+ {'Kill All', no}]},
+ {'Module', [{'Interpret...', 0},
+ {'Delete All Modules', no},
+ separator]},
+ {'Process', [{'Step', 0},
+ {'Next', 0},
+ {'Continue', 0},
+ {'Finish ', 0},
+ separator,
+ {'Attach', 0},
+ {'Kill', no}]},
+ {'Break', [{'Line Break...', 5},
+ {'Conditional Break...', no},
+ {'Function Break...', no},
+ separator,
+ {'Enable All', no},
+ {'Disable All', no},
+ {'Delete All', 0},
+ separator]},
+ {'Options', [{'Trace Window', no, cascade,
+ [{'Button Area', no, check},
+ {'Evaluator Area', no, check},
+ {'Bindings Area', no, check},
+ {'Trace Area', no, check}]},
+ {'Auto Attach', no, cascade,
+ [{'First Call', no, check},
+ {'On Break', no, check},
+ {'On Exit', no, check}]},
+ {'Stack Trace', no, cascade,
+ [{'Stack On, Tail', no, radio},
+ {'Stack On, No Tail', no, radio},
+ {'Stack Off', no, radio}]},
+ {'Back Trace Size...', no}]},
+ {'Help', [{'Debugger', no}]}].
+
+menus(enabled, undefined) ->
+ [];
+menus(disabled, undefined) ->
+ ['Step','Next','Continue','Finish ','Attach','Kill'];
+menus(enabled, #pinfo{status=exit}) ->
+ ['Attach'];
+menus(disabled, #pinfo{status=exit}) ->
+ ['Step','Next','Continue','Finish ','Kill'];
+menus(enabled, #pinfo{status=break}) ->
+ ['Step','Next','Continue','Finish ','Attach','Kill'];
+menus(disabled, #pinfo{status=break}) ->
+ [];
+menus(enabled, _PInfo) ->
+ ['Attach','Kill'];
+menus(disabled, _PInfo) ->
+ ['Step','Next','Continue','Finish '].
+
+shortcut(l) -> {always, 'Load Settings...'};
+shortcut(v) -> {always, 'Save Settings...'};
+shortcut(e) -> {always, 'Exit'};
+
+shortcut(i) -> {always, 'Interpret...'};
+
+shortcut(s) -> {if_enabled, 'Step'};
+shortcut(n) -> {if_enabled, 'Next'};
+shortcut(c) -> {if_enabled, 'Continue'};
+shortcut(f) -> {if_enabled, 'Finish '};
+shortcut(a) -> {if_enabled, 'Attach'};
+
+shortcut(b) -> {always, 'Line Break...'};
+shortcut(d) -> {always, 'Delete All'};
+
+shortcut(_) -> false.
+
+%% Enable/disable functionality depending on the state of the process
+%% currently in Focus
+gui_enable_functions(PInfo) ->
+ Enabled = menus(enabled, PInfo),
+ Disabled = menus(disabled, PInfo),
+ dbg_ui_mon_win:enable(Enabled, true),
+ dbg_ui_mon_win:enable(Disabled, false).
+
+%% Map values used by int to/from GUI names
+map('First Call') -> init; % Auto attach
+map('On Exit') -> exit;
+map('On Break') -> break;
+map(init) -> 'First Call';
+map(exit) -> 'On Exit';
+map(break) -> 'On Break';
+
+map('Stack On, Tail') -> all; % Stack trace
+map('Stack On, No Tail') -> no_tail;
+map('Stack Off') -> false;
+map(all) -> 'Stack On, Tail';
+map(true) -> 'Stack On, Tail';
+map(no_tail) -> 'Stack On, No Tail';
+map(false) -> 'Stack Off'.
+
+
+%%====================================================================
+%% Debugger settings
+%%====================================================================
+
+load_settings(SFile, State) ->
+ case file:read_file(SFile) of
+ {ok, Binary} ->
+ case catch binary_to_term(Binary) of
+ {debugger_settings, Settings} ->
+ load_settings2(Settings,
+ State#state{sfile=SFile,
+ changed=false});
+ _Error -> State
+ end;
+ {error, _Reason} -> State
+ end.
+
+load_settings2(Settings, State) ->
+ {TraceWin, AutoAttach, StackTrace, BackTrace, Files, Breaks} =
+ Settings,
+
+ TraceWinAll = ['Button Area', 'Evaluator Area', 'Bindings Area',
+ 'Trace Area'],
+ lists:foreach(fun(Area) -> dbg_ui_mon_win:select(Area, true) end,
+ TraceWin),
+ lists:foreach(fun(Area) -> dbg_ui_mon_win:select(Area, false) end,
+ TraceWinAll--TraceWin),
+
+ case AutoAttach of
+ false -> int:auto_attach(false);
+ {Flags, Function} -> int:auto_attach(Flags, Function)
+ end,
+
+ int:stack_trace(StackTrace),
+
+ dbg_ui_mon_win:show_option(State#state.win, back_trace, BackTrace),
+
+ case State#state.mode of
+ local -> lists:foreach(fun(File) -> int:i(File) end, Files);
+ global -> lists:foreach(fun(File) -> int:ni(File) end, Files)
+ end,
+ lists:foreach(fun(Break) ->
+ {{Mod, Line}, [Status, Action, _, Cond]} =
+ Break,
+ int:break(Mod, Line),
+ if
+ Status==inactive ->
+ int:disable_break(Mod, Line);
+ true -> ignore
+ end,
+ if
+ Action/=enable ->
+ int:action_at_break(Mod,Line,Action);
+ true -> ignore
+ end,
+ case Cond of
+ CFunction when is_tuple(CFunction) ->
+ int:test_at_break(Mod,Line,CFunction);
+ null -> ignore
+ end
+ end,
+ Breaks),
+
+ State#state{tracewin=TraceWin, backtrace=BackTrace}.
+
+save_settings(SFile, State) ->
+ Settings = {State#state.tracewin,
+ int:auto_attach(),
+ int:stack_trace(),
+ State#state.backtrace,
+ lists:map(fun(Mod) ->
+ int:file(Mod)
+ end,
+ int:interpreted()),
+ int:all_breaks()},
+
+ Binary = term_to_binary({debugger_settings, Settings}),
+ case file:write_file(SFile, Binary) of
+ ok ->
+ State#state{sfile=SFile, changed=false};
+ {error, _Reason} ->
+ State
+ end.
+
+
+%%====================================================================
+%% Other internal functions
+%%====================================================================
+
+registered_name(Pid) ->
+
+ %% Yield in order to give Pid more time to register its name
+ timer:sleep(200),
+
+ Node = node(Pid),
+ if
+ Node==node() ->
+ case erlang:process_info(Pid, registered_name) of
+ {registered_name, Name} -> Name;
+ _ -> undefined
+ end;
+ true ->
+ case rpc:call(Node,erlang,process_info,
+ [Pid,registered_name]) of
+ {registered_name, Name} -> Name;
+ _ -> undefined
+ end
+ end.
+
+trace_function(State) ->
+ {dbg_ui_trace, start, [State#state.tracewin,State#state.backtrace]}.
diff --git a/lib/debugger/src/dbg_ui_mon_win.erl b/lib/debugger/src/dbg_ui_mon_win.erl
new file mode 100644
index 0000000000..ba2f94c550
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_mon_win.erl
@@ -0,0 +1,564 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_mon_win).
+
+%% External exports
+-export([init/0]).
+-export([create_win/3, get_window/1,
+ show_option/3,
+ enable/2, is_enabled/1, select/2,
+ add_module/3, delete_module/2,
+ add_process/6, update_process/4, clear_processes/1,
+ add_break/3, update_break/2, delete_break/2,
+ clear_breaks/1, clear_breaks/2,
+ handle_event/2
+ ]).
+
+-define(default_rows,50).
+
+-record(moduleInfo, {module, menubtn}).
+-record(procInfo, {pid, row}).
+-record(breakInfo, {point, status, break}).
+-record(winInfo, {window, % gsobj()
+ grid, % gsobj()
+ row, % int() Last row in grid
+
+ focus, % int() Selected row in grid
+
+ modules=[], % [#moduleInfo{}] Known modules
+ processes=[], % [#procInfo{}] Known processes
+ breaks=[], % [#breakInfo{}] Known breakpoints
+
+ listbox, % gsobj() Listinng known modules
+
+ %% Auto attach buttons
+ fbutton, % gsobj()
+ bbutton, % gsobj()
+ ebutton, % gsobj()
+ selected=[], % ['First Call'|'On Break'|'On Exit']
+
+ slabel, % showing Stack Trace option
+ blabel % showing Back Trace Size
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+init() ->
+ dbg_ui_win:init().
+
+%%--------------------------------------------------------------------
+%% create_win(GS, Title, Menus) -> #winInfo{}
+%% GS = gsobj()
+%% Title = string()
+%% Menus = [menu()] See dbg_ui_win.erl
+%%--------------------------------------------------------------------
+
+-define(PAD, 5).
+-define(Wf, 150).
+-define(Wg, 770).
+-define(W, 800).
+-define(H, 390).
+
+create_win(GS, Title, Menus) ->
+ Win = gs:window(GS, [{title, Title},
+ {width, ?W}, {height, ?H},
+ {configure,true}, {destroy,true},
+ {keypress,true}, {motion,true}]),
+
+ MenuBar = gs:menubar(Win, []),
+ dbg_ui_win:create_menus(MenuBar, Menus),
+ dbg_ui_winman:windows_menu(MenuBar),
+
+ Font = dbg_ui_win:font(normal),
+
+ Frame = gs:frame(Win, [{x, ?PAD}, {y, 30},
+ {width, ?Wf}, {height, ?H}]),
+ Hlb = 200,
+ Listbox = gs:listbox(Frame, [{x, 0}, {y, 0},
+ {width, ?Wf}, {height, Hlb},
+ {data, listbox},
+ {doubleclick, true},
+ {items, []}]),
+ gs:label(Frame, [{x, 0}, {y, Hlb}, {width, ?Wf}, {height, 20},
+ {align, w},
+ {label, {text, "Auto Attach:"}}, {font, Font}]),
+ Fbtn = gs:checkbutton(Frame, [{x, 0}, {y, Hlb+20},
+ {width, ?Wf}, {height, 20},
+ {label, {text, 'First Call'}},
+ {align, w}, {font, Font},
+ {data, autoattach}]),
+ Bbtn = gs:checkbutton(Frame, [{x, 0}, {y, Hlb+40},
+ {width, ?Wf}, {height, 20},
+ {label, {text, 'On Break'}},
+ {align, w}, {font, Font},
+ {data, autoattach}]),
+ Ebtn = gs:checkbutton(Frame, [{x, 0}, {y, Hlb+60},
+ {width, ?Wf}, {height, 20},
+ {label, {text, 'On Exit'}},
+ {align, w}, {font, Font},
+ {data, autoattach}]),
+ SLabel = gs:label(Frame, [{x, 0}, {y, Hlb+80},
+ {width, ?Wf}, {height, 40},
+ {font, Font}, {align, w}]),
+ BLabel = gs:label(Frame, [{x, 0}, {y, Hlb+120},
+ {width, ?Wf}, {height, 40},
+ {font, Font}, {align, w}]),
+
+ Grid = gs:grid(Win, [{x, 2*?PAD+?Wf}, {y, 30},
+ {width, ?W-(2*?PAD+?Wf)}, {height, ?H-30},
+ {bg, grey}, {fg, black},
+ {vscroll, right}, {hscroll, bottom},
+ calc_columnwidths(?Wg),
+ {rows, {1,?default_rows}}]),
+ gs:gridline(Grid, [{row, 1}, {bw, 5}, {fg, blue},
+ {font, Font},
+ {text, {1,"Pid"}}, {text, {2,"Initial Call"}},
+ {text, {3,"Name"}}, {text, {4,"Status"}},
+ {text, {5,"Information"}}]),
+
+ gs:config(Win, {map, true}),
+ #winInfo{window=Win, grid=Grid, row=1, focus=0,
+ listbox=Listbox,
+ fbutton=Fbtn, bbutton=Bbtn, ebutton=Ebtn,
+ slabel=SLabel, blabel=BLabel}.
+
+%%--------------------------------------------------------------------
+%% get_window(WinInfo) -> Window
+%% WinInfo = #winInfo{}
+%% Window = gsobj()
+%%--------------------------------------------------------------------
+get_window(WinInfo) ->
+ WinInfo#winInfo.window.
+
+%%--------------------------------------------------------------------
+%% show_option(WinInfo, Option, Value) -> void()
+%% WinInfo = #winInfo{}
+%% Option = auto_attach | stack_trace | back_trace
+%% Value = [Flag] % Option==auto_attach
+%% Flag = init | break | exit
+%% | true | all | no_tail | false % Option==stack_trace
+%% | int() % Option==back_trace
+%%--------------------------------------------------------------------
+show_option(WinInfo, Option, Value) ->
+ case Option of
+
+ auto_attach ->
+ lists:foreach(fun (Button) ->
+ gs:config(Button, {select, false})
+ end,
+ option_buttons(WinInfo, [init, break, exit])),
+ lists:foreach(fun (Button) ->
+ gs:config(Button, {select, true})
+ end,
+ option_buttons(WinInfo, Value));
+
+ stack_trace ->
+ Text = case Value of
+ all -> "Stack Trace:\n On (with tail)";
+ true -> "Stack Trace:\n On (with tail)";
+ no_tail -> "Stack Trace:\n On (no tail)";
+ false -> "Stack Trace:\n Off"
+ end,
+ gs:config(WinInfo#winInfo.slabel, {label, {text, Text}});
+
+ back_trace ->
+ Text = "Back Trace Size:\n " ++ integer_to_list(Value),
+ gs:config(WinInfo#winInfo.blabel, {label, {text, Text}})
+ end.
+
+option_buttons(WinInfo, [init|Flags]) ->
+ [WinInfo#winInfo.fbutton|option_buttons(WinInfo, Flags)];
+option_buttons(WinInfo, [break|Flags]) ->
+ [WinInfo#winInfo.bbutton|option_buttons(WinInfo, Flags)];
+option_buttons(WinInfo, [exit|Flags]) ->
+ [WinInfo#winInfo.ebutton|option_buttons(WinInfo, Flags)];
+option_buttons(_WinInfo, []) ->
+ [].
+
+%%--------------------------------------------------------------------
+%% enable([MenuItem], Bool)
+%% is_enabled(MenuItem) -> Bool
+%% MenuItem = atom()
+%% Bool = boolean()
+%%--------------------------------------------------------------------
+enable(MenuItems, Bool) ->
+ lists:foreach(fun(MenuItem) ->
+ gs:config(MenuItem, {enable, Bool})
+ end,
+ MenuItems).
+
+is_enabled(MenuItem) ->
+ gs:read(MenuItem, enable).
+
+%%--------------------------------------------------------------------
+%% select(MenuItem, Bool)
+%% MenuItem = atom()
+%% Bool = boolean()
+%%--------------------------------------------------------------------
+select(MenuItem, Bool) ->
+ dbg_ui_win:select(MenuItem, Bool).
+
+%%--------------------------------------------------------------------
+%% add_module(WinInfo, Name, Mod) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Name = atom()
+%% Mod = atom()
+%%--------------------------------------------------------------------
+add_module(WinInfo, Menu, Mod) ->
+ Modules = WinInfo#winInfo.modules,
+ case lists:keysearch(Mod, #moduleInfo.module, Modules) of
+ {value, _ModInfo} -> WinInfo;
+ false ->
+ %% Create a menu for the module
+ Font = dbg_ui_win:font(normal),
+ MenuBtn = gs:menuitem(Menu, [{label, {text,Mod}},
+ {font, Font},
+ {itemtype, cascade}]),
+ SubMenu = gs:menu(MenuBtn, []),
+ gs:menuitem(SubMenu, [{label, {text,"View"}},
+ {font, Font},
+ {data, {module,Mod,view}}]),
+ gs:menuitem(SubMenu, [{label, {text,"Delete"}},
+ {font, Font},
+ {data, {module,Mod,delete}}]),
+
+ %% Add the module to the listbox
+ gs:config(WinInfo#winInfo.listbox, {add, Mod}),
+
+ ModInfo = #moduleInfo{module=Mod, menubtn=MenuBtn},
+ WinInfo#winInfo{modules=[ModInfo | Modules]}
+ end.
+
+%%--------------------------------------------------------------------
+%% delete_module(WinInfo, Mod) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Mod = atom()
+%%--------------------------------------------------------------------
+delete_module(WinInfo, Mod) ->
+ {value, ModInfo} = lists:keysearch(Mod, #moduleInfo.module,
+ WinInfo#winInfo.modules),
+ gs:destroy(ModInfo#moduleInfo.menubtn),
+ delete_module(WinInfo#winInfo.listbox, atom_to_list(Mod), 0),
+ WinInfo#winInfo{modules=lists:keydelete(Mod, #moduleInfo.module,
+ WinInfo#winInfo.modules)}.
+
+delete_module(Listbox, ModS, Index) ->
+ case gs:read(Listbox, {get, Index}) of
+ ModS ->
+ gs:config(Listbox, {del, Index});
+ _OtherModS ->
+ delete_module(Listbox, ModS, Index+1)
+ end.
+
+%%--------------------------------------------------------------------
+%% add_process(WinInfo, Pid, Name, Function, Status, Info) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Pid = pid()
+%% Name = undefined | atom()
+%% Function = {Mod, Func, Args}
+%% Status = idle | running | break | exit
+%% Info = {} | term()
+%%--------------------------------------------------------------------
+add_process(WinInfo, Pid, Name, {Mod,Func,Args}, Status, Info) ->
+ Grid = WinInfo#winInfo.grid,
+ Row = (WinInfo#winInfo.row)+1,
+ GridLine = case gs:read(Grid, {obj_at_row, Row}) of
+ undefined ->
+ if Row>?default_rows ->
+ gs:config(Grid,[{rows,{1,Row}}]);
+ true -> ok
+ end,
+ gs:gridline(Grid,[{row,Row}, {bw,5}, {fg,black},
+ {font,dbg_ui_win:font(normal)},
+ {click, true},
+ {doubleclick, true}]);
+ GSObj ->
+ GSObj
+ end,
+ Name2 = case Name of undefined -> ""; _ -> Name end,
+ FuncS = io_lib:format("~w:~w/~w", [Mod, Func, length(Args)]),
+ Info2 = case Info of {} -> ""; _ -> Info end,
+ Options = [{text, {1,Pid}}, {text, {2,FuncS}}, {text, {3,Name2}},
+ {text, {4,Status}}, {text, {5,Info2}},
+ {data, {gridline, Pid}}],
+ gs:config(GridLine, Options),
+
+ ProcInfo = #procInfo{pid=Pid, row=Row},
+ WinInfo#winInfo{processes=[ProcInfo|WinInfo#winInfo.processes],
+ row=Row}.
+
+%%--------------------------------------------------------------------
+%% update_process(WinInfo, Pid, Status, Info)
+%% WinInfo = #winInfo{}
+%% Pid = pid()
+%% Status = idle | running | break | exit
+%% Info = {} | term()
+%%--------------------------------------------------------------------
+update_process(WinInfo, Pid, Status, Info) ->
+ {value, ProcInfo} = lists:keysearch(Pid, #procInfo.pid,
+ WinInfo#winInfo.processes),
+
+ Grid = WinInfo#winInfo.grid,
+ GridLine = gs:read(Grid, {obj_at_row, ProcInfo#procInfo.row}),
+
+ Info2 = case Info of {} -> ""; _ -> Info end,
+ gs:config(GridLine, [{text, {4,Status}}, {text, {5,Info2}}]).
+
+%%--------------------------------------------------------------------
+%% clear_processes(WinInfo) -> WinInfo
+%% WinInfo = #winInfo{}
+%%--------------------------------------------------------------------
+clear_processes(WinInfo) ->
+ Grid = WinInfo#winInfo.grid,
+ Max = WinInfo#winInfo.row,
+ clear_processes(Grid, 2, Max),
+ gs:config(Grid,[{rows,{1,?default_rows}}]),
+ WinInfo#winInfo{row=1, focus=0, processes=[]}.
+
+clear_processes(Grid, Row, Max) when Row=<Max ->
+ GridLine = gs:read(Grid, {obj_at_row, Row}),
+ case gs:read(GridLine,{text,4}) of
+ "exit" ->
+ Pid = list_to_pid(gs:read(GridLine,{text,1})),
+ dbg_ui_winman:clear_process(dbg_ui_trace:title(Pid));
+ _ ->
+ ok
+ end,
+
+ Options = [{fg, black},
+ {{text,1}, ""}, {{text,2},""}, {{text,3},""},
+ {{text,4}, ""}, {{text,5},""},
+ {data, []}],
+ gs:config(GridLine, Options),
+ clear_processes(Grid, Row+1, Max);
+clear_processes(_Grid, Row, Max) when Row>Max ->
+ done.
+
+%%--------------------------------------------------------------------
+%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Name = atom()
+%% Point = {Mod, Line}
+%% Options = [Status, Action, Mods, Cond]
+%% Status = active | inactive
+%% Action = enable | disable | delete
+%% Mods = null (not used)
+%% Cond = null | {Mod, Func}
+%%--------------------------------------------------------------------
+add_break(WinInfo, Menu, {Point, Options}) ->
+ Break = dbg_ui_win:add_break(Menu, Point),
+ dbg_ui_win:update_break(Break, Options),
+ BreakInfo = #breakInfo{point=Point, break=Break},
+ WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}.
+
+%%--------------------------------------------------------------------
+%% update_break(WinInfo, {Point, Options})
+%% WinInfo = #winInfo{}
+%% Point = {Mod, Line}
+%% Options = [Status, Action, Mods, Cond]
+%% Status = active | inactive
+%% Action = enable | disable | delete
+%% Mods = null (not used)
+%% Cond = null | {Mod, Func}
+%%--------------------------------------------------------------------
+update_break(WinInfo, {Point, Options}) ->
+ {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks),
+ dbg_ui_win:update_break(BreakInfo#breakInfo.break, Options).
+
+%%--------------------------------------------------------------------
+%% delete_break(WinInfo, Point) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Point = {Mod, Line}
+%%--------------------------------------------------------------------
+delete_break(WinInfo, Point) ->
+ {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks),
+ dbg_ui_win:delete_break(BreakInfo#breakInfo.break),
+ WinInfo#winInfo{breaks=lists:keydelete(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks)}.
+
+%%--------------------------------------------------------------------
+%% clear_breaks(WinInfo) -> WinInfo
+%% clear_breaks(WinInfo, Mod) -> WinInfo
+%% WinInfo = #winInfo{}
+%%--------------------------------------------------------------------
+clear_breaks(WinInfo) ->
+ lists:foreach(fun(BreakInfo) ->
+ dbg_ui_win:delete_break(BreakInfo#breakInfo.break)
+ end,
+ WinInfo#winInfo.breaks),
+ WinInfo#winInfo{breaks=[]}.
+clear_breaks(WinInfo, Mod) ->
+ Fun =
+ fun(BreakInfo) ->
+ case BreakInfo#breakInfo.point of
+ {Mod, _Line} ->
+ dbg_ui_win:delete_break(BreakInfo#breakInfo.break),
+ false;
+ _ -> true
+ end
+ end,
+ Breaks = lists:filter(Fun, WinInfo#winInfo.breaks),
+ WinInfo#winInfo{breaks=Breaks}.
+
+%%--------------------------------------------------------------------
+%% handle_event(GSEvent, WinInfo) -> Command
+%% GSEvent = {gs, Id, Event, Data, Arg}
+%% WinInfo = #winInfo{}
+%% Command = ignore
+%% | stopped
+%% | {coords, {X,Y}}
+%%
+%% | {shortcut, Key}
+%% | MenuItem | {Menu, [MenuItem]}
+%% MenuItem = Menu = atom()
+%% | {break, Point, What}
+%% What = delete | {status, Status} | {trigger, Trigger}
+%% | {module, Mod, What}
+%% What = view | delete
+%%
+%% | {focus, Pid, WinInfo}
+%% | default
+%%--------------------------------------------------------------------
+%% Window events
+handle_event({gs, _Id, configure, _Data, [W, H |_]}, WinInfo) ->
+ configure(WinInfo, {W, H}),
+ ignore;
+handle_event({gs, _Id, destroy, _Data, _Arg}, _WinInfo) ->
+ stopped;
+handle_event({gs, _Id, motion, _Data, [X,Y]}, WinInfo) ->
+ {LastX, LastY} = dbg_ui_win:motion(X, Y),
+ Win = WinInfo#winInfo.window,
+ {coords, {gs:read(Win, x)+LastX-5, gs:read(Win, y)+LastY-5}};
+
+%% Menus and keyboard shortcuts
+handle_event({gs, _Id, keypress, _Data, [Key,_,_,1]}, _WinInfo) when
+ Key/='Up', Key/='Down', Key/=p, Key/=n ->
+ {shortcut, Key};
+handle_event({gs, _Id, click, {dbg_ui_winman, Win}, _Arg}, _WinInfo) ->
+ dbg_ui_winman:raise(Win),
+ ignore;
+handle_event({gs, _Id, click, {menuitem, Name}, _Arg}, _WinInfo) ->
+ Name;
+handle_event({gs, _Id, click, {menu, Menu}, _Arg}, _WinInfo) ->
+ Names = dbg_ui_win:selected(Menu),
+ {Menu, Names};
+handle_event({gs, _Id, click, {break, Point, What}, _Arg}, _WinInfo) ->
+ {break, Point, What};
+handle_event({gs, _Id, click, {module, Mod, What}, _Arg}, _WinInfo) ->
+ {module, Mod, What};
+
+%% Listbox
+handle_event({gs, _Id, doubleclick, listbox, [_Index, ModS|_]}, _WI) ->
+ {module, list_to_atom(ModS), view};
+
+%% Auto attach buttons
+handle_event({gs, _Id, click, autoattach, _Arg}, WinInfo) ->
+ Names = lists:foldl(fun (Button, NamesAcc) ->
+ case gs:read(Button, select) of
+ true ->
+ {text, Name} =
+ gs:read(Button, label),
+ [list_to_atom(Name)|NamesAcc];
+ false ->
+ NamesAcc
+ end
+ end,
+ [],
+ [WinInfo#winInfo.ebutton,
+ WinInfo#winInfo.bbutton,
+ WinInfo#winInfo.fbutton]),
+ {'Auto Attach', Names};
+
+%% Process grid
+handle_event({gs, _Id, keypress, _Data, [Key|_]}, WinInfo) when
+ Key=='Up'; Key=='Down' ->
+ Dir = if Key=='Up' -> up; Key=='Down' -> down end,
+ Row = move(WinInfo, Dir),
+
+ if Row>1 ->
+ WinInfo2 = highlight(WinInfo, Row),
+ {value, #procInfo{pid=Pid}} =
+ lists:keysearch(Row, #procInfo.row, WinInfo#winInfo.processes),
+ {focus, Pid, WinInfo2};
+ true ->
+ ignore
+ end;
+handle_event({gs, _Id, click, {gridline, Pid}, [_Col,Row|_]}, WinInfo) ->
+ WinInfo2 = highlight(WinInfo, Row),
+ {focus, Pid, WinInfo2};
+handle_event({gs, _Id, doubleclick, _Data, _Arg}, _WinInfo) ->
+ default;
+
+handle_event(_GSEvent, _WinInfo) ->
+ ignore.
+
+move(WinInfo, Dir) ->
+ Row = WinInfo#winInfo.focus,
+ Last = WinInfo#winInfo.row,
+
+ if
+ Dir==up, Row>1 -> Row-1;
+ Dir==down, Row<Last -> Row+1;
+ true -> Row
+ end.
+
+highlight(WinInfo, Row) ->
+ Grid = WinInfo#winInfo.grid,
+ case WinInfo#winInfo.focus of
+ 0 -> ignore;
+ Focus ->
+ GridLine1 = gs:read(Grid, {obj_at_row, Focus}),
+ gs:config(GridLine1, {fg, black})
+ end,
+ GridLine2 = gs:read(Grid, {obj_at_row, Row}),
+ gs:config(GridLine2, {fg, white}),
+ WinInfo#winInfo{focus=Row}.
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+configure(WinInfo, {W, H}) ->
+ Grid = WinInfo#winInfo.grid,
+ NewW = W - (2*?PAD+?Wf),
+ Dx = NewW - gs:read(Grid, width),
+ Dy = H-42 - gs:read(Grid, height),
+ if
+ (Dx+Dy)=/=0 ->
+ gs:config(Grid, [{width, NewW}, {height, H-30}]),
+ Cols = calc_columnwidths(NewW),
+ gs:config(Grid, Cols);
+ true ->
+ ok
+ end.
+
+calc_columnwidths(Width) ->
+ W = if
+ Width=<?Wg -> ?Wg;
+ true -> Width
+ end,
+ First = lists:map(fun (X) -> round(X) end,
+ [0.13*W, 0.27*W, 0.18*W, 0.18*W]),
+ Last = W - lists:sum(First) - 30,
+ {columnwidths, First++[Last]}.
diff --git a/lib/debugger/src/dbg_ui_settings.erl b/lib/debugger/src/dbg_ui_settings.erl
new file mode 100644
index 0000000000..146aa7e239
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_settings.erl
@@ -0,0 +1,162 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_settings).
+
+-include_lib("kernel/include/file.hrl").
+
+%% External exports
+-export([start/4]).
+
+%% Internal exports
+-export([init/6]).
+
+%% OTP-6011 What's denoted gs="Graphics system id" is now in fact
+%% the object id of the monitor window.
+-record(state, {gs, % term() Graphics system id
+ win, % term() Settings dialog window data
+ monitor, % pid() Monitor pid
+ action % load | save
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start(GS, Pos, Action, SFile)
+%% GS = Graphics system id
+%% Pos = {X,Y}
+%% Action = load | save
+%% SFile = default | string()
+%%--------------------------------------------------------------------
+start(GS, Pos, Action, SFile) ->
+ Title = case Action of
+ load -> "Load Settings Dialog";
+ save -> "Save Settings Dialog"
+ end,
+ case dbg_ui_winman:is_started(Title) of
+ true -> ignore;
+ false ->
+ spawn(?MODULE, init, [self(), GS, Pos, Title, Action, SFile])
+ end.
+
+%%====================================================================
+%% Internal exports
+%%====================================================================
+
+init(Monitor, GS, Pos, Title, Action, SFile) ->
+ {SDir, SFileName} =
+ if
+ %% If settings are saved for the first time, and to
+ %% the default directory HOME/erlang.tools/debugger,
+ %% make sure the directory exists, or create it if
+ %% desired and possible
+ SFile==default -> {default_settings_dir(GS), "NoName.state"};
+ true -> {filename:dirname(SFile), filename:basename(SFile)}
+ end,
+
+ Filter = filename:join(SDir, "*.state"),
+ Extra = fun(_File) -> true end,
+
+ %% Create window
+ Win = case Action of
+ load ->
+ dbg_ui_filedialog_win:create_win(GS, Title, Pos, normal,
+ Filter, Extra);
+ save ->
+ dbg_ui_filedialog_win:create_win(GS, Title, Pos, normal,
+ Filter, Extra, SFileName)
+ end,
+ Window = dbg_ui_filedialog_win:get_window(Win),
+ dbg_ui_winman:insert(Title, Window),
+
+ State = #state{gs=GS, win=Win, monitor=Monitor, action=Action},
+ loop(State).
+
+
+%%====================================================================
+%% Main loop and message handling
+%%====================================================================
+
+loop(State) ->
+ receive
+
+ %% From the GUI
+ GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs ->
+ Cmd = dbg_ui_filedialog_win:handle_event(GuiEvent,
+ State#state.win),
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the dbg_ui_winman process (Debugger window manager)
+ {dbg_ui_winman, update_windows_menu, _Data} ->
+ loop(State);
+ {dbg_ui_winman, destroy} ->
+ exit(normal)
+ end.
+
+gui_cmd(ignore, State) ->
+ State;
+gui_cmd({stopped, _Dir}, _State) ->
+ exit(normal);
+gui_cmd({win, Win}, State) ->
+ State#state{win=Win};
+gui_cmd({select, File}, State) ->
+ State#state.monitor ! {dbg_ui_settings, File, State#state.action},
+ exit(normal).
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+default_settings_dir(GS) ->
+ {ok, [[Home]]} = init:get_argument(home),
+ DefDir = filename:join([Home, ".erlang_tools", "debugger"]),
+
+ case filelib:is_dir(DefDir) of
+ true -> DefDir;
+ false ->
+ {ok, CWD} = file:get_cwd(),
+
+ Msg = ["Default directory", DefDir, "does not exist.",
+ "Click Ok to create it or",
+ "Cancel to use other directory!"],
+ case tool_utils:confirm(GS, Msg) of
+ ok ->
+ ToolsDir = filename:dirname(DefDir),
+ case filelib:is_dir(ToolsDir) of
+ true ->
+ case file:make_dir(DefDir) of
+ ok -> DefDir;
+ _Error -> CWD
+ end;
+ false ->
+ case file:make_dir(ToolsDir) of
+ ok ->
+ case file:make_dir(DefDir) of
+ ok -> DefDir;
+ _Error -> CWD
+ end;
+ _Error -> CWD
+ end
+ end;
+ cancel -> CWD
+ end
+ end.
diff --git a/lib/debugger/src/dbg_ui_trace.erl b/lib/debugger/src/dbg_ui_trace.erl
new file mode 100644
index 0000000000..d318987f60
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_trace.erl
@@ -0,0 +1,814 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_trace).
+
+%% External exports
+-export([start/1, start/3]).
+-export([title/1]).
+
+-define(TRACEWIN, ['Button Area', 'Evaluator Area', 'Bindings Area']).
+-define(BACKTRACE, 100).
+
+-record(state, {gs, % term() Graphics system id
+ win, % term() Attach process window data
+ coords, % {X,Y} Mouse point position
+
+ pid, % pid() Debugged process
+ meta, % pid() Meta process
+ status, % {Status,Mod,Line} � {exit,Where,Reason}
+ % Status = init � idle | break
+ % | wait_break � wait_running
+ % � running
+ % Where={Mod,Line} | null
+
+ cm, % atom() | undefined Current module
+ cm_obsolete=false, % boolean() Curr mod needs reloading
+
+ stack, % {Cur,Max}
+
+ trace, % boolean()
+ stack_trace, % all | no_tail | false
+ backtrace % integer() #call frames to fetch
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start(Pid)
+%% start(Pid, TraceWin, BackTrace)
+%% Pid = pid()
+%% TraceWin = [WinArea]
+%% WinArea = 'Button|Evaluator|Bindings|Trace Area'
+%% Backtrace = integer()
+%%--------------------------------------------------------------------
+start(Pid) -> % Used by debugger:quick/3 (no monitor)
+ start(Pid, ?TRACEWIN, ?BACKTRACE).
+start(Pid, TraceWin, BackTrace) ->
+ case {whereis(dbg_wx_mon), whereis(dbg_ui_mon)} of
+ {undefined, undefined} ->
+ case which_gui() of
+ gs ->
+ start2(Pid, TraceWin, BackTrace);
+ wx ->
+ dbg_wx_trace:start(Pid, TraceWin, BackTrace)
+ end;
+ {undefined, Monitor} when is_pid(Monitor) ->
+ start2(Pid, TraceWin, BackTrace);
+ {Monitor, _} when is_pid(Monitor) ->
+ dbg_wx_trace:start(Pid, TraceWin, BackTrace)
+ end.
+
+start2(Pid, TraceWin, BackTrace) ->
+ %% Inform int about my existence and get the meta pid back
+ case int:attached(Pid) of
+ {ok, Meta} ->
+ init(Pid, Meta, TraceWin, BackTrace);
+ error ->
+ ignore
+ end.
+
+which_gui() ->
+ try
+ wx:new(),
+ wx:destroy(),
+ wx
+ catch _:_ ->
+ gs
+ end.
+
+%%--------------------------------------------------------------------
+%% title(Pid) -> string()
+%% By exporting this function, dbg_ui_mon may check with dbg_ui_winman
+%% if there already is an attach window for a given pid and thus avoid
+%% spawning processes unnecessarily.
+%%--------------------------------------------------------------------
+title(Pid) ->
+ "Attach Process " ++ pid_to_list(Pid).
+
+
+%%====================================================================
+%% Main loop and message handling
+%%====================================================================
+
+init(Pid, Meta, TraceWin, BackTrace) ->
+
+ %% Start necessary stuff
+ GS = dbg_ui_trace_win:init(), % Graphics system
+
+ %% Create attach process window
+ Title = title(Pid),
+ Win = dbg_ui_trace_win:create_win(GS, Title, TraceWin, menus()),
+ Window = dbg_ui_trace_win:get_window(Win),
+ dbg_ui_winman:insert(Title, Window),
+
+ %% Initial process state
+ State1 = #state{gs=GS, win=Win, coords={0,0}, pid=Pid, meta=Meta,
+ status={idle,null,null},
+ stack={1,1}},
+
+ State2 = init_options(TraceWin,
+ int:stack_trace(), % Stack Trace
+ BackTrace, % Back trace size
+ State1),
+
+ State3 = init_contents(int:all_breaks(), % Breakpoints
+ State2),
+
+ int:meta(Meta, trace, State3#state.trace),
+
+ gui_enable_updown(stack_trace, {1,1}),
+ gui_enable_btrace(false, false),
+ dbg_ui_trace_win:display(idle),
+
+ loop(State3).
+
+init_options(TraceWin, StackTrace, BackTrace, State) ->
+ lists:foreach(fun(Area) -> dbg_ui_trace_win:select(Area, true) end,
+ TraceWin),
+
+ Trace = lists:member('Trace Area', TraceWin),
+
+ dbg_ui_trace_win:select(map(StackTrace), true),
+
+ %% Backtrace size is (currently) not shown in window
+
+ State#state{trace=Trace,stack_trace=StackTrace,backtrace=BackTrace}.
+
+init_contents(Breaks, State) ->
+ Win =
+ lists:foldl(fun(Break, Win) ->
+ dbg_ui_trace_win:add_break(Win,
+ 'Break',Break)
+ end,
+ State#state.win,
+ Breaks),
+
+ State#state{win=Win}.
+
+loop(#state{meta=Meta} = State) ->
+ receive
+
+ %% From the GUI main window
+ GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs ->
+ Cmd =
+ dbg_ui_trace_win:handle_event(GuiEvent,State#state.win),
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the GUI help windows
+ {gui, Cmd} ->
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the interpreter
+ {int, Cmd} ->
+ State2 = int_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the meta process
+ {Meta, Cmd} ->
+ State2 = meta_cmd(Cmd, State),
+ loop(State2);
+ {NewMeta, {exit_at, Where, Reason, Cur}} ->
+ State2 = meta_cmd({exit_at, Where, Reason, Cur},
+ State#state{meta=NewMeta}),
+ loop(State2);
+
+ %% From the dbg_ui_edit process
+ {dbg_ui_edit, 'Backtrace:', BackTrace} ->
+ loop(State#state{backtrace=BackTrace});
+ {dbg_ui_edit, Var, Val} ->
+ Cmd = atom_to_list(Var)++"="++io_lib:format("~p", [Val]),
+ State2 = gui_cmd({user_command, lists:flatten(Cmd)}, State),
+ loop(State2);
+
+ %% From the dbg_ui_winman process (Debugger window manager)
+ {dbg_ui_winman, update_windows_menu, Data} ->
+ dbg_ui_winman:update_windows_menu(Data),
+ loop(State);
+ {dbg_ui_winman, destroy} ->
+ exit(stop)
+ end.
+
+%%--Commands from the GUI---------------------------------------------
+
+gui_cmd(ignore, State) ->
+ State;
+gui_cmd({win, Win}, State) ->
+ State#state{win=Win};
+gui_cmd(stopped, _State) ->
+ exit(stop);
+gui_cmd({coords, Coords}, State) ->
+ State#state{coords=Coords};
+
+gui_cmd({shortcut, Key}, State) ->
+ case shortcut(Key) of
+ {always, Cmd} -> gui_cmd(Cmd, State);
+ {if_enabled, Cmd} ->
+ case dbg_ui_trace_win:is_enabled(Cmd) of
+ true -> gui_cmd(Cmd, State);
+ false -> State
+ end;
+ false -> State
+ end;
+
+%% File menu
+gui_cmd('Close', State) ->
+ gui_cmd(stopped, State);
+
+%% Edit menu
+gui_cmd('Go To Line...', State) ->
+ %% Will result in message handled below: {gui, {gotoline, Line}}
+ dbg_ui_trace_win:helpwin(gotoline, State#state.win,
+ State#state.gs, State#state.coords),
+ State;
+gui_cmd({gotoline, Line}, State) ->
+ Win = dbg_ui_trace_win:select_line(State#state.win, Line),
+ State#state{win=Win};
+gui_cmd('Search...', State) ->
+ dbg_ui_trace_win:helpwin(search, State#state.win,
+ State#state.gs, State#state.coords),
+ State;
+
+%% Process menu
+gui_cmd('Step', State) ->
+ int:meta(State#state.meta, step),
+ State;
+gui_cmd('Next', State) ->
+ int:meta(State#state.meta, next),
+ State;
+gui_cmd('Continue', State) ->
+ int:meta(State#state.meta, continue),
+ {Status, Mod, Line} = State#state.status,
+ if
+ Status==wait_break ->
+ Win = dbg_ui_trace_win:unmark_line(State#state.win),
+ gui_enable_functions(wait_running),
+ State#state{win=Win, status={wait_running,Mod,Line}};
+ true ->
+ dbg_ui_trace_win:enable(['Stop'], true),
+ dbg_ui_trace_win:enable(['Continue'], false),
+ State
+ end;
+gui_cmd('Finish', State) ->
+ int:meta(State#state.meta, finish),
+ State;
+gui_cmd('Skip', State) ->
+ int:meta(State#state.meta, skip),
+ State;
+gui_cmd('Time Out', State) ->
+ int:meta(State#state.meta, timeout),
+ State;
+gui_cmd('Stop', State) ->
+ int:meta(State#state.meta, stop),
+ {Status, Mod, Line} = State#state.status,
+ if
+ Status==wait_running ->
+ Win = dbg_ui_trace_win:mark_line(State#state.win, Line,
+ break),
+ gui_enable_functions(wait_break),
+ gui_enable_updown(State#state.stack_trace,
+ State#state.stack),
+ gui_enable_btrace(State#state.trace,
+ State#state.stack_trace),
+ dbg_ui_trace_win:display({wait, Mod, Line}),
+ State#state{win=Win, status={wait_break,Mod,Line}};
+ true ->
+ dbg_ui_trace_win:enable(['Stop'], false),
+ dbg_ui_trace_win:enable(['Continue'], true),
+ State
+ end;
+gui_cmd('Where', State) ->
+ {_Cur, Max} = State#state.stack,
+ Stack = {Max, Max},
+ {_Status, Mod, Line} = State#state.status,
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid, break),
+ gui_update_bindings(State#state.meta),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ dbg_ui_trace_win:display(State#state.status),
+ State#state{win=Win, cm=Mod, stack=Stack};
+
+gui_cmd('Kill', State) ->
+ exit(State#state.pid, kill),
+ State;
+gui_cmd('Messages', State) ->
+ case int:meta(State#state.meta, messages) of
+ [] ->
+ dbg_ui_trace_win:eval_output("< No Messages!\n", bold);
+ Messages ->
+ dbg_ui_trace_win:eval_output("< --- Current Messages ---\n",
+ bold),
+ lists:foldl(
+ fun(Msg, N) ->
+ Str1 = io_lib:format(" ~w:", [N]),
+ dbg_ui_trace_win:eval_output(Str1, bold),
+ Str2 = io_lib:format(" ~s~n",[io_lib:print(Msg)]),
+ dbg_ui_trace_win:eval_output(Str2, normal),
+ N+1
+ end,
+ 1,
+ Messages)
+ end,
+ State;
+gui_cmd('Back Trace', State) ->
+ dbg_ui_trace_win:trace_output("\nBACK TRACE\n----------\n"),
+ lists:foreach(
+ fun({Le, {Mod,Func,Args}}) ->
+ Str = io_lib:format("~p > ~p:~p~p~n",
+ [Le, Mod, Func, Args]),
+ dbg_ui_trace_win:trace_output(Str);
+ ({Le, {Fun,Args}}) ->
+ Str = io_lib:format("~p > ~p~p~n", [Le, Fun, Args]),
+ dbg_ui_trace_win:trace_output(Str);
+ (_) -> ignore
+ end,
+ int:meta(State#state.meta, backtrace, State#state.backtrace)),
+ dbg_ui_trace_win:trace_output("\n"),
+ State;
+gui_cmd('Up', State) ->
+ {Cur, Max} = State#state.stack,
+ case int:meta(State#state.meta, stack_frame, {up, Cur}) of
+ {New, {undefined,-1}, _Bs} -> % call from non-interpreted code
+ Stack = {New, Max},
+ Win = dbg_ui_trace_win:show_no_code(State#state.win),
+ dbg_ui_trace_win:update_bindings([]),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ dbg_ui_trace_win:display({New,null,null}),
+ State#state{win=Win, cm=null, stack=Stack};
+
+ {New, {Mod,Line}, Bs} ->
+ Stack = {New, Max},
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid,
+ where),
+ dbg_ui_trace_win:update_bindings(Bs),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ dbg_ui_trace_win:display({New,Mod,Line}),
+ State#state{win=Win, cm=Mod, stack=Stack};
+ top ->
+ dbg_ui_trace_win:enable(['Up'], false),
+ State
+ end;
+gui_cmd('Down', State) ->
+ {Cur, Max} = State#state.stack,
+ case int:meta(State#state.meta, stack_frame, {down, Cur}) of
+ {New, {undefined,-1}, _Bs} -> % call from non-interpreted code
+ Stack = {New, Max},
+ Win = dbg_ui_trace_win:show_no_code(State#state.win),
+ dbg_ui_trace_win:update_bindings([]),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ dbg_ui_trace_win:display({New,null,null}),
+ State#state{win=Win, cm=null, stack=Stack};
+
+ {New, {Mod,Line}, Bs} ->
+ Stack = {New, Max},
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid,
+ where),
+ dbg_ui_trace_win:update_bindings(Bs),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ dbg_ui_trace_win:display({New,Mod,Line}),
+ State#state{win=Win, cm=Mod, stack=Stack};
+
+ bottom ->
+ gui_cmd('Where', State)
+ end;
+
+%% Break menu
+gui_cmd('Line Break...', State) ->
+ add_break(State#state.gs, State#state.coords, line,
+ State#state.cm,
+ dbg_ui_trace_win:selected_line(State#state.win)),
+ State;
+gui_cmd('Conditional Break...', State) ->
+ add_break(State#state.gs, State#state.coords, conditional,
+ State#state.cm,
+ dbg_ui_trace_win:selected_line(State#state.win)),
+ State;
+gui_cmd('Function Break...', State) ->
+ add_break(State#state.gs, State#state.coords, function,
+ State#state.cm, undefined),
+ State;
+gui_cmd('Enable All', State) ->
+ Breaks = int:all_breaks(),
+ ThisMod = State#state.cm,
+ lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
+ int:enable_break(Mod, Line);
+ (_Break) ->
+ ignore
+ end,
+ Breaks),
+ State;
+gui_cmd('Disable All', State) ->
+ Breaks = int:all_breaks(),
+ ThisMod = State#state.cm,
+ lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
+ int:disable_break(Mod, Line);
+ (_Break) ->
+ ignore
+ end,
+ Breaks),
+ State;
+gui_cmd('Delete All', State) ->
+ int:no_break(State#state.cm),
+ State;
+gui_cmd({break, {Mod, Line}, What}, State) ->
+ case What of
+ add -> int:break(Mod, Line);
+ delete -> int:delete_break(Mod, Line);
+ {status, inactive} -> int:disable_break(Mod, Line);
+ {status, active} -> int:enable_break(Mod, Line);
+ {trigger, Action} -> int:action_at_break(Mod, Line, Action)
+ end,
+ State;
+
+%% Options menu
+gui_cmd({'Trace Window', TraceWin}, State) ->
+ Trace = lists:member('Trace Area', TraceWin),
+ int:meta(State#state.meta, trace, Trace),
+ Win = dbg_ui_trace_win:configure(State#state.win, TraceWin),
+ {Status,_,_} = State#state.status,
+ if
+ Status==break; Status==wait_break ->
+ gui_enable_btrace(Trace, State#state.stack_trace);
+ true -> ignore
+ end,
+ State#state{win=Win, trace=Trace};
+gui_cmd({'Stack Trace', [Name]}, State) ->
+ int:meta(State#state.meta, stack_trace, map(Name)),
+ {Status,_,_} = State#state.status,
+ if
+ Status==break; Status==wait_break ->
+ gui_enable_btrace(State#state.trace, map(Name));
+ true -> ignore
+ end,
+ State;
+gui_cmd('Back Trace Size...', State) ->
+ dbg_ui_edit:start(State#state.gs, State#state.coords, "Backtrace",
+ 'Backtrace:', {integer, State#state.backtrace}),
+ State;
+
+%% Help menu
+gui_cmd('Debugger', State) ->
+ Window = dbg_ui_trace_win:get_window(State#state.win),
+ HelpFile = filename:join([code:lib_dir(debugger),
+ "doc", "html", "part_frame.html"]),
+ tool_utils:open_help(Window, HelpFile),
+ State;
+
+gui_cmd({user_command, Cmd}, State) ->
+ {Status, _Mod, _Line} = State#state.status,
+ if
+ Status==break;
+ Status==wait_break;
+ Status==wait_running ->
+ Cm = State#state.cm,
+ Arg = case State#state.stack of
+ {Cur, Max} when Cur<Max -> {Cm, Cmd, Cur};
+ _Stack -> {Cm, Cmd}
+ end,
+
+ %% Reply will be received as {Meta, {eval_rsp, Res}}
+ int:meta(State#state.meta, eval, Arg);
+ true ->
+ Str = "Commands not allowed",
+ dbg_ui_trace_win:eval_output([$<,Str,10], normal)
+ end,
+ State;
+
+gui_cmd({edit, {Var, Val}}, State) ->
+ dbg_ui_edit:start(State#state.gs, State#state.coords,
+ "Edit variable", Var, {term, Val}),
+ State.
+
+add_break(GS, Coords, Type, undefined, _Line) ->
+ dbg_ui_break:start(GS, Coords, Type);
+add_break(GS, Coords, Type, Mod, undefined) ->
+ dbg_ui_break:start(GS, Coords, Type, Mod);
+add_break(GS, Coords, Type, Mod, Line) ->
+ dbg_ui_break:start(GS, Coords, Type, Mod, Line).
+
+%%--Commands from the interpreter-------------------------------------
+
+int_cmd({interpret, Mod}, State) ->
+ if
+ Mod==State#state.cm ->
+ State#state{cm_obsolete=true};
+ true ->
+ State
+ end;
+int_cmd({no_interpret, Mod}, State) ->
+ if
+ Mod==State#state.cm ->
+ State#state{cm_obsolete=true};
+ true ->
+ Win = dbg_ui_trace_win:remove_code(State#state.win, Mod),
+ State#state{win=Win}
+ end;
+
+int_cmd({new_break, Break}, State) ->
+ Win = dbg_ui_trace_win:add_break(State#state.win, 'Break', Break),
+ State#state{win=Win};
+int_cmd({delete_break, Point}, State) ->
+ Win = dbg_ui_trace_win:delete_break(State#state.win, Point),
+ State#state{win=Win};
+int_cmd({break_options, Break}, State) ->
+ Win = dbg_ui_trace_win:update_break(State#state.win, Break),
+ State#state{win=Win};
+int_cmd(no_break, State) ->
+ Win = dbg_ui_trace_win:clear_breaks(State#state.win),
+ State#state{win=Win};
+int_cmd({no_break, Mod}, State) ->
+ Win = dbg_ui_trace_win:clear_breaks(State#state.win, Mod),
+ State#state{win=Win}.
+
+%%--Commands from the meta process------------------------------------
+
+%% Message received when first attached to a living process
+%% '_Trace' is a boolean indicating if the process is traced or not --
+%% ignore this as we already have ordered tracing or not depending on if
+%% the Trace Area is shown or not.
+meta_cmd({attached, Mod, Line, _Trace}, State) ->
+ Win = if
+ Mod/=undefined ->
+ gui_enable_functions(init),
+ gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid,
+ break);
+ true -> State#state.win
+ end,
+ State#state{win=Win, status={init,Mod,Line}, cm=Mod};
+
+%% Message received when returning to interpreted code
+meta_cmd({re_entry, dbg_ieval, eval_fun}, State) ->
+ State;
+meta_cmd({re_entry, Mod, _Func}, State) ->
+ Obs = State#state.cm_obsolete,
+ case State#state.cm of
+ Mod when Obs==true ->
+ Win = gui_load_module(State#state.win, Mod,State#state.pid),
+ State#state{win=Win, cm_obsolete=false};
+ Mod -> State;
+ Cm ->
+ Win = gui_show_module(State#state.win, Mod, 0,
+ Cm, State#state.pid, break),
+ State#state{win=Win, cm=Mod}
+ end;
+
+%% Message received when attached to a terminated process
+meta_cmd({exit_at, null, Reason, Cur}, State) ->
+ Stack = {Cur, Cur},
+ gui_enable_functions(exit),
+ gui_enable_updown(false, Stack),
+ dbg_ui_trace_win:display({exit, null, Reason}),
+ State#state{status={exit,null,Reason}, stack=Stack};
+meta_cmd({exit_at, {Mod,Line}, Reason, Cur}, State) ->
+ Stack = {Cur+1, Cur+1},
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid, break),
+ gui_enable_functions(exit),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ gui_enable_btrace(State#state.trace, State#state.stack_trace),
+ gui_update_bindings(State#state.meta),
+ dbg_ui_trace_win:display({exit, {Mod,Line}, Reason}),
+ State#state{win=Win, cm=Mod,status={exit,{Mod,Line},Reason},
+ stack=Stack};
+
+meta_cmd({break_at, Mod, Line, Cur}, State) ->
+ Stack = {Cur,Cur},
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid, break),
+ gui_enable_functions(break),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ gui_enable_btrace(State#state.trace, State#state.stack_trace),
+ gui_update_bindings(State#state.meta),
+ dbg_ui_trace_win:display({break, Mod, Line}),
+ State#state{win=Win, cm=Mod, status={break,Mod,Line}, stack=Stack};
+meta_cmd({func_at, Mod, Line, Cur}, State) ->
+ Stack = {Cur,Cur},
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid, where),
+ gui_enable_functions(idle),
+ dbg_ui_trace_win:display(idle),
+ State#state{win=Win, cm=Mod, status={idle,Mod,Line}, stack=Stack};
+meta_cmd({wait_at, Mod, Line, Cur}, #state{status={Status,_,_}}=State)
+ when Status/=init, Status/=break ->
+ Stack = {Cur,Cur},
+ gui_enable_functions(wait_running),
+ dbg_ui_trace_win:display({wait,Mod,Line}),
+ State#state{status={wait_running,Mod,Line}, stack=Stack};
+meta_cmd({wait_at, Mod, Line, Cur}, State) ->
+ Stack = {Cur,Cur},
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid, break),
+ gui_enable_functions(wait_break),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ gui_enable_btrace(State#state.trace, State#state.stack_trace),
+ gui_update_bindings(State#state.meta),
+ dbg_ui_trace_win:display({wait, Mod, Line}),
+ State#state{win=Win, cm=Mod, status={wait_break,Mod,Line},
+ stack=Stack};
+meta_cmd({wait_after_at, Mod, Line, Sp}, State) ->
+ meta_cmd({wait_at, Mod, Line, Sp}, State);
+meta_cmd(running, State) ->
+ Win = dbg_ui_trace_win:unmark_line(State#state.win),
+ gui_enable_functions(running),
+ dbg_ui_trace_win:update_bindings([]),
+ dbg_ui_trace_win:display({running, State#state.cm}),
+ State#state{win=Win, status={running,null,null}};
+
+meta_cmd(idle, State) ->
+ Win = dbg_ui_trace_win:show_no_code(State#state.win),
+ gui_enable_functions(idle),
+ dbg_ui_trace_win:update_bindings([]),
+ dbg_ui_trace_win:display(idle),
+ State#state{win=Win, status={idle,null,null}, cm=undefined};
+
+%% Message about changed trace option can be ignored, the change must
+%% have been ordered by this process. (In theory, the change could have
+%% been ordered by another attached process. The Debugger, though,
+%% allows max one attached process per debugged process).
+meta_cmd({trace, _Bool}, State) ->
+ State;
+
+meta_cmd({stack_trace, Flag}, State) ->
+ dbg_ui_trace_win:select(map(Flag), true),
+ gui_enable_updown(Flag, State#state.stack),
+ {Status,_,_} = State#state.status,
+ if
+ Status==break; Status==wait_break ->
+ gui_enable_btrace(State#state.trace, Flag);
+ true -> ignore
+ end,
+ State#state{stack_trace=Flag};
+
+meta_cmd({trace_output, Str}, State) ->
+ dbg_ui_trace_win:trace_output(Str),
+ State;
+
+%% Reply on a user command
+meta_cmd({eval_rsp, Res}, State) ->
+ Str = io_lib:print(Res),
+ dbg_ui_trace_win:eval_output([$<,Str,10], normal),
+ State.
+
+
+%%====================================================================
+%% GUI auxiliary functions
+%%====================================================================
+
+menus() ->
+ [{'File', [{'Close', no}]},
+ {'Edit', [{'Go To Line...', no},
+ {'Search...', no}]},
+ {'Process', [{'Step', 0},
+ {'Next', 0},
+ {'Continue', 0},
+ {'Finish', 0},
+ {'Skip', no},
+ {'Time Out', no},
+ {'Stop', no},
+ separator,
+ {'Kill', no},
+ separator,
+ {'Messages', 0},
+ {'Back Trace', no},
+ separator,
+ {'Where', 0},
+ {'Up', no},
+ {'Down', no}]},
+ {'Break', [{'Line Break...', 5},
+ {'Conditional Break...', no},
+ {'Function Break...', no},
+ separator,
+ {'Enable All', no},
+ {'Disable All', no},
+ {'Delete All', 0},
+ separator]},
+ {'Options', [{'Trace Window', no, cascade,
+ [{'Button Area', no, check},
+ {'Evaluator Area', no, check},
+ {'Bindings Area', no, check},
+ {'Trace Area', no, check}]},
+ {'Stack Trace', no, cascade,
+ [{'Stack On, Tail', no, radio},
+ {'Stack On, No Tail', no, radio},
+ {'Stack Off', no, radio}]},
+ {'Back Trace Size...', no}]},
+ {'Help', [{'Debugger', no}]}].
+
+%% enable(Status) -> [MenuItem]
+%% Status = init % when first message from Meta has arrived
+%% | idle | break | exit | wait_break � wait_running | running
+enable(init) -> [];
+enable(idle) -> ['Stop','Kill'];
+enable(break) -> ['Step','Next','Continue','Finish','Skip',
+ 'Kill','Messages'];
+enable(exit) -> [];
+enable(wait_break) -> ['Continue','Time Out','Kill'];
+enable(wait_running) -> ['Stop','Kill'];
+enable(running) -> ['Stop','Kill'].
+
+all_buttons() ->
+ ['Step','Next','Continue','Finish','Skip','Time Out','Stop',
+ 'Kill','Messages','Back Trace','Where','Up','Down'].
+
+shortcut(s) -> {if_enabled, 'Step'};
+shortcut(n) -> {if_enabled, 'Next'};
+shortcut(c) -> {if_enabled, 'Continue'};
+shortcut(f) -> {if_enabled, 'Finish'};
+shortcut(m) -> {if_enabled, 'Messages'};
+shortcut(w) -> {if_enabled, 'Where'};
+
+shortcut(b) -> {always, 'Line Break...'};
+shortcut(d) -> {always, 'Delete All'};
+
+shortcut(_) -> false.
+
+map('Stack On, Tail') -> all; % Stack trace
+map('Stack On, No Tail') -> no_tail;
+map('Stack Off') -> false;
+map(all) -> 'Stack On, Tail';
+map(true) -> 'Stack On, Tail';
+map(no_tail) -> 'Stack On, No Tail';
+map(false) -> 'Stack Off'.
+
+
+%% gui_show_module(Win, Mod, Line, Cm, Pid, How) -> Win
+%% gui_show_module(Win, {Mod,Line}, _Reason, Cm, Pid, How) -> Win
+%% How = where | break
+%% Show contents of a module in code area
+gui_show_module(Win, {Mod,Line}, _Reason, Cm, Pid, How) ->
+ gui_show_module(Win, Mod, Line, Cm, Pid, How);
+gui_show_module(Win, Mod, Line, Mod, _Pid, How) ->
+ dbg_ui_trace_win:mark_line(Win, Line, How);
+gui_show_module(Win, Mod, Line, _Cm, Pid, How) ->
+ Win2 = case dbg_ui_trace_win:is_shown(Win, Mod) of
+ {true, Win3} -> Win3;
+ false -> gui_load_module(Win, Mod, Pid)
+ end,
+ dbg_ui_trace_win:mark_line(Win2, Line, How).
+
+gui_load_module(Win, Mod, Pid) ->
+ dbg_ui_trace_win:display({text, "Loading module..."}),
+ Contents = int:contents(Mod, Pid),
+ Win2 = dbg_ui_trace_win:show_code(Win, Mod, Contents),
+ dbg_ui_trace_win:display({text, ""}),
+ Win2.
+
+gui_update_bindings(Meta) ->
+ Bs = int:meta(Meta, bindings, nostack),
+ dbg_ui_trace_win:update_bindings(Bs).
+
+gui_enable_functions(Status) ->
+ Enable = enable(Status),
+ Disable = all_buttons() -- Enable,
+ dbg_ui_trace_win:enable(Disable, false),
+ dbg_ui_trace_win:enable(Enable, true).
+
+gui_enable_updown(Flag, Stack) ->
+ {Enable, Disable} =
+ if
+ Flag==false -> {[], ['Up', 'Down']};
+ true ->
+ case Stack of
+ {1,1} -> {[], ['Up', 'Down']};
+ {2,2} -> {[], ['Up', 'Down']};
+ {Max,Max} -> {['Up'], ['Down']};
+ {2,_Max} -> {['Down'], ['Up']};
+ {_Cur,_Max} -> {['Up', 'Down'], []}
+ end
+ end,
+ dbg_ui_trace_win:enable(Enable, true),
+ dbg_ui_trace_win:enable(Disable, false),
+ if
+ Enable==[] -> dbg_ui_trace_win:enable(['Where'], false);
+ true -> dbg_ui_trace_win:enable(['Where'], true)
+ end.
+
+gui_enable_btrace(Trace, StackTrace) ->
+ Bool = if
+ Trace==false -> false;
+ StackTrace==false -> false;
+ true -> true
+ end,
+ dbg_ui_trace_win:enable(['Back Trace'], Bool).
diff --git a/lib/debugger/src/dbg_ui_trace_win.erl b/lib/debugger/src/dbg_ui_trace_win.erl
new file mode 100644
index 0000000000..dbf93c7f45
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_trace_win.erl
@@ -0,0 +1,1597 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_trace_win).
+
+%% External exports
+-export([init/0]).
+-export([create_win/4, get_window/1,
+ configure/2,
+ enable/2, is_enabled/1, select/2,
+ add_break/3, update_break/2, delete_break/2,
+ clear_breaks/1, clear_breaks/2,
+ display/1, % Help messages
+ is_shown/2, % Code area
+ show_code/3, show_no_code/1, remove_code/2,
+ mark_line/3, unmark_line/1,
+ select_line/2, selected_line/1,
+ eval_output/2, % Evaluator area
+ update_bindings/1, % Bindings area
+ trace_output/1, % Trace area
+ handle_event/2
+ ]).
+-export([helpwin/4, % Help windows
+ helpwin/5]).
+
+-record(breakInfo, {point, status, break}).
+-record(winInfo, {window, % gsobj()
+ size, % {W, H}
+ flags, % {F,F,F,F} F = open|close
+
+ marked_line=0, % integer() Current line
+ selected_line=0, % integer() Selected line
+
+ breaks=[], % [#breakInfo{}] Known breakpoints
+
+ editor, % {Mod, Editor} Visible code editor
+ editors=[] % [{Mod,Editor}] Code editors
+ }).
+
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% init() -> GS
+%% GS = term()
+%%--------------------------------------------------------------------
+init() ->
+ dbg_ui_win:init().
+
+%%--------------------------------------------------------------------
+%% create_win(GS, Title, TraceWin, Menus) -> #winInfo{}
+%% GS = gsobj()
+%% Title = string()
+%% TraceWin = [WinArea]
+%% WinArea = 'Button|Evaluator|Bindings|Trace Area'
+%% Menus = [menu()] See dbg_ui_win.erl
+%%--------------------------------------------------------------------
+create_win(GS, Title, TraceWin, Menus) ->
+ Bu = flip(lists:member('Button Area', TraceWin)),
+ Ev = flip(lists:member('Evaluator Area', TraceWin)),
+ Bi = flip(lists:member('Bindings Area', TraceWin)),
+ Tr = flip(lists:member('Trace Area', TraceWin)),
+
+ Win = gs:window(trace_window, GS, [{title, Title},
+ {width, 550},
+ {configure,true}, {destroy,true},
+ {keypress,true}, {motion,true}]),
+
+ MenuBar = gs:menubar(Win, []),
+ dbg_ui_win:create_menus(MenuBar, Menus),
+ dbg_ui_winman:windows_menu(MenuBar),
+
+ FrameOpts = [{anchor,nw}, {relief,raised}, {bw,2}, {keypress,true}],
+ Editor = code_area(2, 25, FrameOpts, Win),
+ button_area(Bu, 2, 235, FrameOpts, Win),
+ eval_area({Ev,Bi}, 2, 265, FrameOpts, Win),
+ bind_area({Ev,Bi}, 300, 265, FrameOpts, Win),
+ trace_area(Tr, 2, 475, FrameOpts, Win),
+
+ Flags = {Bu, Ev, Bi, Tr},
+ resizebar(rb1(Flags), 'RB1', 2, 225, 710, 10, Win),
+ resizebar(rb2(Flags), 'RB2', 2, 465, 710, 10, Win),
+ resizebar(rb3(Flags), 'RB3', 290, 265, 10, 200, Win),
+ config_v(),
+ config_h(),
+
+ gs:config(Win,{height,
+ 25 +
+ gs:read('CodeArea', height) +
+ gs:read('RB1', height) +
+ gs:read('ButtonArea', height) +
+ max(gs:read('EvalArea', height),
+ gs:read('BindArea', height)) +
+ gs:read('RB2', height) +
+ gs:read('TraceArea', height)}),
+
+ gs:config(Win, {map, true}),
+ #winInfo{window=Win, size={gs:read(Win,width), gs:read(Win,height)},
+ flags=Flags,
+ editor={'$top', Editor}, editors=[{'$top', Editor}]}.
+
+flip(true) -> open;
+flip(false) -> close.
+
+%%--------------------------------------------------------------------
+%% get_window(WinInfo) -> Window
+%% WinInfo = #winInfo{}
+%% Window = gsobj()
+%%--------------------------------------------------------------------
+get_window(WinInfo) ->
+ WinInfo#winInfo.window.
+
+%%--------------------------------------------------------------------
+%% configure(WinInfo, TraceWin) -> WinInfo
+%% WinInfo = #winInfo{}
+%% TraceWin = [WinArea]
+%% WinArea = 'Button|Evaluator|Bindings|Trace Area'
+%% Window areas should be opened or closed.
+%%--------------------------------------------------------------------
+configure(WinInfo, TraceWin) ->
+ {Bu1, Ev1, Bi1, Tr1} = OldFlags = WinInfo#winInfo.flags,
+ Bu2 = flip(lists:member('Button Area', TraceWin)),
+ Ev2 = flip(lists:member('Evaluator Area', TraceWin)),
+ Bi2 = flip(lists:member('Bindings Area', TraceWin)),
+ Tr2 = flip(lists:member('Trace Area', TraceWin)),
+ NewFlags = {Bu2, Ev2, Bi2, Tr2},
+
+ Win = WinInfo#winInfo.window,
+ W = gs:read(Win, width),
+ H = gs:read(Win, height),
+
+ H2 = if
+ Bu1==close, Bu2==open ->
+ resize_button_area(open, width, W-4),
+ gs:config('ButtonArea', {height, 30}),
+ H+30;
+ Bu1==open, Bu2==close ->
+ gs:config('ButtonArea', [{width, 0}, {height, 0}]),
+ H-30;
+ true -> H
+ end,
+ H3 = if
+ Ev1==close, Ev2==open, Bi1==open ->
+ Wnew1 = round((W-10-4)/2), % W = window/2 - rb - pads
+ Hbi1 = gs:read('BindArea', height), % H = bind area h
+ resize_eval_area(open, width, Wnew1),
+ resize_eval_area(open, height, Hbi1),
+ gs:config('RB3', {width, 10}),
+ gs:config('RB3', {height, Hbi1}),
+ resize_bind_area(open, width,
+ Wnew1-gs:read('BindArea', width)),
+ H2;
+ Ev1==close, Ev2==open, Bi1==close ->
+ resize_eval_area(open, width, W-4),
+ resize_eval_area(open, height, 200),
+ H2+200;
+ Ev1==open, Ev2==close, Bi1==open ->
+ gs:config('EvalArea', [{width,0}, {height,0}]),
+ gs:config('RB3', [{width, 0}, {height, 0}]),
+ Wnew2 = W-4,
+ resize_bind_area(open, width,
+ Wnew2-gs:read('BindArea', width)),
+ H2;
+ Ev1==open, Ev2==close, Bi1==close ->
+ Hs1 = gs:read('EvalArea', height),
+ gs:config('EvalArea', [{width, 0}, {height, 0}]),
+ H2-Hs1;
+ true -> H2
+ end,
+ H4 = if
+ Bi1==close, Bi2==open, Ev2==open ->
+ Wnew3 = round((W-10-4)/2), % W = window/2 - rb - pads
+ Hs2 = gs:read('EvalArea', height), % H = eval area h
+ resize_bind_area(open, width, Wnew3),
+ resize_bind_area(open, height, Hs2),
+ gs:config('RB3', [{width,10},{height,Hs2}]),
+ resize_eval_area(open, width,
+ Wnew3-gs:read('EvalArea', width)),
+ H3;
+ Bi1==close, Bi2==open, Ev2==close ->
+ resize_bind_area(open, width, W-4),
+ resize_bind_area(open, height, 200),
+ H3+200;
+ Bi1==open, Bi2==close, Ev2==open ->
+ gs:config('BindArea', [{width, 0}, {height, 0}]),
+ gs:config('RB3', [{width, 0}, {height, 0}]),
+ Wnew4 = W-4,
+ resize_eval_area(open, width,
+ Wnew4-gs:read('EvalArea', width)),
+ H3;
+ Bi1==open, Bi2==close, Ev2==close ->
+ Hbi2 = gs:read('BindArea', height),
+ gs:config('BindArea', [{width, 0}, {height, 0}]),
+ H3-Hbi2;
+ true -> H3
+ end,
+ H5 = if
+ Tr1==close, Tr2==open ->
+ resize_trace_area(open, width, W-4),
+ resize_trace_area(open, height, 200),
+ H4+200;
+ Tr1==open, Tr2==close ->
+ Hf = gs:read('TraceArea', height),
+ gs:config('TraceArea', [{width, 0}, {height, 0}]),
+ H4-Hf;
+ true -> H4
+ end,
+ gs:config(Win, {height, H5}),
+
+ RB1old = rb1(OldFlags), RB1new = rb1(NewFlags),
+ if
+ RB1old==close, RB1new==open ->
+ gs:config('RB1', [{width, W-4}, {height, 10}]),
+ gs:config(Win, {height, gs:read(Win, height)+10});
+ RB1old==open, RB1new==close ->
+ gs:config('RB1', [{width, 0}, {height, 0}, lower]),
+ gs:config(Win, {height, gs:read(Win, height)-10});
+ true -> ignore
+ end,
+
+ RB2old = rb2(OldFlags), RB2new = rb2(NewFlags),
+ if
+ RB2old==close, RB2new==open ->
+ gs:config('RB2', [{width, W-4}, {height, 10}]),
+ gs:config(Win, {height,gs:read(Win, height)+10});
+ RB2old==open, RB2new==close ->
+ gs:config('RB2', [{width, 0}, {height, 0}, lower]),
+ gs:config(Win, {height, gs:read(Win, height)-10});
+ true -> ignore
+ end,
+ config_v(),
+ config_h(),
+
+ flush_configure(),
+
+ WinInfo#winInfo{size={gs:read(Win, width), gs:read(Win, height)},
+ flags=NewFlags}.
+
+flush_configure() ->
+ receive
+ {gs, _Id, configure, _Data, _Arg} ->
+ flush_configure()
+ after 100 ->
+ true
+ end.
+
+%%--------------------------------------------------------------------
+%% enable([MenuItem], Bool)
+%% is_enabled(MenuItem) -> Bool
+%% MenuItem = atom()
+%% Bool = boolean()
+%%--------------------------------------------------------------------
+enable(MenuItems, Bool) ->
+ lists:foreach(fun(MenuItem) ->
+ gs:config(MenuItem, {enable, Bool}),
+ case is_button(MenuItem) of
+ {true, Button} ->
+ gs:config(Button, {enable, Bool});
+ false -> ignore
+ end
+ end,
+ MenuItems).
+
+is_enabled(MenuItem) ->
+ gs:read(MenuItem, enable).
+
+%%--------------------------------------------------------------------
+%% select(MenuItem, Bool)
+%% MenuItem = atom()
+%% Bool = boolean()
+%%--------------------------------------------------------------------
+select(MenuItem, Bool) ->
+ dbg_ui_win:select(MenuItem, Bool).
+
+%%--------------------------------------------------------------------
+%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Name = atom() Menu name
+%% Point = {Mod, Line}
+%% Options = [Status, Action, Mods, Cond]
+%% Status = active | inactive
+%% Action = enable | disable | delete
+%% Mods = null (not used)
+%% Cond = null | {Mod, Func}
+%%--------------------------------------------------------------------
+add_break(WinInfo, Menu, {{Mod,Line},[Status|_Options]}=Break) ->
+ case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of
+ {value, {Mod, Editor}} ->
+ add_break_to_code(Editor, Line, Status);
+ false -> ignore
+ end,
+ add_break_to_menu(WinInfo, Menu, Break).
+
+add_break_to_code(Editor, Line, Status) ->
+ Color = if Status==active -> red; Status==inactive -> blue end,
+ config_editor(Editor, [{overwrite,{{Line,0},"-@- "}},
+ {fg,{{{Line,0},{Line,lineend}}, Color}}]).
+
+add_break_to_menu(WinInfo, Menu, {Point, [Status|_Options]=Options}) ->
+ Break = dbg_ui_win:add_break(Menu, Point),
+ dbg_ui_win:update_break(Break, Options),
+ BreakInfo = #breakInfo{point=Point, status=Status, break=Break},
+ WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}.
+
+%%--------------------------------------------------------------------
+%% update_break(WinInfo, {Point, Options}) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Point = {Mod, Line}
+%% Options = [Status, Action, Mods, Cond]
+%% Status = active | inactive
+%% Action = enable | disable | delete
+%% Mods = null (not used)
+%% Cond = null | {Mod, Func}
+%%--------------------------------------------------------------------
+update_break(WinInfo, {{Mod,Line},[Status|_Options]}=Break) ->
+ case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of
+ {value, {Mod, Editor}} ->
+ add_break_to_code(Editor, Line, Status);
+ false -> ignore
+ end,
+ update_break_in_menu(WinInfo, Break).
+
+update_break_in_menu(WinInfo, {Point, [Status|_Options]=Options}) ->
+ {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks),
+ dbg_ui_win:update_break(BreakInfo#breakInfo.break, Options),
+ BreakInfo2 = BreakInfo#breakInfo{status=Status},
+ WinInfo#winInfo{breaks=lists:keyreplace(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks,
+ BreakInfo2)}.
+
+%%--------------------------------------------------------------------
+%% delete_break(WinInfo, Point) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Point = {Mod, Line}
+%%--------------------------------------------------------------------
+delete_break(WinInfo, {Mod,Line}=Point) ->
+ case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of
+ {value, {Mod, Editor}} -> delete_break_from_code(Editor, Line);
+ false -> ignore
+ end,
+ delete_break_from_menu(WinInfo, Point).
+
+delete_break_from_code(Editor, Line) ->
+ Prefix = string:substr(integer_to_list(Line)++": ", 1, 5),
+ config_editor(Editor, [{overwrite,{{Line,0},Prefix}},
+ {fg,{{{Line,0},{Line,lineend}}, black}}]).
+
+delete_break_from_menu(WinInfo, Point) ->
+ {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks),
+ dbg_ui_win:delete_break(BreakInfo#breakInfo.break),
+ WinInfo#winInfo{breaks=lists:keydelete(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks)}.
+
+%%--------------------------------------------------------------------
+%% clear_breaks(WinInfo) -> WinInfo
+%% clear_breaks(WinInfo, Mod) -> WinInfo
+%% WinInfo = #winInfo{}
+%%--------------------------------------------------------------------
+clear_breaks(WinInfo) ->
+ clear_breaks(WinInfo, all).
+clear_breaks(WinInfo, Mod) ->
+ Remove = if
+ Mod==all -> WinInfo#winInfo.breaks;
+ true ->
+ lists:filter(fun(#breakInfo{point={Mod2,_L}}) ->
+ if
+ Mod2==Mod -> true;
+ true -> false
+ end
+ end,
+ WinInfo#winInfo.breaks)
+ end,
+ lists:foreach(fun(#breakInfo{point=Point}) ->
+ delete_break(WinInfo, Point)
+ end,
+ Remove),
+ Remain = WinInfo#winInfo.breaks -- Remove,
+ WinInfo#winInfo{breaks=Remain}.
+
+%%--------------------------------------------------------------------
+%% display(Arg)
+%% Arg = idle | {Status,Mod,Line} | {running,Mod}
+%% � {exit,Where,Reason} | {text,Text}
+%% Status = break | wait � Level
+%% Level = int()
+%% Mod = atom()
+%% Line = integer()
+%% Where = {Mod,Line} | null
+%% Reason = term()
+%% Text = string()
+%%--------------------------------------------------------------------
+display(Arg) ->
+ Str = case Arg of
+ idle -> "State: uninterpreted";
+ {exit, {Mod,Line}, Reason} ->
+ gs:config(trace_window, raise),
+ io_lib:format("State: EXITED [~w.erl/~w], Reason:~w",
+ [Mod, Line, Reason]);
+ {exit, null, Reason} ->
+ gs:config(trace_window, raise),
+ io_lib:format("State: EXITED [uninterpreted], "
+ "Reason:~w", [Reason]);
+ {Level, null, _Line} when is_integer(Level) ->
+ io_lib:format("*** Call level #~w "
+ "(in non-interpreted code)",
+ [Level]);
+ {Level, Mod, Line} when is_integer(Level) ->
+ io_lib:format("*** Call level #~w [~w.erl/~w]",
+ [Level, Mod, Line]);
+ {Status, Mod, Line} ->
+ What = case Status of
+ wait -> 'receive';
+ _ -> Status
+ end,
+ io_lib:format("State: ~w [~w.erl/~w]",
+ [What, Mod, Line]);
+ {running, Mod} ->
+ io_lib:format("State: running [~w.erl]", [Mod]);
+ {text, Text} -> Text
+ end,
+ gs:config(info_window, {label,{text,lists:flatten(Str)}}).
+
+%%--------------------------------------------------------------------
+%% is_shown(WinInfo, Mod) -> {true, WinInfo} | false
+%% show_code(WinInfo, Mod, Contents) -> WinInfo
+%% show_no_code(WinInfo) -> WinInfo
+%% remove_code(WinInfo, Mod) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Mod = atom()
+%% Contents = string()
+%% Note: remove_code/2 should not be used for currently shown module.
+%%--------------------------------------------------------------------
+is_shown(WinInfo, Mod) ->
+ case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of
+ {value, {Mod, Editor}} ->
+ gs:config(Editor, raise),
+ {true, WinInfo#winInfo{editor={Mod, Editor}}};
+ false -> false
+ end.
+
+show_code(WinInfo, Mod, Contents) ->
+ Editors = WinInfo#winInfo.editors,
+ {Flag, Editor} = case lists:keysearch(Mod, 1, Editors) of
+ {value, {Mod, Ed}} -> {existing, Ed};
+ false -> {new, code_editor()}
+ end,
+
+ %% Insert code and update breakpoints, if any
+ config_editor(Editor, [raise, clear]),
+ show_code(Editor, Contents),
+ lists:foreach(fun(BreakInfo) ->
+ case BreakInfo#breakInfo.point of
+ {Mod2, Line} when Mod2==Mod ->
+ Status = BreakInfo#breakInfo.status,
+ add_break_to_code(Editor, Line,Status);
+ _Point -> ignore
+ end
+ end,
+ WinInfo#winInfo.breaks),
+
+ case Flag of
+ existing ->
+ WinInfo#winInfo{editor={Mod, Editor}};
+ new ->
+ WinInfo#winInfo{editor={Mod, Editor},
+ editors=[{Mod, Editor} | Editors]}
+ end.
+
+show_code(Editor, Text) when length(Text)>1500 ->
+ %% Add some text at a time so that other processes may get scheduled
+ Str = string:sub_string(Text, 1, 1500),
+ config_editor(Editor, {insert,{'end', Str}}),
+ show_code(Editor, string:sub_string(Text, 1501));
+show_code(Editor, Text) ->
+ config_editor(Editor, {insert,{'end',Text}}).
+
+show_no_code(WinInfo) ->
+ {value, {'$top', Editor}} =
+ lists:keysearch('$top', 1, WinInfo#winInfo.editors),
+ gs:config(Editor, raise),
+ WinInfo#winInfo{editor={'$top', Editor}}.
+
+remove_code(WinInfo, Mod) ->
+ Editors = WinInfo#winInfo.editors,
+ case lists:keysearch(Mod, 1, Editors) of
+ {value, {Mod, Editor}} ->
+ gs:destroy(Editor),
+ WinInfo#winInfo{editors=lists:keydelete(Mod, 1, Editors)};
+ false ->
+ WinInfo
+ end.
+
+
+%%--------------------------------------------------------------------
+%% mark_line(WinInfo, Line, How) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Line = integer()
+%% How = break | where
+%% Mark the code line where the process is executing.
+%%--------------------------------------------------------------------
+mark_line(WinInfo, Line, How) ->
+ {_Mod, Editor} = WinInfo#winInfo.editor,
+ mark_line2(Editor, WinInfo#winInfo.marked_line, false),
+ mark_line2(Editor, Line, How),
+ if
+ Line/=0 -> config_editor(Editor, {vscrollpos, Line-5});
+ true -> ignore
+ end,
+ WinInfo#winInfo{marked_line=Line}.
+
+unmark_line(WinInfo) ->
+ mark_line(WinInfo, 0, false).
+
+mark_line2(Editor, Line, How) ->
+ Prefix = case How of
+ break -> "-->";
+ where -> ">>>";
+ false -> " "
+ end,
+ Font = if
+ How==false -> dbg_ui_win:font(normal);
+ true -> dbg_ui_win:font(bold)
+ end,
+ config_editor(Editor, [{overwrite, {{Line,5}, Prefix}},
+ {font_style,
+ {{{Line,0},{Line,lineend}}, Font}}]).
+
+%%--------------------------------------------------------------------
+%% select_line(WinInfo, Line) -> WinInfo
+%% selected_line(WinInfo) -> undefined | Line
+%% WinInfo = #winInfo{}
+%% Line = integer()
+%% Select/unselect a line (unselect if Line=0).
+%%--------------------------------------------------------------------
+select_line(WinInfo, Line) ->
+ {_Mod, Editor} = WinInfo#winInfo.editor,
+
+ %% Since 'Line' may be specified by the user in the 'Go To Line'
+ %% help window, it must be checked that it is correct
+ Size = gs:read(Editor, size),
+ if
+ Line==0 ->
+ select_line(Editor, WinInfo#winInfo.selected_line, false),
+ WinInfo#winInfo{selected_line=0};
+ Line<Size ->
+ select_line(Editor, Line, true),
+ config_editor(Editor, {vscrollpos, Line-5}),
+ WinInfo#winInfo{selected_line=Line};
+ true ->
+ WinInfo
+ end.
+
+select_line(Editor, Line, true) ->
+ config_editor(Editor, {selection, {{Line,0}, {Line,lineend}}});
+select_line(Editor, _Line, false) ->
+ config_editor(Editor, {selection, {{1,0}, {1,0}}}).
+
+selected_line(WinInfo) ->
+ case WinInfo#winInfo.selected_line of
+ 0 -> undefined;
+ Line -> Line
+ end.
+
+%%--------------------------------------------------------------------
+%% eval_output(Str, Face)
+%% Str = string()
+%% Face = normal | bold
+%%--------------------------------------------------------------------
+eval_output(Text, Face) ->
+ Y1 = gs:read('EvalEditor', size),
+ config_editor('EvalEditor', {insert,{'end',Text}}),
+ Y2 = gs:read('EvalEditor', size),
+
+ Font = dbg_ui_win:font(Face),
+ config_editor('EvalEditor',
+ [{font_style, {{{Y1,0},{Y2,lineend}}, Font}},
+ {vscrollpos,Y2}]).
+
+%%--------------------------------------------------------------------
+%% update_bindings(Bs)
+%% Bs = [{Var,Val}]
+%%--------------------------------------------------------------------
+update_bindings(Bs) ->
+ gs:config('BindGrid', {rows, {1,length(Bs)+1}}),
+ Font = dbg_ui_win:font(normal),
+ Last =
+ lists:foldl(fun({Var, Val}, Row) ->
+ Opts = [{text, {1,atom_to_list(Var)}},
+ {text, {2,io_lib:format("~P",
+ [Val, 4])}},
+ {doubleclick, true},
+ {data, {binding,{Var,Val}}}],
+ case gs:read('BindGrid',{obj_at_row,Row}) of
+ undefined ->
+ gs:gridline('BindGrid',
+ [{row, Row},
+ {height, 14},
+ {font, Font} | Opts]);
+ GridLine ->
+ gs:config(GridLine, Opts)
+ end,
+ Row+1
+ end,
+ 2,
+ Bs),
+ delete_gridlines(Last).
+
+delete_gridlines(Row) ->
+ case gs:read('BindGrid', {obj_at_row, Row}) of
+ undefined -> true;
+ GridLine ->
+ gs:destroy(GridLine),
+ delete_gridlines(Row+1)
+ end.
+
+%%--------------------------------------------------------------------
+%% trace_output(Str)
+%% Str = string()
+%%--------------------------------------------------------------------
+trace_output(Str) ->
+ Font = dbg_ui_win:font(normal),
+ config_editor('TraceEditor',
+ [{insert, {'end',Str}},
+ {fg, {{{1,0},'end'},black}},
+ {font_style, {{{1,0},'end'},Font}}]),
+ Max = gs:read('TraceEditor', size),
+ config_editor('TraceEditor', {vscrollpos, Max}).
+
+%%--------------------------------------------------------------------
+%% handle_event(GSEvent, WinInfo) -> Command
+%% GSEvent = {gs, Id, Event, Data, Arg}
+%% WinInfo = #winInfo{}
+%% Command = ignore
+%% | {win, WinInfo}
+%% | stopped
+%% | {coords, {X,Y}}
+%%
+%% | {shortcut, Key}
+%% | MenuItem | {Menu, [MenuItem]}
+%% MenuItem = Menu = atom()
+%% | {break, Point, What}
+%% What = add | delete | {status,Status} |{trigger,Trigger}
+%% | {module, Mod, view}
+%%
+%% | {user_command, Cmd}
+%%
+%% | {edit, {Var, Val}}
+%%--------------------------------------------------------------------
+%% Window events
+handle_event({gs, _Id, configure, _Data, [W, H|_]}, WinInfo) ->
+ case WinInfo#winInfo.size of
+ {W, H} -> ignore;
+ _Size ->
+ configure(WinInfo, W, H),
+ {win, WinInfo#winInfo{size={W, H}}}
+ end;
+handle_event({gs, _Id, destroy, _Data, _Arg}, _WinInfo) ->
+ stopped;
+handle_event({gs, _Id, motion, _Data, [X,Y]}, WinInfo) ->
+ {LastX, LastY} = dbg_ui_win:motion(X, Y),
+ Win = WinInfo#winInfo.window,
+ {coords, {gs:read(Win, x)+LastX-5, gs:read(Win, y)+LastY-5}};
+handle_event({gs, RB, buttonpress, resizebar, _Arg}, WinInfo) ->
+ resize(WinInfo, RB), % Resize window contents
+ ignore;
+
+%% Menus, buttons and keyboard shortcuts
+handle_event({gs, _Id, keypress, _Data, [Key,_,_,1]}, _WinInfo) ->
+ {shortcut, Key};
+handle_event({gs, _Id, click, {dbg_ui_winman, Win}, _Arg}, _WinInfo) ->
+ dbg_ui_winman:raise(Win),
+ ignore;
+handle_event({gs, _Id, click, {menuitem, Name}, _Arg}, _WinInfo) ->
+ Name;
+handle_event({gs, _Id, click, {menu, Menu}, _Arg}, _WinInfo) ->
+ Names = dbg_ui_win:selected(Menu),
+ {Menu, Names};
+handle_event({gs, _Id, click, {break, Point, What}, _Arg}, _WinInfo) ->
+ {break, Point, What};
+handle_event({gs, _Id, click, {module, Mod, view}, _Arg}, _WinInfo) ->
+ {module, Mod, view};
+
+%% Code area
+handle_event({gs, Editor, buttonpress, code_editor, _Arg}, WinInfo) ->
+ {Row, _Col} = gs:read(Editor, insertpos),
+ Again = receive
+ {gs, Editor, buttonpress, code_editor, _} ->
+ gs:read(Editor, insertpos)
+ after 500 ->
+ false
+ end,
+ case Again of
+ {Row, _} ->
+ {Mod, _Editor} = WinInfo#winInfo.editor,
+ Point = {Mod, Row},
+ case lists:keysearch(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks) of
+ {value, _BreakInfo} -> {break, Point, delete};
+ false -> {break, Point, add}
+ end;
+ {Row2, _} ->
+ select_line(Editor, Row2, true),
+ {win, WinInfo#winInfo{selected_line=Row2}};
+ false ->
+ select_line(Editor, Row, true),
+ {win, WinInfo#winInfo{selected_line=Row}}
+ end;
+
+%% Button area
+handle_event({gs, _Id, click, {button, Name}, _Arg}, _WinInfo) ->
+ Name;
+
+%% Evaluator area
+handle_event({gs, 'EvalEntry', keypress, _Data, ['Return'|_]}, _WI) ->
+ Command = case gs:read('EvalEntry', text) of
+ [10] ->
+ eval_output("\n", normal),
+ ignore;
+ Cmd ->
+ eval_output([$>, Cmd, 10], normal),
+ {user_command, Cmd}
+ end,
+ gs:config('EvalEntry', [{text,""}, {focus,false}]),
+ Command;
+
+%% Bindings area
+handle_event({gs, _Id, click, {binding, {Var, Val}}, _Arg}, _WinInfo) ->
+ Str = io_lib:format("< ~p = ~p~n", [Var, Val]),
+ eval_output(Str, bold),
+ ignore;
+handle_event({gs, _Id, doubleclick, {binding, B}, _Arg}, _WinInfo) ->
+ {edit, B};
+
+handle_event(_GSEvent, _WinInfo) ->
+ ignore.
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+%%--Code Area---------------------------------------------------------
+
+code_area(X, Y, FrameOpts, Win) ->
+ gs:frame('CodeArea', Win,
+ [{x,X}, {y,Y}, {width,546}, {height,400} | FrameOpts]),
+ gs:label(info_window, 'CodeArea',
+ [{label,{text,""}}, {font,dbg_ui_win:font(normal)},
+ {x,5}, {y,10}, {width,406}, {height,15},
+ {anchor,nw}, {align,w}]),
+ code_editor('CodeEditor', 536, 365).
+
+code_editor() ->
+ W = gs:read('CodeEditor', width),
+ H = gs:read('CodeEditor', height),
+ code_editor(null, W, H).
+
+code_editor(Name, W, H) ->
+ Editor = if
+ Name==null -> gs:editor('CodeArea', []);
+ true -> gs:editor(Name, 'CodeArea', [])
+ end,
+ gs:config(Editor, [{x,5}, {y,30}, {width,W}, {height,H},
+ {keypress,false}, {buttonpress,true},
+ {data,code_editor}]),
+ config_editor(Editor, [{vscroll,right}, {hscroll,bottom}]),
+ Font = dbg_ui_win:font(normal),
+ config_editor(Editor, [{wrap,none}, {fg,{{{1,0},'end'},black}},
+ {font, Font},
+ {font_style, {{{1,0},'end'},Font}}]),
+ Editor.
+
+resize_code_area(WinInfo, Key, Diff) ->
+ gs:config('CodeArea', {Key,gs:read('CodeArea', Key)+Diff}),
+ case Key of
+ width ->
+ gs:config(info_window, {Key,gs:read(info_window,Key)+Diff});
+ height -> ignore
+ end,
+
+ %% Resize all code editors
+ Value = gs:read('CodeEditor', Key)+Diff,
+ gs:config('CodeEditor', {Key,Value}),
+ Editors = WinInfo#winInfo.editors,
+ lists:foreach(fun({_Mod, Editor}) ->
+ gs:config(Editor, {Key,Value})
+ end,
+ Editors).
+
+%%--Button Area-------------------------------------------------------
+
+buttons() ->
+ [{'Step','StepButton'}, {'Next','NextButton'},
+ {'Continue','ContinueButton'}, {'Finish','FinishButton'},
+ {'Where','WhereButton'}, {'Up','UpButton'}, {'Down','DownButton'}].
+
+is_button(Name) ->
+ case lists:keysearch(Name, 1, buttons()) of
+ {value, {Name, Button}} -> {true, Button};
+ false -> false
+ end.
+
+button_area(Bu, X, Y, FrameOpts, Win) ->
+ {W,H} = case Bu of
+ open -> {546,30};
+ close -> {0,0}
+ end,
+ gs:frame('ButtonArea', Win,
+ [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]),
+ Font = dbg_ui_win:font(normal),
+ lists:foldl(fun({Name, Button}, Xb) ->
+ gs:button(Button, 'ButtonArea',
+ [{label, {text,Name}}, {font,Font},
+ {x, Xb}, {y, 1},
+ {width, 77}, {height, 24},
+ {data, {button,Name}}]),
+ Xb+78
+ end,
+ 1,
+ buttons()).
+
+resize_button_area(close, width, _Diff) ->
+ ignore;
+resize_button_area(open, width, Diff) ->
+ gs:config('ButtonArea', {width, gs:read('ButtonArea', width)+Diff}).
+
+%%--Evaluator Area----------------------------------------------------
+
+eval_area({Ev,Bi}, X, Y, FrameOpts, Win) ->
+ {W,H} = if
+ Ev==open -> {289,200};
+ true -> {0,0}
+ end,
+ Font = dbg_ui_win:font(normal),
+ gs:frame('EvalArea', Win,
+ [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]),
+ gs:label('EvalArea',
+ [{label,{text,"Evaluator:"}}, {font, Font},
+ {x,5}, {y,35}, {width,80}, {height,25},
+ {anchor,sw}, {align,center}]),
+ gs:entry('EvalEntry', 'EvalArea',
+ [{font, Font},
+ {x,80}, {y,35}, {width,185}, {height,25},
+ {anchor,sw}, {keypress,true}]),
+ gs:editor('EvalEditor', 'EvalArea',
+ [{x,5}, {y,35}, {width, 280}, {height, 160},
+ {keypress,false},
+ {vscroll,right}, {hscroll,bottom},
+ {wrap,none}, {fg,{{{1,0},'end'},black}},
+ {font, Font},
+ {font_style,{{{1,0},'end'},Font}}]),
+ gs:config('EvalEditor', {enable, false}),
+ if
+ Ev==open, Bi==close -> resize_eval_area(Ev, width, 257);
+ true -> ignore
+ end.
+
+resize_eval_area(close, _Key, _Diff) ->
+ ignore;
+resize_eval_area(open, Key, Diff) ->
+ New = gs:read('EvalArea', Key)+Diff,
+ gs:config('EvalArea', {Key,New}),
+ case Key of
+ width ->
+ gs:config('EvalEntry', {width,New-104}),
+ gs:config('EvalEditor', {width,New-9});
+ height ->
+ gs:config('EvalEditor', {height,New-40})
+ end.
+
+%%--Bindings Area-----------------------------------------------------
+
+bind_area({Ev,Bi}, X, Y, FrameOpts, Win) ->
+ {W,H} = if
+ Bi==open -> {249,200};
+ true -> {0,0}
+ end,
+ gs:frame('BindArea', Win,
+ [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]),
+
+ Font = dbg_ui_win:font(bold),
+ gs:grid('BindGrid', 'BindArea',
+ [{x,2}, {y,2}, {height,193}, {width,241},
+ {fg,black}, {vscroll,right}, {hscroll,bottom},
+ {font,Font},
+ calc_columnwidths(241), {rows, {1,50}}]),
+ gs:gridline('BindGrid',
+ [{row,1}, {height,14}, {fg,blue},
+ {text,{1,"Name"}}, {text,{2,"Value"}}, {font,Font}]),
+ gs:config('BindGrid', {rows,{1,1}}),
+ if
+ Bi==open, Ev==close -> resize_bind_area(Bi, width, 297);
+ true -> ignore
+ end.
+
+resize_bind_area(close, _Key, _Diff) ->
+ ignore;
+resize_bind_area(open, Key, Diff) ->
+ New = gs:read('BindArea', Key)+Diff,
+ gs:config('BindArea', {Key,New}),
+ case Key of
+ width ->
+ gs:config('BindGrid', {width,New-8}),
+ Cols = calc_columnwidths(New-8),
+ gs:config('BindGrid', Cols);
+ height ->
+ gs:config('BindGrid', {height,New-7})
+ end.
+
+calc_columnwidths(Width) ->
+ if Width =< 291 ->
+ {columnwidths,[90,198]};
+ true ->
+ S = (Width)/(90+198),
+ {columnwidths,[round(90*S),round(198*S)]}
+ end.
+
+%%--Trace Area--------------------------------------------------------
+
+trace_area(Tr, X, Y, FrameOpts, Win) ->
+ {W,H} = case Tr of
+ open -> {546,200};
+ close -> {0,0}
+ end,
+ gs:frame('TraceArea', Win,
+ [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]),
+ Editor = gs:editor('TraceEditor', 'TraceArea',
+ [{x,5}, {y,5}, {width,536}, {height,190},
+ {keypress,false}]),
+ Font = dbg_ui_win:font(normal),
+ config_editor(Editor,
+ [{vscroll,right}, {hscroll,bottom},
+ {wrap,none},{fg,{{{1,0},'end'},black}},
+ {font, Font},
+ {font_style,{{{1,0},'end'},Font}}]).
+
+resize_trace_area(close, _Key, _Diff) ->
+ ignore;
+resize_trace_area(open, Key, Diff) ->
+ New = gs:read('TraceArea', Key)+Diff,
+ gs:config('TraceArea', {Key,New}),
+ gs:config('TraceEditor', {Key,New-10}).
+
+%%--Editors-----------------------------------------------------------
+
+config_editor(Editor, Opts) ->
+ gs:config(Editor, {enable,true}),
+ gs:config(Editor, Opts),
+ gs:config(Editor, {enable,false}).
+
+%%--Resize Bars-------------------------------------------------------
+%% The resize bars are used to resize the areas within the window.
+
+%%--------------------------------------------------------------------
+%% resizebar(Flag, Name, X, Y, W, H, Obj) -> resizebar()
+%% Flag = open | close
+%% Name = atom()
+%% X = Y = integer() Coordinates relative to Obj
+%% W = H = integer() Width and height
+%% Obj = gsobj()
+%% Creates a 'resize bar', a frame object over which the cursor will
+%% be of the 'resize' type.
+%%--------------------------------------------------------------------
+resizebar(Flag, Name, X, Y, W, H, Obj) ->
+ {W2,H2} = case Flag of
+ open -> {W,H};
+ close -> {0,0}
+ end,
+ gs:create(frame, Name, Obj, [{x,X}, {y,Y}, {width,W2}, {height,H2},
+ {bw,2},
+ {cursor,resize},
+ {buttonpress,true}, {buttonrelease,true},
+ {data,resizebar}]).
+
+rb1({_Bu,Ev,Bi,Tr}) ->
+ if
+ Ev==close, Bi==close, Tr==close -> close;
+ true -> open
+ end.
+
+rb2({_Bu,Ev,Bi,Tr}) ->
+ if
+ Tr==open ->
+ if
+ Ev==close, Bi==close -> close;
+ true -> open
+ end;
+ true -> close
+ end.
+
+rb3({_Bu,Ev,Bi,_Tr}) ->
+ if
+ Ev==open, Bi==open -> open;
+ true -> close
+ end.
+
+%%--Configuration-----------------------------------------------------
+%% Resize the window as well as its contents
+
+%%--------------------------------------------------------------------
+%% config_v()
+%% Reconfigure the window vertically.
+%%--------------------------------------------------------------------
+config_v() ->
+ Y1 = 25+gs:read('CodeArea', height),
+ gs:config('RB1', {y,Y1}),
+
+ Y2 = Y1+gs:read('RB1', height),
+ gs:config('ButtonArea', {y,Y2}),
+
+ Y3 = Y2+gs:read('ButtonArea', height),
+ gs:config('EvalArea', {y,Y3}),
+ gs:config('RB3', {y,Y3}),
+ gs:config('BindArea', {y,Y3}),
+
+ Y4 = Y3 + max(gs:read('EvalArea', height),
+ gs:read('BindArea', height)),
+ gs:config('RB2', {y,Y4}),
+
+ Y5 = Y4 + gs:read('RB2', height),
+ gs:config('TraceArea', {y,Y5}).
+
+%%--------------------------------------------------------------------
+%% config_h()
+%% Reconfigure the window horizontally.
+%%--------------------------------------------------------------------
+config_h() ->
+ X1 = 2+gs:read('EvalArea', width),
+ gs:config('RB3', {x,X1}),
+
+ X2 = X1+gs:read('RB3', width),
+ gs:config('BindArea', {x,X2}).
+
+%%--------------------------------------------------------------------
+%% configure(WinInfo, W, H)
+%% The window has been resized, now its contents must be resized too.
+%%--------------------------------------------------------------------
+configure(WinInfo, NewW, NewH) ->
+ {Bu,Ev,Bi,Tr} = Flags = WinInfo#winInfo.flags,
+
+ OldW = gs:read('CodeArea', width)+4,
+ OldH = 25+gs:read('CodeArea', height)+
+ gs:read('RB1', height)+
+ gs:read('ButtonArea', height)+
+ max(gs:read('EvalArea', height), gs:read('BindArea', height))+
+ gs:read('RB2', height)+
+ gs:read('TraceArea', height),
+
+ %% Adjust width unless it is unchanged or less than minimum width
+ if
+ OldW/=NewW ->
+ {Dcode,Deval,Dbind} = configure_widths(OldW,NewW,Flags),
+ resize_code_area(WinInfo, width, Dcode),
+ case rb1(Flags) of
+ open ->
+ gs:config('RB1', {width,gs:read('RB1',width)+Dcode});
+ close -> ignore
+ end,
+ resize_button_area(Bu, width, Dcode),
+ resize_eval_area(Ev, width, Deval),
+ resize_bind_area(Bi, width, Dbind),
+ case rb2(Flags) of
+ open ->
+ gs:config('RB2', {width,gs:read('RB2',width)+Dcode});
+ close -> ignore
+ end,
+ resize_trace_area(Tr, width, Dcode),
+ config_h();
+ true -> ignore
+ end,
+
+ %% Adjust height unless it is unchanged or less than minimum height
+ if
+ OldH/=NewH ->
+ {Dcode2,Deval2,Dtrace} = configure_heights(OldH,NewH,Flags),
+ resize_code_area(WinInfo, height, Dcode2),
+ resize_eval_area(Ev, height, Deval2),
+ case rb3(Flags) of
+ open ->
+ gs:config('RB3',
+ {height,gs:read('RB3',height)+Deval2});
+ close -> ignore
+ end,
+ resize_bind_area(Bi, height, Deval2),
+ resize_trace_area(Tr, height, Dtrace),
+ config_v();
+ true -> ignore
+ end.
+
+%% Compute how much the width of each frame must be increased or
+%% decreased in order to adjust to the new window width.
+configure_widths(OldW, NewW, Flags) ->
+ {_Bu,Ev,Bi,_Tr} = Flags,
+
+ %% Difference between old and new width, considering min window width
+ Diff = abs(max(OldW,330)-max(NewW,330)),
+
+ %% Check how much the frames can be resized in reality
+ Limits = if
+ %% Window larger
+ NewW>OldW ->
+ if
+ Ev==open,Bi==open -> {0,Diff,Diff};
+ Ev==open -> {0,Diff,0};
+ Bi==open -> {0,0,Diff};
+ true -> {Diff,0,0}
+ end;
+
+ %% Window smaller; get difference between min size
+ %% and current size
+ OldW>NewW ->
+ if
+ Ev==open,Bi==open ->
+ {0,
+ gs:read('EvalArea',width)-204,
+ gs:read('BindArea',width)-112};
+ Ev==open -> {0,Diff,0};
+ Bi==open -> {0,0,Diff};
+ true -> {Diff,0,0}
+ end
+ end,
+
+ case Limits of
+
+ %% No Shell or Bind frame, larger window
+ {T,0,0} when NewW>OldW -> {T,0,0};
+
+ %% No Shell or Bind frame, smaller window
+ {T,0,0} when OldW>NewW -> {-T,0,0};
+
+ %% Window larger; divide Diff among the frames and return result
+ {_,Sf,B} when NewW>OldW ->
+ {_,Sf2,B2} = divide([{0,0},{0,Sf},{0,B}],Diff),
+ {Sf2+B2,Sf2,B2};
+
+ %% Window smaller; divide Diff among the frames and return
+ %% the inverted result (the frames should shrink)
+ {_,Sf,B} when OldW>NewW ->
+ {_,Sf2,B2} = divide([{0,0},{0,Sf},{0,B}],Diff),
+ {-(Sf2+B2),-Sf2,-B2}
+ end.
+
+%% Compute how much the height of each frame must be increased or
+%% decreased in order to adjust to the new window height.
+configure_heights(OldH, NewH, Flags) ->
+ {_Bu,Ev,Bi,Tr} = Flags,
+
+ %% Difference between old and new height, considering min win height
+ MinH = min_height(Flags),
+ Diff = abs(max(OldH,MinH)-max(NewH,MinH)),
+
+ %% Check how much the frames can be resized in reality
+ {T,Sf,Ff} = if
+ %% Window larger
+ NewH>OldH ->
+ {Diff,
+ if
+ Ev==close, Bi==close -> 0;
+ true -> Diff
+ end,
+ if
+ Tr==open -> Diff;
+ true -> 0
+ end};
+
+ %% Window smaller; get difference between min size
+ %% and current size
+ OldH>NewH ->
+ {gs:read('CodeArea',height)-100,
+ if
+ Ev==close, Bi==close -> 0;
+ true ->
+ if
+ Ev==open ->
+ gs:read('EvalArea',height)-100;
+ Bi==open ->
+ gs:read('BindArea',height)-100
+ end
+ end,
+ if
+ Tr==open -> gs:read('TraceArea',height)-100;
+ true -> 0
+ end}
+ end,
+
+ if
+ %% Window larger; divide Diff among the frames and return result
+ NewH>OldH -> divide([{0,T},{0,Sf},{0,Ff}],Diff);
+
+ %% Window smaller; divide Diff among the frames and return
+ %% the inverted result (the frames should shrink)
+ OldH>NewH ->
+ {T2,Sf2,Ff2} = divide([{0,T},{0,Sf},{0,Ff}],Diff),
+ {-T2,-Sf2,-Ff2}
+ end.
+
+%% Compute minimum window height
+min_height(Flags) ->
+ {Bu,S,Bi,F} = Flags,
+ H1 = 25 + 100 + 2, % Upper pad + Trace frame + lower pad
+ H2 = H1 + bu(Bu) + s_bi(S,Bi) + f(F),
+ H3 = case rb1(Flags) of
+ open -> H2+10;
+ close -> H2
+ end,
+ H4 = case rb2(Flags) of
+ open -> H3+10;
+ close -> H3
+ end,
+ H4.
+
+bu(close) -> 0;
+bu(open) -> 30.
+
+s_bi(close,close) -> 0;
+s_bi(_,_) -> 100.
+
+f(close) -> 0;
+f(open) -> 100.
+
+%% Try to distribute Diff as evenly as possible between E1, E2 and E3.
+divide([{T,T},{S,S},{F,F}], _Diff) ->
+ {T,S,F};
+divide(L, Diff) ->
+ [{T,Tmax},{S,Smax},{F,Fmax}] = L,
+
+ %% Count how many elements in L can still be filled
+ Rem = remaining(L),
+
+ %% Divide Diff by Rem
+ D = Diff div Rem,
+
+ if
+ %% All of Diff has been distributed
+ D==0 -> {T,S,F};
+
+ true ->
+
+ %% For each element, try to add as much as possible of D
+ {NewT,Dt} = divide2(D,T,Tmax),
+ {NewS,Ds} = divide2(D,S,Smax),
+ {NewF,Df} = divide2(D,F,Fmax),
+
+ %% Recur with a list of elements with new current values
+ %% and decreased Diff
+ divide([{NewT,Tmax},{NewS,Smax},{NewF,Fmax}],
+ (Diff rem Rem)+Dt+Ds+Df)
+ end.
+
+%% Count the number of 'non-filled' elements in L, ie where Curr<Max.
+remaining([]) -> 0;
+remaining([{Max,Max}|T]) -> remaining(T);
+remaining([_H|T]) -> 1 + remaining(T).
+
+divide2(_Diff, Max, Max) -> {Max,0};
+divide2(Diff, Curr, Max) ->
+ New = Curr+Diff,
+ if
+ New>Max -> {Max,New-Max};
+ true -> {New,0}
+ end.
+
+%%--Resizing using resize bars----------------------------------------
+%% Motions event will move the ResizeBar accordingly in Win, when
+%% the mouse button is released, the window is reconfigured.
+
+resize(WinInfo, ResizeBar) ->
+
+ %% Get window dimensions
+ W = gs:read(WinInfo#winInfo.window, width),
+ H = gs:read(WinInfo#winInfo.window, height),
+
+ %% Call resize loop with min and max for the resize bars derived
+ %% from the window dimensions
+ resizeloop(WinInfo, ResizeBar, null,
+ rblimits('RB1',W,H),
+ rblimits('RB2',W,H),
+ rblimits('RB3',W,H)).
+
+resizeloop(WI, RB, Prev, {Min1,Max1},{Min2,Max2},{Min3,Max3}) ->
+ receive
+ {gs,_,motion,_,[_,Y]} when RB=='RB1', Y>Min1,Y<Max1 ->
+ gs:config('RB1', {y,Y}),
+ resizeloop(WI, RB, Y, {Min1,Max1},{Min2,Max2},{Min3,Max3});
+ {gs,_,motion,_,_} when RB=='RB1' ->
+ resizeloop(WI, RB, Prev,{Min1,Max1},{Min2,Max2},{Min3,Max3});
+
+ {gs,_,motion,_,[_,Y]} when RB=='RB2', Y>Min2,Y<Max2 ->
+ gs:config('RB2', {y,Y}),
+ resizeloop(WI, RB, Y, {Min1,Max1},{Min2,Max2},{Min3,Max3});
+ {gs,_,motion,_,_} when RB=='RB2' ->
+ resizeloop(WI, RB, Prev,{Min1,Max1},{Min2,Max2},{Min3,Max3});
+
+ {gs,_,motion,_,[X,_]} when RB=='RB3', X>Min3,X<Max3 ->
+ gs:config('RB3', {x,X}),
+ resizeloop(WI, RB, X, {Min1,Max1},{Min2,Max2},{Min3,Max3});
+ {gs,_,motion,_,_} when RB=='RB3' ->
+ resizeloop(WI, RB, Prev,{Min1,Max1},{Min2,Max2},{Min3,Max3});
+
+ {gs,_,buttonrelease,_,_} ->
+ resize_win(WI, RB, Prev)
+ end.
+
+resize_win(_WinInfo, _RB, null) -> ignore;
+resize_win(WinInfo, 'RB1', Y) ->
+ {_Bu,S,Bi,F} = Flags = WinInfo#winInfo.flags,
+ H = gs:read('CodeArea', height),
+ Diff = H-(Y-25),
+
+ %% Resize Code, Evaluator and Binding areas
+ resize_code_area(WinInfo, height, -Diff),
+ if
+ S==close, Bi==close, F==open ->
+ resize_trace_area(open, height, Diff);
+ true ->
+ resize_eval_area(S, height, Diff),
+ resize_bind_area(Bi, height, Diff)
+ end,
+
+ %% Resize vertical resize bar
+ case rb3(Flags) of
+ open -> gs:config('RB3', {height,gs:read('RB3',height)+Diff});
+ close -> ignore
+ end,
+
+ %% Adjust the frames y coordinates
+ config_v();
+resize_win(WinInfo, 'RB2', Y) ->
+ {_Bu,S,Bi,F} = Flags = WinInfo#winInfo.flags,
+ Prev = gs:read('TraceArea',y),
+ Diff = Prev-(Y+10),
+
+ %% Resize Trace, Evaluator and Binding areas
+ resize_trace_area(F, height, Diff),
+ resize_eval_area(S, height, -Diff),
+ resize_bind_area(Bi, height, -Diff),
+
+ %% Resize vertical resize bar
+ case rb3(Flags) of
+ open -> gs:config('RB3', {height,gs:read('RB3',height)-Diff});
+ close -> ignore
+ end,
+
+ %% Adjust the frames y coordinates
+ config_v();
+
+resize_win(WinInfo, 'RB3', X) ->
+ {_Bu,S,Bi,_F} = WinInfo#winInfo.flags,
+ Prev = gs:read('BindArea', x),
+ Diff = Prev-(X+10),
+
+ %% Resize Binding and Trace areas
+ resize_bind_area(Bi, width, Diff),
+ resize_eval_area(S, width, -Diff),
+
+ %% Adjust the frames x coordinates
+ config_h().
+
+%% Given the window dimensions, return the limits for a resize bar.
+rblimits('RB1',_W,H) ->
+
+ %% Code frame should not have height <100
+ Min = 126,
+
+ %% Max is decided by a minimum distance to 'RB2'
+ %% unless 'RB2' is invisible and 'CodeArea' is visible
+ %% (=> EvalFrame and BindFrame invisible) in which case
+ %% TraceFrame should not have height <100
+ RB2 = gs:read('RB2',height),
+ FF = gs:read('TraceArea',height),
+ Max = case RB2 of
+ 0 when FF/=0 ->
+ H-112;
+ _ ->
+ Y = gs:read('RB2',y),
+ max(Min,Y-140)
+ end,
+
+ {Min,Max};
+rblimits('RB2',_W,H) ->
+
+ %% TraceFrame should not have height <100
+ Max = H-112,
+
+ %% Min is decided by a minimum distance to 'RB1'
+ Y = gs:read('RB1',y),
+ Min = min(Max,Y+140),
+
+ {Min,Max};
+
+rblimits('RB3',W,_H) ->
+
+ %% Neither CodeArea nor BindArea should occupy
+ %% less than 1/3 of the total window width and EvalFrame should
+ %% be at least 289 pixels wide
+ {max(round(W/3),289),round(2*W/3)}.
+
+max(A, B) when A>B -> A;
+max(_A, B) -> B.
+
+min(A, B) when A<B -> A;
+min(_A, B) -> B.
+
+
+%%====================================================================
+%% 'Go To Line' and 'Search' help windows
+%%====================================================================
+
+helpwin(gotoline, WinInfo, GS, Coords) ->
+ spawn_link(?MODULE, helpwin, [gotoline, WinInfo, GS, Coords,self()]);
+helpwin(search, WinInfo, GS, Coords) ->
+ spawn_link(?MODULE, helpwin, [search, WinInfo, GS, Coords, self()]).
+
+helpwin(Type, WinInfo, GS, Coords, AttPid) ->
+ {_Mod, Editor} = WinInfo#winInfo.editor,
+ Data = case Type of
+ gotoline -> null;
+ search ->
+ {{1, 0}, false}
+ end,
+ Win = helpwin(Type, GS, Coords),
+ helpwin_loop(Type, AttPid, Editor, Data, Win).
+
+helpwin_loop(Type, AttPid, Editor, Data, Win) ->
+ receive
+ {gs, _Id, destroy, _Data, _Arg} ->
+ helpwin_stop(Type, AttPid, Editor, Data),
+ true;
+
+ {gs, _Id, keypress, _Data, ['Return'|_]} ->
+ gs:config(btn(Win), flash),
+ Data2 = helpwin_action(Type, default,
+ AttPid, Editor, Data, Win),
+ helpwin_loop(Type, AttPid, Editor, Data2, Win);
+ {gs, _Id, keypress, _Data, _Arg} ->
+ helpwin_loop(Type, AttPid, Editor, Data, Win);
+
+ {gs, _Id, click, _Data, ["Clear"]} ->
+ gs:config(ent(Win), {delete, {0,last}}),
+ Data2 = helpwin_clear(Type, AttPid, Editor, Data, Win),
+ helpwin_loop(Type, AttPid, Editor, Data2, Win);
+ {gs, _Id, click, _Data, ["Close"]} ->
+ helpwin_stop(Type, AttPid, Editor, Data),
+ true;
+ {gs, _Id, click, Action, _Arg} ->
+ Data2 =
+ helpwin_action(Type, Action, AttPid, Editor, Data, Win),
+ helpwin_loop(Type, AttPid, Editor, Data2, Win)
+ end.
+
+helpwin_stop(gotoline, _AttPid, _Editor, _Data) ->
+ ignore;
+helpwin_stop(search, _AttPid, Editor, {Pos, _CS}) ->
+ unmark_string(Editor, Pos).
+
+helpwin_clear(gotoline, _AttPid, _Editor, Data, _Win) ->
+ Data;
+helpwin_clear(search, _AttPid, Editor, {Pos, CS}, Win) ->
+ unmark_string(Editor, Pos),
+ gs:config(lbl(Win), {label, {text,""}}),
+ {{1, 0}, CS}.
+
+helpwin_action(gotoline, default, AttPid, _Editor, Data, Win) ->
+ case string:strip(gs:read(ent(Win), text)) of
+ "" -> ignore;
+ Str ->
+ case catch list_to_integer(Str) of
+ {'EXIT', _Reason} -> ignore;
+ Line -> AttPid ! {gui, {gotoline, Line}}
+ end
+ end,
+ Data;
+helpwin_action(search, case_sensitive, _AttPid, _Ed, {Pos, CS}, _Win) ->
+ Bool = if CS==true -> false; CS==false -> true end,
+ {Pos, Bool};
+helpwin_action(search, default, _AttPid, Editor, {Pos, CS}, Win) ->
+ gs:config(lbl(Win), {label, {text, ""}}),
+ unmark_string(Editor, Pos),
+ case gs:read(ent(Win), text) of
+ "" -> {Pos, CS};
+ Str ->
+ gs:config(lbl(Win), {label, {text,"Searching..."}}),
+ Str2 = lowercase(CS, Str),
+ case search(Str2, Editor, gs:read(Editor, size), Pos, CS) of
+ {Row, Col} ->
+ gs:config(lbl(Win), {label, {text,""}}),
+ mark_string(Editor, {Row, Col}, Str),
+ {{Row, Col}, CS};
+ not_found ->
+ gs:config(lbl(Win), {label, {text,"Not found"}}),
+ {Pos, CS}
+ end
+ end.
+
+search(_Str, _Editor, Max, {Row, _Col}, _CS) when Row>Max ->
+ not_found;
+search(Str, Editor, Max, {Row, Col}, CS) ->
+ SearchIn = lowercase(CS, gs:read(Editor,
+ {get,{{Row,Col+1},{Row,lineend}}})),
+ case string:str(SearchIn, Str) of
+ 0 -> search(Str, Editor, Max, {Row+1, 0}, CS);
+ N -> {Row, Col+N}
+ end.
+
+lowercase(true, Str) -> Str;
+lowercase(false, Str) ->
+ lists:map(fun(Char) ->
+ if
+ Char>=$A, Char=<$Z -> Char+32;
+ true -> Char
+ end
+ end,
+ Str).
+
+mark_string(Editor, {Row, Col}, Str) ->
+ Between = {{Row,Col}, {Row,Col+length(Str)}},
+ Font = dbg_ui_win:font(bold),
+ gs:config(Editor, [{vscrollpos, Row-5},
+ {font_style, {Between, Font}},
+ {fg, {Between, red}}]).
+
+unmark_string(Editor, {Row, Col}) ->
+ Between = {{Row,Col}, {Row,lineend}},
+ Font = dbg_ui_win:font(normal),
+ gs:config(Editor, [{vscrollpos, Row-5},
+ {font_style, {Between, Font}},
+ {fg, {Between, black}}]).
+
+helpwin(Type, GS, {X, Y}) ->
+ W = 200, Pad=10, Wbtn = 50,
+
+ Title =
+ case Type of search -> "Search"; gotoline -> "Go To Line" end,
+ Win = gs:window(GS, [{title, Title}, {x, X}, {y, Y}, {width, W},
+ {destroy, true}]),
+
+ Ent = gs:entry(Win, [{x, Pad}, {y, Pad}, {width, W-2*Pad},
+ {keypress, true}]),
+ Hent = gs:read(Ent, height),
+
+ Font = dbg_ui_win:font(normal),
+
+ {Ybtn, Lbl} =
+ case Type of
+ search ->
+ Ycb = Pad+Hent,
+ gs:checkbutton(Win, [{label, {text, "Case Sensitive"}},
+ {font, Font},
+ {align, w},
+ {x, Pad}, {y, Ycb},
+ {width, W-2*Pad}, {height, 15},
+ {data, case_sensitive}]),
+ Ylbl = Ycb+15,
+ {Ylbl+Hent+Pad,
+ gs:label(Win, [{x, Pad}, {y, Ylbl},
+ {width, W-2*Pad}, {height, Hent}])};
+ gotoline -> {Pad+Hent+Pad, null}
+ end,
+
+ BtnLbl = case Type of search -> "Search"; gotoline -> "Go" end,
+ Btn = gs:button(Win, [{label, {text, BtnLbl}}, {font, Font},
+ {x, W/2-3/2*Wbtn-Pad}, {y, Ybtn},
+ {width, Wbtn}, {height, Hent},
+ {data, default}]),
+ gs:button(Win, [{label, {text, "Clear"}}, {font, Font},
+ {x, W/2-1/2*Wbtn}, {y, Ybtn},
+ {width, Wbtn}, {height, Hent}]),
+ gs:button(Win, [{label, {text, "Close"}}, {font, Font},
+ {x, W/2+1/2*Wbtn+Pad}, {y, Ybtn},
+ {width, Wbtn}, {height, Hent}]),
+
+ H = Ybtn+Hent+Pad,
+ gs:config(Win, [{height, H}, {map, true}]),
+ {Ent, Lbl, Btn}.
+
+ent(Win) -> element(1, Win).
+lbl(Win) -> element(2, Win).
+btn(Win) -> element(3, Win).
diff --git a/lib/debugger/src/dbg_ui_view.erl b/lib/debugger/src/dbg_ui_view.erl
new file mode 100644
index 0000000000..075275f196
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_view.erl
@@ -0,0 +1,256 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_view).
+
+%% External exports
+-export([start/2]).
+
+%% Internal exports
+-export([init/3]).
+
+-record(state, {gs, % term() Graphics system id
+ win, % term() Attach process window data
+ coords, % {X,Y} Mouse point position
+ mod % atom() Module
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start(GS, Mod)
+%% Mod = atom()
+%%--------------------------------------------------------------------
+start(GS, Mod) ->
+ Title = "View Module " ++ atom_to_list(Mod),
+ case dbg_ui_winman:is_started(Title) of
+ true -> ignore;
+ false -> spawn(?MODULE, init, [GS, Mod, Title])
+ end.
+
+
+%%====================================================================
+%% Main loop and message handling
+%%====================================================================
+
+init(GS, Mod, Title) ->
+
+ %% Subscribe to messages from the interpreter
+ int:subscribe(),
+
+ %% Create attach process window
+ Win1 = dbg_ui_trace_win:create_win(GS, Title, ['Code Area'], menus()),
+ Window = dbg_ui_trace_win:get_window(Win1),
+ dbg_ui_winman:insert(Title, Window),
+
+ Win2 = gui_load_module(Win1, Mod),
+ Win3 =
+ lists:foldl(fun(Break, Win) ->
+ dbg_ui_trace_win:add_break(Win, 'Break', Break)
+ end,
+ Win2,
+ int:all_breaks(Mod)),
+
+ loop(#state{gs=GS, win=Win3, coords={0,0}, mod=Mod}).
+
+loop(State) ->
+ receive
+
+ %% From the GUI main window
+ GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs ->
+ Cmd = dbg_ui_trace_win:handle_event(GuiEvent, State#state.win),
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the GUI help windows
+ {gui, Cmd} ->
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the interpreter
+ {int, Cmd} ->
+ State2 = int_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the dbg_ui_winman process (Debugger window manager)
+ {dbg_ui_winman, update_windows_menu, Data} ->
+ dbg_ui_winman:update_windows_menu(Data),
+ loop(State);
+ {dbg_ui_winman, destroy} ->
+ exit(stop);
+
+ %% Help window termination -- ignore
+ {'EXIT', _Pid, _Reason} ->
+ loop(State)
+ end.
+
+%%--Commands from the GUI---------------------------------------------
+
+gui_cmd(ignore, State) ->
+ State;
+gui_cmd({win, Win}, State) ->
+ State#state{win=Win};
+gui_cmd(stopped, _State) ->
+ exit(stop);
+gui_cmd({coords, Coords}, State) ->
+ State#state{coords=Coords};
+
+gui_cmd({shortcut, Key}, State) ->
+ case shortcut(Key) of
+ false -> State;
+ Cmd -> gui_cmd(Cmd, State)
+ end;
+
+%% File menu
+gui_cmd('Close', State) ->
+ gui_cmd(stopped, State);
+
+%% Edit menu
+gui_cmd('Go To Line...', State) ->
+ %% Will result in message handled below: {gui, {gotoline, Line}}
+ dbg_ui_trace_win:helpwin(gotoline, State#state.win,
+ State#state.gs, State#state.coords),
+ State;
+gui_cmd({gotoline, Line}, State) ->
+ Win = dbg_ui_trace_win:select_line(State#state.win, Line),
+ State#state{win=Win};
+gui_cmd('Search...', State) ->
+ dbg_ui_trace_win:helpwin(search, State#state.win,
+ State#state.gs, State#state.coords),
+ State;
+
+%% Break menu
+gui_cmd('Line Break...', State) ->
+ add_break(State#state.gs, State#state.coords, line,
+ State#state.mod,
+ dbg_ui_trace_win:selected_line(State#state.win)),
+ State;
+gui_cmd('Conditional Break...', State) ->
+ add_break(State#state.gs, State#state.coords, conditional,
+ State#state.mod,
+ dbg_ui_trace_win:selected_line(State#state.win)),
+ State;
+gui_cmd('Function Break...', State) ->
+ add_break(State#state.gs, State#state.coords, function,
+ State#state.mod, undefined),
+ State;
+gui_cmd('Enable All', State) ->
+ Breaks = int:all_breaks(),
+ ThisMod = State#state.mod,
+ lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
+ int:enable_break(Mod, Line);
+ (_Break) ->
+ ignore
+ end,
+ Breaks),
+ State;
+gui_cmd('Disable All', State) ->
+ Breaks = int:all_breaks(),
+ ThisMod = State#state.mod,
+ lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
+ int:disable_break(Mod, Line);
+ (_Break) ->
+ ignore
+ end,
+ Breaks),
+ State;
+gui_cmd('Delete All', State) ->
+ int:no_break(State#state.mod),
+ State;
+gui_cmd({break, {Mod, Line}, What}, State) ->
+ case What of
+ add -> int:break(Mod, Line);
+ delete -> int:delete_break(Mod, Line);
+ {status, inactive} -> int:disable_break(Mod, Line);
+ {status, active} -> int:enable_break(Mod, Line);
+ {trigger, Action} -> int:action_at_break(Mod, Line, Action)
+ end,
+ State;
+
+%% Help menu
+gui_cmd('Debugger', State) ->
+ Window = dbg_ui_trace_win:get_window(State#state.win),
+ HelpFile = filename:join([code:lib_dir(debugger),
+ "doc", "html", "part_frame.html"]),
+ tool_utils:open_help(Window, HelpFile),
+ State.
+
+add_break(GS, Coords, Type, undefined, _Line) ->
+ dbg_ui_break:start(GS, Coords, Type);
+add_break(GS, Coords, Type, Mod, undefined) ->
+ dbg_ui_break:start(GS, Coords, Type, Mod);
+add_break(GS, Coords, Type, Mod, Line) ->
+ dbg_ui_break:start(GS, Coords, Type, Mod, Line).
+
+%%--Commands from the interpreter-------------------------------------
+
+int_cmd({new_break, {{Mod,_Line},_Options}=Break}, #state{mod=Mod}=State) ->
+ Win = dbg_ui_trace_win:add_break(State#state.win, 'Break', Break),
+ State#state{win=Win};
+int_cmd({delete_break, {Mod,_Line}=Point}, #state{mod=Mod}=State) ->
+ Win = dbg_ui_trace_win:delete_break(State#state.win, Point),
+ State#state{win=Win};
+int_cmd({break_options, {{Mod,_Line},_Options}=Break}, #state{mod=Mod}=State) ->
+ Win = dbg_ui_trace_win:update_break(State#state.win, Break),
+ State#state{win=Win};
+int_cmd(no_break, State) ->
+ Win = dbg_ui_trace_win:clear_breaks(State#state.win),
+ State#state{win=Win};
+int_cmd({no_break, _Mod}, State) ->
+ Win = dbg_ui_trace_win:clear_breaks(State#state.win),
+ State#state{win=Win};
+int_cmd(_, State) ->
+ State.
+
+
+%%====================================================================
+%% GUI auxiliary functions
+%%====================================================================
+
+menus() ->
+ [{'File', [{'Close', 0}]},
+ {'Edit', [{'Go To Line...', 0},
+ {'Search...', 0}]},
+ {'Break', [{'Line Break...', 5},
+ {'Conditional Break...', 13},
+ {'Function Break...', 0},
+ separator,
+ {'Enable All', no},
+ {'Disable All', no},
+ {'Delete All', 0},
+ separator]},
+ {'Help', [{'Debugger', no}]}].
+
+shortcut(c) -> 'Close';
+shortcut(g) -> 'Go To Line...';
+shortcut(s) -> 'Search...';
+shortcut(b) -> 'Line Break...';
+shortcut(r) -> 'Conditional Break...';
+shortcut(f) -> 'Function Break...';
+shortcut(d) -> 'Delete All';
+
+shortcut(_) -> false.
+
+gui_load_module(Win, Mod) ->
+ dbg_ui_trace_win:display({text, "Loading module..."}),
+ Contents = int:contents(Mod, any),
+ Win2 = dbg_ui_trace_win:show_code(Win, Mod, Contents),
+ dbg_ui_trace_win:display({text, ""}),
+ Win2.
diff --git a/lib/debugger/src/dbg_ui_win.erl b/lib/debugger/src/dbg_ui_win.erl
new file mode 100644
index 0000000000..9840aa54da
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_win.erl
@@ -0,0 +1,277 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_win).
+
+%% External exports
+-export([init/0,
+ font/1, min_size/3, min_size/4,
+ create_menus/2, select/2, selected/1,
+ add_break/2, update_break/2, delete_break/1,
+ motion/2
+
+ ]).
+
+-record(break, {mb, smi, emi, dimi, demi}).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% init() -> GS
+%% GS = term()
+%%--------------------------------------------------------------------
+init() ->
+ gs:start([{kernel, true}]).
+
+%%--------------------------------------------------------------------
+%% font(Style) -> Font
+%% Style = normal | bold
+%% Select a suitable font. Defaults to {screen,12} and, if it does not
+%% exist, {courier,12}.
+%%--------------------------------------------------------------------
+font(Style) ->
+ GS = init(),
+ Style2 = if
+ Style==normal -> [];
+ true -> [Style]
+ end,
+ case gs:read(GS, {choose_font, {screen,Style2,12}}) of
+ Font when element(1, Font)==screen ->
+ Font;
+ _ ->
+ gs:read(GS, {choose_font, {courier,Style2,12}})
+ end.
+
+%%--------------------------------------------------------------------
+%% min_size(Strings, MinW, MinH) -> {W, H}
+%% min_size(Font, Strings, MinW, MinH) -> {W, H}
+%% Font = GS font - defaults to dbg_ui_win:font(normal)
+%% Strings = [string()]
+%% MinW = MinH = int()
+%% W = H = int()
+%%--------------------------------------------------------------------
+min_size(Strings, MinW, MinH) ->
+ min_size(font(normal), Strings, MinW, MinH).
+
+min_size(Font, Strings, MinW, MinH) ->
+ GS = init(),
+ min_size(GS, Font, Strings, MinW, MinH).
+
+min_size(GS, Font, [String|Strings], MinW, MinH) ->
+ {W, H} = gs:read(GS, {font_wh, {Font, String}}),
+ min_size(GS, Font, Strings, max(MinW, W), max(MinH, H));
+min_size(_GS, _Font, [], W, H) ->
+ {W, H}.
+
+max(X, Y) when X>Y -> X;
+max(_X, Y) -> Y.
+
+%%--------------------------------------------------------------------
+%% create_menus(MenuBar, [Menu])
+%% MenuBar = gsobj()
+%% Menu = {Name, [Item]}
+%% Name = atom()
+%% Item = {Name, N} | {Name, N, Type} | {Name, N, cascade, [Item]}
+%% | separator
+%% N = no | integer()
+%% Type = check | radio
+%% Create the specified menus and menuitems.
+%%
+%% Normal menuitems are specified as {Name, N}. Generates the event:
+%% {gs, _Id, click, {menuitem, Name}, _Arg}
+%%
+%% Check and radio menuitems are specified as {Name, N, check|radio}.
+%% They are assumed to be children to a cascade menuitem! (And all children
+%% to one cascade menuitem are assumed to be either check OR radio
+%% menuitems)!
+%% Selecting a check/radio menuitem generates the event:
+%% {gs, _Id, click, {menu, Menu}, _Arg}
+%% where Menu is the name of the parent, the cascade menuitem.
+%% Use selected(Menu) to retrieve which check/radio menuitems are
+%% selected.
+%%--------------------------------------------------------------------
+create_menus(MenuBar, [{Title, Items}|Menus]) ->
+ Title2 = " "++(atom_to_list(Title))++" ",
+ MenuBtn = gs:menubutton(MenuBar, [{label, {text,Title2}},
+ {font, font(normal)}]),
+ case Title of
+ 'Help' -> gs:config(MenuBtn, {side, right});
+ _ -> ignore
+ end,
+ Menu = gs:menu(Title, MenuBtn, []),
+ create_items(Menu, Items, Title),
+ create_menus(MenuBar, Menus);
+create_menus(_MenuBar, []) ->
+ done.
+
+create_items(Menu, [Item|Items], Group) ->
+ create_item(Menu, Item, Group),
+ create_items(Menu, Items, Group);
+create_items(_Menu, [], _Group) ->
+ done.
+
+create_item(Menu, {Name, _N, cascade, Items}, _Group) ->
+ MenuBtn = gs:menuitem(Menu, [{label, {text,Name}},
+ {font, font(normal)},
+ {itemtype, cascade}]),
+ SubMenu = gs:menu(Name, MenuBtn, []),
+ create_items(SubMenu, Items, Name);
+create_item(Menu, separator, _Group) ->
+ gs:menuitem(Menu, [{itemtype, separator}]);
+create_item(Menu, MenuItem, Group) ->
+ Options = case MenuItem of
+ {Name, N} ->
+ [{data, {menuitem,Name}}];
+ {Name, N, check} ->
+ [{itemtype, check}, {data, {menu, Group}}];
+ {Name, N, radio} ->
+ [{itemtype, radio}, {data, {menu, Group}},
+ {group, group(Group)}]
+ end,
+ gs:menuitem(Name, Menu, [{label, {text,Name}},
+ {font, font(normal)} | Options]),
+ if
+ is_integer(N) -> gs:config(Name, {underline, N});
+ true -> ignore
+ end.
+
+%% When grouping radio buttons, the group id must be an atom unique for
+%% each window.
+group(Group) ->
+ list_to_atom(atom_to_list(Group)++pid_to_list(self())).
+
+%%--------------------------------------------------------------------
+%% select(MenuItem, Bool)
+%% MenuItem = atom()
+%% Bool = boolean()
+%%--------------------------------------------------------------------
+select(MenuItem, Bool) ->
+ gs:config(MenuItem, {select, Bool}).
+
+%%--------------------------------------------------------------------
+%% selected(Menu) -> [Name]
+%% Menu = Name = atom()
+%%--------------------------------------------------------------------
+selected(Menu) ->
+ Children = gs:read(Menu, children),
+ Selected = lists:filter(fun(Child) -> gs:read(Child, select) end,
+ Children),
+ lists:map(fun(Child) ->
+ {text, Name} = gs:read(Child, label),
+ list_to_atom(Name)
+ end,
+ Selected).
+
+%%--------------------------------------------------------------------
+%% add_break(Name, Point) -> #break{}
+%% Name = atom()
+%% Point = {Mod, Line}
+%% The break will generate the following events:
+%% {gs, _Id, click, {break, Point, Event}, _Arg}
+%% Event = delete | {trigger, Action} | {status, Status}
+%% Action = enable | disable | delete
+%% Status = active | inactive
+%%--------------------------------------------------------------------
+add_break(Menu, Point) ->
+ Font = font(normal),
+
+ %% Create a name for the breakpoint
+ {Mod, Line} = Point,
+ Label = io_lib:format("~w ~5w", [Mod, Line]),
+
+ %% Create a menu for the breakpoint
+ MenuBtn = gs:menuitem(Menu, [{label, {text,Label}}, {font, Font},
+ {itemtype, cascade}]),
+ SubMenu = gs:menu(MenuBtn, []),
+ SMI = gs:menuitem(SubMenu, [{data, {break,Point,null}}]),
+ gs:menuitem(SubMenu, [{label, {text,"Delete"}}, {font, Font},
+ {data, {break,Point,delete}}]),
+ TriggerMenuBtn = gs:menuitem(SubMenu,
+ [{label,{text,"Trigger Action"}},
+ {font, Font},
+ {itemtype, cascade}]),
+ TriggerMenu = gs:menu(TriggerMenuBtn, []),
+ Group = element(3, erlang:now()),
+ EMI = gs:menuitem(TriggerMenu, [{label, {text,"Enable"}},
+ {font, Font},
+ {itemtype, radio}, {group, Group},
+ {data,
+ {break,Point,{trigger,enable}}}]),
+ DiMI = gs:menuitem(TriggerMenu, [{label, {text,"Disable"}},
+ {font, Font},
+ {itemtype, radio}, {group, Group},
+ {data,
+ {break,Point,{trigger,disable}}}]),
+ DeMI = gs:menuitem(TriggerMenu, [{label, {text,"Delete"}},
+ {font, Font},
+ {itemtype, radio}, {group, Group},
+ {data,
+ {break,Point,{trigger,delete}}}]),
+
+ #break{mb=MenuBtn, smi=SMI, emi=EMI, dimi=DiMI, demi=DeMI}.
+
+%%--------------------------------------------------------------------
+%% update_break(Break, Options)
+%% Break = #break{}
+%% Options = [Status, Action, Mods, Cond]
+%% Status = active | inactive
+%% Action = enable | disable | delete
+%% Mods = null (not used)
+%% Cond = null | {Mod, Func}
+%%--------------------------------------------------------------------
+update_break(Break, Options) ->
+ [Status, Trigger|_] = Options,
+ {break, Point, _Status} = gs:read(Break#break.smi, data),
+
+ {Label, Data} = case Status of
+ active ->
+ {"Disable", {break,Point,{status,inactive}}};
+ inactive ->
+ {"Enable", {break,Point,{status,active}}}
+ end,
+ gs:config(Break#break.smi, [{label, {text,Label}},
+ {font, font(normal)},
+ {data, Data}]),
+
+ TriggerMI = case Trigger of
+ enable -> Break#break.emi;
+ disable -> Break#break.dimi;
+ delete -> Break#break.demi
+ end,
+ gs:config(TriggerMI, {select, true}).
+
+%%--------------------------------------------------------------------
+%% delete_break(Break)
+%% Break = #break{}
+%%--------------------------------------------------------------------
+delete_break(Break) ->
+ gs:destroy(Break#break.mb).
+
+%%--------------------------------------------------------------------
+%% motion(X, Y) -> {X, Y}
+%% X = Y = integer()
+%%--------------------------------------------------------------------
+motion(X, Y) ->
+ receive
+ {gs, _Id, motion, _Data, [NX,NY]} ->
+ motion(NX, NY)
+ after 0 ->
+ {X, Y}
+ end.
diff --git a/lib/debugger/src/dbg_ui_winman.erl b/lib/debugger/src/dbg_ui_winman.erl
new file mode 100644
index 0000000000..71023cd0d6
--- /dev/null
+++ b/lib/debugger/src/dbg_ui_winman.erl
@@ -0,0 +1,177 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_ui_winman).
+-behaviour(gen_server).
+
+%% External exports
+-export([start/0]).
+-export([insert/2, is_started/1,
+ clear_process/1,
+ raise/1,
+ windows_menu/1, update_windows_menu/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(win, {owner, % pid()
+ title, % string()
+ win % gsobj()
+ }).
+
+-record(state, {wins=[] % [#win{}]
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start()
+%%--------------------------------------------------------------------
+start() ->
+ gen_server:start({local,?MODULE}, ?MODULE, [], []).
+
+%%--------------------------------------------------------------------
+%% insert(Title, Win)
+%% Title = string()
+%% Win = gsobj()
+%%--------------------------------------------------------------------
+insert(Title, Win) ->
+ gen_server:cast(?MODULE, {insert, self(), Title, Win}).
+
+%%--------------------------------------------------------------------
+%% is_started(Title) -> true | false
+%% Title = string()
+%%--------------------------------------------------------------------
+is_started(Title) ->
+ case gen_server:call(?MODULE, {is_started, Title}, infinity) of
+ {true, Win} ->
+ raise(Win),
+ true;
+ false ->
+ false
+ end.
+
+%%--------------------------------------------------------------------
+%% clear_process(Title)
+%% Title = string()
+%%--------------------------------------------------------------------
+clear_process(Title) ->
+ gen_server:cast(?MODULE, {clear_process, Title}).
+
+%%--------------------------------------------------------------------
+%% raise(Win)
+%% Win = gsobj()
+%%--------------------------------------------------------------------
+raise(Win) ->
+ gs:config(Win, [raise, {iconify, false}, {setfocus, true}]).
+
+%%--------------------------------------------------------------------
+%% windows_menu(MenuBar)
+%% MenuBar = gsobj()
+%%--------------------------------------------------------------------
+windows_menu(MenuBar) ->
+ gs:menubutton('WindowsMenuBtn', MenuBar,
+ [{label,{text," Windows "}},
+ {font, dbg_ui_win:font(normal)}]),
+ gs:menu('WindowsMenu', 'WindowsMenuBtn', []).
+
+%%--------------------------------------------------------------------
+%% update_windows_menu(Data)
+%% Data = {New, Old}
+%% New = Old = list()
+%%--------------------------------------------------------------------
+update_windows_menu([MonInfo|Infos]) ->
+ gs:destroy('WindowsMenu'),
+ gs:menu('WindowsMenu', 'WindowsMenuBtn', []),
+ menuitem(MonInfo),
+ gs:menuitem(separator, 'WindowsMenu', [{itemtype, separator}]),
+ lists:foreach(fun(Info) -> menuitem(Info) end, Infos).
+
+menuitem({Title, Win}) ->
+ gs:menuitem(Title, 'WindowsMenu', [{label, {text,Title}},
+ {font, dbg_ui_win:font(normal)},
+ {data, {dbg_ui_winman,Win}}]).
+
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+init(_Arg) ->
+ process_flag(trap_exit, true),
+ {ok, #state{}}.
+
+handle_call({is_started, Title}, _From, State) ->
+ Reply = case lists:keysearch(Title, #win.title, State#state.wins) of
+ {value, Win} -> {true, Win#win.win};
+ false -> false
+ end,
+ {reply, Reply, State}.
+
+handle_cast({insert, Pid, Title, Win}, State) ->
+ link(Pid),
+ Wins = State#state.wins ++ [#win{owner=Pid, title=Title, win=Win}],
+ inform_all(Wins),
+ {noreply, State#state{wins=Wins}};
+
+handle_cast({clear_process, Title}, State) ->
+ OldWins = State#state.wins,
+ Wins = case lists:keysearch(Title, #win.title, OldWins) of
+ {value, #win{owner=Pid}} ->
+ Msg = {dbg_ui_winman, destroy},
+ Pid ! Msg,
+ lists:keydelete(Title, #win.title, OldWins);
+ false ->
+ OldWins
+ end,
+ {noreply, State#state{wins=Wins}}.
+
+handle_info({'EXIT', Pid, _Reason}, State) ->
+ [Mon | _Wins] = State#state.wins,
+ if
+ Pid==Mon#win.owner -> {stop, normal, State};
+ true ->
+ Wins2 = lists:keydelete(Pid, #win.owner, State#state.wins),
+ inform_all(Wins2),
+ {noreply, State#state{wins=Wins2}}
+ end.
+
+terminate(_Reason, State) ->
+ delete_all(State#state.wins),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+inform_all(Wins) ->
+ Infos = lists:map(fun(#win{title=Title, win=Win}) -> {Title, Win} end,
+ Wins),
+ Msg = {dbg_ui_winman, update_windows_menu, Infos},
+ lists:foreach(fun(#win{owner=Pid}) -> Pid ! Msg end, Wins).
+
+delete_all(Wins) ->
+ Msg = {dbg_ui_winman, destroy},
+ lists:foreach(fun(#win{owner=Pid}) -> Pid ! Msg end, Wins).
diff --git a/lib/debugger/src/dbg_wx_break.erl b/lib/debugger/src/dbg_wx_break.erl
new file mode 100644
index 0000000000..a19203f7d0
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_break.erl
@@ -0,0 +1,102 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(dbg_wx_break).
+
+%% External exports
+-export([start/3, start/4, start/5]).
+
+%% Internal exports
+-export([init/6]).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start(Wx, Pos, Type)
+%% start(Wx, Pos, Type, Module, Line)
+%% Wx = Parent Window
+%% Pos = {X, Y}
+%% X = Y = integer()
+%% Type = line | conditional | function
+%% Module = atom()
+%% Line = integer()
+%%--------------------------------------------------------------------
+start(Wx, Pos, Type) ->
+ start(Wx, Pos, Type, "", "").
+start(Wx, Pos, Type, Mod) ->
+ start(Wx, Pos, Type, Mod, "").
+start(Wx, Pos, Type, Mod, Line) ->
+ Env = wx:get_env(),
+ spawn_link(?MODULE, init, [Wx, Env, Pos, Type, Mod, Line]).
+
+
+%%====================================================================
+%% Internal exports
+%%====================================================================
+
+init(Wx, Env, Pos, Type, Mod, Line) ->
+ wx:set_env(Env),
+ Win = wx:batch(fun() -> dbg_wx_break_win:create_win(Wx, Pos, Type, Mod, Line) end),
+ if
+ Type==function, is_atom(Mod) ->
+ Win2 = gui_cmd({module, Mod}, Win),
+ loop(Win2);
+ true ->
+ loop(Win)
+ end.
+
+loop(Win) ->
+ receive
+
+ %% From the GUI
+ GuiEvent when element(1, GuiEvent)==gs; element(1, GuiEvent)==wx ->
+ Cmd = wx:batch(fun() -> dbg_wx_break_win:handle_event(GuiEvent, Win) end),
+ Win2 = gui_cmd(Cmd, Win),
+ loop(Win2)
+ end.
+
+gui_cmd(ignore, Win) ->
+ Win;
+gui_cmd(stopped, _Win) ->
+ exit(normal);
+gui_cmd({win, Win2}, _Win) ->
+ Win2;
+gui_cmd({module, Mod}, Win) ->
+ Funcs = int:functions(Mod),
+ dbg_wx_break_win:update_functions(Win, Funcs);
+gui_cmd({break, DataL, Action}, _Win) ->
+ Fun =
+ fun(Data) ->
+ case Data of
+ [Mod, Line] ->
+ int:break(Mod, Line),
+ int:action_at_break(Mod, Line, Action);
+ [Mod, Line, CMod, CFunc] ->
+ int:break(Mod, Line),
+ int:test_at_break(Mod, Line, {CMod, CFunc}),
+ int:action_at_break(Mod, Line, Action);
+ [Mod, Func, Arity] ->
+ int:break_in(Mod, Func, Arity)
+ end
+ end,
+ lists:foreach(Fun, DataL),
+ exit(normal).
diff --git a/lib/debugger/src/dbg_wx_break_win.erl b/lib/debugger/src/dbg_wx_break_win.erl
new file mode 100644
index 0000000000..5dafb0fbe6
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_break_win.erl
@@ -0,0 +1,272 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(dbg_wx_break_win).
+
+%% External exports
+-export([create_win/5,
+ update_functions/2,
+ handle_event/2]).
+
+-include_lib("wx/include/wx.hrl").
+
+-record(winInfo, {type, % line | conditional | function
+ win, % wxobj()
+ entries, % [{atom|integer, wxobj()}]
+ trigger, % [{wxobj(),enable | disable | delete}]
+ listbox, % wxobj()
+ text, % wxobj()
+ ok, % wxobj()
+ funcs=[] % [[Name, Arity]]
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% create_win(Win, Pos, Type, Mod, Line) -> #winInfo{}
+%% Win = Top level window
+%% Pos = {X, Y}
+%% X = Y = integer()
+%% Type = line | conditional | function
+%% Mod = atom() | ""
+%% Line = integer() | ""
+%%--------------------------------------------------------------------
+
+create_win(Parent, Pos, function, Mod, _Line) ->
+ Win = wxDialog:new(Parent, ?wxID_ANY, "Function Break",
+ [{pos, Pos},
+ {style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]),
+ MainS = wxBoxSizer:new(?wxVERTICAL),
+ Label = wxStaticText:new(Win, ?wxID_ANY, "Module:"),
+ Int = int:interpreted(),
+ IntStrs = [atom_to_list(M) || M <- Int],
+ Text = wxComboBox:new(Win, ?wxID_ANY,
+ [{value, dbg_wx_win:to_string(Mod)},
+ {choices, IntStrs}]),
+
+ Expand = [{border, 5}, {flag,?wxLEFT bor ?wxRIGHT bor ?wxEXPAND}],
+ wxSizer:add(MainS, Label, [{border,5},
+ {flag,?wxTOP bor ?wxLEFT bor ?wxRIGHT}]),
+ wxSizer:add(MainS, Text, Expand),
+ FunLabel = wxStaticText:new(Win, ?wxID_ANY, "Function:"),
+ LB = wxListBox:new(Win, ?wxID_ANY, [{size,{-1, 100}},{style,?wxLB_MULTIPLE}]),
+ wxSizer:add(MainS, FunLabel, Expand),
+ wxSizer:add(MainS, LB, [{proportion,1}|Expand]),
+ wxSizer:setMinSize(MainS, 300, 400),
+ OK = wxDialog:createStdDialogButtonSizer(Win, ?wxOK bor ?wxCANCEL),
+ wxSizer:add(MainS, OK, [{border,5},{flag,?wxALL}]),
+ wxDialog:setSizer(Win,MainS),
+ wxSizer:fit(MainS, Win),
+ wxSizer:setSizeHints(MainS,Win),
+ wxComboBox:setFocus(Text),
+ wxDialog:connect(Win, command_button_clicked),
+ wxComboBox:connect(Text, command_text_updated),
+ wxListBox:connect(LB, command_listbox_selected),
+ wxListBox:connect(LB, command_listbox_doubleclicked),
+ OkId = wxDialog:getAffirmativeId(Win),
+ OKButt = wxWindow:findWindowById(OkId, [{parent, Win}]),
+ wxWindow:disable(OKButt),
+ wxDialog:centreOnParent(Win),
+ wxDialog:show(Win),
+
+ #winInfo{type=function, win=Win, text=Text, ok=OKButt,
+ entries=[], trigger=enable,
+ listbox=LB, funcs=[]};
+
+create_win(Parent, Pos, Type, Mod, Line) ->
+ Title = case Type of
+ line -> "Line Break";
+ conditional -> "Conditional Break"
+ end,
+ Style = ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER,
+ Win = wxDialog:new(Parent, ?wxID_ANY, Title,
+ [{pos, Pos},
+ {style, Style}]),
+ %% Create Sizers
+ MainS = wxBoxSizer:new(?wxVERTICAL),
+
+ %% Add module
+ Int = int:interpreted(),
+ IntStrs = [atom_to_list(M) || M <- Int],
+ ModT = wxComboBox:new(Win, ?wxID_ANY, [{choices,IntStrs}]),
+ ModSz = create_label_of_control(Win, "Module:", ModT, Mod),
+ wxSizer:add(MainS,ModSz,[{flag, ?wxEXPAND}]),
+ %% Create rest of text input fields
+ Add = fun({IType, Label, Def}) ->
+ {Sz, Text} = create_sizer_with_text(Win, Label, Def),
+ wxSizer:add(MainS, Sz, [{flag, ?wxEXPAND}]),
+ {Text, IType}
+ end,
+ Inputs = case Type of
+ line ->
+ [{integer,"Line:",Line}];
+ conditional ->
+ [{integer,"Line:",Line},
+ {atom,"C-Module:",""},
+ {atom,"C-Function:",""}]
+ end,
+ %% Add the rest of the entries
+ Entries = wx:map(Add, Inputs),
+ %% Create and add radio box
+ {TriggerBox,Trigger} = create_trigger_box(Win),
+ wxSizer:add(MainS, TriggerBox, [{border,5},{flag,?wxALL bor ?wxEXPAND}]),
+
+ wxSizer:addStretchSpacer(MainS),
+ %% Put it together
+ OK = wxDialog:createStdDialogButtonSizer(Win, ?wxOK bor ?wxCANCEL),
+ wxSizer:add(MainS, OK, [{border,5},{flag,?wxALL}]),
+ wxSizer:setMinSize(MainS, 300, -1),
+ wxDialog:setSizer(Win,MainS),
+ wxSizer:fit(MainS, Win),
+ wxSizer:setSizeHints(MainS,Win),
+ wxComboBox:setFocus(ModT),
+ wxDialog:connect(Win, command_button_clicked),
+ wxDialog:connect(Win, command_text_updated),
+ OkId = wxDialog:getAffirmativeId(Win),
+ OKButt = wxWindow:findWindowById(OkId),
+ wxWindow:disable(OKButt),
+ wxDialog:centreOnParent(Win),
+ wxDialog:show(Win),
+ #winInfo{type=Type, win=Win, text=ModT,
+ entries=Entries, trigger=Trigger, ok=OKButt}.
+
+%%--------------------------------------------------------------------
+%% update_functions(WinInfo, Funcs) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Funcs = [{Name, Arity}]
+%% Name = atom()
+%% Arity = integer()
+%%--------------------------------------------------------------------
+update_functions(WinInfo, Funcs) ->
+ Items = lists:map(fun([N, A]) ->
+ lists:flatten(io_lib:format("~p/~p", [N,A]))
+ end,
+ Funcs),
+ wxListBox:set(WinInfo#winInfo.listbox, Items),
+ WinInfo#winInfo{funcs=Funcs}.
+
+%%--------------------------------------------------------------------
+%% handle_event(WxEvent, WinInfo) -> Command
+%% WxEvent = #wx{}
+%% WinInfo = #winInfo{}
+%% Command = ignore
+%% | stopped
+%% | {win, WinInfo}
+%% | {module, Mod}
+%% | {break, [[Mod, Line]], Action}
+%% | {break, [[Mod, Line, CMod, CFunc]], Action}
+%% | {break, [[Mod, Func, Arity]], Action}
+%%--------------------------------------------------------------------
+handle_event(#wx{id=?wxID_CANCEL}, #winInfo{win=Win}) ->
+ wxDialog:destroy(Win),
+ stopped;
+handle_event(#wx{event=#wxCommand{type=command_text_updated}},
+ #winInfo{type=function, text=Text, ok=Ok}) ->
+ Module = wxComboBox:getValue(Text),
+ wxWindow:disable(Ok),
+ {module, list_to_atom(Module)};
+handle_event(#wx{event=#wxCommand{type=command_text_updated}},
+ #winInfo{text=Text, ok=Ok, entries=Es}) ->
+ Module = wxComboBox:getValue(Text),
+ case check_input(Es) of
+ error -> wxWindow:disable(Ok);
+ _Data when Module =/= "" -> wxWindow:enable(Ok);
+ _ -> wxWindow:disable(Ok)
+ end,
+ ignore;
+handle_event(#wx{event=#wxCommand{type=command_listbox_selected}},
+ #winInfo{type=function, listbox=LB, ok=Ok}) ->
+ case wxListBox:getSelections(LB) of
+ {N,_} when N > 0 -> wxWindow:enable(Ok);
+ _ -> wxWindow:disable(Ok)
+ end,
+ ignore;
+handle_event(#wx{id=OKorListBox, event=#wxCommand{type=OkorDoubleClick}},
+ #winInfo{type=function,win=Win,listbox=LB,funcs=Funcs,text=Text})
+ when OKorListBox =:= ?wxID_OK;
+ OkorDoubleClick =:= command_listbox_doubleclicked ->
+ Mod = wxComboBox:getValue(Text),
+ {_, IndexL} = wxListBox:getSelections(LB),
+ Breaks = lists:map(fun(Index) ->
+ Func = lists:nth(Index+1, Funcs),
+ [list_to_atom(Mod) | Func]
+ end,
+ IndexL),
+ wxDialog:destroy(Win),
+ {break, Breaks, enable};
+handle_event(#wx{id=?wxID_OK},#winInfo{win=Win,text=Text, entries=Es, trigger=Trigger}) ->
+ %% Non function box
+ Mod = wxComboBox:getValue(Text),
+ Data = check_input(Es),
+ Trigged = get_trigger(Trigger),
+ wxDialog:destroy(Win),
+ {break, [[list_to_atom(Mod)|Data]], Trigged};
+
+handle_event(_WxEvent, _WinInfo) ->
+ %% io:format("Ev: ~p ~n", [_WxEvent]),
+ ignore.
+
+check_input(Entries) ->
+ check_input(Entries, []).
+check_input([{Entry, Type} | Entries], Data) ->
+ Str = wxTextCtrl:getValue(Entry),
+ case erl_scan:string(Str) of
+ {ok, [{Type, _Line, Val}], _EndLine} ->
+ check_input(Entries, [Val|Data]);
+ _Error -> error
+ end;
+check_input([], Data) -> lists:reverse(Data).
+
+create_sizer_with_text(Parent,Label,Def) ->
+ Text = wxTextCtrl:new(Parent, ?wxID_ANY),
+ Sz = create_label_of_control(Parent, Label, Text, Def),
+ {Sz, Text}.
+
+create_label_of_control(Parent, Label, Control, Def) ->
+ Sizer = wxBoxSizer:new(?wxHORIZONTAL),
+ Text = wxStaticText:new(Parent, ?wxID_ANY, Label),
+ Border = {border, 5},
+ Flag = ?wxRIGHT bor ?wxLEFT bor ?wxALIGN_CENTRE_VERTICAL,
+ wxSizer:add(Sizer, Text, [{proportion,1}, {flag,Flag}, Border]),
+ wxSizer:add(Sizer, Control, [{proportion,3}, {flag,Flag bor ?wxEXPAND}, Border]),
+ wxControl:setLabel(Control, dbg_wx_win:to_string(Def)),
+ Sizer.
+
+create_trigger_box(Win) ->
+ SBox = wxStaticBox:new(Win, ?wxID_ANY, "Trigger Action:"),
+ SBS = wxStaticBoxSizer:new(SBox, ?wxVERTICAL),
+ Ebtn = wxRadioButton:new(Win, ?wxID_ANY, "Enable"),
+ wxSizer:add(SBS,Ebtn),
+ Dibtn = wxRadioButton:new(Win, ?wxID_ANY, "Disable"),
+ wxSizer:add(SBS,Dibtn),
+ Debtn = wxRadioButton:new(Win, ?wxID_ANY, "Delete"),
+ wxSizer:add(SBS,Debtn),
+ wxRadioButton:setValue(Ebtn, true),
+ {SBS, [{Ebtn,enable},{Dibtn,disable},{Debtn,delete}]}.
+
+get_trigger([{Btn,Op}|R]) ->
+ case wxRadioButton:getValue(Btn) of
+ true -> Op;
+ false -> get_trigger(R)
+ end.
+
+
diff --git a/lib/debugger/src/dbg_wx_code.erl b/lib/debugger/src/dbg_wx_code.erl
new file mode 100644
index 0000000000..99826d9bdb
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_code.erl
@@ -0,0 +1,163 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(dbg_wx_code).
+
+-export([code_area/1,
+ load_code/2, unload_code/1,
+ add_break_to_code/3, del_break_from_code/2,
+ find/4,
+ goto_pos/2, current_pos/1,
+ mark_line/3, get_no_lines/1, goto_line/2]).
+
+-include_lib("wx/include/wx.hrl").
+
+-define(stc, wxStyledTextCtrl).
+
+code_area(Parent) ->
+ FixedFont = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL,[]),
+ %%Ed = wxStyledTextCtrl:new(Parent, [{size, {700, 500}}]),
+ Ed = wxStyledTextCtrl:new(Parent),
+
+ ?stc:styleClearAll(Ed),
+ ?stc:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont),
+ ?stc:setLexer(Ed, ?wxSTC_LEX_ERLANG),
+ ?stc:setMarginType(Ed, 0, ?wxSTC_MARGIN_NUMBER),
+ LW = ?stc:textWidth(Ed, ?wxSTC_STYLE_LINENUMBER, "9"),
+ ?stc:setMarginWidth(Ed, 0, LW),
+
+ ?stc:setSelectionMode(Ed, ?wxSTC_SEL_LINES),
+ %%?stc:hideSelection(Ed, true),
+
+ Styles = [{?wxSTC_ERLANG_DEFAULT, {0,0,0}},
+ {?wxSTC_ERLANG_COMMENT, {160,53,35}},
+ {?wxSTC_ERLANG_VARIABLE, {150,100,40}},
+ {?wxSTC_ERLANG_NUMBER, {5,5,100}},
+ {?wxSTC_ERLANG_KEYWORD, {130,40,172}},
+ {?wxSTC_ERLANG_STRING, {170,45,132}},
+ {?wxSTC_ERLANG_OPERATOR, {30,0,0}},
+ {?wxSTC_ERLANG_ATOM, {0,0,0}},
+ {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}},
+ {?wxSTC_ERLANG_CHARACTER,{236,155,172}},
+ {?wxSTC_ERLANG_MACRO, {40,144,170}},
+ {?wxSTC_ERLANG_RECORD, {40,100,20}},
+ {?wxSTC_ERLANG_SEPARATOR,{0,0,0}},
+ {?wxSTC_ERLANG_NODE_NAME,{0,0,0}}],
+ SetStyle = fun({Style, Color}) ->
+ ?stc:styleSetFont(Ed, Style, FixedFont),
+ ?stc:styleSetForeground(Ed, Style, Color)
+ end,
+ [SetStyle(Style) || Style <- Styles],
+ ?stc:setKeyWords(Ed, 0, keyWords()),
+
+ %% Margins Markers
+ %% Breakpoint Should be a pixmap?
+ ?stc:markerDefine(Ed, 0, ?wxSTC_MARK_CIRCLE, [{foreground, {170,20,20}}]),
+ ?stc:markerDefine(Ed, 0, ?wxSTC_MARK_CIRCLE, [{background, {200,120,120}}]),
+ %% Disabled Breakpoint
+ ?stc:markerDefine(Ed, 1, ?wxSTC_MARK_CIRCLE, [{foreground, {20,20,170}}]),
+ ?stc:markerDefine(Ed, 1, ?wxSTC_MARK_CIRCLE, [{background, {120,120,200}}]),
+
+ %% Current Line
+ ?stc:markerDefine(Ed, 2, ?wxSTC_MARK_ARROW, [{foreground, {20,170,20}}]),
+ ?stc:markerDefine(Ed, 2, ?wxSTC_MARK_ARROW, [{background, {200,255,200}}]),
+ ?stc:markerDefine(Ed, 3, ?wxSTC_MARK_BACKGROUND, [{background, {200,255,200}}]),
+
+ %% Scrolling
+ Policy = ?wxSTC_CARET_SLOP bor ?wxSTC_CARET_JUMPS bor ?wxSTC_CARET_EVEN,
+ ?stc:setYCaretPolicy(Ed, Policy, 3),
+ ?stc:setVisiblePolicy(Ed, Policy, 3),
+
+ ?stc:connect(Ed, stc_doubleclick),
+ ?stc:setReadOnly(Ed, true),
+ Ed.
+
+load_code(Ed, Code) ->
+ ?stc:setReadOnly(Ed, false),
+ ?stc:setTextRaw(Ed, Code),
+ Lines = ?stc:getLineCount(Ed),
+ Sz = trunc(math:log10(Lines))+1,
+ LW = ?stc:textWidth(Ed, ?wxSTC_STYLE_LINENUMBER, lists:duplicate(Sz, $9)),
+ %%io:format("~p ~p ~p~n", [Lines, Sz, LW]),
+ ?stc:setMarginWidth(Ed, 0, LW+5),
+ ?stc:setReadOnly(Ed, true),
+ Ed.
+
+unload_code(Ed) ->
+ ?stc:setReadOnly(Ed, false),
+ ?stc:setTextRaw(Ed, <<0:8>>),
+ ?stc:setReadOnly(Ed, true),
+ Ed.
+
+add_break_to_code(Ed, Line, active) ->
+ ?stc:markerDelete(Ed, Line-1, 1),
+ ?stc:markerAdd(Ed, Line-1, 0);
+add_break_to_code(Ed, Line, inactive) ->
+ ?stc:markerDelete(Ed, Line-1, 0),
+ ?stc:markerAdd(Ed, Line-1, 1).
+
+del_break_from_code(Ed,Line) ->
+ ?stc:markerDelete(Ed, Line-1, 0),
+ ?stc:markerDelete(Ed, Line-1, 1).
+
+mark_line(Ed,Prev,Line) ->
+ goto_line(Ed, Line),
+ ?stc:markerDelete(Ed, Prev-1, 2),
+ ?stc:markerAdd(Ed, Line-1, 2),
+ ?stc:markerDelete(Ed, Prev-1, 3),
+ ?stc:markerAdd(Ed, Line-1, 3).
+
+get_no_lines(Ed) ->
+ ?stc:getLineCount(Ed).
+
+goto_line(_Ed,0) -> ignore;
+goto_line(Ed,Line) ->
+ ?stc:gotoLine(Ed, Line-1).
+
+current_pos(Ed) ->
+ ?stc:getCurrentPos(Ed).
+
+goto_pos(Ed,Pos) ->
+ ?stc:gotoPos(Ed, Pos).
+
+find(Ed, Str, Case, Next) ->
+ ?stc:searchAnchor(Ed),
+ Flag =
+ if Case -> ?wxSTC_FIND_MATCHCASE;
+ true -> 0
+ end,
+ Res =
+ if
+ Next -> ?stc:searchNext(Ed, Flag, Str);
+ true -> ?stc:searchPrev(Ed, Flag, Str)
+ end,
+ case Res >= 0 of
+ true ->
+ %% io:format("Found ~p ~n",[Res]),
+ ?stc:scrollToLine(Ed,?stc:lineFromPosition(Ed,Res) - 3),
+ true;
+ false ->
+ false
+ end.
+
+keyWords() ->
+ L = ["after","begin","case","try","cond","catch","andalso","orelse",
+ "end","fun","if","let","of","query","receive","when","bnot","not",
+ "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"],
+ lists:flatten([K ++ " " || K <- L] ++ [0]).
diff --git a/lib/debugger/src/dbg_wx_filedialog_win.erl b/lib/debugger/src/dbg_wx_filedialog_win.erl
new file mode 100644
index 0000000000..d883438639
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_filedialog_win.erl
@@ -0,0 +1,572 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_wx_filedialog_win).
+
+-behaviour(wx_object).
+
+%% API
+-export([new/3, getFilename/1, getFilenames/1, getDirectory/1, destroy/1]).
+
+%% Internal
+-export([init/1, handle_call/3, handle_event/2,
+ handle_info/2, code_change/3, terminate/2]).
+
+-include_lib("wx/include/wx.hrl").
+-include("dbg_wx_filedialog_win.hrl").
+
+-define(ID_PATH, 200).
+-define(COMPLETION_WIN, 201).
+-record(state, {win,
+ back, forward, up, %% Buttons
+ text, %% Text (Path)
+ ptext,
+ icons=[],
+ completion,
+ list,
+ path,
+ files,
+ rstack = [],
+ fstack = [],
+ filter,
+ sort,
+ cancel,
+ ok}).
+
+-record(file,
+ {name = "", %% File or dir name
+ type = "file", %% Type descr
+ date = "", %% Modification date
+ icon = 0, %% Icon
+ color = {0,0,0}
+ }).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Client API
+
+
+%% Options are:
+%% {message, Title}
+%% {defaultDir, Path}
+%% {filter, fun(Dir,File) -> skip | {DescriptionStr, icon_type}}
+%% {icons, [{icon_type,wxBitmap}]}
+
+new(Parent, Id, Options0) ->
+ wx_object:start_link(?MODULE, [Parent, Id, Options0], []).
+
+getFilename(FD) ->
+ wx_object:call(FD, getFilename).
+getFilenames(FD) ->
+ wx_object:call(FD, getFilenames).
+getDirectory(FD) ->
+ wx_object:call(FD, getDirectory).
+destroy(FD) ->
+ wx_object:call(FD, destroy).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Object Callbacks
+init([Parent, Id, Options0]) ->
+ Name = proplists:get_value(message, Options0, "Open"),
+ Size = proplists:get_value(size, Options0, ?wxDefaultSize),
+ Pos = proplists:get_value(pos, Options0, ?wxDefaultPosition),
+ {ok, DefPath} = file:get_cwd(),
+ Path = proplists:get_value(defaultDir, Options0, DefPath),
+ ExtraIcons = proplists:get_value(icons, Options0, []),
+ Filter = proplists:get_value(filter, Options0, fun file_type_and_icon/2),
+ SortCol = sort_col(proplists:get_value(sort, Options0, name)),
+ Dlg = wxDialog:new(Parent, Id, Name, [{size,Size}, {pos,Pos},
+ {style, ?wxDEFAULT_DIALOG_STYLE
+ bor ?wxRESIZE_BORDER}]),
+
+ %% Top
+ Back = wxButton:new(Dlg, ?wxID_BACKWARD),
+ wxButton:disable(Back),
+ Forw = wxButton:new(Dlg, ?wxID_FORWARD),
+ wxButton:disable(Forw),
+ Up = wxButton:new(Dlg, ?wxID_UP),
+ Dir = wxTextCtrl:new(Dlg, ?ID_PATH, [{style, ?wxTE_PROCESS_ENTER}]),
+ update_dir(Path, Dir),
+ wxTextCtrl:connect(Dir, command_text_updated),
+ wxTextCtrl:connect(Dir, command_text_enter),
+ Self = self(),
+ IsTab = fun(Ev = #wx{event=#wxKey{keyCode=KC,
+ controlDown=false,shiftDown=false, altDown=false}},
+ _Object) when KC =:= ?WXK_TAB ; KC =:= ?WXK_ESCAPE ->
+ Self ! Ev;
+ (_Ev, Object) ->
+ %% Let the default handler handle anything else
+ wxEvent:skip(Object)
+ end,
+
+ wxTextCtrl:connect(Dir, char, [{callback, IsTab}]),
+
+ Top = wxBoxSizer:new(?wxHORIZONTAL),
+ wxSizer:add(Top, Back, [{border, 2},{flag,?wxALL bor ?wxEXPAND}]),
+ wxSizer:add(Top, Forw, [{border, 2},{flag,?wxALL bor ?wxEXPAND}]),
+ wxSizer:add(Top, Up, [{border, 2},{flag,?wxALL bor ?wxEXPAND}]),
+
+ %% List Ctrl
+ {Art, IconMap} = create_icons(ExtraIcons),
+
+ LC = wxListCtrl:new(Dlg, [{style, ?wxLC_REPORT bor ?wxVSCROLL}, {size, {400,200}}]),
+ wxListCtrl:assignImageList(LC, Art, ?wxIMAGE_LIST_SMALL),
+
+ LI = wxListItem:new(),
+ Add = fun(MenuName, Row) ->
+ wxListItem:setText(LI, MenuName),
+ wxListItem:setAlign(LI, ?wxLIST_FORMAT_LEFT),
+ wxListCtrl:insertColumn(LC, Row, LI),
+ Row + 1
+ end,
+ lists:foldl(Add, 0, ["Name", "Type", "Modified"]),
+ wxListItem:destroy(LI),
+
+ Files = list_files(Path, {SortCol,false}, Filter),
+ update_files(Files,LC,IconMap),
+
+ wxListCtrl:setColumnWidth(LC, 0, ?wxLIST_AUTOSIZE),
+ wxListCtrl:setColumnWidth(LC, 1, ?wxLIST_AUTOSIZE),
+ wxListCtrl:setColumnWidth(LC, 2, ?wxLIST_AUTOSIZE),
+ wxListCtrl:connect(LC, command_list_item_activated),
+ wxListCtrl:connect(LC, command_list_col_click),
+ wxListCtrl:connect(LC, size, [{skip, true}]),
+
+ %% Bottom buttons
+ Bott = wxDialog:createButtonSizer(Dlg, ?wxCANCEL bor ?wxOK),
+ wxDialog:connect(Dlg, command_button_clicked),
+
+ %% Ok done
+ Box = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(Box, Top, [{border, 2}, {flag,?wxALL bor ?wxEXPAND}]),
+ wxSizer:add(Box, Dir, [{border, 2}, {flag,?wxALL bor ?wxEXPAND}]),
+ wxSizer:add(Box, LC, [{border, 2}, {flag,?wxALL bor ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(Box, Bott, [{border, 2}, {flag,?wxALL bor ?wxEXPAND}]),
+
+ wxWindow:setSizer(Dlg, Box),
+ wxSizer:fit(Box, Dlg),
+ wxSizer:setSizeHints(Box,Dlg),
+ State = #state{win=Dlg,
+ back=Back, forward=Forw, up=Up,
+ text=Dir,
+ list=LC,
+ icons=IconMap,
+ sort ={SortCol,false},
+ filter = Filter,
+ path=Path,
+ files=Files
+ },
+ {Dlg, State}.
+
+%% calls
+handle_call(getFilename, _From, State = #state{list=LC, files=Fs}) ->
+ case wxListCtrl:getNextItem(LC, -1, [{state,?wxLIST_STATE_SELECTED}]) of
+ -1 ->
+ {reply, "", State};
+ Item ->
+ {reply, (lists:nth(Item+1,Fs))#file.name, State}
+ end;
+
+handle_call(getFilenames, _From, State = #state{list=LC, files=Fs}) ->
+ Items = get_selection(LC,-1, []),
+ Files = [(lists:nth(Item+1,Fs))#file.name || Item <- Items],
+ {reply, Files, State};
+
+handle_call(getDirectory, _From, State = #state{path=Dir}) ->
+ {reply, Dir, State};
+
+handle_call(destroy, _From, State) ->
+ {stop, normal, ok, State}.
+
+%% events
+handle_event(#wx{id=?wxID_UP}, State0) ->
+ State = update_window(change_dir(0, State0)),
+ {noreply, State};
+
+handle_event(#wx{id=?wxID_BACKWARD}, State0 = #state{rstack=[Prev|Stack]}) ->
+ State = update_window(change_dir(Prev, State0#state{rstack=Stack}, forward)),
+ {noreply, State};
+
+handle_event(#wx{id=?wxID_FORWARD}, State0 = #state{fstack=[Prev|Stack]}) ->
+ State = update_window(change_dir(Prev, State0#state{fstack=Stack}, reverse)),
+ {noreply, State};
+
+handle_event(#wx{id=Id=?wxID_CANCEL}, State = #state{win=Win}) ->
+ wxDialog:endModal(Win,Id),
+ {noreply, State};
+
+handle_event(#wx{id=Id=?wxID_OK}, State = #state{win=Win}) ->
+ wxDialog:endModal(Win,Id),
+ {noreply, State};
+
+handle_event(#wx{event=#wxList{type=command_list_col_click, col=Column0}},
+ State0 = #state{files=Fs, sort=Sort0}) ->
+ case Column0 >= 0 of
+ true ->
+ Column = sort_col(Column0+1),
+ Sort = case Sort0 of
+ {Column,Bool} -> {Column, not Bool};
+ {_,_} -> {Column, false}
+ end,
+ {noreply, update_window(State0#state{files=sort_files(Fs,Sort),sort=Sort})};
+ false ->
+ {noreply, State0}
+ end;
+
+handle_event(#wx{event=#wxList{itemIndex=Index}},
+ State0 = #state{files=Fs,win=Win}) ->
+ case lists:nth(Index+1, Fs) of
+ #file{type="directory"} ->
+ State = update_window(change_dir(Index, State0)),
+ {noreply, State};
+ _Dbg = #file{} ->
+ wxDialog:endModal(Win,?wxID_OK),
+ {noreply, State0}
+ end;
+
+handle_event(#wx{event=#wxCommand{type=command_text_updated, cmdString=Wanted}},
+ State = #state{ptext=Previous, completion=Comp}) ->
+ case Previous =:= undefined orelse lists:prefix(Wanted, Previous) of
+ true ->
+ case Comp of
+ {Temp,_} -> wxWindow:destroy(Temp);
+ undefined -> ok
+ end,
+ {noreply, State#state{ptext=Wanted,completion=undefined}};
+ false ->
+ {noreply, show_completion(Wanted, State)}
+ end;
+
+handle_event(#wx{event=#wxCommand{type=command_text_enter, cmdString=Wanted}},
+ State) ->
+ case filelib:is_dir(Wanted, erl_prim_loader) of
+ true ->
+ {Path0, Dir} = split_dir(Wanted),
+ Path = filename:join(Path0,Dir),
+ {noreply, update_window(change_dir(Path, State))};
+ false ->
+ {Path, _} = split_dir(Wanted),
+ {noreply, update_window(change_dir(Path, State))}
+ end;
+
+handle_event(#wx{event=#wxKey{keyCode=?WXK_TAB}},
+ State = #state{text=TC, ptext=Wanted, completion=Comp}) ->
+ case wxTextCtrl:getSelection(TC) of
+ {Pos,Pos} ->
+ {noreply, show_completion(Wanted, State)};
+ _ ->
+ wxTextCtrl:setInsertionPointEnd(TC),
+ destroy_completion(Comp),
+ {noreply, State#state{completion=undefined}}
+ end;
+
+handle_event(#wx{id=?COMPLETION_WIN, event=#wxCommand{cmdString=[]}}, State) ->
+ {noreply, State};
+handle_event(#wx{id=?COMPLETION_WIN, obj=LB,
+ event=_Ev=#wxCommand{cmdString=Dir,commandInt=N}},
+ State = #state{ptext=Wanted0, text=TC}) ->
+ case wxListBox:isSelected(LB, N) of
+ true ->
+ Wanted = case Wanted0 of
+ undefined -> wxTextCtrl:getValue(TC);
+ _ -> Wanted0
+ end,
+ Path1 = case filelib:is_dir(Wanted, erl_prim_loader) of
+ true -> Wanted;
+ false ->
+ {ThePath, _} = split_dir(Wanted),
+ ThePath
+ end,
+ Path = filename:join(Path1, Dir),
+ {noreply, update_window(change_dir(Path, State))};
+ false ->
+ {noreply, State}
+ end;
+
+handle_event(#wx{event=#wxSize{size={Width,_}}}, State = #state{list=LC}) ->
+ wx:batch(fun() ->
+ Tot = wx:foldl(fun(C,Sum) ->
+ Sum + wxListCtrl:getColumnWidth(LC, C)
+ end, 0, [1,2]),
+ wxListCtrl:setColumnWidth(LC, 0, Width-Tot-30)
+ end),
+ {noreply, State};
+
+handle_event(Event,State) ->
+ io:format("~p Got ~p ~n",[self(), Event]),
+ {noreply, State}.
+
+handle_info(_Msg, State) ->
+ {noreply,State}.
+
+terminate(_Reason,State) ->
+ wxDialog:destroy(State#state.win),
+ ok.
+
+code_change(_,_,State) ->
+ State.
+
+%%%%%%%%%%%%%%%%%%%%%%% Internal %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+update_window(State = #state{files=Fs, list=LC,
+ path=Path, text=TC,
+ icons=Icons,
+ completion = Comp}) ->
+ update_files(Fs, LC, Icons),
+ update_dir(Path, TC),
+ if State#state.rstack == [] -> wxButton:disable(State#state.back);
+ true -> wxButton:enable(State#state.back)
+ end,
+ if State#state.fstack == [] -> wxButton:disable(State#state.forward);
+ true -> wxButton:enable(State#state.forward)
+ end,
+ if Path == "/" -> wxButton:disable(State#state.up);
+ true -> wxButton:enable(State#state.up)
+ end,
+ destroy_completion(Comp),
+ State#state{completion=undefined, ptext=undefined}.
+
+update_dir(Path, TC) ->
+ case Path of
+ "/" ->
+ wxTextCtrl:setValue(TC, Path);
+ _ ->
+ wxTextCtrl:setValue(TC, Path ++ "/")
+ end,
+ wxTextCtrl:setInsertionPointEnd(TC).
+
+update_files(Files,LC, Icons) ->
+ wxListCtrl:deleteAllItems(LC),
+ wx:foldl(fun(F=#file{name=Name,type=TypeStr,date=Date, color=Color},Row) ->
+ wxListCtrl:insertItem(LC, Row, ""),
+ if (Row rem 2) =:= 0 ->
+ wxListCtrl:setItemBackgroundColour(LC, Row, {240,240,255});
+ true -> ignore
+ end,
+ wxListCtrl:setItemTextColour(LC, Row, Color),
+ wxListCtrl:setItem(LC, Row, 0, Name, [{imageId,get_icon(F,Icons)}]),
+ wxListCtrl:setItem(LC, Row, 2, format_date(Date)),
+ wxListCtrl:setItem(LC, Row, 1, TypeStr),
+ Row+1
+ end, 0, Files).
+
+show_completion(undefined, State = #state{text=TC}) ->
+ show_completion(wxTextCtrl:getValue(TC), State);
+show_completion(Wanted, State = #state{text=TC, win=Win, list=LC, completion=Comp}) ->
+ Paths0 = filelib:wildcard(Wanted ++ "*", erl_prim_loader),
+ Paths = [File || File <- Paths0, filelib:is_dir(File, erl_prim_loader)],
+ case Paths of
+ [Path] ->
+ Start = length(Wanted),
+ wxTextCtrl:setValue(TC, Path++"/"),
+ wxTextCtrl:setInsertionPoint(TC, Start),
+ wxTextCtrl:setSelection(TC, Start, -1),
+ destroy_completion(Comp),
+ wxWindow:setFocus(TC),
+ State#state{ptext=Path, completion=undefined};
+ Paths when Comp =:= undefined ->
+ {PosX,PosY} = wxListCtrl:getPosition(LC),
+ {SzX, SzY} = wxListCtrl:getSize(LC),
+ Pos0 = {PosX+5,PosY},
+ Size = {SzX-50,SzY-50},
+ Files = [filename:basename(File) || File <- Paths],
+ Temp = case os:type() of
+ {win32,nt} ->
+ Pos = wxWindow:clientToScreen(Win,Pos0),
+ wxFrame:new(Win, -1, "",
+ [{pos, Pos}, {size, Size},
+ {style, ?wxFRAME_FLOAT_ON_PARENT}]);
+ _ ->
+ wxWindow:new(Win, -1,
+ [{pos, Pos0}, {size, Size},
+ {style, ?wxFRAME_FLOAT_ON_PARENT}])
+ end,
+ LB = wxListBox:new(Temp, ?COMPLETION_WIN,
+ [{style, ?wxLB_SINGLE}, {choices, Files}, {size, Size}]),
+
+ wxListBox:connect(LB, command_listbox_doubleclicked),
+ wxListBox:connect(LB, command_listbox_selected),
+ wxWindow:show(Temp),
+ wxWindow:setFocus(TC),
+ State#state{completion = {Temp, LB}, ptext=Wanted};
+ Paths ->
+ {_Temp, LB} = Comp,
+ wxListBox:clear(LB),
+ Files = [filename:basename(File) || File <- Paths],
+ wxListBox:insertItems(LB,Files,0),
+ wxWindow:setFocus(TC),
+ State#state{ptext=Wanted}
+ end.
+
+destroy_completion(undefined) -> ok;
+destroy_completion({Window, _}) ->
+ Parent = wxWindow:getParent(Window),
+ wxWindow:destroy(Window),
+ wxWindow:refresh(Parent).
+
+split_dir(Path0) ->
+ Split1 = filename:split(Path0),
+ case lists:reverse(Split1) of
+ [File| Split2] when Split2 =/= [] ->
+ Split3 = lists:reverse(Split2),
+ Path = filename:join(Split3),
+ {Path, File};
+ _ ->
+ {"/", ""}
+ end.
+
+change_dir(What,State) ->
+ change_dir(What,State,new).
+
+change_dir(Num, State = #state{files=Fs0, path=Path0},Stack)
+ when is_integer(Num) ->
+ case lists:nth(Num+1, Fs0) of
+ #file{name=".."} ->
+ {Path,_} = split_dir(Path0);
+ #file{name=Dir} ->
+ Path = filename:join(Path0, Dir)
+ end,
+ change_dir(Path, State, Stack);
+change_dir(Path, State0 = #state{path=Path0, sort=Sort, filter=Filter},StackDir) ->
+ Files = list_files(Path, Sort, Filter),
+ add_to_stack(StackDir, Path0, State0#state{files=Files, path=Path}).
+
+add_to_stack(new, Path, State = #state{rstack=Stack0}) ->
+ Stack = [Path|Stack0],
+ State#state{rstack=Stack, fstack=[]};
+add_to_stack(reverse, Path, State = #state{rstack=Stack0}) ->
+ Stack = [Path|Stack0],
+ State#state{rstack=Stack};
+add_to_stack(forward, Path, State = #state{fstack=Stack0}) ->
+ Stack = [Path|Stack0],
+ State#state{fstack=Stack}.
+
+list_files(Dir, Sort, Filter) ->
+ Contents0 = filelib:wildcard(Dir ++ "/*", erl_prim_loader),
+ Contents = case Dir of
+ "/" -> Contents0;
+ _ -> [".."|Contents0]
+ end,
+ {Ds0,Fs0} = get_file_info(Contents, Dir, Filter, [], []),
+ sort_files(lists:reverse(Ds0), Fs0, Sort).
+
+sort_files(Mixed, Sort) ->
+ {Ds,Fs} =
+ lists:foldr(fun(Dir = #file{type="directory"}, {Ds,Fs}) ->
+ {[Dir|Ds],Fs};
+ (File, {Ds,Fs}) ->
+ {Ds,[File|Fs]}
+ end, {[],[]}, Mixed),
+ sort_files(Ds,Fs,Sort).
+
+sort_files(Ds0, Fs0, {SortElement, Rev}) ->
+ {Top,Ds1} = case Ds0 of
+ [Up=#file{name=".."}|Rest] -> {Up,Rest};
+ _ -> {undefined, Ds0}
+ end,
+ Ds = lists:keysort(SortElement, Ds1),
+ Fs = case Rev of
+ true -> lists:reverse(lists:keysort(SortElement,Fs0));
+ false -> lists:keysort(SortElement,Fs0)
+ end,
+ case Top of
+ undefined -> Ds ++ Fs;
+ _ -> [Top|Ds++Fs]
+ end.
+
+get_file_info([AbsName|Rest],Dir,Filter, Files,Dirs) ->
+ Name = filename:basename(AbsName),
+ Mod = filelib:last_modified(AbsName, erl_prim_loader),
+ IsDir = filelib:is_dir(AbsName, erl_prim_loader),
+ Entry0 = #file{name=Name, date=Mod},
+ case IsDir of
+ true when Name =:= ".." ->
+ Entry = Entry0#file{type="directory",icon=prev_dir},
+ get_file_info(Rest, Dir, Filter, Files, [Entry|Dirs]);
+ true ->
+ Entry = Entry0#file{type="directory",icon=dir},
+ get_file_info(Rest, Dir, Filter, Files, [Entry|Dirs]);
+ false ->
+ case Filter(Dir,Name) of
+ {Type,Icon,Color} ->
+ Entry = Entry0#file{type=Type,icon=Icon,color=Color},
+ get_file_info(Rest, Dir, Filter, [Entry|Files], Dirs);
+ skip ->
+ get_file_info(Rest, Dir, Filter, Files, Dir)
+ end
+ end;
+get_file_info([], _, _, Fs,Ds) ->
+ {Ds,Fs}.
+
+format_date({{Y,M,D},{H,Mi,S}}) ->
+ lists:flatten(io_lib:format("~w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",[Y,M,D,H,Mi,S]));
+format_date(_) ->
+ "unknown".
+
+get_selection(LC, Prev, Acc) ->
+ case wxListCtrl:getNextItem(LC, Prev, [{state,?wxLIST_STATE_SELECTED}]) of
+ -1 ->
+ lists:reverse(Acc);
+ Item ->
+ get_selection(LC, Item, [Item|Acc])
+ end.
+
+file_type_and_icon(_Dir, Name) ->
+ case filename:extension(Name) of
+ ".erl" ->
+ {"erl src", erl_src, {0,90,0}};
+ ".hrl" ->
+ {"erl hrl", erl_hrl, {0,90,0}};
+ ".beam" ->
+ {"erl bin", erl_bin, {0,0,0}};
+ _ ->
+ {"file", file, {0,0,0}}
+ end.
+
+create_icons(Extra) ->
+ Art = wxImageList:new(16,16),
+ BuiltIn0 = [{file, "wxART_NORMAL_FILE"},
+ {dir, "wxART_FOLDER"},
+ {prev_dir, "wxART_GO_DIR_UP"}],
+ BuiltIn = [{Type, wxArtProvider:getBitmap(ArtID, [{size, {16,16}}])} ||
+ {Type,ArtID} <- BuiltIn0],
+
+ Test = [{Type, wxBitmap:new(wxImage:new(16,16,Bin))}
+ || {Type,Bin} <-
+ [{erl_src, ?ERL_SRC},
+ {erl_hrl, ?ERL_HRL},
+ {erl_bin, ?ERL_BIN}]],
+
+ Icons = BuiltIn ++ Test ++ Extra,
+ [wxImageList:add(Art, Image) || {_, Image} <- Icons],
+ {Art, ids(Icons, 0)}.
+
+get_icon(#file{icon=Icon}, Icons) ->
+ proplists:get_value(Icon,Icons, 0).
+
+ids([{Type,_}|Rest], Id) ->
+ [{Type,Id}|ids(Rest,Id+1)];
+ids([],_) -> [].
+
+sort_col(1) -> #file.name;
+sort_col(2) -> #file.type;
+sort_col(3) -> #file.date;
+sort_col(name) -> #file.name;
+sort_col(type) -> #file.type;
+sort_col(date) -> #file.date.
+
diff --git a/lib/debugger/src/dbg_wx_filedialog_win.hrl b/lib/debugger/src/dbg_wx_filedialog_win.hrl
new file mode 100644
index 0000000000..aa739ad49e
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_filedialog_win.hrl
@@ -0,0 +1,180 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-define(ERL_SRC,
+ <<255,255,255,255,255,255,255,255,255,192,192,192,192,192,
+ 192,192,192,192,192,192,192,192,192,192,192,192,192,192,
+ 192,192,192,192,192,192,192,192,160,160,160,191,191,191,
+ 255,255,255,255,255,255,191,191,191,128,128,128,128,128,
+ 128,96,96,96,128,128,128,128,128,128,128,128,128,128,
+ 128,128,128,128,128,128,128,128,128,128,128,128,128,128,
+ 192,192,192,144,144,144,191,191,191,255,255,255,128,128,
+ 128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,
+ 128,128,0,0,0,0,0,0,192,192,192,192,192,192,144,144,144,
+ 191,191,191,128,128,128,0,0,0,64,64,64,128,128,128,128,
+ 128,128,0,0,0,191,191,191,128,128,128,0,0,0,128,128,128,
+ 0,0,0,0,0,0,223,223,223,128,128,128,128,128,128,64,64,
+ 64,128,128,128,0,0,0,128,128,128,128,128,128,128,128,
+ 128,0,0,0,128,128,128,0,0,0,0,0,0,128,128,128,0,0,0,0,0,
+ 0,255,255,255,255,255,255,255,255,255,128,128,128,128,
+ 128,128,0,0,0,0,0,0,128,128,128,64,64,64,0,0,0,64,64,64,
+ 0,0,0,0,0,0,128,128,128,0,0,0,0,0,0,255,255,255,255,255,
+ 255,255,255,255,128,128,128,160,160,192,64,64,128,64,64,
+ 128,64,64,128,64,64,128,64,64,128,64,64,128,64,64,128,
+ 64,64,128,64,64,128,64,64,128,64,64,128,255,255,255,255,
+ 255,255,255,255,255,128,128,128,239,239,239,224,224,224,
+ 224,224,224,192,192,192,224,224,224,224,224,224,224,224,
+ 224,224,224,224,224,224,224,224,224,224,224,224,224,224,
+ 224,224,255,255,255,255,255,255,255,255,255,128,128,128,
+ 255,255,255,255,255,255,255,255,255,192,192,192,255,255,
+ 255,192,128,128,239,239,239,255,255,255,255,255,255,239,
+ 239,239,255,255,255,255,255,255,255,255,255,208,176,176,
+ 223,191,191,128,128,128,255,255,255,255,255,255,255,255,
+ 255,192,192,192,255,255,255,176,112,112,255,255,255,255,
+ 255,255,223,191,191,128,0,0,160,64,64,255,255,255,255,
+ 255,255,239,239,239,192,128,128,128,128,128,255,255,255,
+ 255,255,255,255,255,255,192,192,192,255,255,255,192,128,
+ 128,255,255,255,255,255,255,255,255,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255,255,255,255,192,128,128,
+ 128,128,128,255,255,255,255,255,255,255,255,255,192,192,
+ 192,255,255,255,192,128,128,255,255,255,255,255,255,223,
+ 191,191,192,128,128,192,128,128,192,128,128,192,128,128,
+ 192,128,128,192,128,128,128,128,128,255,255,255,255,255,
+ 255,255,255,255,192,192,192,255,255,255,192,128,128,255,
+ 255,255,255,255,255,192,128,128,128,0,0,128,0,0,128,0,0,
+ 144,48,48,128,0,0,192,128,128,128,128,128,255,255,255,
+ 255,255,255,255,255,255,192,192,192,255,255,255,176,112,
+ 112,255,255,255,255,255,255,239,239,239,144,48,48,128,0,
+ 0,144,48,48,239,239,239,192,160,160,192,128,128,128,128,
+ 128,255,255,255,255,255,255,255,255,255,192,192,192,255,
+ 255,255,192,128,128,239,239,239,255,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255,255,255,255,255,208,176,
+ 176,223,191,191,128,128,128,255,255,255,255,255,255,255,
+ 255,255,192,192,192,128,128,128,128,128,128,128,128,128,
+ 128,128,128,128,128,128,128,128,128,128,128,128,128,128,
+ 128,128,128,128,128,128,128,128,128,128,64,64,64>>).
+
+-define(ERL_HRL,
+ <<255,255,255,255,255,255,255,255,255,192,192,192,192,192,
+ 192,192,192,192,192,192,192,192,192,192,192,192,192,192,
+ 192,192,192,192,192,192,192,192,160,160,160,191,191,191,
+ 255,255,255,255,255,255,191,191,191,128,128,128,128,128,
+ 128,96,96,96,128,128,128,128,128,128,128,128,128,128,
+ 128,128,128,128,128,128,128,128,128,128,128,128,128,128,
+ 192,192,192,144,144,144,191,191,191,255,255,255,128,128,
+ 128,0,0,0,128,128,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,
+ 128,128,0,0,0,0,0,0,0,0,0,192,192,192,192,192,192,144,
+ 144,144,191,191,191,128,128,128,0,0,0,128,128,128,128,
+ 128,128,64,64,64,128,128,128,128,128,128,64,64,64,128,
+ 128,128,0,0,0,0,0,0,0,0,0,223,223,223,128,128,128,128,
+ 128,128,64,64,64,128,128,128,0,0,0,128,128,128,0,0,0,
+ 128,128,128,128,128,128,0,0,0,0,0,0,128,128,128,0,0,0,0,
+ 0,0,0,0,0,255,255,255,255,255,255,255,255,255,128,128,
+ 128,128,128,128,0,0,0,64,64,64,0,0,0,64,64,64,64,64,64,
+ 0,0,0,0,0,0,64,64,64,64,64,64,0,0,0,0,0,0,255,255,255,
+ 255,255,255,255,255,255,128,128,128,160,160,192,64,64,
+ 128,64,64,128,64,64,128,64,64,128,64,64,128,64,64,128,
+ 64,64,128,64,64,128,64,64,128,64,64,128,64,64,128,255,
+ 255,255,255,255,255,255,255,255,128,128,128,239,239,239,
+ 224,224,224,224,224,224,192,192,192,224,224,224,224,224,
+ 224,224,224,224,224,224,224,224,224,224,224,224,224,224,
+ 224,224,224,224,224,255,255,255,255,255,255,255,255,255,
+ 128,128,128,255,255,255,255,255,255,255,255,255,192,192,
+ 192,255,255,255,192,128,128,239,239,239,255,255,255,255,
+ 255,255,239,239,239,255,255,255,255,255,255,255,255,255,
+ 208,176,176,223,191,191,128,128,128,255,255,255,255,255,
+ 255,255,255,255,192,192,192,255,255,255,176,112,112,255,
+ 255,255,255,255,255,223,191,191,128,0,0,160,64,64,255,
+ 255,255,255,255,255,239,239,239,192,128,128,128,128,128,
+ 255,255,255,255,255,255,255,255,255,192,192,192,255,255,
+ 255,192,128,128,255,255,255,255,255,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+ 192,128,128,128,128,128,255,255,255,255,255,255,255,255,
+ 255,192,192,192,255,255,255,192,128,128,255,255,255,255,
+ 255,255,223,191,191,192,128,128,192,128,128,192,128,128,
+ 192,128,128,192,128,128,192,128,128,128,128,128,255,255,
+ 255,255,255,255,255,255,255,192,192,192,255,255,255,192,
+ 128,128,255,255,255,255,255,255,192,128,128,128,0,0,128,
+ 0,0,128,0,0,144,48,48,128,0,0,192,128,128,128,128,128,
+ 255,255,255,255,255,255,255,255,255,192,192,192,255,255,
+ 255,176,112,112,255,255,255,255,255,255,239,239,239,144,
+ 48,48,128,0,0,144,48,48,239,239,239,192,160,160,192,128,
+ 128,128,128,128,255,255,255,255,255,255,255,255,255,192,
+ 192,192,255,255,255,192,128,128,239,239,239,255,255,255,
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+ 255,208,176,176,223,191,191,128,128,128,255,255,255,255,
+ 255,255,255,255,255,192,192,192,128,128,128,128,128,128,
+ 128,128,128,128,128,128,128,128,128,128,128,128,128,128,
+ 128,128,128,128,128,128,128,128,128,128,128,128,128,64,
+ 64,64>>).
+
+-define(ERL_BIN,
+ <<255,255,255,255,255,255,255,255,255,192,192,192,192,192,
+ 192,192,192,192,192,192,192,192,192,192,192,192,192,192,
+ 192,192,192,192,192,192,192,192,160,160,160,191,191,191,
+ 255,255,255,255,255,255,239,239,239,224,224,224,224,224,
+ 224,192,192,192,224,224,224,224,224,224,224,224,224,224,
+ 224,224,224,224,224,224,224,224,224,224,224,224,224,224,
+ 192,192,192,144,144,144,191,191,191,255,255,255,160,160,
+ 192,64,64,128,64,64,128,64,64,128,64,64,128,64,64,128,
+ 64,64,128,64,64,128,64,64,128,64,64,128,64,64,128,64,64,
+ 128,192,192,192,192,192,192,144,144,144,191,191,191,128,
+ 128,128,64,64,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,223,223,223,128,128,128,128,128,
+ 128,64,64,64,128,128,128,191,191,191,64,64,64,0,0,0,128,
+ 128,128,0,0,0,64,64,64,128,128,128,64,64,64,64,64,64,64,
+ 64,64,0,0,0,255,255,255,255,255,255,255,255,255,128,128,
+ 128,128,128,128,128,128,128,128,128,128,128,128,128,128,
+ 128,128,128,128,128,128,128,128,128,128,128,128,128,128,
+ 128,128,128,128,128,128,0,0,0,255,255,255,255,255,255,
+ 255,255,255,128,128,128,128,128,128,191,191,191,128,128,
+ 128,64,64,64,128,128,128,0,0,0,128,128,128,191,191,191,
+ 128,128,128,128,128,128,128,128,128,0,0,0,255,255,255,
+ 255,255,255,255,255,255,128,128,128,191,191,191,128,128,
+ 128,128,128,128,96,96,96,128,128,128,128,128,128,128,
+ 128,128,128,128,128,128,128,128,128,128,128,128,128,128,
+ 128,128,128,255,255,255,255,255,255,255,255,255,128,128,
+ 128,255,255,255,255,255,255,255,255,255,192,192,192,255,
+ 255,255,192,128,128,239,239,239,255,255,255,255,255,255,
+ 239,239,239,255,255,255,255,255,255,255,255,255,208,176,
+ 176,223,191,191,128,128,128,255,255,255,255,255,255,255,
+ 255,255,192,192,192,255,255,255,176,112,112,255,255,255,
+ 255,255,255,223,191,191,128,0,0,160,64,64,255,255,255,
+ 255,255,255,239,239,239,192,128,128,128,128,128,255,255,
+ 255,255,255,255,255,255,255,192,192,192,255,255,255,192,
+ 128,128,255,255,255,255,255,255,255,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255,255,255,255,255,192,128,
+ 128,128,128,128,255,255,255,255,255,255,255,255,255,192,
+ 192,192,255,255,255,192,128,128,255,255,255,255,255,255,
+ 223,191,191,192,128,128,192,128,128,192,128,128,192,128,
+ 128,192,128,128,192,128,128,128,128,128,255,255,255,255,
+ 255,255,255,255,255,192,192,192,255,255,255,192,128,128,
+ 255,255,255,255,255,255,192,128,128,128,0,0,128,0,0,128,
+ 0,0,144,48,48,128,0,0,192,128,128,128,128,128,255,255,
+ 255,255,255,255,255,255,255,192,192,192,255,255,255,176,
+ 112,112,255,255,255,255,255,255,239,239,239,144,48,48,
+ 128,0,0,144,48,48,239,239,239,192,160,160,192,128,128,
+ 128,128,128,255,255,255,255,255,255,255,255,255,192,192,
+ 192,255,255,255,192,128,128,239,239,239,255,255,255,255,
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+ 208,176,176,223,191,191,128,128,128,255,255,255,255,255,
+ 255,255,255,255,192,192,192,128,128,128,128,128,128,128,
+ 128,128,128,128,128,128,128,128,128,128,128,128,128,128,
+ 128,128,128,128,128,128,128,128,128,128,128,128,64,64,64>>).
+
diff --git a/lib/debugger/src/dbg_wx_interpret.erl b/lib/debugger/src/dbg_wx_interpret.erl
new file mode 100644
index 0000000000..f711ba679d
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_interpret.erl
@@ -0,0 +1,131 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(dbg_wx_interpret).
+
+-include_lib("kernel/include/file.hrl").
+-include_lib("wx/include/wx.hrl").
+
+%% External exports
+-export([start/4]).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start(Win, Pos, Dir, Mode)
+%% GS = Graphics system id
+%% Dir = string()
+%% Pos = {X,Y}
+%% Mode = local | global
+%%--------------------------------------------------------------------
+start(Win, Pos, SDir, Mode) ->
+ Title = "Interpret Modules",
+
+ FD = dbg_wx_filedialog_win:new(Win, -1,
+ [{message,Title}, {pos,Pos},
+ {defaultDir,SDir},
+ {sort, type},
+ {filter, fun filter_files/2}]),
+
+ case wxFileDialog:showModal(FD) of
+ ?wxID_OK ->
+ Files = dbg_wx_filedialog_win:getFilenames(FD),
+ Dir = dbg_wx_filedialog_win:getDirectory(FD),
+ dbg_wx_filedialog_win:destroy(FD),
+ interpret_all(Dir, Files, Mode, Win, []),
+ self() ! {dbg_ui_interpret, Dir},
+ ok;
+ _ ->
+ dbg_wx_filedialog_win:destroy(FD),
+ cancel
+ end.
+
+filter_files(Dir, Name) ->
+ case filename:extension(Name) of
+ ".erl" ->
+ File = filename:join(Dir, Name),
+ case int:interpretable(File) of
+ true ->
+ {"erl src", erl_src, {0,0,0}};
+ _ ->
+ {"erl src no dbg", erl_src, {128,128,128}}
+ end;
+ ".hrl" ->
+ {"erl hrl", erl_hrl, {128,128,128}};
+ ".beam" ->
+ {"erl bin", erl_bin, {128,128,128}};
+ _ ->
+ {"file", file, {128,128,128}}
+ end.
+
+%%% Standard file browser variant
+%% start(Win, Pos, SDir, Mode) ->
+%% Title = "Interpret Dialog",
+%% Filter = "*.erl",
+
+%% FD = FileDialog:new(Win, [{message,Title},{pos, Pos},
+%% {defaultDir,SDir},
+%% {wildCard, Filter},
+%% {style,?wxFD_OPEN bor ?wxFD_MULTIPLE}]),
+
+%% case wxFileDialog:showModal(FD) of
+%% ?wxID_OK ->
+%% Files = wxFileDialog:getFilenames(FD),
+%% Dir = wxFileDialog:getDirectory(FD),
+%% wxFileDialog:destroy(FD),
+%% interpret_all(Dir, Files, Mode, Win),
+%% self() ! {dbg_ui_interpret, Dir},
+%% ok;
+%% _ ->
+%% wxFileDialog:destroy(FD),
+%% cancel
+%% end.
+
+interpret_all(Dir, [File0|Files], Mode, Window, Errors) ->
+ File = filename:join(Dir, File0),
+ Res = case Mode of
+ local -> int:i(File);
+ global -> int:ni(File)
+ end,
+ case Res of
+ {module, _Mod} ->
+ interpret_all(Dir, Files, Mode, Window, Errors);
+ error ->
+ interpret_all(Dir, Files, Mode, Window, [File0|Errors])
+ end;
+interpret_all(_Dir, [], _Mode, _Window, []) ->
+ true;
+interpret_all(Dir, [], _Mode, Window, Errors) ->
+ Msg = lists:map(fun(Name) ->
+ File = filename:join(Dir, Name),
+ Error = format_error(int:interpretable(File)),
+ ["\n ",Name,": ",Error]
+ end, Errors),
+ All = ["Error when interpreting: ", Msg],
+ dbg_wx_win:confirm(Window, lists:flatten(All)),
+ true.
+
+format_error({error,no_beam}) -> "No BEAM file";
+format_error({error,no_debug_info}) -> "No debug_info in BEAM file";
+format_error({error,badarg}) -> "File does not exist";
+format_error({error,{app,App}}) ->
+ "Cannot interpret "++atom_to_list(App)++" modules".
diff --git a/lib/debugger/src/dbg_wx_mon.erl b/lib/debugger/src/dbg_wx_mon.erl
new file mode 100644
index 0000000000..d81069ec90
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_mon.erl
@@ -0,0 +1,759 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(dbg_wx_mon).
+
+-include_lib("kernel/include/file.hrl").
+-include_lib("wx/include/wx.hrl").
+
+%% External exports
+-export([start/2, stop/0]).
+
+-define(TRACEWIN, ['Search Area', 'Button Area', 'Evaluator Area', 'Bindings Area']).
+-define(BACKTRACE, 100).
+
+-record(pinfo, {pid, % pid()
+ status % break | exit | idle | running | waiting
+ }).
+
+-record(state, {mode, % local | global
+ starter, % bool() 'true' if int was started by me
+
+ win, % term() Monitor window data
+ focus, % undefined | #pinfo{} Process in focus
+ coords, % {X,Y} Mouse pointer position
+
+ intdir, % string() Default dir
+ pinfos, % [#pinfo{}] Debugged processes
+
+ tracewin, % [Area] Areas shown in trace window
+ backtrace, % integer() Number of call frames to fetch
+
+ attach, % false | {Flags, Function}
+
+ sfile, % default | string() Settings file
+ changed % boolean() Settings have been changed
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start(Mode, SFile) -> {ok, Pid} | {error, Reason}
+%% Mode = local | global
+%% SFile = string() | default Settings file
+%% Pid = pid()
+%% Reason = {already_started,Pid} | term()
+%%--------------------------------------------------------------------
+start(Mode, SFile) ->
+ case whereis(?MODULE) of
+ undefined ->
+ CallingPid = self(),
+ Pid = spawn(fun () -> init(CallingPid, Mode, SFile) end),
+ receive
+ {initialization_complete, Pid} ->
+ {ok, Pid};
+ Error ->
+ Error
+ end;
+
+ Pid ->
+ {error, {already_started,Pid}}
+ end.
+
+%%--------------------------------------------------------------------
+%% stop() -> ok
+%%--------------------------------------------------------------------
+stop() ->
+ case whereis(?MODULE) of
+ undefined ->
+ ok;
+ Pid ->
+ Flag = process_flag(trap_exit, true),
+ link(Pid),
+ Pid ! stop,
+ receive
+ {'EXIT', Pid, stop} ->
+ process_flag(trap_exit, Flag),
+ ok
+ end
+ end.
+
+
+%%====================================================================
+%% Initialization
+%%====================================================================
+
+init(CallingPid, Mode, SFile) ->
+ register(?MODULE, self()),
+
+ %% Graphics system
+ case catch dbg_wx_mon_win:init() of
+ {'EXIT', Reason} ->
+ CallingPid ! {error, Reason};
+ GS ->
+ try
+ init2(CallingPid, Mode, SFile, GS)
+ catch
+ exit:stop -> stop;
+ Error:Reason ->
+ io:format("~p: Crashed {~p,~p} in~n ~p",
+ [?MODULE, Error, Reason, erlang:get_stacktrace()])
+ end
+ end.
+
+init2(CallingPid, Mode, SFile, GS) ->
+ %% Start Int if necessary and subscribe to information from it
+ Bool = case int:start() of
+ {ok, _Int} -> true;
+ {error, {already_started, _Int}} -> false
+ end,
+ int:subscribe(),
+
+ %% Start other necessary stuff
+ dbg_wx_win:init(),
+ dbg_wx_winman:start(), % Debugger window manager
+
+ %% Create monitor window
+ Title = "Monitor",
+ Win = dbg_wx_mon_win:create_win(GS, Title, menus()),
+ Window = dbg_wx_mon_win:get_window(Win),
+ dbg_wx_winman:insert(Title, Window),
+
+ %% Initial process state
+ State1 = #state{mode = Mode,
+ starter = Bool,
+
+ win = Win,
+ focus = undefined,
+ coords = {0,0},
+
+ intdir = element(2, file:get_cwd()),
+ pinfos = [],
+
+ sfile = SFile,
+ changed = false
+ },
+
+ State2 = init_options(?TRACEWIN, % Trace Window
+ int:auto_attach(), % Auto Attach
+ int:stack_trace(), % Stack Trace
+ ?BACKTRACE, % Back Trace Size
+ State1),
+
+ State3 = init_contents(int:interpreted(), % Modules
+ int:all_breaks(), % Breakpoints
+ int:snapshot(), % Processes
+ State2),
+
+ %% Disable/enable functionality according to process in focus (none)
+ gui_enable_functions(State3#state.focus),
+
+ CallingPid ! {initialization_complete, self()},
+
+ if
+ SFile==default ->
+ loop(State3);
+ true ->
+ loop(load_settings(SFile, State3))
+ end.
+
+init_options(TraceWin, AutoAttach, StackTrace, BackTrace, State) ->
+ lists:foreach(fun(Area) ->
+ dbg_wx_mon_win:select(Area, true)
+ end,
+ TraceWin),
+
+ case AutoAttach of
+ false -> ignore;
+ {Flags, _Function} ->
+ dbg_wx_mon_win:show_option(State#state.win,
+ auto_attach, Flags),
+ lists:foreach(fun(Flag) ->
+ dbg_wx_mon_win:select(map(Flag), true)
+ end,
+ Flags)
+ end,
+
+ dbg_wx_mon_win:show_option(State#state.win,
+ stack_trace, StackTrace),
+ dbg_wx_mon_win:select(map(StackTrace), true),
+
+ dbg_wx_mon_win:show_option(State#state.win, back_trace, BackTrace),
+
+ State#state{tracewin=TraceWin, backtrace=BackTrace}.
+
+init_contents(Mods, Breaks, Processes, State) ->
+ Win2 =
+ lists:foldl(fun(Mod, Win) ->
+ dbg_wx_mon_win:add_module(Win,'Module',Mod)
+ end,
+ State#state.win,
+ Mods),
+
+ Win3 =
+ lists:foldl(fun(Break, Win) ->
+ dbg_wx_mon_win:add_break(Win,'Break',Break)
+ end,
+ Win2,
+ Breaks),
+
+ lists:foldl(fun(PidTuple, State0) ->
+ int_cmd({new_process, PidTuple}, State0)
+ end,
+ State#state{win=Win3},
+ Processes).
+
+
+%%====================================================================
+%% Main loop and message handling
+%%====================================================================
+
+loop(State) ->
+ receive
+ stop ->
+ gui_cmd(stopped, State);
+
+ %% From the wx-GUI
+ #wx{} = GuiEvent ->
+ Cmd = dbg_wx_mon_win:handle_event(GuiEvent,State#state.win),
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the interpreter process
+ {int, Cmd} ->
+ State2 = int_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the dbg_ui_interpret process
+ {dbg_ui_interpret, Dir} ->
+ loop(State#state{intdir=Dir});
+
+ %% From the dbg_wx_winman process (Debugger window manager)
+ {dbg_ui_winman, update_windows_menu, Data} ->
+ Window = dbg_wx_mon_win:get_window(State#state.win),
+ dbg_wx_winman:update_windows_menu(Window,Data),
+ loop(State);
+
+ %% From the trace window
+ {dbg_wx_trace, From, get_env} ->
+ From ! {env, self(), wx:get_env(), dbg_wx_mon_win:get_window(State#state.win)},
+ loop(State)
+ end.
+
+%%--Commands from the GUI---------------------------------------------
+%% Act upon a command from the GUI. In most cases, it is only necessary
+%% to call a relevant int-function. int will then report when the action
+%% has been taken.
+
+gui_cmd(ignore, State) ->
+ State;
+gui_cmd(stopped, State) ->
+ if
+ State#state.starter==true -> int:stop();
+ true -> int:auto_attach(false)
+ end,
+ exit(stop);
+gui_cmd({coords, Coords}, State) ->
+ State#state{coords=Coords};
+
+gui_cmd({shortcut, Key}, State) ->
+ case shortcut(Key) of
+ {always, Cmd} -> gui_cmd(Cmd, State);
+ {if_enabled, Cmd} ->
+ case dbg_wx_mon_win:is_enabled(Cmd) of
+ true -> gui_cmd(Cmd, State);
+ false -> State
+ end;
+ false -> State
+ end;
+
+%% File Menu
+gui_cmd('Load Settings...', State) ->
+ Window = dbg_wx_mon_win:get_window(State#state.win),
+ case dbg_wx_settings:load(Window, State#state.coords,State#state.sfile) of
+ cancel -> State;
+ {ok, File} -> load_settings(File,State)
+ end;
+gui_cmd('Save Settings...', State) ->
+ Window = dbg_wx_mon_win:get_window(State#state.win),
+ case dbg_wx_settings:save(Window, State#state.coords,State#state.sfile) of
+ cancel -> State;
+ {ok, File} -> save_settings(File,State)
+ end;
+gui_cmd('Exit', State) ->
+ gui_cmd(stopped, State);
+
+%% Edit Menu
+gui_cmd('Refresh', State) ->
+ int:clear(),
+ Win = dbg_wx_mon_win:clear_processes(State#state.win),
+ gui_enable_functions(undefined),
+ State2 = State#state{win=Win, focus=undefined, pinfos=[]},
+ lists:foldl(fun(PidTuple, S) ->
+ int_cmd({new_process,PidTuple}, S)
+ end,
+ State2,
+ int:snapshot());
+gui_cmd('Kill All', State) ->
+ lists:foreach(fun(PInfo) ->
+ case PInfo#pinfo.status of
+ exit -> ignore;
+ _Status -> exit(PInfo#pinfo.pid, kill)
+ end
+ end,
+ State#state.pinfos),
+ State;
+
+%% Module Menu
+gui_cmd('Interpret...', State) ->
+ Window = dbg_wx_mon_win:get_window(State#state.win),
+ dbg_wx_interpret:start(Window, State#state.coords,
+ State#state.intdir, State#state.mode),
+ State;
+gui_cmd('Delete All Modules', State) ->
+ lists:foreach(fun(Mod) -> int:nn(Mod) end, int:interpreted()),
+ State;
+gui_cmd({module, Mod, What}, State) ->
+ case What of
+ delete -> int:nn(Mod);
+ view ->
+ Window = dbg_wx_mon_win:get_window(State#state.win),
+ dbg_wx_view:start(Window, Mod)
+ end,
+ State;
+
+%% Process Menu
+gui_cmd('Step', State) ->
+ int:step((State#state.focus)#pinfo.pid),
+ State;
+gui_cmd('Next', State) ->
+ int:next((State#state.focus)#pinfo.pid),
+ State;
+gui_cmd('Continue', State) ->
+ int:continue((State#state.focus)#pinfo.pid),
+ State;
+gui_cmd('Finish ', State) ->
+ int:finish((State#state.focus)#pinfo.pid),
+ State;
+gui_cmd('Attach', State) ->
+ Pid = (State#state.focus)#pinfo.pid,
+ case dbg_wx_winman:is_started(dbg_wx_trace:title(Pid)) of
+ true -> ignore;
+ false -> int:attach(Pid, trace_function(State))
+ end,
+ State;
+gui_cmd('Kill', State) ->
+ exit((State#state.focus)#pinfo.pid, kill),
+ State;
+
+%% Break Menu
+gui_cmd('Line Break...', State) ->
+ Window = dbg_wx_mon_win:get_window(State#state.win),
+ dbg_wx_break:start(Window, State#state.coords, line),
+ State;
+gui_cmd('Conditional Break...', State) ->
+ Window = dbg_wx_mon_win:get_window(State#state.win),
+ dbg_wx_break:start(Window, State#state.coords, conditional),
+ State;
+gui_cmd('Function Break...', State) ->
+ Window = dbg_wx_mon_win:get_window(State#state.win),
+ dbg_wx_break:start(Window, State#state.coords, function),
+ State;
+gui_cmd('Enable All', State) ->
+ Breaks = int:all_breaks(),
+ lists:foreach(fun ({{Mod, Line}, _Options}) ->
+ int:enable_break(Mod, Line)
+ end,
+ Breaks),
+ State;
+gui_cmd('Disable All', State) ->
+ Breaks = int:all_breaks(),
+ lists:foreach(fun ({{Mod, Line}, _Options}) ->
+ int:disable_break(Mod, Line)
+ end,
+ Breaks),
+ State;
+gui_cmd('Delete All', State) ->
+ int:no_break(),
+ State;
+gui_cmd({break, {Mod, Line}, What}, State) ->
+ case What of
+ delete -> int:delete_break(Mod, Line);
+ {status, inactive} -> int:disable_break(Mod, Line);
+ {status, active} -> int:enable_break(Mod, Line);
+ {trigger, Action} -> int:action_at_break(Mod, Line, Action)
+ end,
+ State;
+
+%% Options Commands
+gui_cmd({'Trace Window', TraceWin}, State) ->
+ State2 = State#state{tracewin=TraceWin},
+ case State#state.attach of
+ false -> ignore;
+ {Flags, {dbg_ui_trace, start, StartFlags}} ->
+ case trace_function(State2) of
+ {_, _, StartFlags} -> ignore;
+ NewFunction -> % {_, _, NewStartFlags}
+ int:auto_attach(Flags, NewFunction)
+ end;
+ _AutoAttach -> ignore
+ end,
+ State2;
+gui_cmd({'Auto Attach', When}, State) ->
+ if
+ When==[] -> int:auto_attach(false);
+ true ->
+ Flags = lists:map(fun(Name) -> map(Name) end, When),
+ int:auto_attach(Flags, trace_function(State))
+ end,
+ State;
+gui_cmd({'Stack Trace', [Name]}, State) ->
+ int:stack_trace(map(Name)),
+ State;
+gui_cmd('Back Trace Size...', State) ->
+ Window = dbg_wx_mon_win:get_window(State#state.win),
+ What = {integer, State#state.backtrace},
+ case dbg_wx_win:entry(Window, "Backtrace", 'Backtrace:', What) of
+ cancel ->
+ State;
+ {_,BackTrace} ->
+ dbg_wx_mon_win:show_option(State#state.win,back_trace, BackTrace),
+ State#state{backtrace=BackTrace}
+ end;
+
+%% Help Menu
+gui_cmd('Debugger', State) ->
+ HelpFile = filename:join([code:lib_dir(debugger),
+ "doc", "html", "part_frame.html"]),
+ Window = dbg_wx_mon_win:get_window(State#state.win),
+ dbg_wx_win:open_help(Window, HelpFile),
+ State;
+
+gui_cmd({focus, Pid, Win}, State) ->
+ {value, PInfo} =
+ lists:keysearch(Pid, #pinfo.pid, State#state.pinfos),
+ gui_enable_functions(PInfo),
+ State#state{win=Win, focus=PInfo};
+gui_cmd(default, State) ->
+ case lists:member('Attach', menus(enabled, State#state.focus)) of
+ true -> gui_cmd('Attach', State);
+ false -> State
+ end.
+
+%%--Commands from the interpreter-------------------------------------
+
+int_cmd({interpret, Mod}, State) ->
+ Win = dbg_wx_mon_win:add_module(State#state.win, 'Module', Mod),
+ State#state{win=Win};
+int_cmd({no_interpret, Mod}, State) ->
+ Win = dbg_wx_mon_win:delete_module(State#state.win, Mod),
+ State#state{win=Win};
+
+int_cmd({new_process, {Pid, Function, Status, Info}}, State) ->
+
+ %% Create record with information about the process
+ Name = registered_name(Pid),
+ PInfo = #pinfo{pid=Pid, status=Status},
+
+ %% Update window
+ Win = dbg_wx_mon_win:add_process(State#state.win,
+ Pid, Name, Function, Status, Info),
+
+ %% Store process information
+ PInfos = State#state.pinfos ++ [PInfo],
+ State#state{win=Win, pinfos=PInfos};
+int_cmd({new_status, Pid, Status, Info}, State) ->
+
+ %% Find stored information about the process
+ PInfos = State#state.pinfos,
+ {value, PInfo} = lists:keysearch(Pid, #pinfo.pid, PInfos),
+
+ %% Update process information
+ PInfo2 = PInfo#pinfo{status=Status},
+ PInfos2 = lists:keyreplace(Pid, #pinfo.pid, PInfos, PInfo2),
+ State2 = State#state{pinfos=PInfos2},
+
+ %% Update window
+ dbg_wx_mon_win:update_process(State2#state.win, Pid, Status, Info),
+ case State2#state.focus of
+ #pinfo{pid=Pid} ->
+ gui_enable_functions(PInfo2),
+ State2#state{focus=PInfo2};
+ _ ->
+ State2
+ end;
+
+int_cmd({new_break, Break}, State) ->
+ Win = dbg_wx_mon_win:add_break(State#state.win, 'Break', Break),
+ State#state{win=Win};
+int_cmd({delete_break, Point}, State) ->
+ Win = dbg_wx_mon_win:delete_break(State#state.win, Point),
+ State#state{win=Win};
+int_cmd({break_options, Break}, State) ->
+ dbg_wx_mon_win:update_break(State#state.win, Break),
+ State;
+int_cmd(no_break, State) ->
+ Win = dbg_wx_mon_win:clear_breaks(State#state.win),
+ State#state{win=Win};
+int_cmd({no_break, Mod}, State) ->
+ Win = dbg_wx_mon_win:clear_breaks(State#state.win, Mod),
+ State#state{win=Win};
+
+int_cmd({auto_attach, AutoAttach}, State) ->
+ OnFlags = case AutoAttach of
+ false -> [];
+ {Flags, _Function} -> Flags
+ end,
+ OffFlags = [init, exit, break] -- OnFlags,
+ dbg_wx_mon_win:show_option(State#state.win, auto_attach, OnFlags),
+ lists:foreach(fun(Flag) ->
+ dbg_wx_mon_win:select(map(Flag), true)
+ end,
+ OnFlags),
+ lists:foreach(fun(Flag) ->
+ dbg_wx_mon_win:select(map(Flag), false)
+ end,
+ OffFlags),
+ State#state{attach=AutoAttach};
+int_cmd({stack_trace, Flag}, State) ->
+ dbg_wx_mon_win:show_option(State#state.win, stack_trace, Flag),
+ dbg_wx_mon_win:select(map(Flag), true),
+ State.
+
+
+%%====================================================================
+%% GUI auxiliary functions
+%%====================================================================
+
+menus() ->
+ [{'File', [{'Load Settings...', 0},
+ {'Save Settings...', 2},
+ separator,
+ {'Exit', 0}]},
+ {'Edit', [{'Refresh', no},
+ {'Kill All', no}]},
+ {'Module', [{'Interpret...', 0},
+ {'Delete All Modules', no},
+ separator]},
+ {'Process', [{'Step', 0},
+ {'Next', 0},
+ {'Continue', 0},
+ {'Finish ', 0},
+ separator,
+ {'Attach', 0},
+ {'Kill', no}]},
+ {'Break', [{'Line Break...', 5},
+ {'Conditional Break...', no},
+ {'Function Break...', no},
+ separator,
+ {'Enable All', no},
+ {'Disable All', no},
+ {'Delete All', 0},
+ separator]},
+ {'Options', [{'Trace Window', no, cascade,
+ [{'Search Area', no, check},
+ {'Button Area', no, check},
+ {'Evaluator Area', no, check},
+ {'Bindings Area', no, check},
+ {'Trace Area', no, check}]},
+ {'Auto Attach', no, cascade,
+ [{'First Call', no, check},
+ {'On Break', no, check},
+ {'On Exit', no, check}]},
+ {'Stack Trace', no, cascade,
+ [{'Stack On, Tail', no, radio},
+ {'Stack On, No Tail', no, radio},
+ {'Stack Off', no, radio}]},
+ {'Back Trace Size...', no}]},
+ {'Windows', []},
+ {'Help', [{'Debugger', no}]}].
+
+menus(enabled, undefined) ->
+ [];
+menus(disabled, undefined) ->
+ ['Step','Next','Continue','Finish ','Attach','Kill'];
+menus(enabled, #pinfo{status=exit}) ->
+ ['Attach'];
+menus(disabled, #pinfo{status=exit}) ->
+ ['Step','Next','Continue','Finish ','Kill'];
+menus(enabled, #pinfo{status=break}) ->
+ ['Step','Next','Continue','Finish ','Attach','Kill'];
+menus(disabled, #pinfo{status=break}) ->
+ [];
+menus(enabled, _PInfo) ->
+ ['Attach','Kill'];
+menus(disabled, _PInfo) ->
+ ['Step','Next','Continue','Finish '].
+
+shortcut(l) -> {always, 'Load Settings...'};
+shortcut(v) -> {always, 'Save Settings...'};
+shortcut(e) -> {always, 'Exit'};
+
+shortcut(i) -> {always, 'Interpret...'};
+
+shortcut(s) -> {if_enabled, 'Step'};
+shortcut(n) -> {if_enabled, 'Next'};
+shortcut(c) -> {if_enabled, 'Continue'};
+shortcut(f) -> {if_enabled, 'Finish '};
+shortcut(a) -> {if_enabled, 'Attach'};
+
+shortcut(b) -> {always, 'Line Break...'};
+shortcut(d) -> {always, 'Delete All'};
+
+shortcut(_) -> false.
+
+%% Enable/disable functionality depending on the state of the process
+%% currently in Focus
+gui_enable_functions(PInfo) ->
+ Enabled = menus(enabled, PInfo),
+ Disabled = menus(disabled, PInfo),
+ dbg_wx_mon_win:enable(Enabled, true),
+ dbg_wx_mon_win:enable(Disabled, false).
+
+%% Map values used by int to/from GUI names
+map('First Call') -> init; % Auto attach
+map('On Exit') -> exit;
+map('On Break') -> break;
+map(init) -> 'First Call';
+map(exit) -> 'On Exit';
+map(break) -> 'On Break';
+
+map('Stack On, Tail') -> all; % Stack trace
+map('Stack On, No Tail') -> no_tail;
+map('Stack Off') -> false;
+map(all) -> 'Stack On, Tail';
+map(true) -> 'Stack On, Tail';
+map(no_tail) -> 'Stack On, No Tail';
+map(false) -> 'Stack Off'.
+
+
+%%====================================================================
+%% Debugger settings
+%%====================================================================
+
+load_settings(SFile, State) ->
+ case file:read_file(SFile) of
+ {ok, Binary} ->
+ case catch binary_to_term(Binary) of
+ {debugger_settings, Settings} ->
+ load_settings2(Settings,
+ State#state{sfile=SFile,
+ changed=false});
+ _Error -> State
+ end;
+ {error, _Reason} -> State
+ end.
+
+load_settings2(Settings, State) ->
+ {TraceWin, AutoAttach, StackTrace, BackTrace, Files, Breaks} =
+ Settings,
+
+ TraceWinAll = ['Button Area', 'Evaluator Area', 'Bindings Area',
+ 'Trace Area'],
+ lists:foreach(fun(Area) -> dbg_wx_mon_win:select(Area, true) end,
+ TraceWin),
+ lists:foreach(fun(Area) -> dbg_wx_mon_win:select(Area, false) end,
+ TraceWinAll--TraceWin),
+
+ case AutoAttach of
+ false -> int:auto_attach(false);
+ {Flags, Function} -> int:auto_attach(Flags, Function)
+ end,
+
+ int:stack_trace(StackTrace),
+
+ dbg_wx_mon_win:show_option(State#state.win, back_trace, BackTrace),
+
+ case State#state.mode of
+ local -> lists:foreach(fun(File) -> int:i(File) end, Files);
+ global -> lists:foreach(fun(File) -> int:ni(File) end, Files)
+ end,
+ lists:foreach(fun(Break) ->
+ {{Mod, Line}, [Status, Action, _, Cond]} =
+ Break,
+ int:break(Mod, Line),
+ if
+ Status==inactive ->
+ int:disable_break(Mod, Line);
+ true -> ignore
+ end,
+ if
+ Action/=enable ->
+ int:action_at_break(Mod,Line,Action);
+ true -> ignore
+ end,
+ case Cond of
+ CFunction when is_tuple(CFunction) ->
+ int:test_at_break(Mod,Line,CFunction);
+ null -> ignore
+ end
+ end,
+ Breaks),
+
+ State#state{tracewin=TraceWin, backtrace=BackTrace}.
+
+save_settings(SFile, State) ->
+ Settings = {State#state.tracewin,
+ int:auto_attach(),
+ int:stack_trace(),
+ State#state.backtrace,
+ lists:map(fun(Mod) ->
+ int:file(Mod)
+ end,
+ int:interpreted()),
+ int:all_breaks()},
+
+ Binary = term_to_binary({debugger_settings, Settings}),
+ case file:write_file(SFile, Binary) of
+ ok ->
+ State#state{sfile=SFile, changed=false};
+ {error, _Reason} ->
+ State
+ end.
+
+
+%%====================================================================
+%% Other internal functions
+%%====================================================================
+
+registered_name(Pid) ->
+
+ %% Yield in order to give Pid more time to register its name
+ timer:sleep(200),
+
+ Node = node(Pid),
+ if
+ Node==node() ->
+ case erlang:process_info(Pid, registered_name) of
+ {registered_name, Name} -> Name;
+ _ -> undefined
+ end;
+ true ->
+ case rpc:call(Node,erlang,process_info,
+ [Pid,registered_name]) of
+ {registered_name, Name} -> Name;
+ _ -> undefined
+ end
+ end.
+
+trace_function(State) ->
+ {dbg_wx_trace, start, [State#state.tracewin, State#state.backtrace]}.
diff --git a/lib/debugger/src/dbg_wx_mon_win.erl b/lib/debugger/src/dbg_wx_mon_win.erl
new file mode 100644
index 0000000000..dfb327fa6a
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_mon_win.erl
@@ -0,0 +1,586 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(dbg_wx_mon_win).
+
+%% External exports
+-export([init/0]).
+-export([create_win/3, get_window/1,
+ show_option/3,
+ enable/2, is_enabled/1, select/2,
+ add_module/3, delete_module/2,
+ add_process/6, update_process/4, clear_processes/1,
+ add_break/3, update_break/2, delete_break/2,
+ clear_breaks/1, clear_breaks/2,
+ handle_event/2
+ ]).
+
+-import(dbg_wx_win, [to_string/1, to_string/2]).
+
+-include_lib("wx/include/wx.hrl").
+
+-define(default_rows,10).
+
+-record(moduleInfo, {module, menubtn}).
+-record(procInfo, {pid, row}).
+-record(breakInfo, {point, status, break}).
+-record(break, {mb, smi, emi, dimi, demi}). %% BUGBUG defined in dbg_ui_win
+-record(winInfo, {window, % gsobj()
+ grid, % gsobj()
+ row, % int() Last row in grid
+
+ focus, % int() Selected row in grid
+
+ modules=[], % [#moduleInfo{}] Known modules
+ processes=[], % [#procInfo{}] Known processes
+ breaks=[], % [#breakInfo{}] Known breakpoints
+
+ listbox, % gsobj() Listinng known modules
+
+ %% Auto attach buttons
+ fbutton, % gsobj()
+ bbutton, % gsobj()
+ ebutton, % gsobj()
+ selected=[], % ['First Call'|'On Break'|'On Exit']
+
+ slabel, % showing Stack Trace option
+ blabel % showing Back Trace Size
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+init() ->
+ dbg_wx_win:init().
+
+%%--------------------------------------------------------------------
+%% create_win(GS, Title, Menus) -> #winInfo{}
+%% GS = gsobj()
+%% Title = string()
+%% Menus = [menu()] See dbg_ui_win.erl
+%%--------------------------------------------------------------------
+
+-define(GRID,1000).
+
+-define(PAD, 5).
+-define(Wf, 150).
+-define(Wg, 770).
+-define(W, 800).
+-define(H, 390).
+
+create_win(_Wx, Title, Menus) ->
+ wx:batch(fun() -> create_win_batch(Title, Menus) end).
+
+create_win_batch(Title, Menus) ->
+ Win = wxFrame:new(wx:null(), ?wxID_ANY, Title,
+ [{size, {?W,?H}}]),
+ wxFrame:connect(Win, close_window, [{skip, true}]),
+ MenuBar = wxMenuBar:new(),
+ dbg_wx_win:create_menus(MenuBar, Menus, Win, 1),
+ wxFrame:setMenuBar(Win, MenuBar),
+
+ MainSz = wxBoxSizer:new(?wxHORIZONTAL),
+ LeftSz = wxBoxSizer:new(?wxVERTICAL),
+
+ Panel = wxPanel:new(Win),
+ Hlb = 200,
+ Listbox = wxListBox:new(Panel, ?wxID_ANY, [{size,{?Wf,Hlb}},
+ {style,?wxLB_SINGLE}]),
+ wxSizer:add(LeftSz,Listbox,[{border, 3}]),
+ wxListBox:connect(Listbox, command_listbox_doubleclicked),
+ wxListBox:connect(Listbox, right_down),
+
+ SBox = wxStaticBox:new(Panel, ?wxID_ANY, "Auto Attach:"),
+ SBS = wxStaticBoxSizer:new(SBox, ?wxVERTICAL),
+ Fbtn = wxCheckBox:new(Panel, ?wxID_ANY, "First Call"),
+ wxSizer:add(SBS,Fbtn),
+ Bbtn = wxCheckBox:new(Panel, ?wxID_ANY, "On Break"),
+ wxSizer:add(SBS,Bbtn),
+ Ebtn = wxCheckBox:new(Panel, ?wxID_ANY, "On Exit"),
+ wxSizer:add(SBS,Ebtn),
+ wxFrame:connect(Panel, command_checkbox_clicked),
+ wxSizer:add(LeftSz,SBS, [{flag,?wxEXPAND}]),
+
+ SLabel = wxStaticText:new(Panel, ?wxID_ANY, "Stack Trace:\n On (with tail)"),
+ wxSizer:add(LeftSz,SLabel),
+ BLabel = wxStaticText:new(Panel, ?wxID_ANY, "Back Trace Size:\n 50000"),
+ wxSizer:add(LeftSz,BLabel),
+
+ %% Create list_crtl / grid
+ Grid = wxListCtrl:new(Panel, [{winid, ?GRID},
+ {style, ?wxLC_REPORT bor ?wxLC_SINGLE_SEL
+ bor ?wxLC_HRULES },
+ {size, {600, -1}}]),
+ LI = wxListItem:new(),
+ wxListItem:setText(LI, "Pid"),
+ wxListItem:setAlign(LI, ?wxLIST_FORMAT_CENTRE),
+ wxListCtrl:insertColumn(Grid, 0, LI),
+ wxListItem:setText(LI, "Initial Call"),
+ wxListItem:setAlign(LI, ?wxLIST_FORMAT_LEFT),
+ wxListCtrl:insertColumn(Grid, 1, LI),
+ wxListItem:setText(LI, "Name"),
+ wxListCtrl:insertColumn(Grid, 2, LI),
+ wxListItem:setAlign(LI, ?wxLIST_FORMAT_CENTRE),
+ wxListItem:setText(LI, "Status"),
+ wxListCtrl:insertColumn(Grid, 3, LI),
+ wxListItem:setText(LI, "Information"),
+ wxListItem:setAlign(LI, ?wxLIST_FORMAT_LEFT),
+ wxListCtrl:insertColumn(Grid, 4, LI),
+ wxListItem:destroy(LI),
+
+ wxListCtrl:setColumnWidth(Grid, 0, 80),
+ wxListCtrl:setColumnWidth(Grid, 1, 150),
+ wxListCtrl:setColumnWidth(Grid, 2, 100),
+ wxListCtrl:setColumnWidth(Grid, 3, 70),
+ wxListCtrl:setColumnWidth(Grid, 4, 200),
+ wxListCtrl:connect(Grid, command_list_item_activated),
+ wxListCtrl:connect(Grid, command_list_item_selected),
+ wxListCtrl:connect(Grid, size, [{skip, true}]),
+ wxListCtrl:connect(Grid, key_up, [{id, ?GRID}, {skip,true}]),
+
+ wxWindow:connect(Win, enter_window, [{skip,true}]),
+ wxWindow:setFocus(Grid),
+
+ %% Put it in the window
+ wxSizer:add(MainSz, LeftSz, [{border, 3}, {flag,?wxALL bor ?wxEXPAND}]),
+ wxSizer:add(MainSz, Grid, [{border, 3}, {flag,?wxALL bor ?wxEXPAND},
+ {proportion, 1}]),
+
+ wxWindow:setSizer(Panel,MainSz),
+ wxSizer:fit(MainSz, Win),
+ wxSizer:setSizeHints(MainSz,Win),
+
+ IconFile = dbg_wx_win:find_icon("erlang_bug.png"),
+ Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
+ wxFrame:setIcon(Win, Icon),
+ wxIcon:destroy(Icon),
+ wxFrame:show(Win),
+ dbg_wx_winman:raise(Win),
+ #winInfo{window=Win, grid=Grid, row=0, focus=0,
+ listbox=Listbox,
+ fbutton=Fbtn, bbutton=Bbtn, ebutton=Ebtn,
+ slabel=SLabel, blabel=BLabel}.
+
+%%--------------------------------------------------------------------
+%% get_window(WinInfo) -> Window
+%% WinInfo = #winInfo{}
+%% Window = wxobj()
+%%--------------------------------------------------------------------
+get_window(WinInfo) ->
+ WinInfo#winInfo.window.
+
+%%--------------------------------------------------------------------
+%% show_option(WinInfo, Option, Value) -> void()
+%% WinInfo = #winInfo{}
+%% Option = auto_attach | stack_trace | back_trace
+%% Value = [Flag] % Option==auto_attach
+%% Flag = init | break | exit
+%% | true | all | no_tail | false % Option==stack_trace
+%% | int() % Option==back_trace
+%%--------------------------------------------------------------------
+show_option(WinInfo, Option, Value) ->
+ case Option of
+ auto_attach ->
+ wx:foreach(fun(Button) ->
+ wxCheckBox:setValue(Button, false)
+ end,
+ option_buttons(WinInfo, [init, break, exit])),
+ wx:foreach(fun(Button) ->
+ wxCheckBox:setValue(Button, true)
+ end,
+ option_buttons(WinInfo, Value));
+
+ stack_trace ->
+ Text = case Value of
+ all -> "Stack Trace:\n On (with tail)";
+ true -> "Stack Trace:\n On (with tail)";
+ no_tail -> "Stack Trace:\n On (no tail)";
+ false -> "Stack Trace:\n Off"
+ end,
+ wxStaticText:setLabel(WinInfo#winInfo.slabel, Text);
+
+ back_trace ->
+ Text = "Back Trace Size:\n " ++ integer_to_list(Value),
+ wxStaticText:setLabel(WinInfo#winInfo.blabel, Text)
+ end.
+
+option_buttons(WinInfo, [init|Flags]) ->
+ [WinInfo#winInfo.fbutton|option_buttons(WinInfo, Flags)];
+option_buttons(WinInfo, [break|Flags]) ->
+ [WinInfo#winInfo.bbutton|option_buttons(WinInfo, Flags)];
+option_buttons(WinInfo, [exit|Flags]) ->
+ [WinInfo#winInfo.ebutton|option_buttons(WinInfo, Flags)];
+option_buttons(_WinInfo, []) ->
+ [].
+
+%%--------------------------------------------------------------------
+%% enable([MenuItem], Bool)
+%% is_enabled(MenuItem) -> Bool
+%% MenuItem = atom()
+%% Bool = boolean()
+%%--------------------------------------------------------------------
+enable(MenuItems, Bool) ->
+ lists:foreach(fun(MenuItem) ->
+ MI = get(MenuItem),
+ wxMenuItem:enable(MI, [{enable, Bool}])
+ end,
+ MenuItems).
+
+is_enabled(MenuItem) ->
+ MI = get(MenuItem),
+ wxMenuItem:isEnabled(MI).
+
+%%--------------------------------------------------------------------
+%% select(MenuItem, Bool)
+%% MenuItem = atom()
+%% Bool = boolean()
+%%--------------------------------------------------------------------
+select(MenuItem, Bool) ->
+ MI = get(MenuItem),
+ wxMenuItem:check(MI, [{check, Bool}]).
+
+%%--------------------------------------------------------------------
+%% add_module(WinInfo, Name, Mod) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Name = atom()
+%% Mod = atom()
+%%--------------------------------------------------------------------
+add_module(WinInfo, MenuName, Mod) ->
+ Win = WinInfo#winInfo.window,
+ Modules = WinInfo#winInfo.modules,
+ case lists:keysearch(Mod, #moduleInfo.module, Modules) of
+ {value, _ModInfo} -> WinInfo;
+ false ->
+ %% Create a menu for the module
+ Menu = get(MenuName),
+ Sub = wxMenu:new([]),
+ ViewItem = wxMenu:append(Sub, ?wxID_ANY, "View"),
+ ViewId = wxMenuItem:getId(ViewItem),
+ wxMenu:connect(Win, command_menu_selected,
+ [{id,ViewId}, {userData, {module,Mod,view}}]),
+ DelItem = wxMenu:append(Sub, ?wxID_ANY, "Delete"),
+ DelId = wxMenuItem:getId(DelItem),
+ wxMenu:connect(Win, command_menu_selected,
+ [{id,DelId}, {userData, {module,Mod,delete}}]),
+ MenuBtn = wxMenu:append(Menu, ?wxID_ANY, atom_to_list(Mod), Sub),
+ wxListBox:append(WinInfo#winInfo.listbox, atom_to_list(Mod)),
+
+ ModInfo = #moduleInfo{module=Mod, menubtn={Menu,MenuBtn}},
+ WinInfo#winInfo{modules=[ModInfo | Modules]}
+ end.
+
+%%--------------------------------------------------------------------
+%% delete_module(WinInfo, Mod) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Mod = atom()
+%%--------------------------------------------------------------------
+delete_module(WinInfo, Mod) ->
+ {value, ModInfo} = lists:keysearch(Mod, #moduleInfo.module,
+ WinInfo#winInfo.modules),
+ {Menu, MenuBtn} = ModInfo#moduleInfo.menubtn,
+ wxMenu:'Destroy'(Menu, MenuBtn),
+ ListBox = WinInfo#winInfo.listbox,
+ Id = wxListBox:findString(ListBox, atom_to_list(Mod)),
+ wxListBox:delete(ListBox,Id),
+ WinInfo#winInfo{modules=lists:keydelete(Mod, #moduleInfo.module,
+ WinInfo#winInfo.modules)}.
+
+%%--------------------------------------------------------------------
+%% add_process(WinInfo, Pid, Name, Function, Status, Info) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Pid = pid()
+%% Name = undefined | atom()
+%% Function = {Mod, Func, Args}
+%% Status = idle | running | break | exit
+%% Info = {} | term()
+%%--------------------------------------------------------------------
+add_process(WinInfo, Pid, Name, {Mod,Func,Args}, Status, Info) ->
+ Grid = WinInfo#winInfo.grid,
+ Row = (WinInfo#winInfo.row),
+
+ Name2 = case Name of undefined -> ""; _ -> to_string(Name) end,
+ FuncS = to_string("~w:~w/~w", [Mod, Func, length(Args)]),
+ Info2 = case Info of {} -> ""; _ -> to_string(Info) end,
+ Pid2 = to_string("~p",[Pid]),
+
+ Add = fun() ->
+ _Dbg = wxListCtrl:insertItem(Grid, Row,""),
+ %%wxListCtrl:setItemData(Grid,Temp,Row),
+ if (Row rem 2) =:= 0 ->
+ wxListCtrl:setItemBackgroundColour(Grid, Row, {240,240,255});
+ true -> ignore
+ end,
+
+ wxListCtrl:setItem(Grid, Row, 0, Pid2),
+ wxListCtrl:setItem(Grid, Row, 1, FuncS),
+ wxListCtrl:setItem(Grid, Row, 2, Name2),
+ wxListCtrl:setItem(Grid, Row, 3, to_string(Status)),
+ wxListCtrl:setItem(Grid, Row, 4, Info2),
+ ok
+ end,
+ wx:batch(Add),
+
+ ProcInfo = #procInfo{pid=Pid, row=Row},
+ WinInfo#winInfo{processes=[ProcInfo|WinInfo#winInfo.processes],
+ row=Row+1}.
+
+%%--------------------------------------------------------------------
+%% update_process(WinInfo, Pid, Status, Info)
+%% WinInfo = #winInfo{}
+%% Pid = pid()
+%% Status = idle | running | break | exit
+%% Info = {} | term()
+%%--------------------------------------------------------------------
+update_process(WinInfo, Pid, Status, Info) ->
+ {value, ProcInfo} = lists:keysearch(Pid, #procInfo.pid,
+ WinInfo#winInfo.processes),
+
+ Grid = WinInfo#winInfo.grid,
+ Row = ProcInfo#procInfo.row,
+ Info2 = case Info of {} -> ""; _ -> Info end,
+ wxListCtrl:setItem(Grid, Row, 3, to_string(Status)),
+ wxListCtrl:setItem(Grid, Row, 4, to_string(Info2)).
+
+%%--------------------------------------------------------------------
+%% clear_processes(WinInfo) -> WinInfo
+%% WinInfo = #winInfo{}
+%%--------------------------------------------------------------------
+clear_processes(WinInfo) ->
+ Grid = WinInfo#winInfo.grid,
+ Max = WinInfo#winInfo.row,
+ wx:batch(fun() -> clear_processes(Grid, Max-1) end),
+ WinInfo#winInfo{row=0, focus=0, processes=[]}.
+
+clear_processes(Grid, Row) when Row >= 0 ->
+ Item = wxListItem:new(),
+ wxListItem:setId(Item,Row),
+ wxListItem:setColumn(Item, 3),
+ case wxListCtrl:getItem(Grid, Item) of
+ true ->
+ case wxListItem:getText(Item) of
+ "exit" ->
+ wxListItem:setColumn(Item, 0),
+ wxListCtrl:getItem(Grid, Item),
+ Pid = list_to_pid(wxListItem:getText(Item)),
+ dbg_wx_winman:clear_process(dbg_wx_trace:title(Pid));
+ _ ->
+ ok
+ end;
+ false ->
+ ignore
+ end,
+ wxListItem:destroy(Item),
+ wxListCtrl:deleteItem(Grid, Row),
+ clear_processes(Grid, Row-1);
+clear_processes(_Grid, _Row) ->
+ done.
+
+%%--------------------------------------------------------------------
+%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Name = atom()
+%% Point = {Mod, Line}
+%% Options = [Status, Action, Mods, Cond]
+%% Status = active | inactive
+%% Action = enable | disable | delete
+%% Mods = null (not used)
+%% Cond = null | {Mod, Func}
+%%--------------------------------------------------------------------
+add_break(WinInfo, Menu, {Point, Options}) ->
+ Break = dbg_wx_win:add_break(WinInfo#winInfo.window, Menu, Point),
+ dbg_wx_win:update_break(Break, Options),
+ BreakInfo = #breakInfo{point=Point, break=Break},
+ WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}.
+
+%%--------------------------------------------------------------------
+%% update_break(WinInfo, {Point, Options})
+%% WinInfo = #winInfo{}
+%% Point = {Mod, Line}
+%% Options = [Status, Action, Mods, Cond]
+%% Status = active | inactive
+%% Action = enable | disable | delete
+%% Mods = null (not used)
+%% Cond = null | {Mod, Func}
+%%--------------------------------------------------------------------
+update_break(WinInfo, {Point, Options}) ->
+ {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks),
+ dbg_wx_win:update_break(BreakInfo#breakInfo.break, Options).
+
+%%--------------------------------------------------------------------
+%% delete_break(WinInfo, Point) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Point = {Mod, Line}
+%%--------------------------------------------------------------------
+delete_break(WinInfo, Point) ->
+ {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks),
+ dbg_wx_win:delete_break(BreakInfo#breakInfo.break),
+ WinInfo#winInfo{breaks=lists:keydelete(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks)}.
+
+%%--------------------------------------------------------------------
+%% clear_breaks(WinInfo) -> WinInfo
+%% clear_breaks(WinInfo, Mod) -> WinInfo
+%% WinInfo = #winInfo{}
+%%--------------------------------------------------------------------
+clear_breaks(WinInfo) ->
+ lists:foreach(fun(BreakInfo) ->
+ dbg_wx_win:delete_break(BreakInfo#breakInfo.break)
+ end,
+ WinInfo#winInfo.breaks),
+ WinInfo#winInfo{breaks=[]}.
+clear_breaks(WinInfo, Mod) ->
+ Fun =
+ fun(BreakInfo) ->
+ case BreakInfo#breakInfo.point of
+ {Mod, _Line} ->
+ dbg_wx_win:delete_break(BreakInfo#breakInfo.break),
+ false;
+ _ -> true
+ end
+ end,
+ Breaks = lists:filter(Fun, WinInfo#winInfo.breaks),
+ WinInfo#winInfo{breaks=Breaks}.
+
+%%--------------------------------------------------------------------
+%% handle_event(WxEvent, WinInfo) -> Command
+%% WxEvent = #wx{}
+%% WinInfo = #winInfo{}
+%% Command = ignore
+%% | stopped
+%% | {coords, {X,Y}}
+%%
+%% | {shortcut, Key}
+%% | MenuItem | {Menu, [MenuItem]}
+%% MenuItem = Menu = atom()
+%% | {break, Point, What}
+%% What = delete | {status, Status} | {trigger, Trigger}
+%% | {module, Mod, What}
+%% What = view | delete
+%%
+%% | {focus, Pid, WinInfo}
+%% | default
+%%--------------------------------------------------------------------
+%% Window events
+handle_event(#wx{event=#wxSize{size={W,_}}}, #winInfo{grid=Grid}) ->
+ wx:batch(fun() ->
+ Tot = wx:foldl(fun(C,Sum) ->
+ Sum + wxListCtrl:getColumnWidth(Grid, C)
+ end, 0, [0,1,2,3]),
+ wxListCtrl:setColumnWidth(Grid, 4, W-Tot-4)
+ end),
+ ignore;
+handle_event(_Ev=#wx{event=#wxClose{}}, _WinInfo) ->
+%% io:format("~p Received ~p close ~p~n", [?MODULE, self(), _Ev]),
+ stopped;
+
+%% Menus and keyboard shortcuts
+handle_event(#wx{userData={dbg_ui_winman, Win},
+ event=#wxCommand{type=command_menu_selected}}, _Wi) ->
+ dbg_wx_winman:raise(Win),
+ ignore;
+handle_event(_Ev = #wx{event=#wxKey{keyCode=Key, controlDown=true}}, _WinInfo) ->
+ if
+ Key/=?WXK_UP, Key/=?WXK_DOWN, Key /=? WXK_RETURN ->
+ try
+ {shortcut, list_to_atom([Key+($a-$A)])}
+ catch _:_ -> ignore
+ end;
+ true ->
+ ignore
+ end;
+
+handle_event(#wx{userData={break, Point, status},
+ event=#wxCommand{type=command_menu_selected}},
+ WinInfo) ->
+ {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks),
+ %% This is a temporary hack !!
+ #breakInfo{break=#break{smi=Smi}} = BreakInfo,
+
+ case wxMenuItem:getText(Smi) of
+ "Enable" -> {break, Point, {status, active}};
+ "Disable" -> {break, Point, {status, inactive}}
+ end;
+
+%% Listbox
+handle_event(#wx{event=#wxCommand{type=command_listbox_doubleclicked, cmdString=ModS}},
+ _WinInfo) ->
+ {module, list_to_atom(ModS), view};
+handle_event(#wx{obj=ListBox, event=#wxMouse{type=right_down, x=X,y=Y}},
+ #winInfo{listbox=ListBox}) ->
+ case wxListBox:hitTest(ListBox, {X,Y}) of
+ ?wxNOT_FOUND -> ignore;
+ Row ->
+ ModS = wxListBox:getString(ListBox,Row),
+ io:format("Re-loading/interpreting: ~s~n", [ModS]),
+ int:i(list_to_atom(ModS)),
+ ignore
+ end;
+
+%% Auto attach buttons
+handle_event(#wx{event=#wxCommand{type=command_checkbox_clicked}},
+ WinInfo) ->
+ Check = fun(Button, NamesAcc) ->
+ case wxCheckBox:isChecked(Button) of
+ true ->
+ Name = wxCheckBox:getLabel(Button),
+ [list_to_atom(Name)|NamesAcc];
+ false ->
+ NamesAcc
+ end
+ end,
+ Names = wx:foldl(Check, [],
+ [WinInfo#winInfo.ebutton,
+ WinInfo#winInfo.bbutton,
+ WinInfo#winInfo.fbutton]),
+ {'Auto Attach', Names};
+
+%% Process grid
+handle_event(#wx{event=#wxList{type=command_list_item_selected,
+ itemIndex=Row}}, WinInfo) ->
+ #winInfo{processes=Pids} = WinInfo,
+ {value, #procInfo{pid=Pid}} =
+ lists:keysearch(Row, #procInfo.row, Pids),
+ {focus, Pid, WinInfo#winInfo{focus=Row}};
+handle_event(#wx{event=#wxList{type=command_list_item_activated}},
+ _WinInfo) ->
+ default;
+handle_event(#wx{event=#wxMouse{type=enter_window}}, #winInfo{grid=Grid}) ->
+ %% Keyboard focus
+ wxWindow:setFocus(Grid),
+ ignore;
+
+%% Menu Events
+handle_event(#wx{userData=Data,
+ event=_Cmd=#wxCommand{type=command_menu_selected}},
+ _WinInfo) ->
+ Data;
+handle_event(_Event, _WinInfo) ->
+%% io:format("Ev: ~p~n",[_Event]),
+ ignore.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+
diff --git a/lib/debugger/src/dbg_wx_settings.erl b/lib/debugger/src/dbg_wx_settings.erl
new file mode 100644
index 0000000000..8f87815949
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_settings.erl
@@ -0,0 +1,110 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(dbg_wx_settings).
+
+-include_lib("kernel/include/file.hrl").
+-include_lib("wx/include/wx.hrl").
+
+%% External exports
+-export([load/3, save/3]).
+
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%====================================================================
+%% load/save(Win, Pos, Str) -> {ok, FileName} | cancel
+%%====================================================================
+load(Win, Pos, SFile) ->
+ Str = "Load Settings Dialog",
+ open_win(Win, Pos, SFile, Str, ?wxFD_OPEN).
+
+save(Win, Pos, SFile) ->
+ Str = "Save Settings Dialog",
+ open_win(Win, Pos, SFile, Str, ?wxFD_SAVE).
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+open_win(Win, Pos, SFile, Str, What) ->
+ {SDir, SFileName} =
+ if
+ %% If settings are saved for the first time, and to
+ %% the default directory HOME/erlang.tools/debugger,
+ %% make sure the directory exists, or create it if
+ %% desired and possible
+ SFile==default -> {default_settings_dir(Win), "NoName.state"};
+ true -> {filename:dirname(SFile), filename:basename(SFile)}
+ end,
+
+ FD = wxFileDialog:new(Win, [{message,Str},{pos, Pos},
+ {defaultDir,SDir},
+ {defaultFile,SFileName},
+ {wildCard, "*.state"},
+ {style,What}]),
+ case wxFileDialog:showModal(FD) of
+ ?wxID_OK ->
+ File = wxFileDialog:getFilename(FD),
+ Dir = wxFileDialog:getDirectory(FD),
+ wxFileDialog:destroy(FD),
+ {ok, filename:join(Dir,File)};
+ _ ->
+ wxFileDialog:destroy(FD),
+ cancel
+ end.
+
+default_settings_dir(Win) ->
+ {ok, [[Home]]} = init:get_argument(home),
+ DefDir = filename:join([Home, ".erlang_tools", "debugger"]),
+
+ case filelib:is_dir(DefDir) of
+ true -> DefDir;
+ false ->
+ {ok, CWD} = file:get_cwd(),
+
+ Msg = ["Default directory", DefDir, "does not exist.",
+ "Click Ok to create it or",
+ "Cancel to use other directory!"],
+ case dbg_wx_win:confirm(Win, Msg) of
+ ok ->
+ ToolsDir = filename:dirname(DefDir),
+ case filelib:is_dir(ToolsDir) of
+ true ->
+ case file:make_dir(DefDir) of
+ ok -> DefDir;
+ _Error -> CWD
+ end;
+ false ->
+ case file:make_dir(ToolsDir) of
+ ok ->
+ case file:make_dir(DefDir) of
+ ok -> DefDir;
+ _Error -> CWD
+ end;
+ _Error -> CWD
+ end
+ end;
+ cancel -> CWD
+ end
+ end.
diff --git a/lib/debugger/src/dbg_wx_src_view.erl b/lib/debugger/src/dbg_wx_src_view.erl
new file mode 100644
index 0000000000..5337ec2aac
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_src_view.erl
@@ -0,0 +1,66 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg_wx_src_view).
+
+-export([code_area/2]).
+-include_lib("wx/include/wx.hrl").
+
+-define(stc, wxStyledTextCtrl).
+
+code_area(Parent, Sizer) ->
+ FixedFont = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL,[]),
+ Ed = wxStyledTextCtrl:new(Parent, [{size, {400, 500}}]),
+ ?stc:styleClearAll(Ed),
+ ?stc:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont),
+ ?stc:setLexer(Ed, ?wxSTC_LEX_ERLANG),
+ ?stc:setMarginType(Ed, 0, ?wxSTC_MARGIN_NUMBER),
+ LW = ?stc:textWidth(Ed, ?wxSTC_STYLE_LINENUMBER, "999"),
+ ?stc:setMarginWidth(Ed, 0, LW),
+
+ ?stc:setReadOnly(Ed, true),
+
+ Styles = [{?wxSTC_ERLANG_DEFAULT, {0,0,0}},
+ {?wxSTC_ERLANG_COMMENT, {222,53,35}},
+ {?wxSTC_ERLANG_VARIABLE, {170,110,50}},
+ {?wxSTC_ERLANG_NUMBER, {5,5,100}},
+ {?wxSTC_ERLANG_KEYWORD, {238,80,239}},
+ {?wxSTC_ERLANG_STRING, {236,155,172}},
+ {?wxSTC_ERLANG_OPERATOR, {30,0,0}},
+ {?wxSTC_ERLANG_ATOM, {0,0,0}},
+ {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}},
+ {?wxSTC_ERLANG_CHARACTER,{236,155,172}},
+ {?wxSTC_ERLANG_MACRO, {92,194,241}},
+ {?wxSTC_ERLANG_RECORD, {60,150,40}},
+ {?wxSTC_ERLANG_SEPARATOR,{0,0,0}},
+ {?wxSTC_ERLANG_NODE_NAME,{0,0,0}}],
+ SetStyle = fun({Style, Color}) ->
+ ?stc:styleSetFont(Ed, Style, FixedFont),
+ ?stc:styleSetForeground(Ed, Style, Color)
+ end,
+ [SetStyle(Style) || Style <- Styles],
+ ?stc:setKeyWords(Ed, 0, keyWords()),
+ wxSizer:add(Sizer, Ed, [{proportion,1}, {flag, ?wxEXPAND}]),
+ Ed.
+
+
+keyWords() ->
+ L = ["after","begin","case","try","cond","catch","andalso","orelse",
+ "end","fun","if","let","of","query","receive","when","bnot","not",
+ "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"],
+ lists:flatten([K ++ " " || K <- L] ++ [0]).
diff --git a/lib/debugger/src/dbg_wx_trace.erl b/lib/debugger/src/dbg_wx_trace.erl
new file mode 100644
index 0000000000..f9fdf593c4
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_trace.erl
@@ -0,0 +1,839 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(dbg_wx_trace).
+
+%% External exports
+-export([start/1, start/3]).
+-export([title/1]).
+
+-define(TRACEWIN, ['Button Area', 'Evaluator Area', 'Bindings Area']).
+-define(BACKTRACE, 100).
+
+-record(state, {win, % term() Attach process window data
+ coords, % {X,Y} Mouse point position
+
+ pid, % pid() Debugged process
+ meta, % pid() Meta process
+ status, % {Status,Mod,Line} � {exit,Where,Reason}
+ % Status = init � idle | break
+ % | wait_break � wait_running
+ % � running
+ % Where={Mod,Line} | null
+
+ cm, % atom() | undefined Current module
+ cm_obsolete=false, % boolean() Curr mod needs reloading
+
+ stack, % {Cur,Max}
+
+ trace, % boolean()
+ stack_trace, % all | no_tail | false
+ backtrace % integer() #call frames to fetch
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start(Pid)
+%% start(Parent, Pid, TraceWin, BackTrace)
+%% Parent = #wxObj{}
+%% Pid = pid()
+%% TraceWin = [WinArea]
+%% WinArea = 'Button|Evaluator|Bindings|Trace Area'
+%% Backtrace = integer()
+%%--------------------------------------------------------------------
+start(Pid) -> % Used by debugger:quick/3 (no monitor)
+ start(Pid, ?TRACEWIN, ?BACKTRACE).
+start(Pid, TraceWin, BackTrace) ->
+ case {whereis(dbg_wx_mon), whereis(dbg_ui_mon)} of
+ {undefined, undefined} ->
+ case which_gui() of
+ gs ->
+ dbg_ui_trace:start(Pid, TraceWin, BackTrace);
+ wx ->
+ Parent = wx:new(),
+ Env = wx:get_env(),
+ start(Pid, Env, Parent, TraceWin, BackTrace)
+ end;
+ {undefined, Monitor} when is_pid(Monitor) ->
+ dbg_ui_trace:start(Pid, TraceWin, BackTrace);
+ {Monitor, _} when is_pid(Monitor) ->
+ Monitor ! {?MODULE, self(), get_env},
+ receive
+ {env, Monitor, Env, Parent} ->
+ start(Pid, Env, Parent, TraceWin, BackTrace)
+ end
+ end.
+
+start(Pid, Env, Parent, TraceWin, BackTrace) ->
+ %% Inform int about my existence and get the meta pid back
+ case int:attached(Pid) of
+ {ok, Meta} ->
+ try
+ wx:set_env(Env),
+ init(Pid, Parent, Meta, TraceWin, BackTrace)
+ catch
+ _:stop ->
+ exit(stop);
+ E:R ->
+ io:format("TraceWin Crashed ~p~n",[E]),
+ io:format(" ~p in ~p~n",[R, erlang:get_stacktrace()]),
+ exit(R)
+ end;
+ error ->
+ ignore
+ end.
+
+which_gui() ->
+ try
+ wx:new(),
+ wx:destroy(),
+ wx
+ catch _:_ ->
+ gs
+ end.
+
+%%--------------------------------------------------------------------
+%% title(Pid) -> string()
+%% By exporting this function, dbg_wx_mon may check with dbg_wx_winman
+%% if there already is an attach window for a given pid and thus avoid
+%% spawning processes unnecessarily.
+%%--------------------------------------------------------------------
+title(Pid) ->
+ "Attach Process " ++ pid_to_list(Pid).
+
+
+%%====================================================================
+%% Main loop and message handling
+%%====================================================================
+
+init(Pid, Parent, Meta, TraceWin, BackTrace) ->
+
+ %% Start necessary stuff
+ dbg_wx_trace_win:init(), % Graphics system
+
+ %% Create attach process window
+ Title = title(Pid),
+ Win = dbg_wx_trace_win:create_win(Parent, Title, TraceWin, menus()),
+ Window = dbg_wx_trace_win:get_window(Win),
+ dbg_wx_winman:insert(Title, Window),
+
+ %% Initial process state
+ State1 = #state{win=Win, coords={0,0}, pid=Pid, meta=Meta,
+ status={idle,null,null},
+ stack={1,1}},
+
+ State2 = init_options(TraceWin,
+ int:stack_trace(), % Stack Trace
+ BackTrace, % Back trace size
+ State1),
+
+ State3 = init_contents(int:all_breaks(), % Breakpoints
+ State2),
+
+ int:meta(Meta, trace, State3#state.trace),
+
+ gui_enable_updown(stack_trace, {1,1}),
+ gui_enable_btrace(false, false),
+ dbg_wx_trace_win:display(Win,idle),
+
+ loop(State3).
+
+init_options(TraceWin, StackTrace, BackTrace, State) ->
+ lists:foreach(fun(Area) -> dbg_wx_trace_win:select(Area, true) end,
+ TraceWin),
+
+ Trace = lists:member('Trace Area', TraceWin),
+
+ dbg_wx_trace_win:select(map(StackTrace), true),
+
+ %% Backtrace size is (currently) not shown in window
+
+ State#state{trace=Trace,stack_trace=StackTrace,backtrace=BackTrace}.
+
+init_contents(Breaks, State) ->
+ Win =
+ lists:foldl(fun(Break, Win) ->
+ dbg_wx_trace_win:add_break(Win,'Break',Break)
+ end,
+ State#state.win,
+ Breaks),
+
+ State#state{win=Win}.
+
+loop(#state{meta=Meta} = State) ->
+ receive
+ %% From the GUI main window
+ GuiEvent when element(1, GuiEvent)==wx ->
+ Cmd = wx:batch(fun() ->
+ dbg_wx_trace_win:handle_event(GuiEvent,State#state.win)
+ end),
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the GUI help windows
+ {gui, Cmd} ->
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the interpreter
+ {int, Cmd} ->
+ State2 = int_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the meta process
+ {Meta, Cmd} ->
+ State2 = meta_cmd(Cmd, State),
+ loop(State2);
+ {NewMeta, {exit_at, Where, Reason, Cur}} ->
+ State2 = meta_cmd({exit_at, Where, Reason, Cur},
+ State#state{meta=NewMeta}),
+ loop(State2);
+
+ %% From the dbg_wx_winman process (Debugger window manager)
+ {dbg_ui_winman, update_windows_menu, Data} ->
+ Window = dbg_wx_trace_win:get_window(State#state.win),
+ dbg_wx_winman:update_windows_menu(Window,Data),
+ loop(State);
+ {dbg_ui_winman, destroy} ->
+ dbg_wx_trace_win:stop(State#state.win),
+ exit(stop)
+ end.
+
+%%--Commands from the GUI---------------------------------------------
+
+gui_cmd(ignore, State) ->
+ State;
+gui_cmd({win, Win}, State) ->
+ State#state{win=Win};
+gui_cmd(stopped, State) ->
+ dbg_wx_trace_win:stop(State#state.win),
+ exit(stop);
+gui_cmd({coords, Coords}, State) ->
+ State#state{coords=Coords};
+
+gui_cmd({shortcut, Key}, State) ->
+ case shortcut(Key) of
+ {always, Cmd} -> gui_cmd(Cmd, State);
+ {if_enabled, Cmd} ->
+ case dbg_wx_trace_win:is_enabled(Cmd) of
+ true -> gui_cmd(Cmd, State);
+ false -> State
+ end;
+ false -> State
+ end;
+
+%% File menu
+gui_cmd('Close', State) ->
+ gui_cmd(stopped, State);
+
+%% Edit menu
+gui_cmd('Go To Line', State) ->
+ %% Will result in message handled below: {gui, {gotoline, Line}}
+ Win = dbg_wx_trace_win:helpwin(gotoline, State#state.win),
+ State#state{win=Win};
+gui_cmd('Search', State) ->
+ Win = dbg_wx_trace_win:helpwin(search, State#state.win),
+ State#state{win=Win};
+gui_cmd({gotoline, Line}, State) ->
+ Win = dbg_wx_trace_win:select_line(State#state.win, Line),
+ State#state{win=Win};
+
+%% Process menu
+gui_cmd('Step', State) ->
+ int:meta(State#state.meta, step),
+ State;
+gui_cmd('Next', State) ->
+ int:meta(State#state.meta, next),
+ State;
+gui_cmd('Continue', State) ->
+ int:meta(State#state.meta, continue),
+ {Status, Mod, Line} = State#state.status,
+ if
+ Status==wait_break ->
+ Win = dbg_wx_trace_win:unmark_line(State#state.win),
+ gui_enable_functions(wait_running),
+ State#state{win=Win, status={wait_running,Mod,Line}};
+ true ->
+ dbg_wx_trace_win:enable(['Stop'], true),
+ dbg_wx_trace_win:enable(['Continue'], false),
+ State
+ end;
+gui_cmd('Finish', State) ->
+ int:meta(State#state.meta, finish),
+ State;
+gui_cmd('Skip', State) ->
+ int:meta(State#state.meta, skip),
+ State;
+gui_cmd('Time Out', State) ->
+ int:meta(State#state.meta, timeout),
+ State;
+gui_cmd('Stop', State) ->
+ int:meta(State#state.meta, stop),
+ {Status, Mod, Line} = State#state.status,
+ if
+ Status==wait_running ->
+ Win = dbg_wx_trace_win:mark_line(State#state.win, Line,
+ break),
+ gui_enable_functions(wait_break),
+ gui_enable_updown(State#state.stack_trace,
+ State#state.stack),
+ gui_enable_btrace(State#state.trace,
+ State#state.stack_trace),
+ dbg_wx_trace_win:display(State#state.win,{wait, Mod, Line}),
+ State#state{win=Win, status={wait_break,Mod,Line}};
+ true ->
+ dbg_wx_trace_win:enable(['Stop'], false),
+ dbg_wx_trace_win:enable(['Continue'], true),
+ State
+ end;
+gui_cmd('Where', State) ->
+ {_Cur, Max} = State#state.stack,
+ Stack = {Max, Max},
+ {_Status, Mod, Line} = State#state.status,
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid, break),
+ gui_update_bindings(State#state.win, State#state.meta),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ dbg_wx_trace_win:display(State#state.win,State#state.status),
+ State#state{win=Win, cm=Mod, stack=Stack};
+
+gui_cmd('Kill', State) ->
+ exit(State#state.pid, kill),
+ State;
+gui_cmd('Messages', State) ->
+ case int:meta(State#state.meta, messages) of
+ [] ->
+ dbg_wx_trace_win:eval_output(State#state.win,"< No Messages!\n", bold);
+ Messages ->
+ dbg_wx_trace_win:eval_output(State#state.win,"< --- Current Messages ---\n",
+ bold),
+ lists:foldl(
+ fun(Msg, N) ->
+ Str1 = io_lib:format(" ~w:", [N]),
+ dbg_wx_trace_win:eval_output(State#state.win,Str1, bold),
+ Str2 = io_lib:format(" ~s~n",[io_lib:print(Msg)]),
+ dbg_wx_trace_win:eval_output(State#state.win,Str2, normal),
+ N+1
+ end,
+ 1,
+ Messages)
+ end,
+ State;
+gui_cmd('Back Trace', State) ->
+ dbg_wx_trace_win:trace_output(State#state.win,"\nBACK TRACE\n----------\n"),
+ lists:foreach(
+ fun({Le, {Mod,Func,Args}}) ->
+ Str = io_lib:format("~p > ~p:~p~p~n",
+ [Le, Mod, Func, Args]),
+ dbg_wx_trace_win:trace_output(State#state.win,Str);
+ ({Le, {Fun,Args}}) ->
+ Str = io_lib:format("~p > ~p~p~n", [Le, Fun, Args]),
+ dbg_wx_trace_win:trace_output(State#state.win,Str);
+ (_) -> ignore
+ end,
+ int:meta(State#state.meta, backtrace, State#state.backtrace)),
+ dbg_wx_trace_win:trace_output(State#state.win,"\n"),
+ State;
+gui_cmd('Up', State) ->
+ {Cur, Max} = State#state.stack,
+ case int:meta(State#state.meta, stack_frame, {up, Cur}) of
+ {New, {undefined,-1}, _Bs} -> % call from non-interpreted code
+ Stack = {New, Max},
+ Win = dbg_wx_trace_win:show_no_code(State#state.win),
+ dbg_wx_trace_win:update_bindings(State#state.win,[]),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ dbg_wx_trace_win:display(State#state.win,{New,null,null}),
+ State#state{win=Win, cm=null, stack=Stack};
+
+ {New, {Mod,Line}, Bs} ->
+ Stack = {New, Max},
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid,
+ where),
+ dbg_wx_trace_win:update_bindings(State#state.win,Bs),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ dbg_wx_trace_win:display(State#state.win,{New,Mod,Line}),
+ State#state{win=Win, cm=Mod, stack=Stack};
+ top ->
+ dbg_wx_trace_win:enable(['Up'], false),
+ State
+ end;
+gui_cmd('Down', State) ->
+ {Cur, Max} = State#state.stack,
+ case int:meta(State#state.meta, stack_frame, {down, Cur}) of
+ {New, {undefined,-1}, _Bs} -> % call from non-interpreted code
+ Stack = {New, Max},
+ Win = dbg_wx_trace_win:show_no_code(State#state.win),
+ dbg_wx_trace_win:update_bindings(State#state.win, []),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ dbg_wx_trace_win:display(State#state.win, {New,null,null}),
+ State#state{win=Win, cm=null, stack=Stack};
+
+ {New, {Mod,Line}, Bs} ->
+ Stack = {New, Max},
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid,
+ where),
+ dbg_wx_trace_win:update_bindings(State#state.win, Bs),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ dbg_wx_trace_win:display(State#state.win, {New,Mod,Line}),
+ State#state{win=Win, cm=Mod, stack=Stack};
+
+ bottom ->
+ gui_cmd('Where', State)
+ end;
+
+%% Break menu
+gui_cmd('Line Break...', State) ->
+ add_break(State#state.win, State#state.coords, line,
+ State#state.cm,
+ dbg_wx_trace_win:selected_line(State#state.win)),
+ State;
+gui_cmd('Conditional Break...', State) ->
+ add_break(State#state.win, State#state.coords, conditional,
+ State#state.cm,
+ dbg_wx_trace_win:selected_line(State#state.win)),
+ State;
+gui_cmd('Function Break...', State) ->
+ add_break(State#state.win, State#state.coords, function,
+ State#state.cm, undefined),
+ State;
+gui_cmd('Enable All', State) ->
+ Breaks = int:all_breaks(),
+ ThisMod = State#state.cm,
+ lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
+ int:enable_break(Mod, Line);
+ (_Break) ->
+ ignore
+ end,
+ Breaks),
+ State;
+gui_cmd('Disable All', State) ->
+ Breaks = int:all_breaks(),
+ ThisMod = State#state.cm,
+ lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
+ int:disable_break(Mod, Line);
+ (_Break) ->
+ ignore
+ end,
+ Breaks),
+ State;
+gui_cmd('Delete All', State) ->
+ int:no_break(State#state.cm),
+ State;
+gui_cmd({break, {Mod, Line}, What}, State) ->
+ case What of
+ add -> int:break(Mod, Line);
+ delete -> int:delete_break(Mod, Line);
+ {status, inactive} -> int:disable_break(Mod, Line);
+ {status, active} -> int:enable_break(Mod, Line);
+ {trigger, Action} -> int:action_at_break(Mod, Line, Action)
+ end,
+ State;
+
+%% Options menu
+gui_cmd({'Trace Window', TraceWin}, State) ->
+ Trace = lists:member('Trace Area', TraceWin),
+ int:meta(State#state.meta, trace, Trace),
+ Win = dbg_wx_trace_win:configure(State#state.win, TraceWin),
+ {Status,_,_} = State#state.status,
+ if
+ Status==break; Status==wait_break ->
+ gui_enable_btrace(Trace, State#state.stack_trace);
+ true -> ignore
+ end,
+ State#state{win=Win, trace=Trace};
+gui_cmd({'Stack Trace', [Name]}, State) ->
+ int:meta(State#state.meta, stack_trace, map(Name)),
+ {Status,_,_} = State#state.status,
+ if
+ Status==break; Status==wait_break ->
+ gui_enable_btrace(State#state.trace, map(Name));
+ true -> ignore
+ end,
+ State;
+gui_cmd('Back Trace Size...', State) ->
+ Win = dbg_wx_trace_win:get_window(State#state.win),
+ case dbg_wx_win:entry(Win, "Backtrace",'Backtrace:', {integer, State#state.backtrace}) of
+ cancel -> State;
+ {_, BackTrace} -> State#state{backtrace=BackTrace}
+ end;
+
+%% Help menu
+gui_cmd('Debugger', State) ->
+ Window = dbg_wx_trace_win:get_window(State#state.win),
+ HelpFile = filename:join([code:lib_dir(debugger),
+ "doc", "html", "part_frame.html"]),
+ dbg_wx_win:open_help(Window, HelpFile),
+ State;
+
+gui_cmd({user_command, Cmd}, State) ->
+ {Status, _Mod, _Line} = State#state.status,
+ if
+ Status==break;
+ Status==wait_break;
+ Status==wait_running ->
+ Cm = State#state.cm,
+ Arg = case State#state.stack of
+ {Cur, Max} when Cur<Max -> {Cm, Cmd, Cur};
+ _Stack -> {Cm, Cmd}
+ end,
+
+ %% Reply will be received as {Meta, {eval_rsp, Res}}
+ int:meta(State#state.meta, eval, Arg);
+ true ->
+ Str = "Commands not allowed",
+ dbg_wx_trace_win:eval_output(State#state.win, [$<,Str,10], normal)
+ end,
+ State;
+
+gui_cmd({edit, {Var, Val}}, State) ->
+ Window = dbg_wx_trace_win:get_window(State#state.win),
+ case dbg_wx_win:entry(Window, "Edit variable", Var, {term, Val}) of
+ cancel ->
+ State;
+ {Var, Term} ->
+ Cmd = atom_to_list(Var)++"="++io_lib:format("~p", [Term]),
+ gui_cmd({user_command, lists:flatten(Cmd)}, State)
+ end.
+
+add_break(WI, Coords, Type, undefined, _Line) ->
+ Win = dbg_wx_trace_win:get_window(WI),
+ dbg_wx_break:start(Win, Coords, Type);
+add_break(WI, Coords, Type, Mod, undefined) ->
+ Win = dbg_wx_trace_win:get_window(WI),
+ dbg_wx_break:start(Win, Coords, Type, Mod);
+add_break(WI, Coords, Type, Mod, Line) ->
+ Win = dbg_wx_trace_win:get_window(WI),
+ dbg_wx_break:start(Win, Coords, Type, Mod, Line).
+
+%%--Commands from the interpreter-------------------------------------
+
+int_cmd({interpret, Mod}, State) ->
+ if
+ Mod==State#state.cm ->
+ State#state{cm_obsolete=true};
+ true ->
+ State
+ end;
+int_cmd({no_interpret, Mod}, State) ->
+ if
+ Mod==State#state.cm ->
+ State#state{cm_obsolete=true};
+ true ->
+ Win = dbg_wx_trace_win:remove_code(State#state.win, Mod),
+ State#state{win=Win}
+ end;
+
+int_cmd({new_break, Break}, State) ->
+ Win = dbg_wx_trace_win:add_break(State#state.win, 'Break', Break),
+ State#state{win=Win};
+int_cmd({delete_break, Point}, State) ->
+ Win = dbg_wx_trace_win:delete_break(State#state.win, Point),
+ State#state{win=Win};
+int_cmd({break_options, Break}, State) ->
+ Win = dbg_wx_trace_win:update_break(State#state.win, Break),
+ State#state{win=Win};
+int_cmd(no_break, State) ->
+ Win = dbg_wx_trace_win:clear_breaks(State#state.win),
+ State#state{win=Win};
+int_cmd({no_break, Mod}, State) ->
+ Win = dbg_wx_trace_win:clear_breaks(State#state.win, Mod),
+ State#state{win=Win}.
+
+%%--Commands from the meta process------------------------------------
+
+%% Message received when first attached to a living process
+%% '_Trace' is a boolean indicating if the process is traced or not --
+%% ignore this as we already have ordered tracing or not depending on if
+%% the Trace Area is shown or not.
+meta_cmd({attached, Mod, Line, _Trace}, State) ->
+ Win = if
+ Mod/=undefined ->
+ gui_enable_functions(init),
+ gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid,
+ break);
+ true -> State#state.win
+ end,
+ State#state{win=Win, status={init,Mod,Line}, cm=Mod};
+
+%% Message received when returning to interpreted code
+meta_cmd({re_entry, dbg_ieval, eval_fun}, State) ->
+ State;
+meta_cmd({re_entry, Mod, _Func}, State) ->
+ Obs = State#state.cm_obsolete,
+ case State#state.cm of
+ Mod when Obs==true ->
+ Win = gui_load_module(State#state.win, Mod,State#state.pid),
+ State#state{win=Win, cm_obsolete=false};
+ Mod -> State;
+ Cm ->
+ Win = gui_show_module(State#state.win, Mod, 0,
+ Cm, State#state.pid, break),
+ State#state{win=Win, cm=Mod}
+ end;
+
+%% Message received when attached to a terminated process
+meta_cmd({exit_at, null, Reason, Cur}, State) ->
+ Stack = {Cur, Cur},
+ gui_enable_functions(exit),
+ gui_enable_updown(false, Stack),
+ dbg_wx_trace_win:display(State#state.win, {exit, null, Reason}),
+ State#state{status={exit,null,Reason}, stack=Stack};
+meta_cmd({exit_at, {Mod,Line}, Reason, Cur}, State) ->
+ Stack = {Cur+1, Cur+1},
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid, break),
+ gui_enable_functions(exit),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ gui_enable_btrace(State#state.trace, State#state.stack_trace),
+ gui_update_bindings(State#state.win, State#state.meta),
+ dbg_wx_trace_win:display(State#state.win, {exit, {Mod,Line}, Reason}),
+ State#state{win=Win, cm=Mod,status={exit,{Mod,Line},Reason},
+ stack=Stack};
+
+meta_cmd({break_at, Mod, Line, Cur}, State) ->
+ Stack = {Cur,Cur},
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid, break),
+ gui_enable_functions(break),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ gui_enable_btrace(State#state.trace, State#state.stack_trace),
+ gui_update_bindings(State#state.win, State#state.meta),
+ dbg_wx_trace_win:display(State#state.win, {break, Mod, Line}),
+ State#state{win=Win, cm=Mod, status={break,Mod,Line}, stack=Stack};
+meta_cmd({func_at, Mod, Line, Cur}, State) ->
+ Stack = {Cur,Cur},
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid, where),
+ gui_enable_functions(idle),
+ dbg_wx_trace_win:display(State#state.win, idle),
+ State#state{win=Win, cm=Mod, status={idle,Mod,Line}, stack=Stack};
+meta_cmd({wait_at, Mod, Line, Cur}, #state{status={Status,_,_}}=State)
+ when Status/=init, Status/=break ->
+ Stack = {Cur,Cur},
+ gui_enable_functions(wait_running),
+ dbg_wx_trace_win:display(State#state.win, {wait,Mod,Line}),
+ State#state{status={wait_running,Mod,Line}, stack=Stack};
+meta_cmd({wait_at, Mod, Line, Cur}, State) ->
+ Stack = {Cur,Cur},
+ Win = gui_show_module(State#state.win, Mod, Line,
+ State#state.cm, State#state.pid, break),
+ gui_enable_functions(wait_break),
+ gui_enable_updown(State#state.stack_trace, Stack),
+ gui_enable_btrace(State#state.trace, State#state.stack_trace),
+ gui_update_bindings(State#state.win, State#state.meta),
+ dbg_wx_trace_win:display(State#state.win, {wait, Mod, Line}),
+ State#state{win=Win, cm=Mod, status={wait_break,Mod,Line},
+ stack=Stack};
+meta_cmd({wait_after_at, Mod, Line, Sp}, State) ->
+ meta_cmd({wait_at, Mod, Line, Sp}, State);
+meta_cmd(running, State) ->
+ Win = dbg_wx_trace_win:unmark_line(State#state.win),
+ gui_enable_functions(running),
+ dbg_wx_trace_win:update_bindings(State#state.win, []),
+ dbg_wx_trace_win:display(State#state.win, {running, State#state.cm}),
+ State#state{win=Win, status={running,null,null}};
+
+meta_cmd(idle, State) ->
+ Win = dbg_wx_trace_win:show_no_code(State#state.win),
+ gui_enable_functions(idle),
+ dbg_wx_trace_win:update_bindings(State#state.win, []),
+ dbg_wx_trace_win:display(State#state.win, idle),
+ State#state{win=Win, status={idle,null,null}, cm=undefined};
+
+%% Message about changed trace option can be ignored, the change must
+%% have been ordered by this process. (In theory, the change could have
+%% been ordered by another attached process. The Debugger, though,
+%% allows max one attached process per debugged process).
+meta_cmd({trace, _Bool}, State) ->
+ State;
+
+meta_cmd({stack_trace, Flag}, State) ->
+ dbg_wx_trace_win:select(map(Flag), true),
+ gui_enable_updown(Flag, State#state.stack),
+ {Status,_,_} = State#state.status,
+ if
+ Status==break; Status==wait_break ->
+ gui_enable_btrace(State#state.trace, Flag);
+ true -> ignore
+ end,
+ State#state{stack_trace=Flag};
+
+meta_cmd({trace_output, Str}, State) ->
+ dbg_wx_trace_win:trace_output(State#state.win, Str),
+ State;
+
+%% Reply on a user command
+meta_cmd({eval_rsp, Res}, State) ->
+ Str = io_lib:print(Res),
+ dbg_wx_trace_win:eval_output(State#state.win, [$<,Str,10], normal),
+ State.
+
+
+%%====================================================================
+%% GUI auxiliary functions
+%%====================================================================
+
+menus() ->
+ [{'File', [{'Close', no}]},
+ {'Edit', [{'Go To Line', 0},
+ {'Search', 1}]},
+ {'Process', [{'Step', 0},
+ {'Next', 0},
+ {'Continue', 0},
+ {'Finish', 0},
+ {'Skip', no},
+ {'Time Out', no},
+ {'Stop', no},
+ separator,
+ {'Kill', no},
+ separator,
+ {'Messages', 0},
+ {'Back Trace', no},
+ separator,
+ {'Where', 0},
+ {'Up', no},
+ {'Down', no}]},
+ {'Break', [{'Line Break...', 5},
+ {'Conditional Break...', no},
+ {'Function Break...', no},
+ separator,
+ {'Enable All', no},
+ {'Disable All', no},
+ {'Delete All', 0},
+ separator]},
+ {'Options', [{'Trace Window', no, cascade,
+ [{'Search Area', no, check},
+ {'Button Area', no, check},
+ {'Evaluator Area', no, check},
+ {'Bindings Area', no, check},
+ {'Trace Area', no, check}]},
+ {'Stack Trace', no, cascade,
+ [{'Stack On, Tail', no, radio},
+ {'Stack On, No Tail', no, radio},
+ {'Stack Off', no, radio}]},
+ {'Back Trace Size...', no}]},
+ {'Windows', []},
+ {'Help', [{'Debugger', no}]}].
+
+%% enable(Status) -> [MenuItem]
+%% Status = init % when first message from Meta has arrived
+%% | idle | break | exit | wait_break � wait_running | running
+enable(init) -> [];
+enable(idle) -> ['Stop','Kill'];
+enable(break) -> ['Step','Next','Continue','Finish','Skip',
+ 'Kill','Messages'];
+enable(exit) -> [];
+enable(wait_break) -> ['Continue','Time Out','Kill'];
+enable(wait_running) -> ['Stop','Kill'];
+enable(running) -> ['Stop','Kill'].
+
+all_buttons() ->
+ ['Step','Next','Continue','Finish','Skip','Time Out','Stop',
+ 'Kill','Messages','Back Trace','Where','Up','Down'].
+
+shortcut(e) -> {if_enabled, 'Search'};
+shortcut(g) -> {if_enabled, 'Go To Line'};
+shortcut(s) -> {if_enabled, 'Step'};
+shortcut(n) -> {if_enabled, 'Next'};
+shortcut(c) -> {if_enabled, 'Continue'};
+shortcut(f) -> {if_enabled, 'Finish'};
+shortcut(m) -> {if_enabled, 'Messages'};
+shortcut(w) -> {if_enabled, 'Where'};
+
+shortcut(b) -> {always, 'Line Break...'};
+shortcut(d) -> {always, 'Delete All'};
+
+shortcut(_) -> false.
+
+map('Stack On, Tail') -> all; % Stack trace
+map('Stack On, No Tail') -> no_tail;
+map('Stack Off') -> false;
+map(all) -> 'Stack On, Tail';
+map(true) -> 'Stack On, Tail';
+map(no_tail) -> 'Stack On, No Tail';
+map(false) -> 'Stack Off'.
+
+
+%% gui_show_module(Win, Mod, Line, Cm, Pid, How) -> Win
+%% gui_show_module(Win, {Mod,Line}, _Reason, Cm, Pid, How) -> Win
+%% How = where | break
+%% Show contents of a module in code area
+gui_show_module(Win, {Mod,Line}, _Reason, Cm, Pid, How) ->
+ gui_show_module(Win, Mod, Line, Cm, Pid, How);
+gui_show_module(Win, Mod, Line, Mod, _Pid, How) ->
+ dbg_wx_trace_win:mark_line(Win, Line, How);
+gui_show_module(Win, Mod, Line, _Cm, Pid, How) ->
+ Win2 = case dbg_wx_trace_win:is_shown(Win, Mod) of
+ {true, Win3} -> Win3;
+ false -> gui_load_module(Win, Mod, Pid)
+ end,
+ dbg_wx_trace_win:mark_line(Win2, Line, How).
+
+gui_load_module(Win, Mod, _Pid) ->
+ dbg_wx_trace_win:display(Win,{text, "Loading module..."}),
+ %% Contents = int:contents(Mod, Pid),
+ {ok, Contents} = dbg_iserver:call({raw_contents, Mod, any}),
+ Win2 = dbg_wx_trace_win:show_code(Win, Mod, Contents),
+ dbg_wx_trace_win:display(Win,{text, ""}),
+ Win2.
+
+gui_update_bindings(Win,Meta) ->
+ Bs = int:meta(Meta, bindings, nostack),
+ dbg_wx_trace_win:update_bindings(Win,Bs).
+
+gui_enable_functions(Status) ->
+ Enable = enable(Status),
+ Disable = all_buttons() -- Enable,
+ dbg_wx_trace_win:enable(Disable, false),
+ dbg_wx_trace_win:enable(Enable, true).
+
+gui_enable_updown(Flag, Stack) ->
+ {Enable, Disable} =
+ if
+ Flag==false -> {[], ['Up', 'Down']};
+ true ->
+ case Stack of
+ {1,1} -> {[], ['Up', 'Down']};
+ {2,2} -> {[], ['Up', 'Down']};
+ {Max,Max} -> {['Up'], ['Down']};
+ {2,_Max} -> {['Down'], ['Up']};
+ {_Cur,_Max} -> {['Up', 'Down'], []}
+ end
+ end,
+ dbg_wx_trace_win:enable(Enable, true),
+ dbg_wx_trace_win:enable(Disable, false),
+ if
+ Enable==[] -> dbg_wx_trace_win:enable(['Where'], false);
+ true -> dbg_wx_trace_win:enable(['Where'], true)
+ end.
+
+gui_enable_btrace(Trace, StackTrace) ->
+ Bool = if
+ Trace==false -> false;
+ StackTrace==false -> false;
+ true -> true
+ end,
+ dbg_wx_trace_win:enable(['Back Trace'], Bool).
diff --git a/lib/debugger/src/dbg_wx_trace_win.erl b/lib/debugger/src/dbg_wx_trace_win.erl
new file mode 100755
index 0000000000..6e7a291493
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_trace_win.erl
@@ -0,0 +1,1029 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(dbg_wx_trace_win).
+
+%% External exports
+-export([init/0, stop/1]).
+-export([create_win/4,
+ get_window/1,
+ configure/2,
+ enable/2, is_enabled/1, select/2,
+ add_break/3, update_break/2, delete_break/2,
+ clear_breaks/1, clear_breaks/2,
+ display/2, % Help messages
+ is_shown/2, % Code area
+ show_code/3, show_no_code/1, remove_code/2,
+ mark_line/3, unmark_line/1,
+ select_line/2, selected_line/1,
+ eval_output/3, % Evaluator area
+ update_bindings/2, % Bindings area
+ trace_output/2, % Trace area
+ handle_event/2
+ ]).
+-export([helpwin/2]).
+
+-include_lib("wx/include/wx.hrl").
+
+-record(breakInfo, {point, status, break}).
+-record(break, {mb, smi, emi, dimi, demi}). %% BUGBUG defined in dbg_wx_win
+-record(winInfo, {window, % wxobj()
+ size, % {W, H}
+ find, % #find{}
+ m_szr, % {Panel, Sizer},
+ e_szr, % {bool Shown, Sizer},
+
+ code, % code editor #sub{}
+ sb, % status bar
+ sg, % Search/Goto #sub{}
+ bs, % Buttons #sub{} subwindow info
+ eval, % Eval #sub{} subwindow info
+ bind, % Bindings #sub{} subwindow info
+ trace, % Trace #sub{} subwindow info
+
+ marked_line=0, % integer() Current line
+ selected_line=0, % integer() Selected line
+
+ breaks=[], % [#breakInfo{}] Known breakpoints
+
+ editor, % {Mod, Editor} Visible code editor
+ editors=[] % [{Mod,Editor}] Code editors
+ }).
+
+-record(sub, {enable=true, % Subwindow is enabled
+ win, % Sash Sub window obj
+ in, % undefined or input window obj
+ out, % undefined or output window obj
+ name % name
+ }).
+
+-record(sa, {search, % Search input ctrl
+ goto, % Goto input ctrl
+ radio}). % Radio buttons
+
+-record(find, {start, % start pos
+ strlen, % search string len
+ found % status
+ }).
+
+
+-define(StepButton, 401).
+-define(NextButton, 402).
+-define(ContinueButton, 403).
+-define(FinishButton, 404).
+-define(WhereButton, 405).
+-define(UpButton, 406).
+-define(DownButton, 407).
+
+-define(EVAL_ENTRY, 410).
+-define(EVAL_LOG, 411).
+-define(BIND_PANEL, 412).
+-define(SEARCH_ENTRY, 413).
+-define(GOTO_ENTRY, 414).
+
+
+-define(SASH_CODE, 425).
+-define(SASH_EVAL, 426).
+-define(SASH_TRACE, 427).
+
+-define(WIN_W, 700).
+-define(WIN_H, 650).
+
+-define(CODE_H, 400).
+-define(BUTT_H, 50). % Maximum
+-define(EVAL_H, 200).
+-define(BIND_H, 200).
+-define(TRACE_H, 100).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% init() -> GS
+%% GS = term()
+%%--------------------------------------------------------------------
+init() ->
+ dbg_wx_win:init().
+
+stop(#winInfo{window=Win}) ->
+ (catch wxFrame:destroy(Win)),
+ ok.
+
+%%--------------------------------------------------------------------
+%% create_win(GS, Title, TraceWin, Menus) -> #winInfo{}
+%% GS = gsobj()
+%% Title = string()
+%% TraceWin = [WinArea]
+%% WinArea = 'Button|Evaluator|Bindings|Trace Area'
+%% Menus = [menu()] See dbg_wx_win.erl
+%%--------------------------------------------------------------------
+create_win(Parent, Title, Windows, Menus) ->
+ Do =
+ fun() ->
+ Win = wxFrame:new(Parent, ?wxID_ANY, dbg_wx_win:to_string(Title),
+ [{size, {?WIN_W,?WIN_H}}]),
+ Panel = wxPanel:new(Win, [{size, {?WIN_W,?WIN_H}}]),
+ MenuBar = wxMenuBar:new(),
+ dbg_wx_win:create_menus(MenuBar, Menus, Win, 1),
+ wxFrame:setMenuBar(Win, MenuBar),
+
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ Code = code_area(Panel),
+ wxSizer:add(Sizer, Code#sub.win,
+ [{proportion,1}, {border, 2},
+ {flag, ?wxEXPAND bor ?wxDOWN}]),
+ wxSizer:setVirtualSizeHints(Sizer, Code#sub.win),
+
+ ExpandWithBorder = [{border, 3},{flag,?wxEXPAND bor ?wxALL}],
+ Search = search_area(Panel),
+ wxSizer:add(Sizer, Search#sub.win, ExpandWithBorder),
+ Bs = button_area(Panel),
+ wxSizer:add(Sizer, Bs#sub.win, ExpandWithBorder),
+
+ InfoArea = wxBoxSizer:new(?wxHORIZONTAL),
+ wxSizer:setMinSize(InfoArea, {100, ?EVAL_H}),
+ Eval = eval_area(Panel),
+ wxSizer:add(InfoArea, Eval#sub.win, [{proportion,1},{flag,?wxEXPAND}]),
+ Bind = bind_area(Panel),
+ wxSizer:add(InfoArea, Bind#sub.win,
+ [{proportion,1},{border, 2},
+ {flag,?wxEXPAND bor ?wxLEFT}]),
+ wxSizer:add(Sizer, InfoArea, ExpandWithBorder),
+
+ Trace = trace_area(Panel),
+ wxSizer:add(Sizer, Trace#sub.win, ExpandWithBorder),
+ SB = wxFrame:createStatusBar(Win,[]),
+
+ %% Note id and lastId to get the event when it dragged is complete
+ wxFrame:connect(Win, sash_dragged, [{id,?SASH_CODE},
+ {lastId,?SASH_TRACE}]),
+ wxFrame:connect(Win, close_window, [{skip, true}]),
+ wxFrame:connect(Win, size, [{skip, true}]),
+ wxWindow:connect(Win, key_up, [{skip,true}]),
+ wxWindow:setFocus(Code#sub.out),
+
+ Wi0 = #winInfo{window=Win,
+ m_szr={Panel, Sizer},
+ e_szr={true, InfoArea},
+ code=Code, sb=SB, sg=Search, bs=Bs,
+ eval=Eval, trace=Trace, bind=Bind,
+ editor={'$top', Code#sub.out},
+ editors=[{'$top', Code#sub.out}]},
+
+ Wi = show_windows(enable_windows(Wi0,Windows)),
+ wxWindow:setSizer(Panel, Sizer),
+ wxSizer:fit(Sizer, Win),
+ wxSizer:setSizeHints(Sizer,Win),
+
+ IconFile = dbg_wx_win:find_icon("erlang_bug.png"),
+ Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
+ wxFrame:setIcon(Win, Icon),
+ wxIcon:destroy(Icon),
+
+ wxFrame:show(Win),
+ put(window, Win),
+ Wi
+ end,
+
+ try wx:batch(Do)
+ catch E:R ->
+ io:format("Crashed ~p ~p",[E,R]),
+ erlang:error(E)
+ end.
+
+
+%%--------------------------------------------------------------------
+%% get_window(WinInfo) -> Window
+%% WinInfo = #winInfo{}
+%% Window = gsobj()
+%%--------------------------------------------------------------------
+get_window(WinInfo) ->
+ WinInfo#winInfo.window.
+
+%%--------------------------------------------------------------------
+%% configure(WinInfo, Windows) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Windows = [WinArea]
+%% WinArea = 'Button|Evaluator|Bindings|Trace Area'
+%% Window areas should be opened or closed.
+%%--------------------------------------------------------------------
+configure(Wi=#winInfo{window=Win,m_szr={Panel,Sizer}}) ->
+ wx:batch(fun() ->
+ show_windows(Wi),
+ wxSizer:layout(Sizer),
+ %%wxWindow:setSizerAndFit(Panel,Sizer),
+ wxWindow:setSizer(Panel, Sizer),
+ wxSizer:fit(Sizer, Win),
+ wxSizer:setSizeHints(Sizer,Win),
+ Wi
+ end).
+
+configure(Wi0=#winInfo{window=Win,m_szr={Panel,Sizer}}, Windows) ->
+ wx:batch(fun() ->
+ Wi = enable_windows(Wi0, Windows),
+ show_windows(Wi),
+ wxSizer:layout(Sizer),
+ wxWindow:setSizer(Panel, Sizer),
+ wxSizer:fit(Sizer, Win),
+ wxSizer:setSizeHints(Sizer,Win),
+ Wi
+ end).
+
+enable_windows(Wi=#winInfo{e_szr={_,InfoArea},bs=Bs0,sg=SG0,
+ eval=Eval0,trace=Trace0,bind=Bind0},Windows) ->
+ Subs = [Window#sub{enable=lists:member(Window#sub.name,Windows)}
+ || Window <- [SG0,Bs0,Eval0,Trace0,Bind0]],
+ [SG, Bs,Eval,Trace,Bind] = Subs,
+ ESzr = Eval#sub.enable orelse Bind#sub.enable,
+ Wi#winInfo{e_szr={ESzr, InfoArea},sg=SG,bs=Bs,
+ eval=Eval,trace=Trace,bind=Bind}.
+
+
+show_windows(Wi=#winInfo{m_szr={_,Sizer}, e_szr={_,InfoArea},bs=Bs,sg=SG,
+ eval=Eval,trace=Trace,bind=Bind}) ->
+ case SG#sub.enable of
+ false -> wxSizer:hide(Sizer, SG#sub.win);
+ _ -> wxSizer:show(Sizer, SG#sub.win)
+ end,
+ case Bs#sub.enable of
+ false -> wxSizer:hide(Sizer, Bs#sub.win);
+ _ -> wxSizer:show(Sizer, Bs#sub.win)
+ end,
+ if (not Eval#sub.enable) andalso (not Bind#sub.enable) ->
+ wxSizer:hide(Sizer, InfoArea);
+ not Eval#sub.enable ->
+ wxSizer:show(Sizer, InfoArea),
+ wxSizer:hide(InfoArea, Eval#sub.win),
+ wxSizer:show(InfoArea, Bind#sub.win);
+ not Bind#sub.enable ->
+ [EvalSI|_] = wxSizer:getChildren(InfoArea),
+ wxSizerItem:setProportion(EvalSI, 1),
+ wxSizer:show(Sizer, InfoArea),
+ wxSizer:hide(InfoArea, Bind#sub.win),
+ wxSizer:show(InfoArea, Eval#sub.win),
+ true;
+ true ->
+ wxSizer:show(Sizer, InfoArea),
+ wxSizer:show(InfoArea, Eval#sub.win),
+ wxSizer:show(InfoArea, Bind#sub.win)
+ end,
+ case Trace#sub.enable of
+ false -> wxSizer:hide(Sizer, Trace#sub.win);
+ _ -> wxSizer:show(Sizer, Trace#sub.win)
+ end,
+ Wi.
+
+%%--------------------------------------------------------------------
+%% enable([MenuItem], Bool)
+%% is_enabled(MenuItem) -> Bool
+%% MenuItem = atom()
+%% Bool = boolean()
+%%--------------------------------------------------------------------
+enable(MenuItems, Bool) ->
+ wx:foreach(fun(MenuItem) ->
+ MI = get(MenuItem),
+ wxMenuItem:enable(MI, [{enable, Bool}]),
+ case is_button(MenuItem) of
+ {true, ButtonId} ->
+ Parent = get(window),
+ Butt = wxWindow:findWindowById(ButtonId,
+ [{parent, Parent}]),
+ case wx:is_null(Butt) of
+ true -> ignore;
+ false ->
+ wxButton:enable(Butt, [{enable, Bool}])
+ end;
+ _ ->
+ ignore
+ end
+ end,
+ MenuItems).
+
+is_enabled(MenuItem) ->
+ MI = get(MenuItem),
+ wxMenuItem:isEnabled(MI).
+
+%%--------------------------------------------------------------------
+%% select(MenuItem, Bool)
+%% MenuItem = atom()
+%% Bool = boolean()
+%%--------------------------------------------------------------------
+select(MenuItem, Bool) ->
+ MI = get(MenuItem),
+ wxMenuItem:check(MI, [{check, Bool}]).
+
+%%--------------------------------------------------------------------
+%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Name = atom() Menu name
+%% Point = {Mod, Line}
+%% Options = [Status, Action, Mods, Cond]
+%% Status = active | inactive
+%% Action = enable | disable | delete
+%% Mods = null (not used)
+%% Cond = null | {Mod, Func}
+%%--------------------------------------------------------------------
+add_break(WinInfo, Menu, {{Mod,Line},[Status|_Options]}=Break) ->
+ case WinInfo#winInfo.editor of
+ {Mod, Editor} ->
+ dbg_wx_code:add_break_to_code(Editor, Line, Status);
+ _ -> ignore
+ end,
+ add_break_to_menu(WinInfo, Menu, Break).
+
+add_break_to_menu(WinInfo, Menu, {Point, [Status|_Options]=Options}) ->
+ Break = dbg_wx_win:add_break(WinInfo#winInfo.window, Menu, Point),
+ dbg_wx_win:update_break(Break, Options),
+ BreakInfo = #breakInfo{point=Point, status=Status, break=Break},
+ WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}.
+
+%%--------------------------------------------------------------------
+%% update_break(WinInfo, {Point, Options}) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Point = {Mod, Line}
+%% Options = [Status, Action, Mods, Cond]
+%% Status = active | inactive
+%% Action = enable | disable | delete
+%% Mods = null (not used)
+%% Cond = null | {Mod, Func}
+%%--------------------------------------------------------------------
+update_break(WinInfo, {{Mod,Line},[Status|_Options]}=Break) ->
+ case WinInfo#winInfo.editor of
+ {Mod, Editor} ->
+ dbg_wx_code:add_break_to_code(Editor, Line, Status);
+ _ -> ignore
+ end,
+ update_break_in_menu(WinInfo, Break).
+
+update_break_in_menu(WinInfo, {Point, [Status|_Options]=Options}) ->
+ {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks),
+ dbg_wx_win:update_break(BreakInfo#breakInfo.break, Options),
+ BreakInfo2 = BreakInfo#breakInfo{status=Status},
+ WinInfo#winInfo{breaks=lists:keyreplace(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks,
+ BreakInfo2)}.
+
+%%--------------------------------------------------------------------
+%% delete_break(WinInfo, Point) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Point = {Mod, Line}
+%%--------------------------------------------------------------------
+delete_break(WinInfo, {Mod,Line}=Point) ->
+ case WinInfo#winInfo.editor of
+ {Mod, Editor} -> dbg_wx_code:del_break_from_code(Editor, Line);
+ _ -> ignore
+ end,
+ delete_break_from_menu(WinInfo, Point).
+
+delete_break_from_menu(WinInfo, Point) ->
+ {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks),
+ dbg_wx_win:delete_break(BreakInfo#breakInfo.break),
+ WinInfo#winInfo{breaks=lists:keydelete(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks)}.
+
+%%--------------------------------------------------------------------
+%% clear_breaks(WinInfo) -> WinInfo
+%% clear_breaks(WinInfo, Mod) -> WinInfo
+%% WinInfo = #winInfo{}
+%%--------------------------------------------------------------------
+clear_breaks(WinInfo) ->
+ clear_breaks(WinInfo, all).
+clear_breaks(WinInfo, Mod) ->
+ Remove = if
+ Mod==all -> WinInfo#winInfo.breaks;
+ true ->
+ lists:filter(fun(#breakInfo{point={Mod2,_L}}) ->
+ if
+ Mod2==Mod -> true;
+ true -> false
+ end
+ end,
+ WinInfo#winInfo.breaks)
+ end,
+ lists:foreach(fun(#breakInfo{point=Point}) ->
+ delete_break(WinInfo, Point)
+ end,
+ Remove),
+ Remain = WinInfo#winInfo.breaks -- Remove,
+ WinInfo#winInfo{breaks=Remain}.
+
+%%--------------------------------------------------------------------
+%% display(Arg)
+%% Arg = idle | {Status,Mod,Line} | {running,Mod}
+%% � {exit,Where,Reason} | {text,Text}
+%% Status = break | wait � Level
+%% Level = int()
+%% Mod = atom()
+%% Line = integer()
+%% Where = {Mod,Line} | null
+%% Reason = term()
+%% Text = string()
+%%--------------------------------------------------------------------
+display(#winInfo{window=Win, sb=Sb},Arg) ->
+ Str = case Arg of
+ idle -> "State: uninterpreted";
+ {exit, {Mod,Line}, Reason} ->
+ wxWindow:raise(Win),
+ dbg_wx_win:to_string("State: EXITED [~w.erl/~w], Reason:~w",
+ [Mod, Line, Reason]);
+ {exit, null, Reason} ->
+ wxWindow:raise(Win),
+ dbg_wx_win:to_string("State: EXITED [uninterpreted], "
+ "Reason:~w", [Reason]);
+ {Level, null, _Line} when is_integer(Level) ->
+ dbg_wx_win:to_string("*** Call level #~w "
+ "(in non-interpreted code)",
+ [Level]);
+ {Level, Mod, Line} when is_integer(Level) ->
+ dbg_wx_win:to_string("*** Call level #~w [~w.erl/~w]",
+ [Level, Mod, Line]);
+ {Status, Mod, Line} ->
+ What = case Status of
+ wait -> 'receive';
+ _ -> Status
+ end,
+ dbg_wx_win:to_string("State: ~w [~w.erl/~w]",
+ [What, Mod, Line]);
+ {running, Mod} ->
+ dbg_wx_win:to_string("State: running [~w.erl]", [Mod]);
+ {text, Text} -> dbg_wx_win:to_string(Text)
+ end,
+ wxStatusBar:setStatusText(Sb, Str).
+
+%%--------------------------------------------------------------------
+%% is_shown(WinInfo, Mod) -> {true, WinInfo} | false
+%% show_code(WinInfo, Mod, Contents) -> WinInfo
+%% show_no_code(WinInfo) -> WinInfo
+%% remove_code(WinInfo, Mod) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Mod = atom()
+%% Contents = string()
+%% Note: remove_code/2 should not be used for currently shown module.
+%%--------------------------------------------------------------------
+is_shown(WinInfo, Mod) ->
+ case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of
+ {value, {Mod, Editor}} ->
+ gs:config(Editor, raise), %% BUGBUG
+ {true, WinInfo#winInfo{editor={Mod, Editor}}};
+ false -> false
+ end.
+
+show_code(WinInfo = #winInfo{editor={_, Ed}}, Mod, Contents) ->
+ %% Insert code and update breakpoints, if any
+ dbg_wx_code:load_code(Ed, Contents),
+
+ lists:foreach(fun(BreakInfo) ->
+ case BreakInfo#breakInfo.point of
+ {Mod2, Line} when Mod2==Mod ->
+ Status = BreakInfo#breakInfo.status,
+ dbg_wx_code:add_break_to_code(Ed, Line,Status);
+ _Point -> ignore
+ end
+ end,
+ WinInfo#winInfo.breaks),
+
+ WinInfo#winInfo{editor={Mod,Ed},find=undefined}.
+
+show_no_code(WinInfo = #winInfo{editor={_, Ed}}) ->
+ dbg_wx_code:unload_code(Ed),
+ WinInfo#winInfo{editor={'$top', Ed}}.
+
+remove_code(WinInfo, _Mod) ->
+ WinInfo.
+
+%%--------------------------------------------------------------------
+%% mark_line(WinInfo, Line, How) -> WinInfo
+%% WinInfo = #winInfo{}
+%% Line = integer()
+%% How = break | where
+%% Mark the code line where the process is executing.
+%%--------------------------------------------------------------------
+mark_line(WinInfo = #winInfo{editor={_,Ed}}, Line, _How) ->
+ dbg_wx_code:mark_line(Ed, WinInfo#winInfo.marked_line, Line),
+ WinInfo#winInfo{marked_line=Line}.
+
+unmark_line(WinInfo) ->
+ mark_line(WinInfo, 0, false).
+
+
+%%--------------------------------------------------------------------
+%% select_line(WinInfo, Line) -> WinInfo
+%% selected_line(WinInfo) -> undefined | Line
+%% WinInfo = #winInfo{}
+%% Line = integer()
+%% Select/unselect a line (unselect if Line=0).
+%%--------------------------------------------------------------------
+select_line(WinInfo, Line) ->
+ {_Mod, Ed} = WinInfo#winInfo.editor,
+
+ %% Since 'Line' may be specified by the user in the 'Go To Line'
+ %% help window, it must be checked that it is correct
+ Size = dbg_wx_code:get_no_lines(Ed),
+ if
+ Line==0 ->
+ dbg_wx_code:goto_line(Ed,1),
+ WinInfo#winInfo{selected_line=0};
+ Line<Size ->
+ dbg_wx_code:goto_line(Ed,Line),
+ WinInfo#winInfo{selected_line=Line};
+ true ->
+ WinInfo
+ end.
+
+selected_line(#winInfo{editor={_,Ed}}) ->
+ wxStyledTextCtrl:getCurrentLine(Ed)+1.
+
+%%--------------------------------------------------------------------
+%% eval_output(winInfo{}, Str, Face)
+%% Str = string()
+%% Face = normal | bold
+%%--------------------------------------------------------------------
+eval_output(#winInfo{eval=#sub{out=Log}}, Text, _Face) ->
+ wxTextCtrl:appendText(Log, dbg_wx_win:to_string(Text)),
+ ok.
+
+%%--------------------------------------------------------------------
+%% update_bindings(Bs)
+%% Bs = [{Var,Val}]
+%%--------------------------------------------------------------------
+update_bindings(#winInfo{bind=#sub{out=BA}}, Bs) ->
+ wxListCtrl:deleteAllItems(BA),
+ wx:foldl(fun({Var,Val},Row) ->
+ wxListCtrl:insertItem(BA, Row, ""),
+ wxListCtrl:setItem(BA, Row, 0, dbg_wx_win:to_string(Var)),
+ wxListCtrl:setItem(BA, Row, 1, dbg_wx_win:to_string("~200p",[Val])),
+ Row+1
+ end, 0, Bs),
+ put(bindings,Bs),
+ ok.
+
+%%--------------------------------------------------------------------
+%% trace_output(Str)
+%% Str = string()
+%%--------------------------------------------------------------------
+trace_output(#winInfo{trace=#sub{out=Log}}, Text) ->
+ wxTextCtrl:appendText(Log, dbg_wx_win:to_string(Text)),
+ ok.
+
+%%--------------------------------------------------------------------
+%% handle_event(GSEvent, WinInfo) -> Command
+%% GSEvent = {gs, Id, Event, Data, Arg}
+%% WinInfo = #winInfo{}
+%% Command = ignore
+%% | {win, WinInfo}
+%% | stopped
+%% | {coords, {X,Y}}
+%%
+%% | {shortcut, Key}
+%% | MenuItem | {Menu, [MenuItem]}
+%% MenuItem = Menu = atom()
+%% | {break, Point, What}
+%% What = add | delete | {status,Status} |{trigger,Trigger}
+%% | {module, Mod, view}
+%%
+%% | {user_command, Cmd}
+%%
+%% | {edit, {Var, Val}}
+%%--------------------------------------------------------------------
+%% Window events
+handle_event(_Ev=#wx{event=#wxClose{}}, _WinInfo) ->
+ stopped;
+
+handle_event(#wx{event=#wxSize{size=Size}}, Wi0) ->
+ Wi = Wi0#winInfo{size=Size},
+ resize(Wi),
+ {win, Wi};
+
+handle_event(#wx{event=#wxSash{dragStatus=?wxSASH_STATUS_OUT_OF_RANGE}},_Wi) ->
+ ignore;
+handle_event(#wx{id=?SASH_CODE, event=#wxSash{dragRect={_X,_Y,_W,H}}}, Wi) ->
+ #winInfo{code=Code,m_szr={_,Sizer},e_szr={Enable,InfoSzr},trace=Trace} = Wi,
+
+ case Enable orelse Trace#sub.enable of
+ false ->
+ ignore;
+ true ->
+ {_, CMH} = wxWindow:getMinSize(Code#sub.win),
+ case CMH > H of
+ true -> wxSashWindow:setMinSize(Code#sub.win, {500, H});
+ _ -> ignore
+ end,
+ {_, CH} = wxWindow:getSize(Code#sub.win),
+ Change = CH - H,
+ ChangeH = fun(Item) ->
+ {ItemW, ItemH} = wxSizerItem:getMinSize(Item),
+ wxSizerItem:setInitSize(Item, ItemW, max(ItemH+Change,-1))
+ end,
+ if Enable ->
+ {IW, IH} = wxSizer:getMinSize(InfoSzr),
+ [ChangeH(Child) || Child <- wxSizer:getChildren(InfoSzr)],
+ wxSizer:setMinSize(InfoSzr, {IW, IH+Change}),
+ ok;
+ Trace#sub.enable ->
+ {TW, TH} = wxWindow:getMinSize(Trace#sub.win),
+ wxWindow:setMinSize(Trace#sub.win, {TW, TH+Change}),
+ ok
+ end,
+ wxSizer:layout(Sizer),
+ ignore
+ end;
+
+handle_event(#wx{id=?SASH_EVAL, event=#wxSash{dragRect={_X,_Y,W,_H}}}, Wi) ->
+ #winInfo{m_szr={_,Sizer},e_szr={Enable,InfoSzr},
+ eval=#sub{enable=Enable, win=EvalSzr}} = Wi,
+ case Enable of
+ false ->
+ ignore;
+ true ->
+ [Eval,Bind] = wxSizer:getChildren(InfoSzr),
+ {Tot,_} = wxSizer:getSize(InfoSzr),
+ EvalWidth = Tot-W,
+
+ Change = fun(Szr, Width) ->
+ {_EW,EH} = wxSizerItem:getMinSize(Szr),
+ wxSizerItem:setInitSize(Szr, Width, EH)
+ end,
+
+ Change(Eval, EvalWidth),
+ [Change(Kid, EvalWidth) || Kid <- wxSizer:getChildren(EvalSzr)],
+ Change(Bind, W),
+
+ wxSizerItem:setProportion(Eval, 0),
+ wxSizer:layout(InfoSzr),
+ wxSizer:layout(Sizer),
+
+ resize(Wi),
+ ignore
+ end;
+
+handle_event(#wx{id=?SASH_TRACE, event=#wxSash{dragRect={_X,_Y,_W,H}}}, Wi) ->
+ #winInfo{code=Code,m_szr={_,Sizer},e_szr={Enable,InfoSzr},trace=Trace} = Wi,
+ {TW, TH} = wxWindow:getSize(Trace#sub.win),
+ Change = TH - H,
+ case Enable of
+ false -> %% Eval Area or Bindings
+ {_, CH} = wxWindow:getSize(Code#sub.win),
+ {_, CMH} = wxWindow:getMinSize(Code#sub.win),
+ case CMH > CH+Change of
+ true -> wxSashWindow:setMinSize(Code#sub.win, {500, CH+Change});
+ _ -> ignore
+ end,
+ wxWindow:setMinSize(Trace#sub.win, {TW, H}),
+ wxSizer:layout(Sizer),
+ ignore;
+ true -> %% Change the Eval and Bindings area
+ ChangeH = fun(Item) ->
+ {ItemW, ItemH} = wxSizerItem:getMinSize(Item),
+ wxSizerItem:setInitSize(Item, ItemW, max(ItemH+Change,-1))
+ end,
+ {IW, IH} = wxSizer:getMinSize(InfoSzr),
+ [ChangeH(Child) || Child <- wxSizer:getChildren(InfoSzr)],
+ Wanted = IH+Change,
+ wxSizer:setMinSize(InfoSzr, {IW, Wanted}),
+ {_,RH} = wxSizer:getMinSize(InfoSzr),
+ case RH > Wanted of
+ true -> %% Couldn't get the size we wanted try adjusting the code area
+ {_, CH} = wxWindow:getSize(Code#sub.win),
+ {_, CMH} = wxWindow:getMinSize(Code#sub.win),
+ CC = CH - (RH-Wanted),
+ case CMH > CC of
+ true when CC > 50 ->
+ wxWindow:setMinSize(Trace#sub.win, {TW, H}),
+ wxSashWindow:setMinSize(Code#sub.win, {500, CC});
+ _ when CC < 50 ->
+ ignore;
+ _ ->
+ wxWindow:setMinSize(Trace#sub.win, {TW, H})
+ end,
+ ok;
+ false ->
+ wxWindow:setMinSize(Trace#sub.win, {TW, H})
+ end,
+ wxSizer:layout(Sizer),
+ ignore
+ end;
+
+%% Menus, buttons and keyboard shortcuts
+handle_event(_Ev = #wx{event=#wxKey{keyCode=Key, controlDown=true}}, _WinInfo) ->
+ %% io:format("Key ~p ~n",[_Ev]),
+ if
+ Key/=?WXK_UP, Key/=?WXK_DOWN, Key /=? WXK_RETURN ->
+ try
+ {shortcut, list_to_atom([Key+($a-$A)])}
+ catch _:_ -> ignore
+ end;
+ true ->
+ ignore
+ end;
+handle_event(#wx{userData={dbg_ui_winman, Win},
+ event=#wxCommand{type=command_menu_selected}}, _WinInfo) ->
+ dbg_wx_winman:raise(Win),
+ ignore;
+
+handle_event(#wx{userData={break, Point, status},
+ event=#wxCommand{type=command_menu_selected}},
+ WinInfo) ->
+ {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
+ WinInfo#winInfo.breaks),
+ %% This is a temporary hack !!
+ #breakInfo{break=#break{smi=Smi}} = BreakInfo,
+
+ case wxMenuItem:getText(Smi) of
+ "Enable" -> {break, Point, {status, active}};
+ "Disable" -> {break, Point, {status, inactive}}
+ end;
+
+handle_event(#wx{userData=Data,
+ event=_Cmd=#wxCommand{type=command_menu_selected}},
+ _WinInfo) ->
+ %%io:format("Command ~p ~p~n",[Data,_Cmd]),
+ Data;
+
+%% Code area
+handle_event(#wx{event=#wxStyledText{type=stc_doubleclick}},
+ WinInfo = #winInfo{editor={Mod,Ed}}) ->
+ Line = wxStyledTextCtrl:getCurrentLine(Ed),
+ Point = {Mod, Line+1},
+ case lists:keysearch(Point, #breakInfo.point,WinInfo#winInfo.breaks) of
+ {value, _BreakInfo} -> {break, Point, delete};
+ false -> {break, Point, add}
+ end;
+
+%% Search Area
+handle_event(#wx{id=?GOTO_ENTRY, event=#wxCommand{cmdString=Str}}, WinInfo) ->
+ try
+ Line = list_to_integer(Str),
+ {gotoline, Line}
+ catch
+ _:_ ->
+ display(WinInfo, {text,"Not a line number"}),
+ ignore
+ end;
+handle_event(#wx{id=?SEARCH_ENTRY, event=#wxFocus{}}, Wi) ->
+ {win, Wi#winInfo{find=undefined}};
+handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{type=command_text_enter,cmdString=Str}},
+ Wi = #winInfo{code=Code,find=Find, sg=#sub{in=#sa{radio={NextO,_,CaseO}}}})
+ when Find =/= undefined ->
+ Dir = wxRadioButton:getValue(NextO) xor wx_misc:getKeyState(?WXK_SHIFT),
+ Case = wxCheckBox:getValue(CaseO),
+ Pos = if Find#find.found, Dir -> %% Forward Continuation
+ wxStyledTextCtrl:getAnchor(Code#sub.out);
+ Find#find.found -> %% Backward Continuation
+ wxStyledTextCtrl:getCurrentPos(Code#sub.out);
+ Dir -> %% Forward wrap
+ 0;
+ true -> %% Backward wrap
+ wxStyledTextCtrl:getLength(Code#sub.out)
+ end,
+ dbg_wx_code:goto_pos(Code#sub.out,Pos),
+ case dbg_wx_code:find(Code#sub.out, Str, Case, Dir) of
+ true ->
+ display(Wi, {text,""}),
+ {win, Wi#winInfo{find=Find#find{found=true}}};
+ false ->
+ display(Wi, {text,"Not found (Hit Enter to wrap search)"}),
+ {win, Wi#winInfo{find=Find#find{found=false}}}
+ end;
+handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=""}},
+ Wi=#winInfo{code=Code}) ->
+ %% Reset search (and selection pos)
+ Pos = dbg_wx_code:current_pos(Code#sub.out),
+ dbg_wx_code:goto_pos(Code#sub.out,Pos),
+ {win, Wi#winInfo{find=undefined}};
+handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=Str}},
+ Wi = #winInfo{code=Code,find=Find,
+ sg=#sub{in=#sa{radio={NextO,_,CaseO}}}}) ->
+ Dir = wxRadioButton:getValue(NextO),
+ Case = wxCheckBox:getValue(CaseO),
+
+ Cont = case Find of
+ undefined ->
+ Pos = dbg_wx_code:current_pos(Code#sub.out),
+ #find{start=Pos, strlen=length(Str)};
+ #find{strlen=Old} when Old < length(Str) ->
+ Find#find{strlen=length(Str)};
+ _ ->
+ dbg_wx_code:goto_pos(Code#sub.out,Find#find.start),
+ Find#find{strlen=length(Str)}
+ end,
+ case dbg_wx_code:find(Code#sub.out, Str, Case, Dir) of
+ true ->
+ display(Wi, {text,""}),
+ {win, Wi#winInfo{find=Cont#find{found=true}}};
+ false ->
+ display(Wi, {text,"Not found (Hit Enter to wrap search)"}),
+ {win, Wi#winInfo{find=Cont#find{found=false}}}
+ end;
+
+%% Button area
+handle_event(#wx{id=ID, event=#wxCommand{type=command_button_clicked}},_Wi) ->
+ {value, {Button, _}} = lists:keysearch(ID, 2, buttons()),
+ Button;
+
+%% Evaluator area
+handle_event(#wx{id=?EVAL_ENTRY, event=#wxCommand{type=command_text_enter}},
+ Wi = #winInfo{eval=#sub{in=TC}}) ->
+ case wxTextCtrl:getValue(TC) of
+ [10] ->
+ eval_output(Wi, "\n", normal),
+ ignore;
+ Cmd ->
+ eval_output(Wi, [$>, Cmd, 10], normal),
+ wxTextCtrl:setValue(TC,""),
+ {user_command, Cmd}
+ end;
+
+%% Bindings area
+handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Row}},Wi) ->
+ Bs = get(bindings),
+ {Var,Val} = lists:nth(Row+1, Bs),
+ Str = io_lib:format("< ~s = ~p~n", [Var, Val]),
+ eval_output(Wi, Str, bold),
+ ignore;
+handle_event(#wx{event=#wxList{type=command_list_item_activated, itemIndex=Row}},_Wi) ->
+ Bs = get(bindings),
+ Binding = lists:nth(Row+1, Bs),
+ {edit, Binding};
+
+handle_event(_GSEvent, _WinInfo) ->
+ %%io:format("~p: unhandled ~p~n",[?MODULE, _GSEvent]),
+ ignore.
+
+
+%%====================================================================
+%% resize(WinInfo) -> WinInfo
+
+resize(#winInfo{bind=Bind}) ->
+ %% Tweak the Binding settings text size
+ if
+ Bind#sub.enable =:= false ->
+ ok;
+ Bind#sub.enable ->
+ {EW, _} = wxWindow:getClientSize(Bind#sub.out),
+ B0W = wxListCtrl:getColumnWidth(Bind#sub.out, 0),
+ wxListCtrl:setColumnWidth(Bind#sub.out, 1, EW - B0W),
+ ok
+ end.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+%%--Code Area-------------------------------------------------------
+code_area(Win) ->
+ CodeWin = wxSashWindow:new(Win, [{id,?SASH_CODE},
+ {size, {?WIN_W,?CODE_H}},
+ {style, ?wxSW_3D}]),
+ Code = dbg_wx_code:code_area(CodeWin),
+ wxSashWindow:setSashVisible(CodeWin, ?wxSASH_BOTTOM, true),
+ wxWindow:setMinSize(CodeWin, {600, ?CODE_H}),
+ #sub{name='Code Area',enable=true, win=CodeWin, out=Code}.
+
+
+%%--Button Area-------------------------------------------------------
+
+buttons() ->
+ [{'Step',?StepButton}, {'Next',?NextButton},
+ {'Continue',?ContinueButton}, {'Finish',?FinishButton},
+ {'Where',?WhereButton}, {'Up',?UpButton}, {'Down',?DownButton}].
+
+is_button(Name) ->
+ case lists:keysearch(Name, 1, buttons()) of
+ {value, {Name, Button}} -> {true, Button};
+ false -> false
+ end.
+
+button_area(Parent) ->
+ Sz = wxBoxSizer:new(?wxHORIZONTAL),
+ wx:foreach(fun({Name, Button}) ->
+ B=wxButton:new(Parent, Button,
+ [{label,dbg_wx_win:to_string(Name)}]),
+ Id = wxWindow:getId(B),
+ wxSizer:add(Sz,B, []),
+ wxButton:connect(B, command_button_clicked, [{id,Id}])
+ end, buttons()),
+ #sub{name='Button Area', win=Sz}.
+
+%%--Search/Goto Area-------------------------------------------------
+
+search_area(Parent) ->
+ HSz = wxBoxSizer:new(?wxHORIZONTAL),
+ wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Find:"),
+ [{flag,?wxALIGN_CENTER_VERTICAL}]),
+ TC1 = wxTextCtrl:new(Parent, ?SEARCH_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]),
+ wxSizer:add(HSz, TC1, [{proportion,3}, {flag, ?wxEXPAND}]),
+ Nbtn = wxRadioButton:new(Parent, ?wxID_ANY, "Next"),
+ wxRadioButton:setValue(Nbtn, true),
+ wxSizer:add(HSz,Nbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]),
+ Pbtn = wxRadioButton:new(Parent, ?wxID_ANY, "Previous"),
+ wxSizer:add(HSz,Pbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]),
+ Cbtn = wxCheckBox:new(Parent, ?wxID_ANY, "Match Case"),
+ wxSizer:add(HSz,Cbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]),
+ wxSizer:add(HSz, 15,15, [{proportion,1}, {flag, ?wxEXPAND}]),
+ wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Goto Line:"),
+ [{flag,?wxALIGN_CENTER_VERTICAL}]),
+ TC2 = wxTextCtrl:new(Parent, ?GOTO_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]),
+ wxSizer:add(HSz, TC2, [{proportion,0}, {flag, ?wxEXPAND}]),
+ wxTextCtrl:connect(TC1, command_text_updated),
+ wxTextCtrl:connect(TC1, command_text_enter),
+ wxTextCtrl:connect(TC1, kill_focus),
+ wxTextCtrl:connect(TC2, command_text_enter),
+ wxWindow:connect(Parent, command_button_clicked),
+
+ #sub{name='Search Area', win=HSz,
+ in=#sa{search=TC1,goto=TC2,radio={Nbtn,Pbtn,Cbtn}}}.
+
+%%--Evaluator Area----------------------------------------------------
+
+eval_area(Parent) ->
+ VSz = wxBoxSizer:new(?wxVERTICAL),
+ HSz = wxBoxSizer:new(?wxHORIZONTAL),
+
+ wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Evaluator:"),
+ [{flag,?wxALIGN_CENTER_VERTICAL}]),
+ TC = wxTextCtrl:new(Parent, ?EVAL_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]),
+ wxSizer:add(HSz, TC, [{proportion,1}, {flag, ?wxEXPAND}]),
+ wxSizer:add(VSz, HSz, [{flag, ?wxEXPAND}]),
+ TL = wxTextCtrl:new(Parent, ?EVAL_LOG, [{style, ?wxTE_DONTWRAP bor
+ ?wxTE_MULTILINE bor ?wxTE_READONLY}]),
+ wxSizer:add(VSz, TL, [{proportion,5}, {flag, ?wxEXPAND}]),
+
+ wxTextCtrl:connect(TC, command_text_enter),
+ #sub{name='Evaluator Area', win=VSz, in=TC, out=TL}.
+
+%%--Bindings Area-----------------------------------------------------
+
+bind_area(Parent) ->
+ Style = {style, ?wxSW_3D bor ?wxCLIP_CHILDREN},
+ Win = wxSashWindow:new(Parent, [{id, ?SASH_EVAL},Style]),
+ wxSashWindow:setSashVisible(Win, ?wxSASH_LEFT, true),
+
+ BA = wxListCtrl:new(Win, [{style, ?wxLC_REPORT bor ?wxLC_SINGLE_SEL}]),
+ LI = wxListItem:new(),
+
+ wxListItem:setText(LI, "Name"),
+ wxListItem:setAlign(LI, ?wxLIST_FORMAT_LEFT),
+ wxListCtrl:insertColumn(BA, 0, LI),
+
+ wxListItem:setText(LI, "Value"),
+ wxListCtrl:insertColumn(BA, 1, LI),
+ wxListItem:destroy(LI),
+
+ wxListCtrl:setColumnWidth(BA, 0, 100),
+ wxListCtrl:setColumnWidth(BA, 1, 150),
+ wxListCtrl:connect(BA, command_list_item_selected),
+ wxListCtrl:connect(BA, command_list_item_activated),
+
+ #sub{name='Bindings Area', win=Win, out=BA}.
+
+%%--Trace Area--------------------------------------------------------
+
+trace_area(Parent) ->
+ Style = {style, ?wxSW_3D bor ?wxCLIP_CHILDREN},
+ Win = wxSashWindow:new(Parent, [{id, ?SASH_TRACE},
+ {size, {?WIN_W,?TRACE_H}}, Style]),
+ wxSashWindow:setSashVisible(Win, ?wxSASH_TOP, true),
+ wxWindow:setMinSize(Win, {500, ?TRACE_H}),
+ TC = wxTextCtrl:new(Win, ?wxID_ANY, [{style, ?wxTE_MULTILINE bor ?wxTE_READONLY}]),
+ #sub{name='Trace Area', win=Win, out=TC}.
+
+%%====================================================================
+%% 'Go To Line' and 'Search' help windows
+%%====================================================================
+
+helpwin(Type, WinInfo = #winInfo{sg=Sg =#sub{in=Sa}}) ->
+ Wi = case Sg#sub.enable of
+ false -> configure(WinInfo#winInfo{sg=Sg#sub{enable=true}});
+ true -> WinInfo
+ end,
+ case Type of
+ gotoline -> wxWindow:setFocus(Sa#sa.goto);
+ search -> wxWindow:setFocus(Sa#sa.search)
+ end,
+ Wi.
+
+max(X,Y) when X > Y -> X;
+max(_,Y) -> Y.
+
+
+
diff --git a/lib/debugger/src/dbg_wx_view.erl b/lib/debugger/src/dbg_wx_view.erl
new file mode 100644
index 0000000000..6d34e5650c
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_view.erl
@@ -0,0 +1,270 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(dbg_wx_view).
+
+%% External exports
+-export([start/2]).
+
+%% Internal exports
+-export([init/4]).
+
+-record(state, {gs, % term() Graphics system id
+ win, % term() Attach process window data
+ coords, % {X,Y} Mouse point position
+ mod % atom() Module
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start(GS, Mod)
+%% Mod = atom()
+%%--------------------------------------------------------------------
+start(GS, Mod) ->
+ Title = "View Module " ++ atom_to_list(Mod),
+ case dbg_wx_winman:is_started(Title) of
+ true -> ignore;
+ false ->
+ Env = wx:get_env(),
+ spawn_link(?MODULE, init, [GS, Env, Mod, Title])
+ end.
+
+
+%%====================================================================
+%% Main loop and message handling
+%%====================================================================
+
+init(GS, Env, Mod, Title) ->
+ wx:set_env(Env),
+ %% Subscribe to messages from the interpreter
+ int:subscribe(),
+
+ %% Create attach process window
+ Win1 = dbg_wx_trace_win:create_win(GS, Title, ['Code Area', 'Search Area'], menus()),
+ Window = dbg_wx_trace_win:get_window(Win1),
+ dbg_wx_winman:insert(Title, Window),
+
+ Win2 = gui_load_module(Win1, Mod),
+ Win3 =
+ lists:foldl(fun(Break, Win) ->
+ dbg_wx_trace_win:add_break(Win, 'Break', Break)
+ end,
+ Win2,
+ int:all_breaks(Mod)),
+
+ try loop(#state{gs=GS, win=Win3, coords={0,0}, mod=Mod})
+ catch _E:normal ->
+ exit(normal);
+ _E:_R ->
+ io:format("~p:~p ~p~n",[?MODULE,_E,_R]),
+ exit(_R)
+ end.
+
+loop(State) ->
+ receive
+
+ %% From the GUI main window
+ GuiEvent when element(1, GuiEvent)==wx ->
+ Cmd = wx:batch(fun() ->
+ dbg_wx_trace_win:handle_event(GuiEvent, State#state.win)
+ end),
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the GUI help windows
+ {gui, Cmd} ->
+ State2 = gui_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the interpreter
+ {int, Cmd} ->
+ State2 = int_cmd(Cmd, State),
+ loop(State2);
+
+ %% From the dbg_wx_winman process (Debugger window manager)
+ {dbg_ui_winman, update_windows_menu, Data} ->
+ Window = dbg_wx_trace_win:get_window(State#state.win),
+ dbg_wx_winman:update_windows_menu(Window,Data),
+ loop(State);
+ {dbg_ui_winman, destroy} ->
+ dbg_wx_trace_win:stop(State#state.win),
+ exit(stop);
+
+ %% Help window termination -- ignore
+ {'EXIT', _Pid, _Reason} ->
+ loop(State)
+ end.
+
+%%--Commands from the GUI---------------------------------------------
+
+gui_cmd(ignore, State) ->
+ State;
+gui_cmd({win, Win}, State) ->
+ State#state{win=Win};
+gui_cmd(stopped, _State) ->
+ exit(normal);
+gui_cmd({coords, Coords}, State) ->
+ State#state{coords=Coords};
+
+gui_cmd({shortcut, Key}, State) ->
+ case shortcut(Key) of
+ false -> State;
+ Cmd -> gui_cmd(Cmd, State)
+ end;
+
+%% File menu
+gui_cmd('Close', State) ->
+ dbg_wx_trace_win:stop(State#state.win),
+ gui_cmd(stopped, State);
+
+%% Edit menu
+gui_cmd('Go To Line', State) ->
+ %% Will result in message handled below: {gui, {gotoline, Line}}
+ Win = dbg_wx_trace_win:helpwin(gotoline, State#state.win),
+ State#state{win=Win};
+gui_cmd({gotoline, Line}, State) ->
+ Win = dbg_wx_trace_win:select_line(State#state.win, Line),
+ State#state{win=Win};
+gui_cmd('Search', State) ->
+ Win = dbg_wx_trace_win:helpwin(search, State#state.win),
+ State#state{win=Win};
+
+%% Break menu
+gui_cmd('Line Break...', State) ->
+ add_break(State#state.gs, State#state.coords, line,
+ State#state.mod,
+ dbg_wx_trace_win:selected_line(State#state.win)),
+ State;
+gui_cmd('Conditional Break...', State) ->
+ add_break(State#state.gs, State#state.coords, conditional,
+ State#state.mod,
+ dbg_wx_trace_win:selected_line(State#state.win)),
+ State;
+gui_cmd('Function Break...', State) ->
+ add_break(State#state.gs, State#state.coords, function,
+ State#state.mod, undefined),
+ State;
+gui_cmd('Enable All', State) ->
+ Breaks = int:all_breaks(),
+ ThisMod = State#state.mod,
+ lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
+ int:enable_break(Mod, Line);
+ (_Break) ->
+ ignore
+ end,
+ Breaks),
+ State;
+gui_cmd('Disable All', State) ->
+ Breaks = int:all_breaks(),
+ ThisMod = State#state.mod,
+ lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
+ int:disable_break(Mod, Line);
+ (_Break) ->
+ ignore
+ end,
+ Breaks),
+ State;
+gui_cmd('Delete All', State) ->
+ int:no_break(State#state.mod),
+ State;
+gui_cmd({break, {Mod, Line}, What}, State) ->
+ case What of
+ add -> int:break(Mod, Line);
+ delete -> int:delete_break(Mod, Line);
+ {status, inactive} -> int:disable_break(Mod, Line);
+ {status, active} -> int:enable_break(Mod, Line);
+ {trigger, Action} -> int:action_at_break(Mod, Line, Action)
+ end,
+ State;
+
+%% Help menu
+gui_cmd('Debugger', State) ->
+ Window = dbg_wx_trace_win:get_window(State#state.win),
+ HelpFile = filename:join([code:lib_dir(debugger),
+ "doc", "html", "part_frame.html"]),
+ dbg_wx_win:open_help(Window, HelpFile),
+ State.
+
+add_break(GS, Coords, Type, undefined, _Line) ->
+ dbg_wx_break:start(GS, Coords, Type);
+add_break(GS, Coords, Type, Mod, undefined) ->
+ dbg_wx_break:start(GS, Coords, Type, Mod);
+add_break(GS, Coords, Type, Mod, Line) ->
+ dbg_wx_break:start(GS, Coords, Type, Mod, Line).
+
+%%--Commands from the interpreter-------------------------------------
+
+int_cmd({new_break, {{Mod,_Line},_Options}=Break}, #state{mod=Mod}=State) ->
+ Win = dbg_wx_trace_win:add_break(State#state.win, 'Break', Break),
+ State#state{win=Win};
+int_cmd({delete_break, {Mod,_Line}=Point}, #state{mod=Mod}=State) ->
+ Win = dbg_wx_trace_win:delete_break(State#state.win, Point),
+ State#state{win=Win};
+int_cmd({break_options, {{Mod,_Line},_Options}=Break}, #state{mod=Mod}=State) ->
+ Win = dbg_wx_trace_win:update_break(State#state.win, Break),
+ State#state{win=Win};
+int_cmd(no_break, State) ->
+ Win = dbg_wx_trace_win:clear_breaks(State#state.win),
+ State#state{win=Win};
+int_cmd({no_break, _Mod}, State) ->
+ Win = dbg_wx_trace_win:clear_breaks(State#state.win),
+ State#state{win=Win};
+int_cmd(_, State) ->
+ State.
+
+
+%%====================================================================
+%% GUI auxiliary functions
+%%====================================================================
+
+menus() ->
+ [{'File', [{'Close', 0}]},
+ {'Edit', [{'Go To Line', 0},
+ {'Search', 0}]},
+ {'Break', [{'Line Break...', 5},
+ {'Conditional Break...', 13},
+ {'Function Break...', 0},
+ separator,
+ {'Enable All', no},
+ {'Disable All', no},
+ {'Delete All', 0},
+ separator]},
+ {'Windows', []},
+ {'Help', [{'Debugger', no}]}].
+
+shortcut(c) -> 'Close';
+shortcut(g) -> 'Go To Line';
+shortcut(s) -> 'Search';
+shortcut(b) -> 'Line Break...';
+shortcut(r) -> 'Conditional Break...';
+shortcut(f) -> 'Function Break...';
+shortcut(d) -> 'Delete All';
+
+shortcut(_) -> false.
+
+gui_load_module(Win, Mod) ->
+ dbg_wx_trace_win:display(Win,{text, "Loading module..."}),
+ {ok, Contents} = dbg_iserver:call({raw_contents, Mod, any}),
+ Win2 = dbg_wx_trace_win:show_code(Win, Mod, Contents),
+ dbg_wx_trace_win:display(Win,{text, ""}),
+ Win2.
diff --git a/lib/debugger/src/dbg_wx_win.erl b/lib/debugger/src/dbg_wx_win.erl
new file mode 100644
index 0000000000..f029990aa4
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_win.erl
@@ -0,0 +1,332 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(dbg_wx_win).
+
+%% External exports
+-export([init/0,
+ create_menus/4, %% For wx
+ add_break/3, update_break/2, delete_break/1,
+ motion/2,
+ confirm/2, notify/2, entry/4, open_help/2,
+ to_string/1, to_string/2,
+ find_icon/1
+ ]).
+
+-record(break, {mb, smi, emi, dimi, demi}).
+-include_lib("wx/include/wx.hrl").
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% init() -> GS
+%% GS = term()
+%%--------------------------------------------------------------------
+init() ->
+ wx:new().
+
+%%--------------------------------------------------------------------
+%% create_menus(MenuBar, [Menu])
+%% MenuBar = gsobj()
+%% Menu = {Name, [Item]}
+%% Name = atom()
+%% Item = {Name, N} | {Name, N, Type} | {Name, N, cascade, [Item]}
+%% | separator
+%% N = no | integer()
+%% Type = check | radio
+%% Create the specified menus and menuitems.
+%%
+%% Normal menuitems are specified as {Name, N}. Generates the event:
+%% {gs, _Id, click, {menuitem, Name}, _Arg}
+%%
+%% Check and radio menuitems are specified as {Name, N, check|radio}.
+%% They are assumed to be children to a cascade menuitem! (And all children
+%% to one cascade menuitem are assumed to be either check OR radio
+%% menuitems)!
+%% Selecting a check/radio menuitem generates the event:
+%% {gs, _Id, click, {menu, Menu}, _Arg}
+%% where Menu is the name of the parent, the cascade menuitem.
+%% Use selected(Menu) to retrieve which check/radio menuitems are
+%% selected.
+%%--------------------------------------------------------------------
+
+create_menus(MB, [{Title,Items}|Ms], Win, Id0) ->
+ Menu = wxMenu:new([]),
+ put(Title,Menu),
+ Id = create_menu_item(Menu, Items, Win, Id0, true),
+ wxMenuBar:append(MB,Menu,menu_name(Title,ignore)),
+ create_menus(MB,Ms,Win,Id);
+create_menus(_MB,[], _Win,Id) -> Id.
+
+create_menu_item(Menu, [separator|Is], Win, Id,Connect) ->
+ wxMenu:appendSeparator(Menu),
+ create_menu_item(Menu,Is,Win,Id+1,Connect);
+create_menu_item(Menu, [{Name, _N, cascade, Items}|Is], Win, Id0,Connect) ->
+ Sub = wxMenu:new([]),
+ Id = create_menu_item(Sub, Items, Win, Id0, false),
+ wxMenu:append(Menu, ?wxID_ANY, menu_name(Name,ignore), Sub),
+ %% Simulate GS sub checkBox/RadioBox behaviour
+ Self = self(),
+ Butts = [{MI,get(MI)} || {MI,_,_} <- Items],
+ IsChecked = fun({MiName,MI},Acc) ->
+ case wxMenuItem:isChecked(MI) of
+ true -> [MiName|Acc];
+ false -> Acc
+ end
+ end,
+ Filter = fun(_,_) ->
+ Enabled = lists:foldl(IsChecked, [], Butts),
+ Self ! #wx{userData={Name, Enabled},
+ event=#wxCommand{type=command_menu_selected}}
+ end,
+ wxMenu:connect(Win, command_menu_selected,
+ [{id,Id0},{lastId, Id-1},{callback,Filter}]),
+ create_menu_item(Menu, Is, Win, Id, Connect);
+create_menu_item(Menu, [{Name,Pos}|Is], Win, Id, Connect) ->
+ Item = wxMenu:append(Menu, Id, menu_name(Name,Pos)),
+ put(Name,Item),
+ if Connect ->
+ wxMenu:connect(Win, command_menu_selected, [{id,Id},{userData, Name}]);
+ true -> ignore
+ end,
+ create_menu_item(Menu,Is,Win,Id+1, Connect);
+create_menu_item(Menu, [{Name,N,check}|Is], Win, Id, Connect) ->
+ Item = wxMenu:appendCheckItem(Menu, Id, menu_name(Name,N)),
+ put(Name,Item),
+ if Connect ->
+ wxMenu:connect(Win, command_menu_selected, [{id,Id},{userData, Name}]);
+ true -> ignore
+ end,
+ create_menu_item(Menu,Is,Win,Id+1,Connect);
+create_menu_item(Menu, [{Name, N, radio}|Is], Win, Id,Connect) ->
+ Item = wxMenu:appendRadioItem(Menu, Id, menu_name(Name,N)),
+ put(Name,Item),
+ if Connect ->
+ wxMenu:connect(Win, command_menu_selected, [{id,Id},{userData, Name}]);
+ true -> ignore
+ end,
+ create_menu_item(Menu,Is,Win,Id+1,Connect);
+create_menu_item(_, [], _, Id,_) ->
+ Id.
+
+%%--------------------------------------------------------------------
+%% add_break(Name, Point) -> #break{}
+%% Name = atom()
+%% Point = {Mod, Line}
+%% The break will generate the following events:
+%% #wx{userData= {break, Point, Event}}
+%% Event = delete | {trigger, Action} | {status, Status}
+%% Action = enable | disable | delete
+%% Status = active | inactive
+%%--------------------------------------------------------------------
+add_break(Win, MenuName, Point) ->
+ %% Create a name for the breakpoint
+ {Mod, Line} = Point,
+ Label = to_string("~w ~5w", [Mod, Line]),
+
+ Menu = get(MenuName),
+ %% Create a menu for the breakpoint
+ Add = fun(Item,Action) ->
+ Id = wxMenuItem:getId(Item),
+ wxMenu:connect(Win, command_menu_selected,
+ [{id,Id}, {userData, Action}])
+ end,
+ Sub = wxMenu:new([]),
+ Dis = wxMenu:append(Sub, ?wxID_ANY, "Disable"),
+ Add(Dis, {break,Point,status}),
+ Del = wxMenu:append(Sub, ?wxID_ANY, "Delete"),
+ Add(Del, {break,Point,delete}),
+ Trigger = wxMenu:new([]),
+ Enable = wxMenu:appendRadioItem(Trigger, ?wxID_ANY,"Enable"),
+ Add(Enable, {break,Point,{trigger,enable}}),
+ TDisable = wxMenu:appendRadioItem(Trigger, ?wxID_ANY,"Disable"),
+ Add(TDisable, {break,Point,{trigger,disable}}),
+ Delete = wxMenu:appendRadioItem(Trigger, ?wxID_ANY,"Delete"),
+ Add(Delete, {break,Point,{trigger,delete}}),
+
+ wxMenu:append(Sub, ?wxID_ANY, "Trigger Action", Trigger),
+ MenuBtn = wxMenu:append(Menu,?wxID_ANY, Label, Sub),
+
+ #break{mb={Menu,MenuBtn},
+ smi=Dis, emi=Enable, dimi=TDisable, demi=Delete}.
+
+%%--------------------------------------------------------------------
+%% update_break(Break, Options)
+%% Break = #break{}
+%% Options = [Status, Action, Mods, Cond]
+%% Status = active | inactive
+%% Action = enable | disable | delete
+%% Mods = null (not used)
+%% Cond = null | {Mod, Func}
+%%--------------------------------------------------------------------
+update_break(Break, Options) ->
+ [Status, Trigger|_] = Options,
+
+ Label = case Status of
+ active -> "Disable";
+ inactive -> "Enable"
+ end,
+ wxMenuItem:setText(Break#break.smi, Label),
+
+ TriggerMI = case Trigger of
+ enable -> Break#break.emi;
+ disable -> Break#break.dimi;
+ delete -> Break#break.demi
+ end,
+ wxMenuItem:check(TriggerMI).
+
+%%--------------------------------------------------------------------
+%% delete_break(Break)
+%% Break = #break{}
+%%--------------------------------------------------------------------
+delete_break(Break) ->
+ {Menu, MenuBtn} = Break#break.mb,
+ wxMenu:'Destroy'(Menu,MenuBtn).
+
+%%--------------------------------------------------------------------
+%% motion(X, Y) -> {X, Y}
+%% X = Y = integer()
+%%--------------------------------------------------------------------
+motion(X, Y) ->
+ receive
+ {gs, _Id, motion, _Data, [NX,NY]} ->
+ motion(NX, NY)
+ after 0 ->
+ {X, Y}
+ end.
+
+
+%%--------------------------------------------------------------------
+%% confirm(MonWin, String) -> ok | cancel
+%%--------------------------------------------------------------------
+
+confirm(Win,Message) ->
+ MD = wxMessageDialog:new(Win,to_string(Message),
+ [{style, ?wxOK bor ?wxCANCEL},
+ {caption, "Confirm"}]),
+ Res = case wxDialog:showModal(MD) of
+ ?wxID_OK -> ok;
+ _ -> cancel
+ end,
+ wxDialog:destroy(MD),
+ Res.
+
+%%--------------------------------------------------------------------
+%% notify(MonWin, String) -> ok
+%%--------------------------------------------------------------------
+
+notify(Win,Message) ->
+ MD = wxMessageDialog:new(Win,to_string(Message),
+ [{style, ?wxOK},
+ {caption, "Confirm"}]),
+ wxDialog:showModal(MD),
+ wxDialog:destroy(MD),
+ ok.
+
+%%--------------------------------------------------------------------
+%% entry(Parent, Title, Prompt, {Type, Value}) -> {Prompt, Val} | cancel
+%%--------------------------------------------------------------------
+
+entry(Parent, Title, Prompt, {Type, Value}) ->
+ Ted = wxTextEntryDialog:new(Parent, to_string(Prompt),
+ [{caption, to_string(Title)},
+ {value, to_string(Value)}]),
+
+ case wxDialog:showModal(Ted) of
+ ?wxID_OK ->
+ Res = case verify(Type, wxTextEntryDialog:getValue(Ted)) of
+ {edit,NewVal} ->
+ {Prompt,NewVal};
+ ignore ->
+ cancel
+ end,
+ wxTextEntryDialog:destroy(Ted),
+ Res;
+ _ ->
+ cancel
+ end.
+
+
+verify(Type, Str) ->
+ case erl_scan:string(Str) of
+ {ok, Tokens, _EndLine} when Type==term ->
+
+ case erl_parse:parse_term(Tokens++[{dot, 1}]) of
+ {ok, Value} -> {edit, Value};
+ _Error ->
+ ignore
+ end;
+ {ok, [{Type, _Line, Value}], _EndLine} when Type/=term ->
+ {edit, Value};
+ _Err ->
+ ignore
+ end.
+
+%%--------------------------------------------------------------------
+%% open_help/2
+%% opens browser with help file
+%%--------------------------------------------------------------------
+open_help(_Parent, HelpHtmlFile) ->
+ wx_misc:launchDefaultBrowser("file://" ++ HelpHtmlFile).
+
+%%--------------------------------------------------------------------
+%% to_string(Term) -> [integer()]
+%% to_string(Format,Args) -> [integer()]
+%%--------------------------------------------------------------------
+
+to_string(Atom) when is_atom(Atom) ->
+ atom_to_list(Atom);
+to_string(Integer) when is_integer(Integer) ->
+ integer_to_list(Integer);
+to_string([]) -> "";
+to_string(List) when is_list(List) ->
+ List;
+to_string(Term) ->
+ io_lib:format("~p",[Term]).
+
+to_string(Format,Args) ->
+ io_lib:format(Format, Args).
+
+menu_name(Atom, N) when is_atom(Atom) ->
+ menu_name(atom_to_list(Atom),N);
+menu_name(Str, Pos) when is_integer(Pos) ->
+ {S1,S2} = lists:split(Pos,Str),
+ S1 ++ [$&|S2];
+menu_name(Str,_) ->
+ Str.
+
+%%--------------------------------------------------------------------
+%% find_icon(File) -> Path or exists
+%%--------------------------------------------------------------------
+
+find_icon(File) ->
+ PrivDir = code:priv_dir(debugger),
+ PrivIcon = filename:append(PrivDir, File),
+ case filelib:is_regular(PrivIcon) of
+ true -> PrivIcon;
+ false ->
+ CurrDir = filename:dirname(code:which(?MODULE)),
+ CurrIcon = filename:append(CurrDir, File),
+ true = filelib:is_regular(CurrIcon),
+ CurrIcon
+ end.
+
diff --git a/lib/debugger/src/dbg_wx_winman.erl b/lib/debugger/src/dbg_wx_winman.erl
new file mode 100755
index 0000000000..1daabe3435
--- /dev/null
+++ b/lib/debugger/src/dbg_wx_winman.erl
@@ -0,0 +1,175 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(dbg_wx_winman).
+-behaviour(gen_server).
+
+%% External exports
+-export([start/0]).
+-export([insert/2, is_started/1,
+ clear_process/1,
+ raise/1,
+ update_windows_menu/2]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(win, {owner, % pid()
+ title, % string()
+ win % gsobj()
+ }).
+
+-record(state, {wins=[] % [#win{}]
+ }).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start()
+%%--------------------------------------------------------------------
+start() ->
+ gen_server:start({local,?MODULE}, ?MODULE, [], []).
+
+%%--------------------------------------------------------------------
+%% insert(Title, Win)
+%% Title = string()
+%% Win = gsobj()
+%%--------------------------------------------------------------------
+insert(Title, Win) ->
+ gen_server:cast(?MODULE, {insert, self(), Title, Win}).
+
+%%--------------------------------------------------------------------
+%% is_started(Title) -> true | false
+%% Title = string()
+%%--------------------------------------------------------------------
+is_started(Title) ->
+ case gen_server:call(?MODULE, {is_started, Title}, infinity) of
+ {true, Win} ->
+ raise(Win),
+ true;
+ false ->
+ false
+ end.
+
+%%--------------------------------------------------------------------
+%% clear_process(Title)
+%% Title = string()
+%%--------------------------------------------------------------------
+clear_process(Title) ->
+ gen_server:cast(?MODULE, {clear_process, Title}).
+
+%%--------------------------------------------------------------------
+%% raise(Win)
+%% Win = gsobj()
+%%--------------------------------------------------------------------
+raise(Win) ->
+ case wxTopLevelWindow:isIconized(Win) of
+ true -> wxTopLevelWindow:iconize(Win, [{iconize, false}]);
+ false -> ignore
+ end,
+ wxWindow:raise(Win).
+
+%%--------------------------------------------------------------------
+%% update_windows_menu(Data)
+%% Data = {New, Old}
+%% New = Old = list()
+%%--------------------------------------------------------------------
+update_windows_menu(Win, [MonInfo|Infos]) ->
+ Menu = get('Windows'),
+ OldItems = wxMenu:getMenuItems(Menu),
+ [wxMenu:delete(Menu, Item) || Item <- OldItems],
+ menuitem(Win, Menu,MonInfo, 700),
+ wxMenu:appendSeparator(Menu),
+ wx:foldl(fun(Info,Acc) -> menuitem(Win,Menu,Info,Acc) end, 701, Infos).
+
+menuitem(Window, Menu, {Title, Win}, Id) ->
+ wxMenu:append(Menu, Id, Title),
+ wxWindow:connect(Window, command_menu_selected,
+ [{id,Id},{userData,{dbg_ui_winman,Win}}]),
+ Id+1.
+
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+init([]) ->
+ process_flag(trap_exit, true),
+ {ok, #state{}}.
+
+handle_call({is_started, Title}, _From, State) ->
+ Reply = case lists:keysearch(Title, #win.title, State#state.wins) of
+ {value, Win} -> {true, Win#win.win};
+ false -> false
+ end,
+ {reply, Reply, State}.
+
+handle_cast({insert, Pid, Title, Win}, State) ->
+ link(Pid),
+ Wins = State#state.wins ++ [#win{owner=Pid, title=Title, win=Win}],
+ inform_all(Wins),
+ {noreply, State#state{wins=Wins}};
+
+handle_cast({clear_process, Title}, State) ->
+ OldWins = State#state.wins,
+ Wins = case lists:keysearch(Title, #win.title, OldWins) of
+ {value, #win{owner=Pid}} ->
+ Msg = {dbg_ui_winman, destroy},
+ Pid ! Msg,
+ lists:keydelete(Title, #win.title, OldWins);
+ false ->
+ OldWins
+ end,
+ {noreply, State#state{wins=Wins}}.
+
+handle_info({'EXIT', Pid, _Reason}, State) ->
+ [Mon | _Wins] = State#state.wins,
+ if
+ Pid==Mon#win.owner -> {stop, normal, State};
+ true ->
+ Wins2 = lists:keydelete(Pid, #win.owner, State#state.wins),
+ inform_all(Wins2),
+ {noreply, State#state{wins=Wins2}}
+ end.
+
+terminate(_Reason, State) ->
+ delete_all(State#state.wins),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+inform_all(Wins) ->
+ Infos = lists:map(fun(#win{title=Title, win=Win}) -> {Title, Win} end,
+ Wins),
+ Msg = {dbg_ui_winman, update_windows_menu, Infos},
+ lists:foreach(fun(#win{owner=Pid}) -> Pid ! Msg end, Wins).
+
+delete_all(Wins) ->
+ Msg = {dbg_ui_winman, destroy},
+ lists:foreach(fun(#win{owner=Pid}) -> Pid ! Msg end, Wins).
diff --git a/lib/debugger/src/debugger.app.src b/lib/debugger/src/debugger.app.src
new file mode 100644
index 0000000000..21cf59a2e1
--- /dev/null
+++ b/lib/debugger/src/debugger.app.src
@@ -0,0 +1,62 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+{application, debugger,
+ [{description, "Debugger"},
+ {vsn, "%VSN%"},
+ {modules, [
+ dbg_debugged,
+ dbg_icmd,
+ dbg_idb,
+ dbg_ieval,
+ dbg_iload,
+ dbg_iserver,
+ dbg_ui_break,
+ dbg_ui_break_win,
+ dbg_ui_edit,
+ dbg_ui_edit_win,
+ dbg_ui_filedialog_win,
+ dbg_ui_interpret,
+ dbg_ui_mon,
+ dbg_ui_mon_win,
+ dbg_ui_settings,
+ dbg_ui_trace,
+ dbg_ui_trace_win,
+ dbg_ui_view,
+ dbg_ui_win,
+ dbg_ui_winman,
+ dbg_wx_break,
+ dbg_wx_break_win,
+ dbg_wx_code,
+ dbg_wx_filedialog_win,
+ dbg_wx_interpret,
+ dbg_wx_mon,
+ dbg_wx_mon_win,
+ dbg_wx_settings,
+ dbg_wx_src_view,
+ dbg_wx_trace,
+ dbg_wx_trace_win,
+ dbg_wx_view,
+ dbg_wx_win,
+ dbg_wx_winman,
+ debugger,
+ i,
+ int
+ ]},
+ {registered, [dbg_iserver, dbg_ui_mon, dbg_ui_winman]},
+ {applications, [kernel, stdlib, gs]}]}.
diff --git a/lib/debugger/src/debugger.appup.src b/lib/debugger/src/debugger.appup.src
new file mode 100644
index 0000000000..7a435e9b22
--- /dev/null
+++ b/lib/debugger/src/debugger.appup.src
@@ -0,0 +1,19 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+{"%VSN%",[],[]}.
diff --git a/lib/debugger/src/debugger.erl b/lib/debugger/src/debugger.erl
new file mode 100644
index 0000000000..b97091ee6b
--- /dev/null
+++ b/lib/debugger/src/debugger.erl
@@ -0,0 +1,118 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(debugger).
+
+%% External exports
+-export([start/0, start/1, start/2, stop/0, quick/3, auto_attach/1]).
+
+%%==Erlang Debugger===================================================
+%%
+%% Graphical user interface to the Erlang Interpreter.
+%% The code for each process is divided into two modules, Name.erl
+%% and Name_win.erl, where Name.erl contains the logic and
+%% Name_win.erl the GS specific functionality.
+%%
+%% debugger
+%% --------
+%% Interface module.
+%%
+%% dbg_ui_winman
+%% -------------
+%% Window manager, keeping track of open windows and Debugger
+%% processes.
+%%
+%% dbg_ui_mon, dbg_ui_mon_win
+%% --------------------------
+%% Monitor window, main window of Debugger, displaying information
+%% about interpreted modules and debugged processes.
+%%
+%% dbg_ui_trace, dbg_ui_trace_win
+%% ------------------------------
+%% Attach process window, showing the code executed by a debugged
+%% process and providing a GUI for stepping, inspecting variables etc.
+%%
+%% dbg_ui_break, dbg_ui_break_win
+%% ------------------------------
+%% Help window for creating new breakpoints.
+%%
+%% dbg_ui_edit, dbg_ui_edit_win
+%% --------------------------------------
+%% Help window for editing terms, used for setting backtrace size
+%% (i.e. how many stack frames to display in the attach process window)
+%% and changing variable values.
+%%
+%% dbg_ui_interpret, dbg_ui_filedialog_win
+%% --------------------------------------
+%% Help window for selecting modules to interpret.
+%%
+%% dbg_ui_settings, dbg_ui_filedialog_win
+%% --------------------------------------
+%% Help window for saving and loading Debugger settings.
+%%
+%% dbg_ui_view
+%% -----------
+%% Help window for viewing interpreted modules (uses dbg_ui_trace_win).
+%%
+%% dbg_ui_win
+%% ----------
+%% GUI specific functionality used by more than one window type.
+%%
+%%====================================================================
+start() ->
+ start(global, default, default).
+start(Mode) when Mode==local; Mode==global ->
+ start(Mode, default, default);
+start(Gui) when Gui==gs; Gui==wx ->
+ start(global, default, Gui);
+start(SFile) when is_list(SFile), is_integer(hd(SFile)) ->
+ start(global, SFile, default).
+
+start(Mode, SFile) ->
+ start(Mode, SFile, default).
+
+start(Mode, SFile, gs) ->
+ dbg_ui_mon:start(Mode, SFile);
+start(Mode, SFile, wx) ->
+ dbg_wx_mon:start(Mode, SFile);
+start(Mode, SFile, default) ->
+ Gui = which_gui(),
+ start(Mode, SFile, Gui).
+
+stop() ->
+ dbg_ui_mon:stop().
+
+quick(M, F, A) ->
+ int:i(M),
+ auto_attach([init]),
+ apply(M, F, A).
+
+auto_attach(Flags) ->
+ case which_gui() of
+ gs -> int:auto_attach(Flags, {dbg_ui_trace, start, []});
+ wx -> int:auto_attach(Flags, {dbg_wx_trace, start, []})
+ end.
+
+which_gui() ->
+ try
+ wx:new(),
+ wx:destroy(),
+ wx
+ catch _:_ ->
+ gs
+ end.
diff --git a/lib/debugger/src/i.erl b/lib/debugger/src/i.erl
new file mode 100644
index 0000000000..7c2fb22946
--- /dev/null
+++ b/lib/debugger/src/i.erl
@@ -0,0 +1,376 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Purpose : User interface to the Erlang debugger/interpreter.
+
+-module(i).
+
+-export([help/0,ia/1,ia/2,ia/3,ia/4,iaa/1,iaa/2,
+ ib/2,ib/3,ib/4,ibd/2,ibe/2,iba/3,ibc/3,ic/0,ii/1,ii/2,
+ il/0,im/0,ini/1,ini/2,inq/1,ip/0,ipb/0,ipb/1,iq/1,
+ ir/0,ir/1,ir/2,ir/3,iv/0,ist/1]).
+
+-import(io, [format/1,format/2]).
+-import(lists, [sort/1,foreach/2]).
+
+iv() ->
+ Vsn = string:substr(filename:basename(code:lib_dir(debugger)), 10),
+ list_to_atom(Vsn).
+
+
+%% -------------------------------------------
+%% Start a new graphical monitor.
+%% A monitor displays status for all processes
+%% running interpreted modules.
+%% -------------------------------------------
+
+im() ->
+ case debugger:start() of
+ {ok, Pid} ->
+ Pid;
+ {error, {already_started, Pid}} ->
+ Pid
+ end.
+
+%% -------------------------------------------
+%% Add Module(s) as being interpreted.
+%% The actual paths will be searched for the
+%% corresponding source file(s) (Module.erl).
+%% Module(s) can be given with absolute path.
+%% -------------------------------------------
+
+ii(Module) ->
+ int:i(Module).
+
+ii(Module,_Options) ->
+ int:i(Module).
+
+%% -------------------------------------------
+%% Don't interpret module(s). The module will be
+%% removed from the set of modules interpreted.
+%% -------------------------------------------
+
+iq(Module) ->
+ int:n(Module).
+
+%% -------------------------------------------
+%% The corresponding functions for distributed
+%% erlang. The loading ... will be performed
+%% at all nodes using the broadcast facility.
+%% -------------------------------------------
+
+ini(Module) ->
+ int:ni(Module).
+
+ini(Module,_Options) ->
+ int:ni(Module).
+
+inq(Module) ->
+ int:nn(Module).
+
+%% -------------------------------------------
+%% Add a new break point at Line in Module.
+%% -------------------------------------------
+
+ib(Module,Line) ->
+ int:break(Module,Line).
+
+%% -------------------------------------------
+%% Break at entrance of specified function.
+%% Breaks is set at the first expression for
+%% all function clauses.
+%% -------------------------------------------
+
+ib(Module,Function,Arity) ->
+ int:break_in(Module,Function,Arity).
+
+%% -------------------------------------------
+%% Break at entrance of specified function.
+%% Breaks is set at the first expression for
+%% all function clauses.
+%% Associate the condition to the break.
+%% -------------------------------------------
+
+ib(Module,Function,Arity,Cond) ->
+ Breaks1 = int:all_breaks(Module),
+ int:break_in(Module,Function,Arity),
+ Breaks2 = int:all_breaks(Module),
+ lists:foreach(fun({Mod,Line}) -> int:test_at_break(Mod,Line,Cond) end,
+ Breaks2--Breaks1).
+
+%% -------------------------------------------
+%% Make an existing break point inactive.
+%% -------------------------------------------
+
+ibd(Mod,Line) ->
+ int:disable_break(Mod,Line).
+
+%% -------------------------------------------
+%% Make an existing break point active.
+%% -------------------------------------------
+
+ibe(Mod,Line) ->
+ int:enable_break(Mod,Line).
+
+%% -------------------------------------------
+%% Set which status a break point shall have
+%% after it has been triggered the next time.
+%% Action is: enable, disable or delete.
+%% -------------------------------------------
+
+iba(Mod,Line,Action) ->
+ int:action_at_break(Mod,Line,Action).
+
+%% -------------------------------------------
+%% Add a conditional function to a break point.
+%% The given function shall have arity 1 and
+%% return either true or false.
+%% The argument of the given function is the
+%% current variable bindings of the process at
+%% the place of the break point, the bindings
+%% can be inspected using int:get_binding/2.
+
+%% Fnk == {Module,Function}
+%% Fnk == {Module,Function,ExtraArgs}
+%% -------------------------------------------
+
+ibc(Mod,Line,Fnk) ->
+ int:test_at_break(Mod,Line,Fnk).
+
+%% -------------------------------------------
+%% Delete break point.
+%% -------------------------------------------
+
+ir(Module,Line) ->
+ int:delete_break(Module,Line).
+
+%% -------------------------------------------
+%% Delete break at entrance of specified function.
+%% -------------------------------------------
+
+ir(Module,Function,Arity) ->
+ int:del_break_in(Module,Function,Arity).
+
+%% -------------------------------------------
+%% Delete all break points in module.
+%% -------------------------------------------
+
+ir(Module) ->
+ int:no_break(Module).
+
+%% -------------------------------------------
+%% Delete all break points (for all modules).
+%% -------------------------------------------
+
+ir() ->
+ int:no_break().
+
+%% -------------------------------------------
+%% Print all interpreted modules.
+%% -------------------------------------------
+
+il() ->
+ Mods = sort(int:interpreted()),
+ ilformat("Module","File"),
+ foreach(fun(Mod) -> ilformat(atom_to_list(Mod), get_file(Mod)) end, Mods).
+
+get_file(Mod) ->
+ case int:file(Mod) of
+ {error,not_loaded} -> % Marked interpreted but not loaded
+ "not loaded";
+ File ->
+ File
+ end.
+
+ilformat(A1, A2) ->
+ format("~-20s ~s\n", [A1,A2]).
+
+%% -------------------------------------------
+%% Print all break points in modules.
+%% -------------------------------------------
+
+ipb() ->
+ Bps = lists:keysort(1,int:all_breaks()),
+ bhformat("Module","Line","Status","Action","Condition"),
+ pb_print(Bps).
+
+ipb(Module) when is_atom(Module) ->
+ ipb1(Module);
+ipb(Module) when is_list(Module) ->
+ ipb1(list_to_atom(Module)).
+
+ipb1(Module) ->
+ Bps = lists:keysort(1,int:all_breaks(Module)),
+ bhformat("Module","Line","Status","Action","Condition"),
+ pb_print(Bps).
+
+pb_print([{{Mod,Line},[Status,Action,_,null|_]}|Bps]) ->
+ bformat(Mod,Line,Status,Action,""),
+ pb_print(Bps);
+pb_print([{{Mod,Line},[Status,Action,_,Cond|_]}|Bps]) ->
+ bformat(Mod,Line,Status,Action,
+ io_lib:format("~w",[Cond])),
+ pb_print(Bps);
+pb_print(_) ->
+ ok.
+
+bhformat(A1, A2, A3, A4, A5) ->
+ format("~-15s ~-9s ~-12s ~-12s ~-21s~n", [A1,A2,A3,A4,A5]).
+
+bformat(A1, A2, A3, A4, A5) ->
+ format("~-15w ~-9w ~-12w ~-12w ~-21s~n", [A1,A2,A3,A4,A5]).
+
+%% -------------------------------------------
+%% Set the stack trace flag.
+%% Flag can be all (true), no_tail or false.
+%% -------------------------------------------
+
+ist(Flag) ->
+ int:stack_trace(Flag),
+ true.
+
+%% -------------------------------------------
+%% Set the automatic attachment flag.
+%% Flags can be init, break and exit.
+%% iaa(Flag) or ia([Flag,Flag,...])
+%% -------------------------------------------
+
+iaa(Flag) ->
+ iaa(Flag,{dbg_ui_trace,start,[]}).
+
+%% -------------------------------------------
+%% Set the automatic attachment flag.
+%% Flags can be init, break and exit.
+%% Use given function to start up an attachment
+%% window.
+%% ia(Flag,Fnk) or ia([Flag,Flag,...],Fnk)
+%% where Fnk == {M,F}
+%% The given Fnk must have arity 3 or 4.
+%% -------------------------------------------
+
+iaa(Flag,Fnk) ->
+ int:auto_attach(Flag,Fnk),
+ true.
+
+%% -------------------------------------------
+%% Attach to process.
+%% -------------------------------------------
+
+ia(Pid) ->
+ ia(Pid,{dbg_ui_trace,start}).
+
+%% -------------------------------------------
+%% Attach to process.
+%% X,Y,Z is combind to a process identity.
+%% -------------------------------------------
+
+ia(X,Y,Z) ->
+ ia(c:pid(X,Y,Z)).
+
+%% -------------------------------------------
+%% Attach to process.
+%% Use Fnk == {M,F} as the attaching interface.
+%% -------------------------------------------
+
+ia(Pid,Fnk) ->
+ case lists:keysearch(Pid, 1, int:snapshot()) of
+ {value, _PidTuple} ->
+ int:attach(Pid,Fnk);
+ false -> no_proc
+ end.
+
+ia(X,Y,Z,Fnk) ->
+ ia(c:pid(X,Y,Z),Fnk).
+
+%% -------------------------------------------
+%% Print status for all interpreted processes.
+%% -------------------------------------------
+
+ip() ->
+ Stats = int:snapshot(),
+ hformat("Pid","Initial Call","Status","Info"),
+ ip(Stats).
+
+ip([{Pid,{M,F,A},Status,{}}|Stats]) ->
+ hformat(io_lib:format("~w",[Pid]),
+ io_lib:format("~p:~p/~p",[M,F,length(A)]),
+ io_lib:format("~w",[Status]),
+ ""),
+ ip(Stats);
+ip([{Pid,{M,F,A},Status,Info}|Stats]) ->
+ hformat(io_lib:format("~w",[Pid]),
+ io_lib:format("~p:~p/~p",[M,F,length(A)]),
+ io_lib:format("~w",[Status]),
+ io_lib:format("~w",[Info])),
+ ip(Stats);
+ip([]) ->
+ ok.
+
+hformat(A1, A2, A3, A4) ->
+ format("~-12s ~-21s ~-9s ~-21s~n", [A1,A2,A3,A4]).
+
+
+%% -------------------------------------------
+%% Delete all terminated processes from the
+%% interpreter.
+%% -------------------------------------------
+
+ic() ->
+ int:clear().
+
+%% -------------------------------------------
+%% Help printout
+%% -------------------------------------------
+
+help() ->
+ format("iv() -- print the current version of the interpreter~n"),
+ format("im() -- pop up a monitor window~n"),
+ format("ii(Mod) -- interpret Mod(s) (or AbsMod(s))~n"),
+ format("ii(Mod,Op) -- interpret Mod(s) (or AbsMod(s))~n"),
+ format(" use Op as options (same as for compile)~n"),
+ format("iq(Mod) -- do not interpret Mod(s)~n"),
+ format("ini(Mod) -- ii/1 at all Erlang nodes~n"),
+ format("ini(Mod,Op) -- ii/2 at all Erlang nodes~n"),
+ format("inq(Mod) -- iq at all Erlang nodes~n"),
+ format("ib(Mod,Line) -- set a break point at Line in Mod~n"),
+ format("ib(M,F,Arity)-- set a break point in M:F/Arity~n"),
+ format("ibd(Mod,Line)-- disable the break point at Line in Mod~n"),
+ format("ibe(Mod,Line)-- enable the break point at Line in Mod~n"),
+ format("iba(M,L,Action)-- set a new action at break~n"),
+ format("ibc(M,L,Action)-- set a new condition for break~n"),
+ format("ir(Mod,Line) -- remove the break point at Line in Mod~n"),
+ format("ir(M,F,Arity)-- remove the break point in M:F/Arity~n"),
+ format("ir(Mod) -- remove all break points in Mod~n"),
+ format("ir() -- remove all existing break points~n"),
+ format("il() -- list all interpreted modules~n"),
+ format("ip() -- print status of all interpreted processes~n"),
+ format("ic() -- remove all terminated interpreted processes~n"),
+ format("ipb() -- list all break points~n"),
+ format("ipb(Mod) -- list all break points in Mod~n"),
+ format("ia(Pid) -- attach to Pid~n"),
+ format("ia(X,Y,Z) -- attach to pid(X,Y,Z)~n"),
+ format("ia(Pid,Fun) -- use own Fun = {M,F} as attach application~n"),
+ format("ia(X,Y,Z,Fun)-- use own Fun = {M,F} as attach application~n"),
+ format("iaa([Flag]) -- set automatic attach to process~n"),
+ format(" Flag is init,break and exit~n"),
+ format("iaa([Fl],Fun)-- use own Fun = {M,F} as attach application~n"),
+ format("ist(Flag) -- set stack trace flag~n"),
+ format(" Flag is all (true),no_tail or false~n"),
+ ok.
+
+
diff --git a/lib/debugger/src/int.erl b/lib/debugger/src/int.erl
new file mode 100644
index 0000000000..eeb4df4a8e
--- /dev/null
+++ b/lib/debugger/src/int.erl
@@ -0,0 +1,736 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(int).
+
+%% External exports
+-export([i/1, i/2, ni/1, ni/2, n/1, nn/1, interpreted/0, file/1,
+ interpretable/1]).
+-export([auto_attach/0, auto_attach/1, auto_attach/2,
+ stack_trace/0, stack_trace/1]).
+-export([break/2, delete_break/2, break_in/3, del_break_in/3,
+ no_break/0, no_break/1,
+ disable_break/2, enable_break/2,
+ action_at_break/3, test_at_break/3, get_binding/2,
+ all_breaks/0, all_breaks/1]).
+-export([snapshot/0, clear/0]).
+-export([continue/1, continue/3]).
+
+%% External exports only to be used by Debugger
+-export([start/0, stop/0, subscribe/0]).
+-export([attach/2, step/1, next/1, finish/1]).
+
+%% External exports only to be used by an attached process
+-export([attached/1, meta/2, meta/3, contents/2, functions/1]).
+
+%% External export only to be used by error_handler
+-export([eval/3]).
+
+-include_lib("kernel/include/file.hrl").
+
+%%==Erlang Interpreter================================================
+%%
+%% int
+%% ---
+%% Interface module.
+%%
+%% i
+%% -
+%% Interface module to int, retained for backwards compatibility only.
+%%
+%% dbg_debugged
+%% ------------
+%% Contains the message loops for a debugged process and is the main
+%% entry point from the breakpoint handler in the error_handler module
+%% (via the int module).
+%%
+%% When a process is debugged, most code is executed in another
+%% process, called the meta process. When the meta process is
+%% interpreting code, the process being debugged just waits in a
+%% receive loop in dbg_debugged. However the debugged process itself
+%% calls any BIFs that must execute in the correct process (such as
+%% link/1 and spawn_link/1), and external code which is not
+%% interpreted.
+%%
+%% dbg_icmd, dbg_ieval
+%% -------------------
+%% Code for the meta process.
+%%
+%% dbg_iserver
+%% -----------
+%% Interpreter main process, keeping and distributing information
+%% about interpreted modules and debugged processes.
+%%
+%% dbg_idb
+%% -------
+%% ETS wrapper, allowing transparent access to tables at a remote node.
+%%
+%% dbg_iload
+%% ---------
+%% Code for interpreting a module.
+%%====================================================================
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% i(AbsMods) -> {module,Mod} | error | ok
+%% ni(AbsMods) -> {module,Mod} | error | ok
+%% AbsMods = AbsMod | [AbsMod]
+%% AbsMod = atom() | string()
+%% Mod = atom()
+%% Options = term() ignored
+%%--------------------------------------------------------------------
+i(AbsMods) -> i2(AbsMods, local, ok).
+i(AbsMods, _Options) -> i2(AbsMods, local, ok).
+ni(AbsMods) -> i2(AbsMods, distributed, ok).
+ni(AbsMods, _Options) -> i2(AbsMods, distributed, ok).
+
+i2([AbsMod|AbsMods], Dist, Acc)
+ when is_atom(AbsMod); is_list(AbsMod); is_tuple(AbsMod) ->
+ Res = int_mod(AbsMod, Dist),
+ case Acc of
+ error ->
+ i2(AbsMods, Dist, Acc);
+ _ ->
+ i2(AbsMods, Dist, Res)
+ end;
+i2([], _Dist, Acc) ->
+ Acc;
+i2(AbsMod, Dist, _Acc) when is_atom(AbsMod); is_list(AbsMod); is_tuple(AbsMod) ->
+ int_mod(AbsMod, Dist).
+
+%%--------------------------------------------------------------------
+%% n(AbsMods) -> ok
+%% nn(AbsMods) -> ok
+%%--------------------------------------------------------------------
+n(AbsMods) -> n2(AbsMods, local).
+nn(AbsMods) -> n2(AbsMods, distributed).
+
+n2([AbsMod|AbsMods], Dist) when is_atom(AbsMod); is_list(AbsMod) ->
+ del_mod(AbsMod, Dist),
+ n2(AbsMods, Dist);
+n2([AbsMod], Dist) when is_atom(AbsMod); is_list(AbsMod) ->
+ del_mod(AbsMod, Dist);
+n2([], _Dist) ->
+ ok;
+n2(AbsMod, Dist) when is_atom(AbsMod); is_list(AbsMod) ->
+ del_mod(AbsMod, Dist).
+
+%%--------------------------------------------------------------------
+%% interpreted() -> [Mod]
+%%--------------------------------------------------------------------
+interpreted() ->
+ dbg_iserver:safe_call(all_interpreted).
+
+%%--------------------------------------------------------------------
+%% file(Mod) -> File | {error, not_loaded}
+%% Mod = atom()
+%% File = string()
+%%--------------------------------------------------------------------
+file(Mod) when is_atom(Mod) ->
+ dbg_iserver:safe_call({file, Mod}).
+
+%%--------------------------------------------------------------------
+%% interpretable(AbsMod) -> true | {error, Reason}
+%% AbsMod = Mod | File
+%% Reason = no_src | no_beam | no_debug_info | badarg | {app, App}
+%%--------------------------------------------------------------------
+interpretable(AbsMod) ->
+ case check(AbsMod) of
+ {ok, _Res} -> true;
+ Error -> Error
+ end.
+
+%%--------------------------------------------------------------------
+%% auto_attach() -> false | {Flags, Function}
+%% auto_attach(false)
+%% auto_attach(false|Flags, Function)
+%% Flags = Flag | [Flag]
+%% Flag = init | break | exit
+%% Function = {Mod, Func} | {Mod, Func, Args}
+%% Will result in calling:
+%% spawn(Mod, Func, [Dist, Pid, Meta | Args]) (living process) or
+%% spawn(Mod, Func, [Dist, Pid, Reason, Info | Args]) (dead process)
+%%--------------------------------------------------------------------
+auto_attach() ->
+ dbg_iserver:safe_call(get_auto_attach).
+
+auto_attach(false) ->
+ dbg_iserver:safe_cast({set_auto_attach, false}).
+
+auto_attach([], _Function) ->
+ auto_attach(false);
+auto_attach(Flags, {Mod, Func}) ->
+ auto_attach(Flags, {Mod, Func, []});
+auto_attach(Flags, {Mod, Func, Args}) when is_atom(Mod),is_atom(Func),is_list(Args) ->
+ check_flags(Flags),
+ dbg_iserver:safe_cast({set_auto_attach, Flags, {Mod, Func, Args}}).
+
+check_flags([init|Flags]) -> check_flags(Flags);
+check_flags([break|Flags]) -> check_flags(Flags);
+check_flags([exit|Flags]) -> check_flags(Flags);
+check_flags([]) -> true.
+
+%%--------------------------------------------------------------------
+%% stack_trace() -> Flag
+%% stack_trace(Flag)
+%% Flag = all | true | no_tail | false
+%%--------------------------------------------------------------------
+stack_trace() ->
+ dbg_iserver:safe_call(get_stack_trace).
+
+stack_trace(true) ->
+ stack_trace(all);
+stack_trace(Flag) ->
+ check_flag(Flag),
+ dbg_iserver:safe_cast({set_stack_trace, Flag}).
+
+check_flag(all) -> true;
+check_flag(no_tail) -> true;
+check_flag(false) -> true.
+
+%%--------------------------------------------------------------------
+%% break(Mod, Line) -> ok | {error, break_exists}
+%% delete_break(Mod, Line) -> ok
+%% break_in(Mod, Func, Arity) -> ok | {error, function_not_found}
+%% del_break_in(Mod, Function, Arity) -> ok | {error, function_not_found}
+%% no_break()
+%% no_break(Mod)
+%% disable_break(Mod, Line) -> ok
+%% enable_break(Mod, Line) -> ok
+%% action_at_break(Mod, Line, Action) -> ok
+%% test_at_break(Mod, Line, Function) -> ok
+%% get_binding(Var, Bindings) -> {value, Value} | unbound
+%% all_breaks() -> [Break]
+%% all_breaks(Mod) -> [Break]
+%% Mod = atom()
+%% Line = integer()
+%% Func = atom() function name
+%% Arity = integer()
+%% Action = enable | disable | delete
+%% Function = {Mod, Func} must have arity 1 (Bindings)
+%% Var = atom()
+%% Bindings = Value = term()
+%% Break = {Point, Options}
+%% Point = {Mod, Line}
+%% Options = [Status, Action, null, Cond]
+%% Status = active | inactive
+%% Cond = null | Function
+%%--------------------------------------------------------------------
+break(Mod, Line) when is_atom(Mod), is_integer(Line) ->
+ dbg_iserver:safe_call({new_break, {Mod, Line},
+ [active, enable, null, null]}).
+
+delete_break(Mod, Line) when is_atom(Mod), is_integer(Line) ->
+ dbg_iserver:safe_cast({delete_break, {Mod, Line}}).
+
+break_in(Mod, Func, Arity) when is_atom(Mod), is_atom(Func), is_integer(Arity) ->
+ case dbg_iserver:safe_call({is_interpreted, Mod, Func, Arity}) of
+ {true, Clauses} ->
+ Lines = first_lines(Clauses),
+ lists:foreach(fun(Line) -> break(Mod, Line) end, Lines);
+ false ->
+ {error, function_not_found}
+ end.
+
+del_break_in(Mod, Func, Arity) when is_atom(Mod), is_atom(Func), is_integer(Arity) ->
+ case dbg_iserver:safe_call({is_interpreted, Mod, Func, Arity}) of
+ {true, Clauses} ->
+ Lines = first_lines(Clauses),
+ lists:foreach(fun(Line) -> delete_break(Mod, Line) end,
+ Lines);
+ false ->
+ {error, function_not_found}
+ end.
+
+first_lines(Clauses) ->
+ lists:map(fun(Clause) -> first_line(Clause) end, Clauses).
+
+first_line({clause,_L,_Vars,_,Exprs}) ->
+ first_line(Exprs);
+%% Common Test adaptation
+first_line([{call_remote,0,ct_line,line,_As}|Exprs]) ->
+ first_line(Exprs);
+first_line([Expr|_Exprs]) -> % Expr = {Op, Line, ..varying no of args..}
+ element(2, Expr).
+
+no_break() ->
+ dbg_iserver:safe_cast(no_break).
+
+no_break(Mod) when is_atom(Mod) ->
+ dbg_iserver:safe_cast({no_break, Mod}).
+
+disable_break(Mod, Line) when is_atom(Mod), is_integer(Line) ->
+ dbg_iserver:safe_cast({break_option, {Mod, Line}, status, inactive}).
+
+enable_break(Mod, Line) when is_atom(Mod), is_integer(Line) ->
+ dbg_iserver:safe_cast({break_option, {Mod, Line}, status, active}).
+
+action_at_break(Mod, Line, Action) when is_atom(Mod), is_integer(Line) ->
+ check_action(Action),
+ dbg_iserver:safe_cast({break_option, {Mod, Line}, action, Action}).
+
+check_action(enable) -> true;
+check_action(disable) -> true;
+check_action(delete) -> true.
+
+test_at_break(Mod, Line, Function) when is_atom(Mod), is_integer(Line) ->
+ check_function(Function),
+ dbg_iserver:safe_cast({break_option, {Mod, Line}, condition, Function}).
+
+check_function({Mod, Func}) when is_atom(Mod), is_atom(Func) -> true.
+
+get_binding(Var, Bs) ->
+ dbg_icmd:get_binding(Var, Bs).
+
+all_breaks() ->
+ dbg_iserver:safe_call(all_breaks).
+all_breaks(Mod) when is_atom(Mod) ->
+ dbg_iserver:safe_call({all_breaks, Mod}).
+
+%%--------------------------------------------------------------------
+%% snapshot() -> [{Pid, Init, Status, Info}]
+%% Pid = pid()
+%% Init = atom() First interpreted function
+%% Status = idle | running | waiting | break | exit
+%% Info = {} | {Mod, Line} | ExitReason
+%% Mod = atom()
+%% Line = integer()
+%% ExitReason = term()
+%%--------------------------------------------------------------------
+snapshot() ->
+ dbg_iserver:safe_call(snapshot).
+
+%%--------------------------------------------------------------------
+%% clear()
+%%--------------------------------------------------------------------
+clear() ->
+ dbg_iserver:safe_cast(clear).
+
+%%--------------------------------------------------------------------
+%% continue(Pid) -> ok | {error, not_interpreted}
+%% continue(X, Y, Z) -> ok | {error, not_interpreted}
+%%--------------------------------------------------------------------
+continue(Pid) when is_pid(Pid) ->
+ case dbg_iserver:safe_call({get_meta, Pid}) of
+ {ok, Meta} when is_pid(Meta) ->
+ dbg_icmd:continue(Meta),
+ ok;
+ Error ->
+ Error
+ end.
+
+continue(X, Y, Z) when is_integer(X), is_integer(Y), is_integer(Z) ->
+ continue(c:pid(X, Y, Z)).
+
+
+%%====================================================================
+%% External exports only to be used by Debugger
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% start()
+%% stop()
+%% Functions for starting and stopping dbg_iserver explicitly.
+%%--------------------------------------------------------------------
+start() -> dbg_iserver:start().
+stop() ->
+ lists:foreach(
+ fun(Mod) ->
+ everywhere(distributed,
+ fun() ->
+ erts_debug:breakpoint({Mod,'_','_'}, false)
+ end)
+ end,
+ interpreted()),
+ dbg_iserver:stop().
+
+%%--------------------------------------------------------------------
+%% subscribe()
+%% Subscribe to information from dbg_iserver. The process calling this
+%% function will receive the following messages:
+%% {int, {interpret, Mod}}
+%% {int, {no_interpret, Mod}}
+%% {int, {new_process, Pid, Function, Status, Info}}
+%% {int, {new_status, Pid, Status, Info}}
+%% {int, {new_break, {Point, Options}}}
+%% {int, {delete_break, Point}}
+%% {int, {break_options, {Point, Options}}}
+%% {int, no_break}
+%% {int, {no_break, Mod}}
+%% {int, {auto_attach, false|{Flags, Function}}}
+%% {int, {stack_trace, Flag}}
+%%--------------------------------------------------------------------
+subscribe() -> dbg_iserver:cast({subscribe, self()}).
+
+%%--------------------------------------------------------------------
+%% attach(Pid, Function)
+%% Pid = pid()
+%% Function = {Mod, Func} | {Mod, Func, Args} (see auto_attach/2)
+%% Tell dbg_iserver to attach to Pid using Function. Will result in:
+%% spawn(Mod, Func, [Pid, Status | Args])
+%%--------------------------------------------------------------------
+attach(Pid, {Mod, Func}) ->
+ attach(Pid, {Mod, Func, []});
+attach(Pid, Function) ->
+ dbg_iserver:cast({attach, Pid, Function}).
+
+%%--------------------------------------------------------------------
+%% step(Pid)
+%% next(Pid)
+%% (continue(Pid))
+%% finish(Pid)
+%%--------------------------------------------------------------------
+step(Pid) ->
+ {ok, Meta} = dbg_iserver:call({get_meta, Pid}),
+ dbg_icmd:step(Meta).
+next(Pid) ->
+ {ok, Meta} = dbg_iserver:call({get_meta, Pid}),
+ dbg_icmd:next(Meta).
+finish(Pid) ->
+ {ok, Meta} = dbg_iserver:call({get_meta, Pid}),
+ dbg_icmd:finish(Meta).
+
+
+%%====================================================================
+%% External exports only to be used by an attached process
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% attached(Pid) -> {ok, Meta} | error
+%% Pid = Meta = pid()
+%% Tell dbg_iserver that I have attached to Pid. dbg_iserver informs
+%% the meta process and returns its pid. dbg_iserver may also refuse,
+%% if there already is a process attached to Pid.
+%%--------------------------------------------------------------------
+attached(Pid) ->
+ dbg_iserver:call({attached, self(), Pid}).
+
+%%--------------------------------------------------------------------
+%% meta(Meta, Cmd)
+%% Meta = pid()
+%% Cmd = step | next | continue | finish | skip | timeout | stop
+%% Cmd = messages => [Message]
+%% meta(Meta, Cmd, Arg)
+%% Cmd = trace, Arg = bool()
+%% Cmd = stack_trace Arg = all | notail | false
+%% Cmd = stack_frame Arg = {up|down, Sp}
+%% => {Sp, Mod, Line} | top | bottom
+%% Cmd = backtrace Arg = integer()
+%% => {Sp, Mod, {Func, Arity}, Line}
+%% Cmd = eval Arg = {Cm, Cmd} | {Cm, Cmd, Sp}
+%%--------------------------------------------------------------------
+meta(Meta, step) -> dbg_icmd:step(Meta);
+meta(Meta, next) -> dbg_icmd:next(Meta);
+meta(Meta, continue) -> dbg_icmd:continue(Meta);
+meta(Meta, finish) -> dbg_icmd:finish(Meta);
+meta(Meta, skip) -> dbg_icmd:skip(Meta);
+meta(Meta, timeout) -> dbg_icmd:timeout(Meta);
+meta(Meta, stop) -> dbg_icmd:stop(Meta);
+meta(Meta, messages) -> dbg_icmd:get(Meta, messages, null).
+
+meta(Meta, trace, Trace) -> dbg_icmd:set(Meta, trace, Trace);
+meta(Meta, stack_trace, Flag) -> dbg_icmd:set(Meta, stack_trace, Flag);
+meta(Meta, bindings, Stack) -> dbg_icmd:get(Meta, bindings, Stack);
+meta(Meta, stack_frame, Arg) -> dbg_icmd:get(Meta, stack_frame, Arg);
+meta(Meta, backtrace, N) -> dbg_icmd:get(Meta, backtrace, N);
+meta(Meta, eval, Arg) -> dbg_icmd:eval(Meta, Arg).
+
+%%--------------------------------------------------------------------
+%% contents(Mod, Pid) -> string()
+%% Mod = atom()
+%% Pid = pid() | any
+%% Return the contents of an interpreted module.
+%%--------------------------------------------------------------------
+contents(Mod, Pid) ->
+ {ok, Bin} = dbg_iserver:call({contents, Mod, Pid}),
+ binary_to_list(Bin).
+
+%%--------------------------------------------------------------------
+%% functions(Mod) -> [[Name, Arity]]
+%% Mod = Name = atom()
+%% Arity = integer()
+%%--------------------------------------------------------------------
+functions(Mod) ->
+ lists:filter(fun([module_info, _Arity]) -> false;
+ (_Func) -> true
+ end,
+ dbg_iserver:call({functions, Mod})).
+
+
+%%====================================================================
+%% External exports only to be used by error_handler
+%%====================================================================
+
+eval(Mod, Func, Args) ->
+ dbg_debugged:eval(Mod, Func, Args).
+
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+%%--Interpreting modules----------------------------------------------
+
+int_mod({Mod, Src, Beam, BeamBin}, Dist)
+ when is_atom(Mod), is_list(Src), is_list(Beam), is_binary(BeamBin) ->
+ try
+ case is_file(Src) of
+ true ->
+ check_application(Src),
+ case check_beam(BeamBin) of
+ {ok, Exp, Abst, _BeamBin} ->
+ load({Mod, Src, Beam, BeamBin, Exp, Abst}, Dist);
+ error ->
+ error
+ end;
+ false ->
+ error
+ end
+ catch
+ throw:Reason ->
+ Reason
+ end;
+int_mod(AbsMod, Dist) when is_atom(AbsMod); is_list(AbsMod) ->
+ case check(AbsMod) of
+ {ok, Res} ->
+ load(Res, Dist);
+ {error, {app, App}} ->
+ io:format("** Cannot interpret ~p module: ~p~n",
+ [App, AbsMod]),
+ error;
+ _Error ->
+ io:format("** Invalid beam file or no abstract code: ~p\n",
+ [AbsMod]),
+ error
+ end.
+
+check(Mod) when is_atom(Mod) -> catch check_module(Mod);
+check(File) when is_list(File) -> catch check_file(File).
+
+load({Mod, Src, Beam, BeamBin, Exp, Abst}, Dist) ->
+ everywhere(Dist,
+ fun() ->
+ code:purge(Mod),
+ erts_debug:breakpoint({Mod,'_','_'}, false),
+ {module,Mod} = code:load_binary(Mod, Beam, BeamBin)
+ end),
+ case erl_prim_loader:get_file(filename:absname(Src)) of
+ {ok, SrcBin, _} ->
+ MD5 = code:module_md5(BeamBin),
+ Bin = term_to_binary({interpreter_module,Exp,Abst,SrcBin,MD5}),
+ {module, Mod} = dbg_iserver:safe_call({load, Mod, Src, Bin}),
+ everywhere(Dist,
+ fun() ->
+ true = erts_debug:breakpoint({Mod,'_','_'}, true) > 0
+ end),
+ {module, Mod};
+ error ->
+ error
+ end.
+
+check_module(Mod) ->
+ case code:which(Mod) of
+ Beam when is_list(Beam) ->
+ case find_src(Beam) of
+ Src when is_list(Src) ->
+ check_application(Src),
+ case check_beam(Beam) of
+ {ok, Exp, Abst, BeamBin} ->
+ {ok, {Mod, Src, Beam, BeamBin, Exp, Abst}};
+ error ->
+ {error, no_debug_info}
+ end;
+ error ->
+ {error, no_src}
+ end;
+ _ ->
+ {error, badarg}
+ end.
+
+check_file(Name0) ->
+ Src =
+ case is_file(Name0) of
+ true ->
+ Name0;
+ false ->
+ Name = Name0 ++ ".erl",
+ case is_file(Name) of
+ true -> Name;
+ false -> error
+ end
+ end,
+ if
+ is_list(Src) ->
+ check_application(Src),
+ Mod = scan_module_name(Src),
+ case find_beam(Mod, Src) of
+ Beam when is_list(Beam) ->
+ case check_beam(Beam) of
+ {ok, Exp, Abst, BeamBin} ->
+ {ok, {Mod, Src, Beam, BeamBin, Exp, Abst}};
+ error ->
+ {error, no_debug_info}
+ end;
+ error ->
+ {error, no_beam}
+ end;
+ true ->
+ {error, badarg}
+ end.
+
+%% Try to avoid interpreting a kernel, stdlib, gs or debugger module.
+check_application(Src) ->
+ case lists:reverse(filename:split(filename:absname(Src))) of
+ [_Mod,"src",AppS|_] ->
+ check_application2(AppS);
+ _ -> ok
+ end.
+check_application2("kernel-"++_) -> throw({error,{app,kernel}});
+check_application2("stdlib-"++_) -> throw({error,{app,stdlib}});
+check_application2("erts-"++_) -> throw({error,{app,erts}});
+check_application2("gs-"++_) -> throw({error,{app,gs}});
+check_application2("debugger-"++_) -> throw({error,{app,debugger}});
+check_application2(_) -> ok.
+
+find_src(Beam) ->
+ Src0 = filename:rootname(Beam) ++ ".erl",
+ case is_file(Src0) of
+ true -> Src0;
+ false ->
+ EbinDir = filename:dirname(Beam),
+ Src = filename:join([filename:dirname(EbinDir), "src",
+ filename:basename(Src0)]),
+ case is_file(Src) of
+ true -> Src;
+ false -> error
+ end
+ end.
+
+find_beam(Mod, Src) ->
+ SrcDir = filename:dirname(Src),
+ BeamFile = packages:last(Mod) ++ code:objfile_extension(),
+ File = filename:join(SrcDir, BeamFile),
+ case is_file(File) of
+ true -> File;
+ false -> find_beam_1(Mod, SrcDir)
+ end.
+
+find_beam_1(Mod, SrcDir) ->
+ RootDir = find_root_dir(SrcDir, packages:first(Mod)),
+ EbinDir = filename:join(RootDir, "ebin"),
+ CodePath = [EbinDir | code:get_path()],
+ BeamFile = to_path(Mod) ++ code:objfile_extension(),
+ lists:foldl(fun(_, Beam) when is_list(Beam) -> Beam;
+ (Dir, error) ->
+ File = filename:join(Dir, BeamFile),
+ case is_file(File) of
+ true -> File;
+ false -> error
+ end
+ end,
+ error,
+ CodePath).
+
+to_path(X) ->
+ filename:join(packages:split(X)).
+
+find_root_dir(Dir, [_|Ss]) ->
+ find_root_dir(filename:dirname(Dir), Ss);
+find_root_dir(Dir, []) ->
+ filename:dirname(Dir).
+
+check_beam(BeamBin) when is_binary(BeamBin) ->
+ case beam_lib:chunks(BeamBin, [abstract_code,exports]) of
+ {ok,{_Mod,[{abstract_code,no_abstract_code}|_]}} ->
+ error;
+ {ok,{_Mod,[{abstract_code,Abst},{exports,Exp}]}} ->
+ {ok,Exp,Abst, BeamBin};
+ _ ->
+ error
+ end;
+check_beam(Beam) when is_list(Beam) ->
+ {ok, Bin, _FullPath} = erl_prim_loader:get_file(filename:absname(Beam)),
+ check_beam(Bin).
+
+is_file(Name) ->
+ filelib:is_regular(filename:absname(Name), erl_prim_loader).
+
+everywhere(distributed, Fun) ->
+ case is_alive() of
+ true -> rpc:multicall(erlang, apply, [Fun,[]]);
+ false -> Fun()
+ end;
+everywhere(local, Fun) ->
+ Fun().
+
+scan_module_name(File) ->
+ case erl_prim_loader:get_file(filename:absname(File)) of
+ {ok, Bin, _FullPath} ->
+ Chars = binary_to_list(Bin),
+ R = (catch {ok, scan_module_name_1(Chars)}),
+ case R of
+ {ok, A} when is_atom(A) -> A;
+ _ -> error
+ end;
+ _ ->
+ error
+ end.
+
+scan_module_name_1(Chars) ->
+ case erl_scan:tokens("", Chars, 1) of
+ {done, {ok, Ts, _}, Rest} ->
+ scan_module_name_2(Ts, Rest);
+ _ ->
+ error
+ end.
+
+scan_module_name_2([{'-',_},{atom,_,module},{'(',_} | _]=Ts, _Chars) ->
+ scan_module_name_3(Ts);
+scan_module_name_2([{'-',_},{atom,_,_} | _], Chars) ->
+ scan_module_name_1(Chars);
+scan_module_name_2(_, _) ->
+ error.
+
+scan_module_name_3(Ts) ->
+ case erl_parse:parse_form(Ts) of
+ {ok, {attribute,_,module,{M,_}}} -> module_atom(M);
+ {ok, {attribute,_,module,M}} -> module_atom(M);
+ _ -> error
+ end.
+
+module_atom(A) when is_atom(A) -> A;
+module_atom(L) when is_list(L) -> list_to_atom(packages:concat(L)).
+
+%%--Stop interpreting modules-----------------------------------------
+
+del_mod(AbsMod, Dist) ->
+ Mod = if
+ is_atom(AbsMod) -> AbsMod;
+ is_list(AbsMod) ->
+ list_to_atom(filename:basename(AbsMod,".erl"))
+ end,
+ dbg_iserver:safe_cast({delete, Mod}),
+ everywhere(Dist,
+ fun() ->
+ erts_debug:breakpoint({Mod,'_','_'}, false),
+ erlang:yield()
+ end),
+ ok.