Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

import from cvs

git-svn-id: https://distel.googlecode.com/svn/trunk@3 d5f8e727-742a-0410-b67d-dd739a6cc792
  • Loading branch information...
commit 9088cba3ec4b8e5e9512859f7a93bd681c9def33 1 parent a8f577f
Luke Gorrie authored
15 doc/.cvsignore
View
@@ -0,0 +1,15 @@
+distel.aux
+distel.cp
+distel.fn
+distel.fns
+distel.info
+distel.ky
+distel.log
+distel.pdf
+distel.pg
+distel.ps
+distel.toc
+distel.tp
+distel.tps
+distel.vr
+distel.vrs
32 doc/Makefile
View
@@ -0,0 +1,32 @@
+# Interface
+infodir = "/usr/local/share/info" # FIXME: configure
+
+all: info postscript
+
+info: distel.info
+
+postscript: distel.ps
+
+install: distel.info
+ @echo "* Installing Info documentation"
+ cp distel.info ${infodir}
+# Debian's install-info is quirky
+ if [ -f /etc/debian_version ]; then \
+ install-info --info-dir=${infodir} --section Emacs Emacs \
+ distel.info; \
+ else \
+ install-info --info-dir=${infodir} --section Emacs \
+ distel.info; \
+ fi
+
+# Helpers
+distel.info: distel.texi
+ makeinfo -o $@ $<
+
+distel.ps: distel.dvi
+ dvips -o $@ $<
+
+distel.dvi: distel.texi
+ texi2dvi $<
+
+
203 doc/dbg.eps
View
@@ -0,0 +1,203 @@
+%!PS-Adobe-2.0 EPSF-2.0
+%%Title: dbg
+%%Creator: Dia v0.90
+%%CreationDate: Thu Jan 23 21:16:39 2003
+%%For: luke
+%%Magnification: 1.0000
+%%Orientation: Portrait
+%%BoundingBox: 0 0 910 163
+%%Pages: 1
+%%EndComments
+%%BeginProlog
+/cp {closepath} bind def
+/c {curveto} bind def
+/f {fill} bind def
+/a {arc} bind def
+/ef {eofill} bind def
+/ex {exch} bind def
+/gr {grestore} bind def
+/gs {gsave} bind def
+/sa {save} bind def
+/rs {restore} bind def
+/l {lineto} bind def
+/m {moveto} bind def
+/rm {rmoveto} bind def
+/n {newpath} bind def
+/s {stroke} bind def
+/sh {show} bind def
+/slc {setlinecap} bind def
+/slj {setlinejoin} bind def
+/slw {setlinewidth} bind def
+/srgb {setrgbcolor} bind def
+/rot {rotate} bind def
+/sc {scale} bind def
+/sd {setdash} bind def
+/ff {findfont} bind def
+/sf {setfont} bind def
+/scf {scalefont} bind def
+/sw {stringwidth pop} bind def
+/tr {translate} bind def
+
+/ellipsedict 8 dict def
+ellipsedict /mtrx matrix put
+/ellipse
+{ ellipsedict begin
+ /endangle exch def
+ /startangle exch def
+ /yrad exch def
+ /xrad exch def
+ /y exch def
+ /x exch def /savematrix mtrx currentmatrix def
+ x y tr xrad yrad sc
+ 0 0 1 startangle endangle arc
+ savematrix setmatrix
+ end
+} def
+
+/mergeprocs {
+dup length
+3 -1 roll
+dup
+length
+dup
+5 1 roll
+3 -1 roll
+add
+array cvx
+dup
+3 -1 roll
+0 exch
+putinterval
+dup
+4 2 roll
+putinterval
+} bind def
+%%EndProlog
+
+%%BeginSetup
+%%EndSetup
+28.346000 -28.346000 scale
+12.050000 -10.114210 translate
+
+1.000000 1.000000 1.000000 srgb
+n 16.500000 6.500000 3.500000 1.500000 0 360 ellipse f
+0.100000 slw
+[] 0 sd
+[] 0 sd
+0.000000 0.000000 0.000000 srgb
+n 16.500000 6.500000 3.500000 1.500000 0 360 ellipse cp s
+ [ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /O /u /t /space /o /f /s /y /xi /xi /n /c /N /r /m /a
+ /l /I /e /p /d /E /i /S /U /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+ /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi /xi
+] /e0 exch def
+/Courier-Bold_e0 undefinefont
+/Courier-Bold_e0
+ /Courier-Bold findfont
+ dup length dict begin
+ {1 index /FID ne {def} {pop pop} ifelse} forall
+ /Encoding e0 def
+ currentdict end
+definefont pop
+/Courier-Bold_e0 ff 0.800000 scf sf
+( !"#$%#&'*+) sw
+2 div 16.475000 ex sub 6.717100 m ( !"#$%#&'*+)
+ gs 1 -1 sc sh gr
+1.000000 1.000000 1.000000 srgb
+n -8.500000 6.500000 3.500000 1.500000 0 360 ellipse f
+0.100000 slw
+[] 0 sd
+[] 0 sd
+0.000000 0.000000 0.000000 srgb
+n -8.500000 6.500000 3.500000 1.500000 0 360 ellipse cp s
+/Courier-Bold_e0 ff 0.800000 scf sf
+(,$-./0) sw
+2 div -8.500000 ex sub 6.717140 m (,$-./0)
+ gs 1 -1 sc sh gr
+1.000000 1.000000 1.000000 srgb
+n 4.500000 6.500000 3.500000 1.500000 0 360 ellipse f
+0.100000 slw
+[] 0 sd
+[] 0 sd
+0.000000 0.000000 0.000000 srgb
+n 4.500000 6.500000 3.500000 1.500000 0 360 ellipse cp s
+/Courier-Bold_e0 ff 0.800000 scf sf
+(1*"2-3-2"24) sw
+2 div 4.475000 ex sub 6.717140 m (1*"2-3-2"24)
+ gs 1 -1 sc sh gr
+0.100000 slw
+[] 0 sd
+[] 0 sd
+0 slc
+n -2.000000 12.829027 8.414817 8.414817 241.423065 298.576935 ellipse s
+0 slj
+n 1.131254 5.407940 m 2.025130 5.439340 l 1.513925 4.705399 l f
+0.100000 slw
+[] 0 sd
+[] 0 sd
+0 slc
+n 10.499985 10.987682 6.573472 6.573472 237.570342 302.429658 ellipse s
+0 slj
+n 13.135354 5.347949 m 14.025100 5.439340 l 13.564365 4.672709 l f
+0.100000 slw
+[] 0 sd
+[] 0 sd
+0 slc
+n 10.499985 2.012318 6.573472 6.573472 57.570342 122.429658 ellipse s
+0 slj
+n 7.864616 7.652051 m 6.974870 7.560660 l 7.435605 8.327291 l f
+0.100000 slw
+[] 0 sd
+[] 0 sd
+0 slc
+n -2.000000 -0.040176 8.600836 8.600836 62.095911 117.904089 ellipse s
+0 slj
+n -5.130947 7.581561 m -6.025130 7.560660 l -5.505341 8.288547 l f
+0.100000 slw
+[] 0 sd
+[] 0 sd
+0 slc
+n 4.000000 -28.815304 38.879514 38.879514 71.245969 108.754031 ellipse s
+0 slj
+n -7.613872 7.878442 m -8.500000 8.000000 l -7.871076 8.635968 l f
+/Courier_e0 undefinefont
+/Courier_e0
+ /Courier findfont
+ dup length dict begin
+ {1 index /FID ne {def} {pop pop} ifelse} forall
+ /Encoding e0 def
+ currentdict end
+definefont pop
+/Courier_e0 ff 0.800000 scf sf
+(1*"2-3-2") sw
+2 div -2.000000 ex sub 5.314210 m (1*"2-3-2")
+ gs 1 -1 sc sh gr
+/Courier_e0 ff 0.800000 scf sf
+(546") sw
+2 div 10.627200 ex sub 5.582105 m (546")
+ gs 1 -1 sc sh gr
+/Courier_e0 ff 0.800000 scf sf
+(7'*+) sw
+2 div 10.627200 ex sub 7.957105 m (7'*+)
+ gs 1 -1 sc sh gr
+/Courier_e0 ff 0.800000 scf sf
+(8*6*"2-3-2") sw
+2 div -2.000000 ex sub 8.000000 m (8*6*"2-3-2")
+ gs 1 -1 sc sh gr
+/Courier_e0 ff 0.800000 scf sf
+(8*6*"2-3-2") sw
+2 div 4.150000 ex sub 9.764210 m (8*6*"2-3-2")
+ gs 1 -1 sc sh gr
+showpage
641 doc/distel.texi
View
@@ -0,0 +1,641 @@
+\input texinfo @c -*-texinfo-*-
+@c %**start of header
+@setfilename distel.info
+@settitle Distel User Manual
+@c %**end of header
+
+@set EDITION Distel Jungerl
+@set UPDATED $Date: 2005/02/20 22:10:15 $
+@set VERSION Jungerl
+
+@titlepage
+@title Distel User Manual
+@subtitle @value{EDITION}, updated @value{UPDATED}
+@author Luke Gorrie
+@end titlepage
+
+@dircategory Emacs
+@direntry
+* Distel: (distel). Erlang development environment.
+@end direntry
+
+@c @setchapternewpage off
+
+@contents
+
+@node Top, Introduction, (dir), (dir)
+@comment node-name, next, previous, up
+
+@noindent
+
+@ifinfo
+This is the user manual for Distel.
+@end ifinfo
+
+@menu
+* Introduction::
+* Programming Aids::
+* Applications::
+
+@detailmenu
+ --- The Detailed Node Listing ---
+
+Introduction
+
+* Principles:: The underlying model of operation.
+* Conventions:: Common conventions for commands.
+
+Programming Aids
+
+* Tags:: Looking up function definitions.
+* Completion:: Completing module and function names.
+* Evaluation:: Evaluating snippets and reloading modules.
+* Refactoring::
+* Documentation:: Looking up online documentation.
+
+Applications
+
+* Process Manager:: See and manipulate Erlang processes.
+* Debugger:: Debug Erlang programs.
+* Interactive Sessions:: Hybrid Emacs buffer / Erlang shell.
+* Profiler:: Profile with fprof.
+
+Debugger
+
+* Basic Commands:: Interpreting modules and setting breakpoints.
+* Monitor Buffer:: Monitoring interpreted processes.
+* Attach Buffer:: Single-stepping a process.
+* Synchronising Breakpoints:: Resynchronising breakpoints after edits.
+* Saving and Restoring:: Temporarily saving debugging state.
+
+@end detailmenu
+@end menu
+
+@node Introduction, Programming Aids, Top, Top
+@chapter Introduction
+
+Distel is a set of Emacs-based programs for interacting with running
+Erlang nodes. The purpose is to make everyday Erlang programming tasks
+easier -- looking up function definitions, debugging and profiling,
+experimenting with code snippets, and so on. It builds on the existing
+@code{erlang-mode} to provide more of the features common to
+Integrated Development Environments.
+
+This manual describes Distel from the user's point of view. For
+details on how Distel works and how to write your own Distel-based
+programs, see the paper @cite{Distel: Distributed Emacs Lisp (for
+Erlang)} from the proceedings of the 2002 Erlang User Conference. The
+paper is also available from the Distel home page,
+@url{http://distel.sourceforge.net}
+
+@ifinfo
+The overall principles of Distel's operation and use are described in
+the following sections.
+@end ifinfo
+
+@menu
+* Principles:: The underlying model of operation.
+* Conventions:: Common conventions for commands.
+@end menu
+
+@node Principles, Conventions, Introduction, Introduction
+@section Principles of Operation
+
+Distel works by talking to Erlang nodes using the Erlang distribution
+protocol. It creates an ``Emacs node,'' similar to the ``C nodes'' of
+@code{erl_interface}, and then talks directly to Erlang nodes with RPC
+and other forms of message-passing.
+
+Of course, this means that to use Distel you need to have an Erlang
+node running. The node should be able to load some supporting modules
+for Distel to make RPCs to -- setting this up is simple, and described
+in the @file{INSTALL} file in the distribution. Other aspects of the
+node's setup, such as which other modules it can find, will also
+affect Distel's operation. More on this in the relevant sections.
+
+@node Conventions, , Principles, Introduction
+@section Conventions of Use
+
+Most Distel commands need to know which Erlang node to talk to.
+(Distel doesn't start an Erlang node, you have to do that yourself.)
+The first command you use will prompt in the minibuffer for the name
+of the node to use. You can answer with either a @code{name@@host}
+node name, or with just the @code{name} part as an abbreviation for a
+node on the local machine.
+
+As a convenience, the node name you enter is cached and then reused in
+future commands. If you later want to talk to a different node you can
+use the command @code{erl-choose-nodename} (@kbd{C-c C-d n}) to select
+a new node to talk to. The currently cached node name is always shown
+in the modeline.
+
+Some commands accept a prefix argument to alter their behaviour in
+some specific way. You can give a prefix with @kbd{C-u} followed by
+the command you want to call. For example, @kbd{C-u M-.} tells the
+@kbd{M-.} command to prompt for the function to lookup, instead
+choosing one by looking at the source text in the buffer. The effect,
+if any, of a prefix on a command is included in the command's
+documentation.
+
+@node Programming Aids, Applications, Introduction, Top
+@chapter Programming Aids
+
+Distel includes a few small sets of related commands to automate
+common programming chores. These are described in the following
+sections.
+
+@menu
+* Tags:: Looking up function definitions.
+* Completion:: Completing module and function names.
+* Evaluation:: Evaluating snippets and reloading modules.
+* Refactoring::
+* Documentation:: Looking up online documentation.
+@end menu
+
+@node Tags, Completion, Programming Aids, Programming Aids
+@section Cross-Referencing (Tags)
+
+A ``dynamic tags'' facility effectively makes each function call in an
+Erlang source file into a hyperlink to the definition of that
+function. For example, if you have a line of code like this:
+
+@example
+lists:keysort(2, L).
+@end example
+
+You can place the point on the function name, press @kbd{M-.}, and up
+pops @file{lists.erl} at the definition of @code{keysort/2}. After you
+have perused the definition to your satisfaction, you press @kbd{M-,}
+to jump back where you came from. You can also jump through several
+(sub)function definitions and then use @kbd{M-,} several times to
+unwind step-by-step back to where you came from.
+
+This feature is a dynamic version of a traditional Emacs facility
+called ``Tags.'' Whereas Tags needs you to maintain a special
+@file{TAGS} file to keep track of definitions, Distel simply asks an
+Erlang node, ``Where is the source file for module @code{foo}?'' The
+Erlang node makes a well-educated guess at which source file we want
+(based on the location and attributes of the beam file for the same
+module), and sends back the path. Emacs then opens the file and scans
+down to the definition of the function with the right arity.
+
+If you have several versions of the same source file (perhaps
+belonging to separate branches in revision control), then Distel will
+find the one that matches the code in the Erlang node you're talking
+to. So, to work on a particular source tree you just connect to a node
+that has the matching code in its code path.
+
+@table @kbd
+
+@item M-.
+Jump from a function call to the definition of the function
+(@code{erl-find-source-under-point}). If the variable
+@code{distel-tags-compliant} is non-nil, or a prefix argument is
+given, this command prompts for the function name to lookup.
+
+@item M-,
+Jump back from a function definition
+(@code{erl-find-source-unwind}). This is a multi-level unwind through
+a stack of positions from which you have jumped with @kbd{M-.} The
+command is also bound to @kbd{M-*} for consistency with ``etags.''
+@end table
+
+To actually find the source file for a particular module, the Erlang
+node first ensures that it can load the module, and then tries each of
+these locations in order:
+
+@enumerate
+@item
+Same directory as the beam file.
+@item
+@file{../src/} from the beam file.
+@item
+@file{../erl/} from the beam file.
+@item
+The directory from which the beam file was compiled. We can find this
+using @code{module_info/1}, because the compiler records it as an
+attribute in the beam file.
+@end enumerate
+
+@node Completion, Evaluation, Tags, Programming Aids
+@section Completion of Modules and Functions
+
+Completion allows you to write some part of a module or remote
+function name and then press @kbd{M-TAB} to have it completed
+automatically. When multiple completions exist they are displayed in a
+popup buffer, much like Emacs's normal filename completion. The
+completion buffer can simply be read to see which completions exist,
+or either @kbd{RET} or the middle mouse button can be used to select
+one.
+
+@table @kbd
+
+@item M-TAB
+Complete the module or function at point. (@code{erl-complete})
+
+@item M-?
+Alternative binding for @code{erl-complete}, since @kbd{M-TAB} is
+often reserved by window managers.
+@end table
+
+@node Evaluation, Refactoring, Completion, Programming Aids
+@section Evaluting Erlang Code
+
+Distel includes some simple ways to evaluate Erlang code, described
+here. More elaborate interactive evaluation is provided by Interactive
+Sessions (@pxref{Interactive Sessions}).
+
+@table @kbd
+
+@item C-c C-d :
+Read an Erlang expression from the minibuffer, evaluate it on an
+Erlang node, and show the result. (@code{erl-eval-expression})
+
+@item C-c C-d L
+Read a module name from the minibuffer and reload that module in an
+Erlang node. (@code{erl-reload-module})
+@end table
+
+@node Refactoring, Documentation, Evaluation, Programming Aids
+@section Refactoring
+
+@emph{The Refactoring feature requires the syntax-tools package to be
+in the Erlang node's code path. You can download syntax-tools from the
+erlang.org ``User Contributions'' area.}
+
+Expressions within functions can be automatically ``refactored'' into
+their own sub-functions by using the @code{erl-refactor-subfunction}
+command (@kbd{C-c C-d f}). The command takes the text of the
+expression, determines which variables it needs from the original
+function, and then generates the new function and puts it on the kill
+ring for insertion by hand (with @code{yank}, @kbd{C-y}). The original
+function is rewritten with a call to the subfunction where the
+refactored expression used to be.
+
+For example, suppose we want to refactor the following function:
+
+@example
+eval_expression(S) ->
+ case parse_expr(S) of
+ @{ok, Parse@} ->
+ case catch erl_eval:exprs(Parse, []) of
+ @{value, V, _@} ->
+ @{ok, flatten(io_lib:format("~p", [V]))@};
+ @{'EXIT', Reason@} ->
+ @{error, Reason@}
+ end;
+ @{error, @{_, erl_parse, Err@}@} ->
+ @{error, Err@}
+ end.
+@end example
+
+In this example we will take the inner @code{case} expression and move
+it into a new function called @code{try_evaluation}. We do this by
+setting the Emacs region (using the mouse or @kbd{C-SPC}) from the
+word @code{case} until the end of the word @code{end} -- marking
+exactly one whole expression. We then enter @kbd{C-c C-d f} to
+refactor, and when prompted for the function name we respond with
+``@code{try_evaluation}''. The original function is then rewritten to:
+
+@example
+eval_expression(S) ->
+ case parse_expr(S) of
+ @{ok, Parse@} ->
+ try_evaluation(Parse);
+ @{error, @{_, erl_parse, Err@}@} ->
+ @{error, Err@}
+ end.
+@end example
+
+And at the front of the kill ring we have the new function definition,
+which can be pasted into the buffer wherever we want. The actual
+definition we get is:
+
+@example
+try_evaluation(Parse) ->
+ case catch erl_eval:exprs(Parse, []) of
+ @{value, V, _@} ->
+ @{ok, flatten(io_lib:format("~p", [V]))@};
+ @{'EXIT', Reason@} ->
+ @{error, Reason@}
+ end.
+@end example
+
+@strong{Important note:} This command is not a ``pure'' refactoring,
+because although it will import variables from the parent function
+into the subfunction, it will not export new bindings created in the
+subfunction back to the parent. However, if you follow good
+programming practice and never ``export'' variables from inner
+expressions, this is not a problem. An example of @emph{bad} code that
+will not refactor correctly is this @code{if} expression:
+
+@example
+if A < B -> X = true;
+ B > A -> X = false
+end,
+foo(X)
+@end example
+
+This is in poor style -- a variable created inside the @code{if} is
+used by code at an outer level of nesting. To work with refactoring,
+and to be in better style, it should be rewritten like this:
+
+@example
+X = if A < B -> true;
+ B > A -> false
+ end,
+foo(X)
+@end example
+
+@node Documentation, , Refactoring, Programming Aids
+@section Documentation
+
+Simple online Erlang documentation is provided via an Erlang program
+called @code{fdoc}. The documentation is automatically scanned out of
+source files by building a searchable database of the comments
+appearing before each function. Naturally, the quality of
+documentation provided by this scheme will depend on the style in
+which the source files are commented.
+
+@table @kbd
+@item C-c C-d d
+Describe an Erlang module or function by
+name. (@code{erl-fdoc-describe})
+@item C-c C-d a
+Show apropos information about Erlang functions, by regular
+expression. All functions whose names or comments match the regexp are
+displayed. (@code{erl-fdoc-apropos})
+@end table
+
+With a prefix argument, these commands rebuild the @code{fdoc}
+database before searching. This is useful after (re)loading a lot of
+modules, since @code{fdoc} only scans the currently loaded modules for
+documentation when it builds the database.
+
+@node Applications, , Programming Aids, Top
+@chapter Applications
+
+This chapter describes the larger applications included with Distel.
+
+@menu
+* Process Manager:: See and manipulate Erlang processes.
+* Debugger:: Debug Erlang programs.
+* Interactive Sessions:: Hybrid Emacs buffer / Erlang shell.
+* Profiler:: Profile with fprof.
+@end menu
+
+@node Process Manager, Debugger, Applications, Applications
+@section Process Manager
+
+The process manager displays a list of all the processes running on an
+Erlang node, and offers commands to manipulate them.
+
+@table @kbd
+@item C-c C-d l
+Popup a process manager buffer. (@code{erl-process-list})
+@end table
+
+Within the process manager's buffer, the following commands are
+available:
+
+@table @kbd
+@item q
+Quit the process manager, and restore the Emacs windows as they were
+before it popped up.
+@item u
+Update the process list.
+@item k
+Kill a process.
+@item RET
+Pop up a buffer showing all the information about a process. The
+buffer also continuously traces the process by appending events to the
+buffer, until the buffer is killed with @kbd{q}.
+@item i
+Show a piece of information about the process, specified by name. The
+name can be any key accepted by the @code{process_info/2} BIF.
+@item b
+Show a backtrace for a process. The backtrace is a fairly low-level
+snapshot of the stack of a process, obtained from
+@code{process_info(P, backtrace)}. It may take a little pratice to
+learn how to read them.
+@item m
+Show the contents of a process's mailbox.
+@end table
+
+@node Debugger, Interactive Sessions, Process Manager, Applications
+@section Debugger
+Distel includes a front-end to the Erlang debugger, using the same
+backend as the standard Tk-based OTP debugger. The Distel debugger has
+three parts: commands in Erlang source buffers for interpreting
+modules and configuring breakpoints, a ``Monitor'' buffer listing
+processes running interpreted code, and one ``Attach'' buffer for each
+process that is being single-stepped.
+
+@menu
+* Basic Commands:: Interpreting modules and setting breakpoints.
+* Monitor Buffer:: Monitoring interpreted processes.
+* Attach Buffer:: Single-stepping a process.
+* Synchronising Breakpoints:: Resynchronising breakpoints after edits.
+* Saving and Restoring:: Temporarily saving debugging state.
+@end menu
+
+@node Basic Commands, Monitor Buffer, Debugger, Debugger
+@subsection Basic Commands
+
+@table @kbd
+@item C-c C-d i
+Toggle interpretedness of the current buffer's module.
+(@code{edb-toggle-interpret})
+@item C-x SPC
+Toggle a breakpoint on the current line.
+(@code{edb-toggle-breakpoint})
+@item C-c C-d m
+Popup the Monitor buffer. (@code{edb-monitor})
+@end table
+
+@node Monitor Buffer, Attach Buffer, Basic Commands, Debugger
+@subsection Monitor Buffer
+
+The monitor buffer displays all processes that the debugger knows
+about, line-by-line. This includes all processes that have run
+interpreted code, and all that are stopped in breakpoints. The current
+status of each process is shown -- running, at breakpoint, or
+exited. You can attach to a debugged process by pressing @kbd{RET} on
+its summary line.
+
+@table @kbd
+@item RET
+Popup an attach buffer for a process.
+@item q
+Hide the Monitor buffer and restore Emacs' window configuration to the
+way it was before.
+@item k
+Kill the monitor. This disconnects Emacs from the Erlang node's
+debugging state and deletes all the local debugging state
+(e.g. breakpoints in buffers.) The next debugger command will
+automatically re-attach the monitor.
+@end table
+
+@node Attach Buffer, Synchronising Breakpoints, Monitor Buffer, Debugger
+@subsection Attach Buffer
+
+An attach buffer corresponds to a particular Erlang process that is
+being debugged. It displays the source to the module currently being
+executed and, when the process is stopped at a breakpoint, an arrow
+showing the next line of execution. The attach buffer is accompanied
+by a buffer showing the variable bindings in the current stack frame.
+
+@table @kbd
+@item SPC
+Step into the next expression. If the expression is a function call,
+the debugger will enter that function. (@code{edb-attach-step})
+@item n
+Step over the next expression, without going down into a subfunction.
+(@code{edb-attach-next})
+@item c
+Continue execution until the next breakpoint.
+(@code{edb-attach-continue})
+@item u
+Show the previous stack frame. (@code{edb-attach-up})
+@item d
+Show the next stack frame. (@code{edb-attach-down})
+@item b
+Toggle a breakpoint on the current line.
+(@code{edb-toggle-breakpoint})
+@item q
+Kill the attach buffer. This does not affect the actual Erlang
+process.
+@item h
+Display online help, showing essentially this information.
+(@code{edb-attach-help})
+@end table
+
+@node Synchronising Breakpoints, Saving and Restoring, Attach Buffer, Debugger
+@subsection Synchronising Breakpoints
+
+At any point in time the breakpoints in a particular buffer will be
+either ``fresh'' or ``stale,'' depending on whether the buffer has
+been modified. Breakpoints are fresh when, as far as Emacs knows, the
+buffer's source text (line numbers) correspond with the code in the
+Erlang node. After the buffer is modified, the breakpoints become
+stale, because edits may change line numbers so that the breakpoints
+in Emacs no longer correspond with the actual program. Stale
+breakpoints are made fresh by using the @code{edb-synch-breakpoints}
+(@kbd{C-c C-d s}) command to reassert that they are correct. This
+command is typically used after recompiling and reloading the module.
+
+Fresh breakpoints are marked in red, stale breakpoints are marked in
+purple.
+
+@table @kbd
+@item C-c C-d s
+Synchronise breakpoints by discarding the ones in the Erlang node and
+then re-setting them from those in the Emacs buffer.
+@end table
+
+@iftex
+The overall debugger state machine for Erlang-mode buffers is shown in
+this figure:
+
+@image{dbg, 6in}
+
+In the ``Normal'' state, no breakpoints exist. In the ``Interpreted''
+state, all breakpoints are fresh. In the ``Out of sync'' state, all
+breakpoints are stale. The transitions illustrate how you can navigate
+between the states.
+@end iftex
+
+Care must be taken to only synchronise breakpoints when the Erlang
+node is actually running the same code that is in the Emacs
+buffer. Otherwise, the Erlang processes may break in unexpected
+places.
+
+When reloading modules during debugging, it is preferable to use the
+@code{erl-reload-module} command (@kbd{C-c C-d L}, @pxref{Evaluation})
+than to call @code{l(mymodule)} directly in the Erlang shell. This is
+because the Distel command is specially coded to make sure reloading
+interpreted modules keeps them interpreted, but this doesn't appear to
+work correctly in the Erlang shell.
+
+@node Saving and Restoring, , Synchronising Breakpoints, Debugger
+@subsection Saving and Restoring Debugger State
+
+The overall debugger state (set of breakpoints and interpreted
+modules) can be temporarily saved inside Emacs and then restored to
+the Erlang node. This is particularly useful when you want to restart
+the Erlang node and then continue debugging where you left off: you
+just save the debug state, restart the node, and then restore.
+
+@table @kbd
+@item C-c C-d S
+Save the set of breakpoints and interpreted modules inside
+Emacs. (@code{edb-save-dbg-state})
+@item C-c C-d R
+Restore Emacs's saved debugger state to the Erlang
+node. (@code{edb-restore-dbg-state})
+@end table
+
+@node Interactive Sessions, Profiler, Debugger, Applications
+@section Interactive Sessions
+
+Interactive sessions are an Erlang version of the Emacs Lisp
+@file{*scratch*} buffer. You can enter arbitrary Erlang expressions
+and function definitions in an interactive session buffer and evaluate
+them immediately, without creating any files.
+
+@table @kbd
+@item C-c C-d e
+Display the Interactive Session buffer for an Erlang node, creating it
+if necessary. (@code{erl-ie-show-session})
+@end table
+
+Within the session buffer, these commands are available:
+
+@table @kbd
+@item C-j
+Evaluate the Erlang expression on the current line, and insert the
+result in-line. (@code{erl-ie-eval-expression})
+@item C-M-x
+Evaluate the function definition before the point. Once defined, the
+function can then be called from expressions in the session buffer,
+and can be redefined later.
+@end table
+
+@node Profiler, , Interactive Sessions, Applications
+@section Profiler
+
+Distel supports profiling function calls via the OTP @code{fprof}
+application. This is a very convenient profiler, in that it doesn't
+require any special compiler options or initialisation -- you can use
+it whenever you want.
+
+@table @kbd
+@item C-c C-d p
+Prompt for an Erlang expression, evaluate it with profiling, and then
+summarise the results. (@code{fprof})
+@item C-c C-d P
+Load and display prerecorded profiler data, from a file created by
+@code{fprof:analyse/1}. (@code{fprof-analyse})
+@end table
+
+After an expression is profiled, the results are popped up in
+``profiler results'' buffer. The buffer contains one line to describe
+each function that was called, with the following columns:
+
+@table @code
+@item Calls
+The total number of times the function was called.
+@item ACC
+The total time (ms) spent in the function, including its callees.
+@item Own
+The total time (ms) spent by the function itself, excluding time spent
+in its callees.
+@end table
+
+Furthermore, pressing @kbd{RET} on a summary line in the results
+buffer will pop up another buffer showing more information about the
+function: how much time it spent on behalf of each of its callers, and
+how much time it spent in each of its subfunctions.
+
+@bye
+
1  doc/short-desc
View
@@ -0,0 +1 @@
+Emacs add-on for communication with Erlang nodes (+ dev tools).
1  ebin/.cvsignore
View
@@ -0,0 +1 @@
+*.beam
4 elisp/.cvsignore
View
@@ -0,0 +1,4 @@
+dilber-util.el
+dilber.el
+erl-test-init.el
+semantic.cache
518 elisp/derl.el
View
@@ -0,0 +1,518 @@
+;;; derl.el --- Distributed Erlang networking code.
+
+;;; Commentary:
+;;
+;; This module implements a useful subset of the Erlang distribution
+;; protocol, and provides a small API for sending messages to remote
+;; nodes.
+
+(require 'net-fsm)
+(require 'epmd)
+(require 'erlext)
+(require 'md5)
+(eval-when-compile
+ (require 'cl))
+
+(defvar erl-nodeup-hook nil
+ "Called with two args, NODE and FSM. NODE is a string of the form
+\"mynode@cockatoo\", FSM is the net-fsm process of the connection.")
+
+(defvar erl-nodedown-hook nil
+ "Called with one arg, NODE, a string of the form \"mynode@cockatoo\"")
+
+(defcustom derl-use-trace-buffer t
+ "*Store erlang message communication in a trace buffer."
+ :type 'boolean
+ :group 'distel)
+
+(defvar derl-cookie nil
+ "*Cookie to use in distributed erlang connections, or NIL.
+When NIL, we read ~/.erlang.cookie.")
+
+;; Local variables
+
+(make-variable-buffer-local
+ (defvar derl-connection-node nil
+ "Local variable recording the node name of the connection."))
+
+(make-variable-buffer-local
+ (defvar derl-hdrlen 2
+ "Size in bytes of length headers of packets. Set to 2 during
+handshake, 4 when connected."))
+
+(make-variable-buffer-local
+ (defvar derl-alive nil
+ "Local variable set to t after handshaking."))
+
+(make-variable-buffer-local
+ (defvar derl-shutting-down nil
+ "Set to T during shutdown, when no longer servicing requests."))
+
+(make-variable-buffer-local
+ (defvar derl-request-queue nil
+ "Messages waiting to be sent to node."))
+
+(make-variable-buffer-local
+ (defvar derl-remote-links '()
+ "List of (LOCAL-PID . REMOTE-PID) for all distributed links (per-node.)
+Used for sending exit signals when the node goes down."))
+
+;; Optional feature flags
+(defconst derl-flag-published #x01)
+(defconst derl-flag-atom-cache #x02)
+(defconst derl-flag-extended-references #x04)
+(defconst derl-flag-dist-monitor #x08)
+(defconst derl-flag-fun-tags #x10)
+(defconst derl-flag-dist-monitor-name #x20)
+(defconst derl-flag-hidden-atom-cache #x40)
+(defconst derl-flag-new-fun-tags #x80)
+(defconst derl-flag-extended-pids-ports #x100)
+
+;; ------------------------------------------------------------
+;; External API
+;; ------------------------------------------------------------
+
+(defun erl-connect (node)
+ "Asynchronously connect to NODE. If the connection succeeds,
+`erl-nodeup-hook' is run. If the connection fails, or goes down
+some time later, `erl-nodedown-hook' is run."
+ (when (eq node erl-node-name)
+ (error "Remote node has the same node name as Emacs: %S" node))
+ (let* ((name (derl-node-name node))
+ (host (derl-node-host node))
+ (buffer (get-buffer-create (derl-buffer-name node)))
+ ;; faking a closure with backtick. fun eh?
+ ;; NB: (funcall '(lambda () 1))
+ ;; => 1
+ ;; (let ((n 1)) `(lambda () ,n))
+ ;; => (lambda () 1)
+ (fail-cont `(lambda ()
+ (kill-buffer ,buffer)
+ (derl-nodedown ',node))))
+ (epmd-port-please name host
+ ;; success continuation
+ `(lambda (port)
+ (fsm-connect ,host port #'derl-state0 ',node
+ nil
+ ,fail-cont
+ ,buffer))
+ fail-cont)))
+
+(defun erl-dist-send (pid msg)
+ "Send a message to a process on a remote node."
+ (derl-dist-request (erl-pid-node pid) #'derl-send pid msg))
+
+(defun erl-dist-reg-send (node name msg)
+ "Send a message to a registered process on a remote node."
+ (derl-dist-request node #'derl-reg-send erl-self name msg))
+
+(defun erl-dist-link (pid)
+ "Link the current process with the remote PID."
+ (derl-dist-request (erl-pid-node pid) #'derl-link erl-self pid))
+
+(defun erl-dist-unlink (pid)
+ "Link the current process with the remote PID."
+ (derl-dist-request (erl-pid-node pid) #'derl-unlink erl-self pid))
+
+(defun erl-dist-exit (from to reason)
+ "Send an exit signal to a remote process."
+ (derl-dist-request (erl-pid-node to) #'derl-exit from to reason))
+
+(defun erl-dist-exit2 (from to reason)
+ "Send an `exit2' signal to a remote process.
+Use the distribution protocol's EXIT2 message."
+ ;; I don't know exactly how EXIT2 differs from EXIT. Browsing the
+ ;; emulator code, it looks like EXIT is for propagating a process
+ ;; crash, and EXIT2 is for the exit/2 BIF (where FROM isn't
+ ;; necessarily linked with TO).
+ (derl-dist-request (erl-pid-node to) #'derl-exit2 from to reason))
+
+;; -----------------------------------------------------------
+;; Handshake protocol states. These follow the protocol diagram in
+;; the distributed_handshake.txt file of lib/kernel/internal_doc/ in
+;; Erlang/OTP.
+;; -----------------------------------------------------------
+
+(defun derl-state0 (event node-name)
+ "Start state: send-name and then transition."
+ (check-event event 'init)
+ (setq derl-connection-node node-name)
+ (setq fsm-put-data-in-buffer t)
+ ;; Do nodedown when the buffer is killed in an unexpected way
+ ;; (e.g. by user)
+ (add-hook 'kill-buffer-hook
+ (lambda () (when derl-alive (derl-nodedown derl-connection-node))))
+ (derl-send-name)
+ (fsm-change-state #'derl-recv-status))
+
+(defun derl-recv-status (event data)
+ "Wait for status message."
+ (check-event event 'data)
+ (let ((msg (derl-take-msg)))
+ (when msg
+ (if (string= msg "sok")
+ (fsm-change-state #'derl-recv-challenge t)
+ (fsm-fail)))))
+
+(defun derl-recv-challenge (event data)
+ "Receive challenge message, send response and our challenge."
+ (check-event event 'data)
+ (when (derl-have-msg)
+ (goto-char (point-min))
+ (erlext-read2) ; skip length
+ (let ((tag (erlext-read1)))
+ (unless (equal 110 tag) ; tag-check (n)
+ (fsm-fail (format nil "wrong-tag: %S" tag))))
+ (let ((version (erlext-read2))
+ (flags (erlext-read4))
+ (challenge (erlext-readn 4))
+ (rem-node (buffer-substring (point) (derl-msg-end))))
+ (derl-eat-msg)
+ (derl-send-challenge-reply challenge)
+ (fsm-change-state #'derl-recv-challenge-ack))))
+
+(defun derl-recv-challenge-ack (event data)
+ "Receive and check challenge ack. If it's OK then the handshake is
+complete and we become live."
+ (check-event event 'data)
+ (when (derl-have-msg)
+ (goto-char (point-min))
+ (erlext-read2) ; skip length
+ (unless (equal 97 (erlext-read1)) ; tag-check (a)
+ (fsm-fail 'wrong-tag))
+ (let ((digest (buffer-substring (point) (+ (point) 16))))
+ (derl-eat-msg)
+ (if (equal (derl-gen-digest (string 0 0 0 42)) digest)
+ (derl-go-live)
+ (fsm-fail)))))
+
+;; Handshake support code
+
+(defun derl-send-name ()
+ (erase-buffer)
+ (derl-send-msg
+ (fsm-build-message
+ (fsm-encode1 110) ; tag (n)
+ (fsm-encode2 5) ; version
+ (fsm-encode4 (logior derl-flag-extended-references
+ derl-flag-extended-pids-ports))
+ (fsm-insert (symbol-name erl-node-name)))))
+
+(defun derl-send-challenge-reply (challenge)
+ (derl-send-msg (fsm-build-message
+ (fsm-encode1 114) ; 114 = ?r
+ (fsm-encode4 42)
+ (fsm-insert (derl-gen-digest challenge)))))
+
+(defun derl-gen-digest (challenge)
+ "Generate a message digest as required for the specification's
+gen_digest() function:
+ (md5 (concat challenge-as-ascii-decimal cookie))"
+ (derl-hexstring-to-binstring
+ (md5 (concat (erl-cookie) (derl-int32-to-decimal challenge)))))
+
+(defun erl-cookie ()
+ (or derl-cookie
+ (with-temp-buffer
+ (insert-file-contents (concat (getenv "HOME") "/.erlang.cookie"))
+ (while (search-forward "\n" nil t)
+ (replace-match ""))
+ (buffer-string))))
+
+;; ------------------------------------------------------------
+;; Alive/connected state
+;; ------------------------------------------------------------
+
+(defun derl-go-live ()
+ (setq derl-alive t)
+ (setq derl-hdrlen 4)
+ (derl-nodeup derl-connection-node)
+ (mapc #'derl-do-request derl-request-queue)
+ (setq derl-request-queue nil)
+ (fsm-change-state #'derl-alive t))
+
+(defun derl-alive (event data)
+ (check-event event 'data 'closed)
+ (if (eq event 'closed)
+ (progn (derl-nodedown derl-connection-node)
+ (setq derl-alive nil)
+ (fsm-fail))
+ (while (derl-handle-tick))
+ (when (derl-have-msg)
+ (let ((msg (derl-take-msg))
+ ctl
+ req)
+ ;; Decode the control message, and the request if it's present
+ (let (default-enable-multibyte-characters)
+ (with-temp-buffer
+ (insert msg)
+ (goto-char (point-min))
+ (assert (= (erlext-read1) 112)) ; type = pass through..
+ (setq ctl (erlext-read-whole-obj))
+ (when (< (point) (point-max))
+ (setq req (erlext-read-whole-obj)))))
+ (ecase (tuple-elt ctl 1)
+ ((1) ;; link: [1 FROM TO]
+ (let ((from (tuple-elt ctl 2))
+ (to (tuple-elt ctl 3)))
+ (derl-trace-input "LINK: %S %S" from to)
+ (add-to-list 'derl-remote-links (cons to from))
+ (erl-add-link to from)))
+ ((2) ;; send: [2 COOKIE TO-PID]
+ (let ((to-pid (tuple-elt ctl 3)))
+ (derl-trace-input "SEND: %S %S" to-pid req)
+ (erl-send to-pid req)))
+ ((3) ;; exit: [FROM TO REASON]
+ (let ((from (tuple-elt ctl 1))
+ (to (tuple-elt ctl 2))
+ (rsn (tuple-elt ctl 3)))
+ (derl-trace-input "EXIT: %S %S %S" from to rsn)
+ (erl-send-exit from to rsn)))
+ ((4) ;; unlink: [4 FROM TO]
+ (let ((from (tuple-elt ctl 2))
+ (to (tuple-elt ctl 3)))
+ (derl-trace-input "UNLINK: %S %S %S" from to)
+ (erl-remove-link to from)))
+ ((6) ;; reg_send: [6 FROM COOKIE NAME]
+ (let ((from (tuple-elt ctl 2))
+ (name (tuple-elt ctl 4)))
+ (derl-trace-input "REG_SEND: %S %S %S" from name req)
+ (condition-case data
+ (erl-send name req)
+ (erl-exit-signal
+ ;; Ignore the error if the name isn't registered -
+ ;; that's what the real nodes do. Seems reasonable,
+ ;; since the send is async, and who knows what the
+ ;; sender is up to now.
+ t))))))
+ ;; Recursively handle other messages
+ (fsm-event 'data 'continue))))
+
+(defun derl-handle-tick ()
+ (when (derl-have-tick)
+ (derl-eat-msg)
+ (derl-send-msg "")
+ t))
+
+(defun derl-have-tick ()
+ (goto-char (point-min))
+ (and (>= (buffer-size) derl-hdrlen)
+ (= 0 (erlext-read4))))
+
+;; ------------------------------------------------------------
+;; Message buffer helpers
+;; ------------------------------------------------------------
+
+(defun derl-send-msg (string)
+ "Send a message (with a length header)."
+ (fsm-send-string (fsm-build-message
+ (fsm-encode (length string) derl-hdrlen)
+ (fsm-insert string))))
+
+(defun derl-take-msg ()
+ "Read and return a message, removing it from the input buffer. If no
+complete message is available, nil is returned and the buffer isn't
+modified."
+ (when (derl-have-msg)
+ (goto-char (point-min))
+ (let* ((length (erlext-read derl-hdrlen))
+ (start (point))
+ (end (+ start length)))
+ (prog1 (buffer-substring start end)
+ (derl-eat-msg)))))
+
+(defun derl-have-msg ()
+ (goto-char (point-min))
+ (when (>= (buffer-size) derl-hdrlen)
+ (let ((len (erlext-read derl-hdrlen)))
+ (>= (buffer-size) (+ derl-hdrlen len)))))
+
+(defun derl-msg-end ()
+ (goto-char (point-min))
+ (+ (point-min) derl-hdrlen (erlext-read derl-hdrlen)))
+
+(defun derl-eat-msg ()
+ (delete-region (point-min) (derl-msg-end)))
+
+;; ------------------------------------------------------------
+;; Distributed erlang protocol requests
+;; ------------------------------------------------------------
+
+(defun derl-dist-request (node &rest request)
+ "Make REQUEST to NODE. If the node isn't live, a connection is
+initiated if necessary and the request is queued."
+ (let ((derl-bufname (derl-buffer-name node)))
+ (unless (get-buffer derl-bufname)
+ (erl-connect node))
+ (with-current-buffer derl-bufname
+ (cond (derl-shutting-down
+ nil)
+ (derl-alive
+ (derl-do-request request))
+ (t
+ (push request derl-request-queue))))))
+
+(defun derl-do-request (req)
+ (apply (car req) (cdr req)))
+
+(defun derl-send (pid msg)
+ (derl-trace-output "SEND: %S %S" pid msg)
+ (derl-send-request (tuple 2 empty-symbol pid) msg))
+
+(defun derl-reg-send (from to term)
+ (derl-trace-output "REG_SEND: %S %S %S" from to term)
+ (derl-send-request (tuple 6 from empty-symbol to) term))
+
+(defun derl-link (from to)
+ (derl-trace-output "LINK: %S %S" from to)
+ (add-to-list 'derl-remote-links (cons from to))
+ (derl-send-request (tuple 1 from to) nil t))
+
+(defun derl-unlink (from to)
+ (derl-trace-output "UNLINK: %S %S" from to)
+ (derl-send-request (tuple 4 from to) nil t))
+
+(defun derl-exit (from to reason)
+ (derl-trace-output "EXIT: %S %S %S" from to reason)
+ (derl-send-request (tuple 3 from to reason) nil t))
+
+(defun derl-exit2 (from to reason)
+ (derl-trace-output "EXIT2: %S %S %S" from to reason)
+ (derl-send-request (tuple 8 from to reason) nil t))
+
+(defun derl-send-request (control message &optional skip-message)
+ (let* ((ctl (erlext-term-to-binary control))
+ (msg (if skip-message "" (erlext-term-to-binary message)))
+ (len (+ 1 (length ctl) (length msg))))
+ (fsm-send-string
+ (fsm-build-message
+ (fsm-encode4 len)
+ (fsm-encode1 121) ; type = pass-through (whatever that means..)
+ (fsm-insert ctl)
+ (fsm-insert msg)))))
+
+;; Tracing
+
+(defface derl-trace-output-face
+ '((t (:inherit font-lock-string-face)))
+ "Face for outgoing messages in the distributed erlang trace
+buffer.")
+
+(defface derl-trace-input-face
+ '((t (:inherit font-lock-comment-face)))
+ "Face for incoming messages in the distributed erlang trace
+buffer.")
+
+(defun derl-trace-output (fmt &rest args)
+ (let ((msg (format ">> %s" (apply #'format (cons fmt args)))))
+ (put-text-property 0 (length msg) 'face 'derl-trace-output-face msg)
+ (derl-trace msg)))
+
+(defun derl-trace-input (fmt &rest args)
+ (let ((msg (format "<< %s" (apply #'format (cons fmt args)))))
+ (put-text-property 0 (length msg) 'face 'derl-trace-input-face msg)
+ (derl-trace msg)))
+
+(defun derl-trace (string)
+ (if derl-use-trace-buffer
+ (with-current-buffer (get-buffer-create
+ (format "*trace %S*" derl-connection-node))
+ (goto-char (point-max))
+ (insert string)
+ (insert "\n"))))
+
+;; ------------------------------------------------------------
+;; Utility
+;; ------------------------------------------------------------
+
+(defun derl-nodedown (node)
+ (setq derl-shutting-down t)
+ (dolist (link derl-remote-links)
+ (let ((local (car link))
+ (remote (cdr link)))
+ (message "LOCAL: %S REMOTE %S" local remote)
+ (erl-send-exit remote local 'noconnection)))
+ (run-hook-with-args 'erl-nodedown-hook node))
+
+(defun derl-nodeup (node)
+ ;; NB: only callable from the state machine
+ (run-hook-with-args 'erl-nodeup-hook node fsm-process))
+
+(eval-and-compile
+ (defun derl-int32-to-decimal (s)
+ "Converts a 32-bit number (represented as a 4-byte string) into its
+decimal printed representation."
+ (format "%.0f" (+ (+ (aref s 3) (* 256 (aref s 2)))
+ (* (+ 0.0 (aref s 1) (* 256 (aref s 0)))
+ 65536)))))
+
+;; Try to establish whether we have enough precision in floating-point
+;; The test is pretty lame, even if it succeeds we cannot be sure
+;; it'll work for all int32's
+;; alas, i'm too ignorant to write a good test
+;; the previous version of the test was nicer, but FSFmacs-specific :<
+
+(unless (string= "1819634533" (derl-int32-to-decimal "luke"))
+ (error "Can't use Emacs's floating-point for `derl-int32-to-decimal'."))
+
+(defun derl-hexstring-to-binstring (s)
+ "Convert the hexidecimal string S into a binary number represented
+as a string of octets."
+ (let ((halves (mapcar #'derl-hexchar-to-int (string-to-list s))))
+ (derl-merge-halves halves)))
+
+(defun derl-merge-halves (halves &optional acc)
+ (if (null halves)
+ (apply #'string (reverse acc))
+ (derl-merge-halves (cddr halves)
+ (cons (+ (ash (car halves) 4)
+ (cadr halves))
+ acc))))
+
+(defun derl-hexchar-to-int (c)
+ (cond ((and (<= ?0 c) (<= c ?9))
+ (- c ?0))
+ ((and (<= ?a c) (<= c ?f))
+ (+ 10 (- c ?a)))
+ (t
+ (error "Not hexchar" c))))
+
+(defun derl-node-p (node)
+ "Check if `node' is a node name, e.g. \"foo@bar\". The @ character
+is not allowed in the node or host name."
+ (and (symbolp node)
+ (string-match "^[^@]+@[^@]+$" (symbol-name node))))
+
+(defun derl-node-name (node)
+ "Take the atom node part of a node name, e.g.
+ (derl-node-name \"foo@bar\") => \"foo\""
+ (assert (derl-node-p node))
+ (let ((string (symbol-name node)))
+ (string-match "^[^@]+" string)
+ (match-string 0 string)))
+
+(defun derl-node-host (node)
+ "Take the host part of a node name, e.g.
+ (derl-node-host \"foo@bar\") => \"bar\""
+ (assert (derl-node-p node))
+ (let ((string (symbol-name node)))
+ (string-match "[^@]+$" string)
+ (match-string 0 string)))
+
+(defun derl-buffer-name (node)
+ (format "*derl %s*" node))
+
+;; ------------------------------------------------------------
+;; Testing and playing around
+;; ------------------------------------------------------------
+
+(defun derl-go (port)
+ (fsm-connect "localhost" port #'derl-state0
+ nil
+ (lambda (result)
+ (message "RESULT: %S" result))
+ (lambda ()
+ (message "FAIL"))))
+
+(provide 'derl)
+
231 elisp/distel-ie.el
View
@@ -0,0 +1,231 @@
+;;;
+;;; distel-ie - an interactive erlang shell
+;;;
+;;; Some of the code has shamelessly been stolen from Luke Gorrie
+;;; [luke@bluetail.com] - ripped from its elegance and replaced by bugs.
+;;; It just goes to show that you can't trust anyone these days. And
+;;; as if that wasn't enough, I'll even blame Luke: "He _made_ me do it!"
+;;;
+;;; So, without any remorse, I hereby declare this code to be:
+;;;
+;;; copyright (c) 2002 david wallin [david.wallin@ul.ie].
+;;;
+;;; (it's probably going to be released onto an unexpecting public under
+;;; some sort of BSD license).
+
+(eval-when-compile (require 'cl))
+(require 'erlang)
+(require 'erl)
+
+(make-variable-buffer-local
+ (defvar erl-ie-node nil
+ "Erlang node that the session is hosted on."))
+
+;;
+;; erl-ie-session
+
+(defun erl-ie-session (node)
+ "Return the erl-ie-session for NODE, creating it if necessary."
+ (interactive (list (erl-ie-read-nodename)))
+
+ (or (get-buffer (erl-ie-buffer-name node))
+ (erl-ie-create-session node)))
+
+(defun erl-ie-create-session (node)
+ (with-current-buffer (get-buffer-create (erl-ie-buffer-name node))
+ (insert "\
+%%% Welcome to the Distel Interactive Erlang Shell.
+%%
+%% C-j evaluates an expression and prints the result in-line.
+%% C-M-x evaluates a whole function definition.
+
+")
+ (push-mark (point) t)
+
+ (erlang-mode)
+ (erl-session-minor-mode 1)
+ (setq erl-ie-node node)
+
+ ;; hiijack stdin/stdout :
+ (let ((output-buffer (current-buffer)))
+ (setq erl-group-leader
+ (erl-spawn (&erl-ie-group-leader-loop output-buffer))))
+
+ (erl-ie-ensure-registered node)
+
+ (current-buffer)))
+
+(defun erl-ie-read-nodename ()
+ "Get the node for the session, either from buffer state or from the user."
+ (or erl-ie-node (erl-target-node)))
+
+(defun erl-ie-buffer-name (node)
+ (format "*ie session <%S>*" node))
+
+;;
+;; erl-ie-ensure-registered
+
+(defun erl-ie-ensure-registered (node)
+ (interactive (list (erl-ie-read-nodename)))
+ (erl-spawn
+ (erl-send-rpc node 'distel_ie 'ensure_registered '())))
+
+
+(defun erl-ie-eval-expression (node)
+ (interactive (list (erl-ie-read-nodename)))
+ (erl-ie-read-nodename)
+ (let ((end (point))
+ (beg (save-excursion
+ (loop do (re-search-backward "\\(\\`\\|^\\<\\)")
+ while (looking-at "end"))
+ (point))))
+ (erl-ie-evaluate beg end node t)))
+
+(defun erl-ie-eval-defun (node)
+ (interactive (list (erl-ie-read-nodename)))
+ (erl-ie-read-nodename)
+ (let* ((beg (save-excursion (erlang-beginning-of-function)
+ (point)))
+ (end (save-excursion (goto-char beg)
+ (erlang-end-of-function)
+ (point))))
+ (erl-ie-evaluate beg end node)))
+
+;;
+;; erl-ie-evaluate
+;;
+;; this is doomed to fail, can end be the smaller value ?
+;; want to change to (interactive "r") somehow ...
+
+(defun erl-ie-evaluate (start end node &optional inline)
+ "Evaluate region START to END on NODE.
+The marked region can be a function definition, a function call or an expression."
+ (interactive (list
+ (region-beginning)
+ (region-end)
+ (erl-ie-read-nodename)))
+
+ (let* ((string (buffer-substring-no-properties start end))
+ (buffer (current-buffer)))
+ (erl-spawn
+ (erl-send (tuple 'distel_ie node)
+ (tuple 'evaluate erl-self string))
+
+ (message "Sent eval request..")
+
+ ;; move cursor to after the marked region
+ (goto-char (min (point-max) (1+ end)))
+ (erl-receive (buffer inline)
+ ((['ok value]
+ (if inline
+ (with-current-buffer buffer
+ ;; Clear "Sent eval request.." message
+ (message "")
+
+ (unless (looking-at "^")
+ (end-of-line)
+ (insert "\n"))
+
+ (let ((beg (point)))
+ ;; Insert value, indent all lines of it 4 places,
+ ;; then draw a " => " at the start.
+ (insert value)
+ (save-excursion (indent-rigidly beg (point) 5)
+ (goto-char beg)
+ (delete-region (point) (+ (point) 5))
+ (insert " --> ")))
+ (insert "\n\n")
+ (push-mark (point) t))
+ (display-message-or-view (format "Result: %s" value)
+ "*Evaluation Result*")))
+
+ (['msg msg]
+ (with-current-buffer buffer
+ (message msg)))
+
+ (['error reason]
+ (with-current-buffer buffer
+
+ ;; TODO: should check the buffer for first non-whitespace
+ ;; before we do:
+ (newline 1)
+ (insert "Error: ") (insert reason) (newline 1)))
+
+ (other
+ (message "Unexpected: %S" other)))))))
+
+
+(defun erl-ie-xor (a b)
+ "Boolean exclusive or of A and B."
+ (or (and a (not b))
+ (and b (not a))))
+
+;;
+;; &erl-ie-group-leader-loop
+
+(defun &erl-ie-group-leader-loop (buf)
+ (erl-receive (buf)
+ ((['put_chars s]
+ (with-current-buffer buf
+ (insert s))))
+ (&erl-ie-group-leader-loop buf)))
+
+
+;;
+;; erl-ie-show-session
+
+(defun erl-ie-show-session (node)
+ "Show the session for NODE, creating if necessary."
+ (interactive (list (erl-ie-read-nodename)))
+ (switch-to-buffer (erl-ie-session node)))
+
+;;
+;; erl-ie-copy-buffer-to-session
+
+(defun erl-ie-copy-buffer-to-session (node)
+ "Open a distel_ie session on NODE with the content of the current buffer.
+The content is pasted at the end of the session buffer. This can be useful
+for debugging a file without ruining the content by mistake."
+ (interactive (list (erl-ie-read-nodename)))
+ (let ((cloned-buffer (buffer-string)))
+
+ (with-current-buffer (erl-ie-session node)
+ (goto-char (point-max))
+ (insert cloned-buffer))
+ (erl-ie-popup-buffer node)))
+
+;;
+;; erl-ie-copy-region-to-session
+
+(defun erl-ie-copy-region-to-session (start end node)
+ "Open a distel_ie session on NODE with the content of the region.
+The content is pasted at the end of the session buffer. This can be useful
+for debugging a file without ruining the content by mistake."
+ (interactive (list
+ (region-beginning)
+ (region-end)
+ (erl-ie-read-nodename)))
+ (let ((cloned-region (buffer-substring-no-properties start end)))
+
+ (with-current-buffer (erl-ie-session node)
+ (goto-char (point-max))
+ (set-mark (point)) ; so the region will be right
+ (insert cloned-region))
+ (erl-ie-popup-buffer node)))
+
+
+(defun erl-ie-popup-buffer (node)
+ (switch-to-buffer (erl-ie-session node)))
+
+;; ------------------------------------------------------------
+;; Session minor mode.
+
+(define-minor-mode erl-session-minor-mode
+ "Minor mode for Distel Interactive Sessions."
+ nil
+ nil
+ '(("\C-j" . erl-ie-eval-expression)
+ ("\C-\M-x" . erl-ie-eval-defun)))
+
+(provide 'distel-ie)
+
255 elisp/distel.el
View
@@ -0,0 +1,255 @@
+;;; distel.el --- Top-level of distel package, loads all subparts
+
+;; Prerequisites
+(require 'erlang)
+(require 'easy-mmode)
+
+(provide 'distel)
+
+;; Customization
+
+(defgroup distel '()
+ "Distel and erlang-extended-mode development tools."
+ :group 'tools)
+
+(defcustom distel-tags-compliant nil
+ "Tags compliant, i.e. let M-. ask for confirmation."
+ :type 'boolean
+ :group 'distel)
+
+(defcustom distel-inhibit-backend-check nil
+ "Don't check for the 'distel' module when we connect to new nodes."
+ :type 'boolean
+ :group 'distel)
+
+;; Compatibility with XEmacs
+(unless (fboundp 'define-minor-mode)
+ (defalias 'define-minor-mode 'easy-mmode-define-minor-mode))
+
+;; Distel modules
+
+(require 'erl)
+(require 'erl-service)
+(require 'edb)
+
+(require 'distel-ie)
+
+(defun distel-setup ()
+ (add-hook 'erlang-mode-hook 'distel-erlang-mode-hook))
+
+(defun distel-erlang-mode-hook ()
+ "Function to enable the Distel extensions to Erlang mode.
+You can add this to `erlang-mode-hook' with:
+ (add-hook 'erlang-mode-hook 'distel-erlang-mode-hook)"
+ (erlang-extended-mode t))
+
+;; Extended feature key bindings (C-c C-d prefix)
+
+(define-minor-mode erlang-extended-mode
+ "Extensions to erlang-mode for communicating with a running Erlang node.
+
+These commands generally communicate with an Erlang node. The first
+time you use one, you will be prompted for the name of the node to
+use. This name will be cached for future commands. To override the
+cache, give a prefix argument with C-u before using the command.
+\\<erlang-extended-mode-map>
+\\[erl-process-list] - List all Erlang processes (\"pman\").
+\\[erl-complete] - Complete a module or remote function name.
+\\[erl-find-source-under-point] - Jump from a function call to its definition.
+\\[erl-find-source-unwind] - Jump back from a function definition (multi-level).
+\\[erl-eval-expression] - Evaluate an erlang expression from the minibuffer.
+\\[erl-reload-module] - Reload an Erlang module.
+\\[erl-reload-modules] - Reload all Erlang modules that are out of date.
+\\[fprof] - Profile (with fprof) an expression from the minibuffer.
+\\[fprof-analyse] - View profiler results from an \"fprof:analyse\" file.
+\\[erl-fdoc-describe] - Describe a module or function with fdoc.
+\\[erl-fdoc-apropos] - Describe all Erlang functions matching a regexp.
+\\[edb-toggle-interpret] - Toggle debug interpreting of the module.
+\\[edb-toggle-breakpoint] - Toggle a debugger breakpoint at the current line.
+\\[edb-synch-breakpoints] - Synchronizes current breakpoints to erlang.
+\\[edb-save-dbg-state] - Save set of interpreted modules and breakpoints
+\\[edb-restore-dbg-state] - Restore saved set of interpreted modules and breakpoints
+\\[edb-monitor] - Popup the debugger's process monitor buffer.
+\\[erl-ie-show-session] - Create an interactive \"session\" buffer.
+\\[erl-ie-copy-buffer-to-session] - Create an interactive \"session\" buffer from current buffer.
+\\[erl-ie-copy-region-to-session] - Create an interactive \"session\" buffer from region.
+\\[erl-refactor-subfunction] - Refactor expressions in the region as a new function.
+
+Most commands that pop up new buffers will save your original window
+configuration, so that you can restore it by pressing 'q'. Use
+`describe-mode' (\\[describe-mode]) on any Distel buffer when you want
+to know what commands are available. To get more information about a
+particular command, use \"\\[describe-key]\" followed by the command's key
+sequence. For general information about Emacs' online help, use
+\"\\[help-for-help]\".
+"
+ nil
+ nil
+ ;; Fake keybinding list just to get the keymap created.
+ ;;
+ ;; define-minor-mode is very inconvenient for redefining keybindings
+ ;; so we do that by hand, below.
+ '(("\M-." 'undefined)))
+
+(defconst distel-keys
+ '(("\C-c\C-di" edb-toggle-interpret)
+ ("\C-x " edb-toggle-breakpoint)
+ ("\C-c\C-db" edb-toggle-breakpoint)
+ ("\C-c\C-ds" edb-synch-breakpoints)
+ ("\C-c\C-dS" edb-save-dbg-state)
+ ("\C-c\C-dR" edb-restore-dbg-state)
+ ("\C-c\C-dm" edb-monitor)
+ ("\C-c\C-d:" erl-eval-expression)
+ ("\C-c\C-dL" erl-reload-module)
+ ("\C-c\C-dr" erl-reload-modules)
+ ("\C-c\C-dp" fprof)
+ ("\C-c\C-dP" fprof-analyse)
+ ("\C-c\C-d." erl-find-source-under-point)
+ ("\C-c\C-d," erl-find-source-unwind)
+ ("\C-c\C-dl" erl-process-list)
+ ("\C-\M-i" erl-complete) ; M-TAB
+ ("\M-?" erl-complete) ; Some windowmanagers hijack M-TAB..
+ ("\C-c\C-de" erl-ie-show-session)
+ ("\C-c\C-df" erl-refactor-subfunction)
+ ("\C-c\C-dd" erl-fdoc-describe)
+ ("\C-c\C-da" erl-fdoc-apropos)
+ ("\C-c\C-dw" erl-who-calls)
+ ("\C-c\C-dn" erl-choose-nodename)
+ ("(" erl-openparen)
+ ;; Possibly "controversial" shorter keys
+ ("\M-." erl-find-source-under-point) ; usually `find-tag'
+ ("\M-," erl-find-source-unwind) ; usually `tags-loop-continue'
+ ("\M-*" erl-find-source-unwind) ; usually `pop-tag-mark'
+ )
+ "Keys to bind in distel-mode-map.")
+
+(defun distel-bind-keys ()
+ "Bind `distel-keys' in `erlang-extended-mode-map'."
+ (interactive)
+ (dolist (spec distel-keys)
+ (define-key erlang-extended-mode-map (car spec) (cadr spec))))
+
+(distel-bind-keys)
+
+;; Setup mode-line info for erlang-extended-mode
+;;
+;; NB: Would use the LIGHTER argument for define-minor-mode, but it's
+;; not working portably: my copy of Emacs21 disagrees with emacs20 and
+;; xemacs on whether it should be quoted.
+
+(defvar distel-modeline-node nil
+ "When non-nil the short name of the currently connected node.")
+
+(add-to-list 'minor-mode-alist
+ '(erlang-extended-mode
+ (" EXT"
+ (distel-modeline-node (":" distel-modeline-node) "")
+ (edb-module-interpreted "<interpreted>" ""))))
+
+(add-hook 'erlang-extended-mode-hook
+ '(lambda ()
+ (if erlang-extended-mode
+ (distel-init)
+ (distel-finish))))
+
+(defun distel-init ()
+ (setq erlang-menu-items
+ (erlang-menu-add-below 'distel-menu-items
+ 'erlang-menu-compile-items
+ erlang-menu-items))
+ (erlang-menu-init))
+
+(defun distel-finish ()
+ (setq erlang-menu-items
+ (erlang-menu-delete 'distel-menu-items erlang-menu-items))
+ (erlang-menu-init))
+
+(defvar distel-menu-items
+ '(nil
+ ("Distel"
+ (("List all erlang processes" erl-process-list)
+ ("Eval an erlang expression" erl-eval-expression)
+ ("Reload an erlang module" erl-reload-module)
+ nil
+ ("Profile an erlang expression" fprof)
+ ("View profiler results" fprof-analyse)
+ nil
+ ("Toggle debug interpreting of the module" edb-toggle-interpret)
+ ("Toggle a breakpoint at current line" edb-toggle-breakpoint)
+ ("Synchronizes current breakpoints to erlang" edb-synch-breakpoints)
+ ("Save debugger state" edb-save-dbg-state)
+ ("Restore debugger state" edb-restore-dbg-state)
+ ("Popup the debugger process monitor" edb-monitor)
+ nil
+ ("Create an interactive erlang session buffer" erl-ie-show-session)
+ nil
+ ("Specify which node to connect to" erl-choose-nodename)
+ )))
+ "*Description of the Distel menu used by Erlang Extended mode.
+
+Please see the documentation of `erlang-menu-base-items'.")
+
+
+;; Bug reportage
+
+(defvar distel-bugs-address "distel-hackers@lists.sourceforge.net"
+ "Email address to send distel bugs to.")
+
+(eval-when-compile (require 'font-lock))
+
+(defun report-distel-problem (summary)
+ "Report a bug to the distel-hackers mailing list."
+ (interactive (list (read-string "One-line summary: ")))
+ (compose-mail distel-bugs-address
+ (format "PROBLEM: %s" summary))
+ (require 'font-lock)
+ (insert (propertize "\
+;; Distel bug report form.
+;;
+;; This is an email message buffer for you to fill in information
+;; about your problem. When finished, you can enter \"C-c C-c\" to
+;; send the report to the distel-hackers mailing list - or update the
+;; 'To: ' header line to send it somewhere else.
+;;
+;; Please describe the problem in detail in this blank space:
+
+"
+ 'face font-lock-comment-face))
+ (save-excursion
+ (insert (propertize "\
+
+
+;; Below is some automatically-gathered debug information. Please make
+;; sure it doesn't contain any secrets that you don't want to send. If
+;; you decide to censor it or have any other special notes, please
+;; describe them here:
+
+
+
+"
+ 'face font-lock-comment-face))
+ (insert "[ ---- Automatically gathered trace information ---- ]\n\n")
+ (insert (format "Emacs node name: %S\n\n" erl-node-name))
+ (insert (format "Node of most recent command: %S\n\n" erl-nodename-cache))
+ (insert "Recent *Messages*:\n")
+ (distel-indented-insert (distel-last-lines "*Messages*" 15) 2)
+ (insert "\n\n")
+ (when erl-nodename-cache
+ (insert (format "Recent interactions with %S:\n" erl-nodename-cache))
+ (distel-indented-insert (distel-last-lines
+ (format "*trace %S*" erl-nodename-cache) 50)
+ 2))
+ (insert "\n\nThe End.\n\n")))
+
+(defun distel-last-lines (buffer n)
+ (with-current-buffer buffer
+ (save-excursion
+ (goto-char (point-max))
+ (forward-line (- n))
+ (buffer-substring (point) (point-max)))))
+
+(defun distel-indented-insert (string level)
+ (let ((pos (point)))
+ (insert string)
+ (indent-rigidly pos (point) level)))
+
884 elisp/edb.el
View
@@ -0,0 +1,884 @@
+;;; edb.el --- Erlang debugger front-end
+
+(eval-when-compile (require 'cl))
+(require 'erl)
+(require 'erl-service)
+(require 'erlang)
+(require 'ewoc)
+
+(eval-and-compile
+ (autoload 'erlang-extended-mode "distel"))
+
+(when (featurep 'xemacs)
+ (require 'overlay))
+
+;; Hack for XEmacs compatibility..
+(unless (fboundp 'line-beginning-position)
+ (defalias 'line-beginning-position 'point-at-bol))
+
+;; ----------------------------------------------------------------------
+;; Configurables
+
+(defcustom edb-popup-monitor-on-event t
+ "*Automatically popup the monitor on interesting events.
+An interesting event is an unattached process reaching a breakpoint,
+or an attached process exiting."
+ :type 'boolean
+ :group 'distel)
+
+(defface edb-breakpoint-face
+ `((((type tty) (class color))
+ (:background "red" :foreground "black"))
+ (((type tty) (class mono))
+ (:inverse-video t))
+ (((class color) (background dark))
+ (:background "darkred" :foreground "white"))
+ (((class color) (background light))
+ (:background "tomato" :foreground "black"))
+ (t (:background "gray")))
+ "Face for marking a breakpoint definition."
+ :group 'distel)
+
+(defface edb-breakpoint-stale-face
+ `((((type tty) (class color))
+ (:background "yellow" :foreground "black"))
+ (((type tty) (class mono))
+ (:inverse-video t))
+ (((class color) (background dark))
+ (:background "purple4"))
+ (((class color) (background light))
+ (:background "medium purple" :foreground "black"))
+ (t (:background "dark gray")))
+ "Face for marking a stale breakpoint definition."
+ :group 'distel)
+
+;; ----------------------------------------------------------------------
+;; Integration with erlang-extended-mode buffers.
+
+(make-variable-buffer-local
+ (defvar edb-module-interpreted nil
+ "Non-nil means that the buffer's Erlang module is interpreted.
+This variable is meaningful in erlang-extended-mode buffers.
+The interpreted status refers to the node currently being monitored by
+edb."))
+
+(defun edb-setup-source-buffer ()
+ (make-local-variable 'kill-buffer-hook)
+ (add-hook 'kill-buffer-hook 'edb-delete-buffer-breakpoints)
+ (make-local-variable 'after-change-functions)
+ (add-to-list 'after-change-functions 'edb-make-breakpoints-stale)
+ (edb-update-interpreted-status)
+ (when edb-module-interpreted
+ (edb-create-buffer-breakpoints (edb-module))))
+
+(add-hook 'erlang-extended-mode-hook
+ 'edb-setup-source-buffer)
+
+;; ----------------------------------------------------------------------
+;; EDB minor mode for erlang-mode source files
+
+(defun edb-toggle-interpret (node module file)
+ "Toggle debug-interpreting of the current buffer's module."
+ (interactive (list (erl-target-node)
+ (edb-module)
+ buffer-file-name))
+ (when (edb-ensure-monitoring node)
+ (erl-spawn
+ (erl-set-name "EDB RPC to toggle interpretation of %S on %S"
+ module node)
+ (erl-send-rpc node 'distel 'debug_toggle (list module file))
+ (erl-receive (module)
+ ((['rex 'interpreted]
+ (message "Interpreting: %S" module))
+ (['rex 'uninterpreted]
+ (message "Stopped interpreting: %S" module))
+ (['rex ['badrpc reason]]
+ (message "Failed to interpret-toggle: %S" reason)))))))
+
+(defun edb-module ()
+ (if (erlang-get-module)
+ (intern (erlang-get-module))
+ (error "Can't determine module for current buffer")))
+
+(defun edb-toggle-breakpoint (node module line)
+ "Toggle a breakpoint on the current line."
+ (interactive (list (erl-target-node)
+ (edb-module)
+ (edb-line-number)))
+ (unless (edb-module-interpreted-p module)
+ (error "Module is not interpreted, can't set breakpoints."))
+ (if edb-buffer-breakpoints-stale
+ (edb-toggle-stale-breakpoint module line)
+ (edb-toggle-real-breakpoint node module line)))
+
+(defun edb-toggle-stale-breakpoint (module line)
+ (let ((overlay (edb-first (lambda (ov) (overlay-get ov 'edb-breakpoint))
+ (overlays-in (line-beginning-position)
+ (1+ (line-end-position))))))
+ (if overlay
+ (delete-overlay overlay)
+ (edb-create-breakpoint module line))))
+
+(defun edb-toggle-real-breakpoint (node module line)
+ (when (edb-ensure-monitoring node)
+ (erl-spawn
+ (erl-set-name "EDB RPC to toggle of breakpoint %S:%S on %S"
+ module line node)
+ (erl-send-rpc node 'distel 'break_toggle (list module line))
+ (erl-receive (module line)
+ ((['rex 'enabled]
+ (message "Enabled breakpoint at %S:%S" module line))
+ (['rex 'disabled]
+ (message "Disabled breakpoint at %S:%S" module line)))))))
+
+(defun edb-module-interpreted-p (module)
+ (assoc module edb-interpreted-modules))
+
+(defun edb-line-number ()
+ "Current line number."
+ ;; Taken from `count-lines' in gud.el
+ (save-restriction
+ (widen)
+ (+ (count-lines 1 (point))
+ (if (bolp) 1 0))))
+
+(defun edb-save-dbg-state (node)
+ "Save debugger state (modules to interpret and breakpoints).
+Use edb-restore-dbg-state to restore the state to the erlang node."
+ (interactive (list (erl-target-node)))
+ (let ((do-save nil))
+ (when (or (null edb-saved-interpreted-modules)
+ (y-or-n-p "You already have a saved debugger state, continue? "))
+ (setq edb-saved-interpreted-modules edb-interpreted-modules)
+ (edb-save-breakpoints node)
+ (message "Debugger state saved."))))
+
+(defun edb-restore-dbg-state (node)
+ "Restore debugger state (modules to interpret and breakpoints)."
+ (interactive (list (erl-target-node)))
+ (if edb-saved-interpreted-modules
+ (when (edb-ensure-monitoring node)
+ (erl-spawn
+ (erl-set-name "EDB RPC to restore debugger state on %S" node)
+ (erl-send-rpc node 'distel 'debug_add
+ (list edb-saved-interpreted-modules))
+ (erl-receive (node)
+ ((['rex 'ok]
+ (when (edb-restore-breakpoints
+ node
+ (lambda ()
+ (message "Debugger state restored.")))))))))
+ (message "No saved debugger state, aborting.")))
+
+
+;; ----------------------------------------------------------------------
+;; Monitor process
+
+(defvar edb-monitor-buffer nil
+ "Monitor process/viewer buffer.")
+
+(defvar edb-monitor-node nil
+ "Node we are debug-monitoring.")
+
+(defvar edb-monitor-mode-map nil
+ "Keymap for Erlang debug monitor mode.")
+
+(defvar edb-interpreted-modules '()
+ "Set of (module filename) being interpreted on the currently monitored node.")
+
+(defvar edb-saved-interpreted-modules '()
+ "Set of (module filename) to interpret if edb-restore-dbg-state is called.")
+
+(unless edb-monitor-mode-map
+ (setq edb-monitor-mode-map (make-sparse-keymap))
+ (define-key edb-monitor-mode-map [return] 'edb-attach-command)
+ (define-key edb-monitor-mode-map [(control m)] 'edb-attach-command)
+ (define-key edb-monitor-mode-map [?a] 'edb-attach-command)
+ (define-key edb-monitor-mode-map [?q] 'erl-bury-viewer)
+ (define-key edb-monitor-mode-map [?k] 'erl-quit-viewer))
+
+(defvar edb-processes nil
+ "EWOC of processes running interpreted code.")
+
+(defstruct (edb-process
+ (:constructor nil)
+ (:constructor make-edb-process (pid mfa status info)))
+ pid mfa status info)
+
+(defun edb-monitor-mode ()
+ "Major mode for viewing debug'able processes.
+
+Available commands:
+\\[edb-attach-command] - Attach to the process at point.
+\\[erl-bury-viewer] - Hide the monitor window.
+\\[erl-quit-viewer] - Quit monitor."
+ (interactive)
+ (kill-all-local-variables)
+ (setq buffer-read-only t)
+ (setq erl-old-window-configuration (current-window-configuration))
+ (use-local-map edb-monitor-mode-map)
+ (setq mode-name "EDB Monitor")
+ (setq major-mode 'edb-monitor-mode))
+
+(defun edb-monitor-insert-process (p)
+ (let ((buffer-read-only nil)
+ (text (edb-monitor-format (erl-pid-to-string (edb-process-pid p))
+ (edb-process-mfa p)
+ (edb-process-status p)
+ (edb-process-info p))))
+ (put-text-property 0 (length text) 'erl-pid (edb-process-pid p) text)
+ (insert text)))
+
+(defun edb-monitor-format (pid mfa status info)
+ (format "%s %s %s %s"
+ (padcut pid 12)
+ (padcut mfa 21)
+ (padcut status 9)
+ (cut info 21)))
+
+(defun padcut (s w)
+ (let ((len (length s)))
+ (cond ((= len w) s)
+ ((< len w) (concat s (make-string (- w len) ? )))
+ ((> len w) (substring s 0 w)))))
+
+(defun cut (s w)
+ (if (> (length s) w)
+ (substring s 0 w)
+ s))
+
+(defun edb-monitor-header ()
+ (edb-monitor-format "PID" "Initial Call" "Status" "Info"))
+
+(defun edb-monitor (node)
+ (interactive (list (erl-target-node)))
+ (when (edb-ensure-monitoring node)
+ (unless (get-buffer-window edb-monitor-buffer)
+ ;; Update the restorable window configuration
+ (with-current-buffer edb-monitor-buffer
+ (setq erl-old-window-configuration
+ (current-window-configuration))))
+ (pop-to-buffer edb-monitor-buffer)
+ (goto-char (point-max))
+ (forward-line -2)))
+
+(defun edb-ensure-monitoring (node)
+ "Make sure the debug monitor is watching the node.
+Returns NIL if this cannot be ensured."
+ (if (edb-monitor-node-change-p node)
+ (when (y-or-n-p (format "Attach debugger to %S instead of %S? "
+ node edb-monitor-node))
+ ;; Kill existing edb then start again
+ (kill-buffer edb-monitor-buffer)
+ (edb-start-monitor node))
+ (if (edb-monitor-live-p)
+ t
+ (edb-start-monitor node))))
+
+(defun edb-monitor-node-change-p (node)
+ "Do we have to detach/reattach to debug on NODE?"
+ (and (edb-monitor-live-p)
+ (not (equal node edb-monitor-node))))
+
+(defun edb-monitor-live-p ()
+ "Are we actively debug-monitoring a node?"
+ (and edb-monitor-buffer
+ (buffer-live-p edb-monitor-buffer)))
+
+(defun edb-monitor-buffer-name (node)
+ (format "*edb %S*" node))
+
+(defun edb-start-monitor (node)
+ "Start debug-monitoring NODE."
+ (erl-spawn
+ (erl-set-name "EDB Monitor on %S" node)
+ (setq edb-monitor-node node)
+ (setq edb-monitor-buffer (current-buffer))
+ (rename-buffer (edb-monitor-buffer-name node))
+ (edb-monitor-mode)
+ (make-local-variable 'kill-buffer-hook)
+ (add-hook 'kill-buffer-hook 'edb-monitor-cleanup)
+ (erl-send-rpc node 'distel 'debug_subscribe (list erl-self))
+ (erl-receive (node)
+ ((['rex [interpreted breaks snapshot]]
+ (setq edb-interpreted-modules interpreted)
+ (edb-init-breakpoints breaks)
+ (edb-update-source-buffers)
+ (setq edb-processes
+ (ewoc-create 'edb-monitor-insert-process
+ (edb-monitor-header)))
+ (mapc (lambda (item)
+ (mlet [pid mfa status info] item
+ (ewoc-enter-last edb-processes
+ (make-edb-process pid
+ mfa
+ status
+ info))))
+ snapshot)
+ (&edb-monitor-loop))))))
+
+(defun &edb-monitor-loop ()
+ "Monitor process main loop.
+Tracks events and state changes from the Erlang node."
+ (erl-receive ()
+ ((['int ['new_status pid status info]]
+ (let ((proc (edb-monitor-lookup pid)))
+ (if (null proc)
+ (message "Unknown process: %s" (erl-pid-to-string pid))
+ (setf (edb-process-status proc) (symbol-name status))
+ (setf (edb-process-info proc) info)
+ (when (and edb-popup-monitor-on-event
+ (edb-interesting-event-p pid status info))
+ (display-buffer (current-buffer))))))
+ ;;
+ (['int ['new_process (pid mfa status info)]]
+ (ewoc-enter-last edb-processes
+ (make-edb-process pid
+ mfa
+ (symbol-name status)
+ info)))
+ ;;
+ (['int ['interpret mod file]]
+ (push (list mod file) edb-interpreted-modules)
+ (edb-update-source-buffers mod))
+ ;;
+ (['int ['no_interpret mod]]
+ (setq edb-interpreted-modules
+ (assq-delete-all mod edb-interpreted-modules))
+ (edb-update-source-buffers mod))
+ ;;
+ (['int ['no_break mod]]
+ (edb-delete-breakpoints mod))
+ ;;
+ (['int ['new_break [[mod line] _info]]]
+ (edb-create-breakpoint mod line))
+ ;;
+ (['int ['delete_break [mod line]]]
+ (edb-delete-breakpoint mod line)))
+ (ewoc-refresh edb-processes)
+ (&edb-monitor-loop)))
+
+(defun edb-get-buffer (mod)
+ (edb-get-buffer2 mod (buffer-list)))
+
+(defun edb-get-buffer2 (mod bufl)
+ (if (null bufl) nil
+ (with-current-buffer (car bufl)
+ (if (and erlang-extended-mode
+ (eq (edb-source-file-module-name) mod))
+ (car bufl)
+ (edb-get-buffer2 mod (cdr bufl))))))
+
+
+(defun edb-interesting-event-p (pid status info)
+ (or (and (eq status 'exit)
+ (edb-attached-p pid))
+ (and (eq status 'break)
+ (not (edb-attached-p pid)))))
+
+(defun edb-update-interpreted-status ()
+ "Update `edb-module-interpreted' for current buffer."
+ (when erlang-extended-mode
+ (let ((mod (edb-source-file-module-name)))
+ (if (and mod (assq mod edb-interpreted-modules))
+ (setq edb-module-interpreted t)
+ (setq edb-module-interpreted nil)
+ ;; the erlang debugger automatically removes breakpoints when a
+ ;; module becomes uninterpreted, so we match it here
+ (edb-delete-breakpoints (edb-source-file-module-name))))
+ (force-mode-line-update)))
+
+(defun edb-update-source-buffers (&optional mod)
+ "Update the debugging state of all Erlang buffers.
+When MOD is given, only update those visiting that module."
+ (mapc (lambda (buf)
+ (with-current-buffer buf
+ (when (and erlang-extended-mode
+ (or (null mod)
+ (eq (edb-source-file-module-name) mod)))
+ (edb-update-interpreted-status))))
+ (buffer-list)))
+
+(defun edb-source-file-module-name ()
+ "Return the Erlang module of the current buffer as a symbol, or NIL."
+ (let ((name (erlang-get-module)))
+ (if name (intern name) nil)))
+
+(defun edb-monitor-lookup (pid)
+ (car (ewoc-collect edb-processes
+ (lambda (p) (equal (edb-process-pid p) pid)))))
+
+(defun edb-monitor-cleanup ()
+ "Cleanup state after the edb process exits."
+ (setq edb-interpreted-modules '())
+ (edb-delete-all-breakpoints)
+ (edb-update-source-buffers))
+
+;; ----------------------------------------------------------------------
+;; Attach process
+
+(make-variable-buffer-local
+ (defvar edb-pid nil
+ "Pid of attached process."))
+
+(make-variable-buffer-local
+ (defvar edb-node nil
+ "Node of attached process."))
+
+(make-variable-buffer-local
+ (defvar edb-module nil
+ "Current module source code in attach buffer."))
+
+(make-variable-buffer-local
+ (defvar edb-variables-buffer nil
+ "Buffer showing variable bindings of attached process."))
+
+(make-variable-buffer-local
+ (defvar edb-attach-buffer nil
+ "True if buffer is attach buffer."))
+
+(defvar edb-attach-with-new-frame nil
+ "When true, attaching to a process opens a new frame.")
+
+;; Attach setup
+
+(defun edb-attach-command ()
+ (interactive)
+ (let ((pid (get-text-property (point) 'erl-pid)))
+ (if pid
+ (progn (when edb-attach-with-new-frame
+ (select-frame (make-frame)))
+ (edb-attach pid))
+ (error "No process at point."))))
+
+(defun edb-attach (pid)
+ (let ((old-window-config (current-window-configuration)))
+ (delete-other-windows)
+ (switch-to-buffer (edb-attach-buffer pid))
+ (setq erl-old-window-configuration old-window-config)))
+
+(defun edb-attach-buffer (pid)
+ (let ((bufname (edb-attach-buffer-name pid)))
+ (or (get-buffer bufname)
+ (edb-new-attach-buffer pid))))
+
+(defun edb-new-attach-buffer (pid)
+ "Start a new attach process and returns its buffer."
+ (erl-pid->buffer
+ (erl-spawn
+ (erl-set-name "EDB Attach to process %S on %S"
+ (erl-pid-id pid)
+ (erl-pid-node pid))
+ (rename-buffer (edb-attach-buffer-name pid))
+ ;; We must inhibit the erlang-new-file-hook, otherwise we trigger
+ ;; it by entering erlang-mode in an empty buffer
+ (let ((erlang-new-file-hook nil))
+ (erlang-mode))
+ (erlang-extended-mode t)
+ (edb-attach-mode t)
+ (setq edb-attach-buffer t)
+ (message "Entered debugger. Press 'h' for help.")
+ (setq buffer-read-only t)
+ (erl-send-rpc (erl-pid-node pid)
+ 'distel 'debug_attach (list erl-self pid))
+ (erl-receive ()
+ ((['rex pid]
+ (assert (erl-pid-p pid))
+ (setq edb-pid pid)
+ (setq edb-node (erl-pid-node pid))
+ (save-excursion (edb-make-variables-window))))
+ (&edb-attach-loop)))))
+
+;; Variables listing window
+
+(defun edb-make-variables-window ()
+ "Make a window and buffer for viewing variable bindings.
+The *Variables* buffer is killed with the current buffer."
+ (split-window-vertically (edb-variables-window-height))
+ (let ((vars-buf (edb-make-variables-buffer)))
+ (setq edb-variables-buffer vars-buf)
+ (make-local-variable 'kill-buffer-hook)
+ (add-hook 'kill-buffer-hook
+ (lambda () (kill-buffer edb-variables-buffer)))
+ (other-window 1)
+ (switch-to-buffer vars-buf)
+ (other-window -1)))
+
+(defun edb-variables-window-height ()
+ (- (min (/ (window-height) 2) 12)))
+
+(defun edb-make-variables-buffer ()
+ "Create the edb variable list buffer."
+ (let ((meta-pid edb-pid))
+ (with-current-buffer (generate-new-buffer "*Variables*")
+ (edb-variables-mode)
+ (setq edb-pid meta-pid)
+ (current-buffer))))
+
+(defun edb-variables-mode ()
+ (kill-all-local-variables)
+ (setq major-mode 'edb-variables)
+ (setq mode-name "EDB Variables")
+ (setq buffer-read-only t)
+ (use-local-map edb-variables-mode-map))
+
+(defvar edb-variables-mode-map nil
+ "Keymap for EDB variables viewing.")
+
+(when (null edb-variables-mode-map)
+ (setq edb-variables-mode-map (make-sparse-keymap))
+ (define-key edb-variables-mode-map [?m] 'edb-show-variable)
+ (define-key edb-variables-mode-map [(control m)] 'edb-show-variable))
+
+(defun edb-show-variable ()
+ "Pop a window showing the full value of the variable at point."
+ (interactive)
+ (let ((var (get-text-property (point) 'edb-variable-name)))
+ (if (null var)
+ (message "No variable at point")
+ (edb-attach-meta-cmd `[get_binding ,var]))))
+
+;; Attach process states
+
+(defun &edb-attach-loop ()
+ "Attached process loop."
+ (erl-receive ()
+ ((['location mod line pos max]
+ (let ((msg (format "Location: %S:%S (Stack pos: %S/%S)"
+ mod line pos max)))
+ (setq header-line-format msg))
+ (&edb-attach-goto-source mod line))
+ (['status status]
+ (unless (memq status '(running idle))
+ (message "Unrecognised status: %S" status))
+ (setq header-line-format (format "Status: %S" status))
+ (setq overlay-arrow-position nil)
+ (&edb-attach-loop))
+ (['variables vars]
+ ;; {variables, [{Name, String}]}
+ (when (buffer-live-p edb-variables-buffer)
+ (with-current-buffer edb-variables-buffer
+ (let ((buffer-read-only nil))
+ (erase-buffer)
+ (mapc (lambda (b)
+ (let ((name (tuple-elt b 1))
+ (string (tuple-elt b 2)))
+ (put-text-property 0 (length string)
+ 'edb-variable-name name
+ string)
+ (insert string)))
+ vars))))
+ (&edb-attach-loop))
+ (['message msg]
+ (message msg)
+ (&edb-attach-loop))
+ (['show_variable value]
+ (save-excursion (display-message-or-view value "*Variable Value*"))
+ (&edb-attach-loop))
+ (other
+ (message "Other: %S" other)
+ (&edb-attach-loop)))))
+
+(defun &edb-attach-goto-source (module line)
+ "Display MODULE:LINE in the attach buffer and reenter attach loop."
+ (if (eq edb-module module)
+ (progn (edb-attach-goto-line line)
+ (&edb-attach-loop))
+ (&edb-attach-find-source module line)))
+
+(defun &edb-attach-find-source (module line)
+ "Load the source code for MODULE into current buffer at LINE.
+Once loaded, reenters the attach loop."
+ (erl-send-rpc edb-node 'distel 'find_source (list module))
+ (erl-receive (module line)
+ ((['rex ['ok path]]
+ (if (file-regular-p path)
+ (progn (setq edb-module module)
+ (let ((buffer-read-only nil))
+ (erase-buffer)
+ (insert-file-contents path))
+ (edb-delete-buffer-breakpoints)
+ (edb-create-buffer-breakpoints module)
+ (edb-attach-goto-line line))
+ (message "No such file: %s" path))))
+ (&edb-attach-loop)))
+
+(defun edb-attach-goto-line (line)
+ (goto-line line)
+ (setq overlay-arrow-string "=>")
+ (setq overlay-arrow-position (copy-marker (point))))
+
+(defun edb-attach-buffer-name (pid)
+ (format "*edbproc %s on %S*"
+ (erl-pid-to-string pid)
+ (erl-pid-node pid)))
+
+(defun edb-attached-p (pid)
+ "Non-nil when we have an attach buffer viewing PID."
+ (buffer-live-p (get-buffer (edb-attach-buffer-name pid))))
+
+;; ----------------------------------------------------------------------
+;; Attach minor mode and commands
+
+(define-minor-mode edb-attach-mode
+ "Minor mode for debugging an Erlang process.
+
+Available commands:
+\\<edb-attach-mode-map>
+\\[edb-attach-help] - Popup this help text.
+\\[erl-quit-viewer] - Quit the viewer (doesn't kill the process)
+\\[edb-attach-step] - Step (into expression)
+\\[edb-attach-next] - Next (over expression)
+\\[edb-attach-up] - Up to the next stack frame
+\\[edb-attach-down] - Down to the next stack frame
+\\[edb-attach-continue] - Continue (until breakpoint)
+\\[edb-toggle-breakpoint] - Toggle a breakpoint on the current line."
+ nil
+ " (attached)"
+ '(([? ] . edb-attach-step)
+ ([?n] . edb-attach-next)
+ ([?c] . edb-attach-continue)
+ ([?u] . edb-attach-up)
+ ([?d] . edb-attach-down)
+ ([?q] . erl-quit-viewer)
+ ([?h] . edb-attach-help)
+ ([?b] . edb-toggle-breakpoint)))
+
+(defun edb-attach-help ()
+ (interactive)
+ (describe-function 'edb-attach-mode))
+
+(defun edb-attach-step ()
+ (interactive)
+ (edb-attach-meta-cmd 'step))
+(defun edb-attach-next ()
+ (interactive)
+ (edb-attach-meta-cmd 'next))
+(defun edb-attach-continue ()
+ (interactive)
+ (edb-attach-meta-cmd 'continue))
+(defun edb-attach-up ()
+ (interactive)
+ (edb-attach-meta-cmd 'up))
+(defun edb-attach-down ()
+ (interactive)
+ (edb-attach-meta-cmd 'down))
+
+(defun edb-attach-meta-cmd (cmd)
+ (erl-send edb-pid `[emacs meta ,cmd]))
+
+;; ----------------------------------------------------------------------
+;; Breakpoints
+
+(defvar edb-breakpoints '()
+ "List of all breakpoints on the currently monitored node.")
+
+(defvar edb-saved-breakpoints '()
+ "List of breakpoints to set if edb-restore-dbg-state is called.")
+
+(make-variable-buffer-local
+ (defvar edb-buffer-breakpoints nil
+ "List of active buffer breakpoints."))
+
+(make-variable-buffer-local
+ (defvar edb-buffer-breakpoints-stale nil
+ "Nil if the breakpoints in the buffer are stale (out of synch)."))
+
+;; breakpoints
+(defun make-bp (mod line) (list mod line))
+(defun bp-mod (bp) (car bp))
+(defun bp-line (bp) (cadr bp))
+
+;; buffer breakpoints
+(defun make-bbp (mod line ov) (list mod line ov))
+(defun bbp-mod (bbp) (car bbp))
+(defun bbp-line (bbp) (cadr bbp))
+(defun bbp-ov (bbp) (caddr bbp))
+
+(defun edb-init-breakpoints (breaks)
+ (setq edb-breakpoints
+ (mapcar (lambda (pos)
+ (let ((mod (aref pos 0))
+ (line (aref pos 1)))
+ (make-bp mod line)))
+ breaks))
+ (mapc
+ (lambda (buf)
+ (with-current-buffer buf
+ (when erlang-extended-mode
+ (edb-create-buffer-breakpoints (edb-source-file-module-name)))))
+ (buffer-list)))
+
+
+(defun edb-create-breakpoint (mod line)
+ "Updates all internal structures in all buffers with new breakpoint."
+ (push (make-bp mod line) edb-breakpoints)
+ (mapc
+ (lambda (buf)
+ (with-current-buffer buf
+ (if (and erlang-extended-mode
+ (eq (edb-source-file-module-name) mod))
+ (let ((bbp (make-bbp mod line (edb-make-breakpoint-overlay line))))
+ (push bbp edb-buffer-breakpoints)))))
+ (buffer-list)))
+
+(defun edb-delete-all-breakpoints ()
+ "Updates all internal structures in all buffers."
+ (edb-del-breakpoints
+ (lambda (bp) t)
+ (lambda (bbp) t)))
+
+(defun edb-delete-breakpoints (mod)
+ "Updates all internal structures in all buffers."
+ (edb-del-breakpoints
+ (lambda (bp) (eq (bp-mod bp) mod))
+ (lambda (bbp) (eq (bbp-mod bbp) mod))
+ mod))
+
+(defun edb-delete-breakpoint (mod line)
+ "Updates all internal structures in all buffers."
+ (edb-del-breakpoints
+ (lambda (bp) (and (eq (bp-mod bp) mod)
+ (eq (bp-line bp) line)))
+ (lambda (bbp) (and (eq (bbp-mod bbp) mod)
+ (eq (bbp-line bbp) line)))
+ mod))
+
+(defun edb-create-buffer-breakpoints (mod)
+ "Creates buffer breakpoints in the current buffer."
+ (when edb-buffer-breakpoints
+ ;; remove old/stale breakpoints
+ (edb-delete-buffer-breakpoints))
+ (setq edb-buffer-breakpoints (edb-mk-bbps mod)))
+
+(defun edb-delete-buffer-breakpoints ()
+ "Deletes all buffer breakpoints in the current buffer."
+ (setq edb-buffer-breakpoints
+ (edb-del-bbps edb-buffer-breakpoints (lambda (bbp) t))))
+
+(defun edb-del-breakpoints (bp-f bbp-f &optional mod)
+ "Updates all internal structures in all buffers."
+ (setq edb-breakpoints (erl-remove-if bp-f edb-breakpoints))
+ (mapc
+ (lambda (buf)
+ (with-current-buffer buf
+ (if (and erlang-extended-mode
+ (or (not mod)
+ (eq (edb-source-file-module-name) mod)))
+ (setq edb-buffer-breakpoints
+ (edb-del-bbps edb-buffer-breakpoints bbp-f)))))
+ (buffer-list)))
+
+(defun edb-synch-breakpoints (node module)
+ "Synchronizes the breakpoints in the current buffer to erlang.
+I.e. deletes all old breakpoints, and re-applies them at the current line."
+ (interactive (list (erl-target-node)
+ (edb-module)))
+ (when (edb-ensure-monitoring node)
+ (let ((id (lambda (r) r)))
+ (mapc (lambda (new-bbp)
+ (let ((bbp (car new-bbp))
+ (new-line (cdr new-bbp)))
+ (erl-rpc id nil node 'distel 'break_delete
+ (list (bbp-mod bbp) (bbp-line bbp)))
+ (erl-rpc id nil node 'distel 'break_add
+ (list module new-line))))
+ (edb-new-bbps))
+ (setq edb-buffer-breakpoints-stale nil))))
+
+(defun edb-make-breakpoints-stale (begin end length)
+ "Make breakpoints in the current buffer stale.
+Has no effect if the buffer's module is not interpreted, or the
+breakpoints are already marked as stale."
+ (when (and (not edb-attach-buffer)
+ (not edb-buffer-breakpoints-stale)
+ edb-module-interpreted)
+ (mapc (lambda (bbp)
+ (let ((ov (bbp-ov bbp)))
+ (overlay-put ov 'face 'edb-breakpoint-stale-face)))
+ edb-buffer-breakpoints)
+ (setq edb-buffer-breakpoints-stale t)))
+
+(defun edb-save-breakpoints (node)
+ (let ((modules '()))
+ (setq edb-saved-breakpoints '())
+ (mapc
+ (lambda (buf)
+ (with-current-buffer buf
+ (if erlang-extended-mode
+ (let ((cur-mod (edb-source-file-module-name)))
+ (unless (member cur-mod modules)
+ (let ((new-lines (mapcar (lambda (new-bbp) (cdr new-bbp))
+ (edb-new-bbps))))
+ (push cur-mod modules)
+ (push (list cur-mod new-lines) edb-saved-breakpoints)))))))
+ (buffer-list))
+ (mapc (lambda (bp)
+ (unless (member (bp-mod bp) modules)
+ (push (list (bp-mod bp) (list (bp-line bp)))
+ edb-saved-breakpoints)))
+ edb-breakpoints)))
+
+(defun edb-restore-breakpoints (node cont)