├── .gitignore
├── LICENSE
├── Makefile.am
├── README.md
├── autogen.sh
├── bin
├── Makefile.am
├── count-lines
├── env.in
├── namespace-markup.in
├── warn-notangle.in
└── wrapper.in
├── configure.ac
├── example
├── .gitignore
└── Makefile.am
├── src
├── Makefile.am
├── convenience-lambda.scm
├── cursor-list.scm
├── emacsy
│ ├── Makefile.am
│ ├── advice.nw
│ ├── agenda.scm
│ ├── block.nw
│ ├── buffer.nw
│ ├── command.nw
│ ├── core.nw
│ ├── coroutine-test.scm
│ ├── coroutine.scm
│ ├── emacsy-c-api.nw
│ ├── emacsy.nw
│ ├── event.nw
│ ├── help.nw
│ ├── job-test.scm
│ ├── job.scm
│ ├── kbd-macro.nw
│ ├── keymap.nw
│ ├── klecl.nw
│ ├── minibuffer.nw
│ ├── mode.nw
│ ├── mode.scm
│ ├── mru-stack.nw
│ ├── self-doc.nw
│ ├── util.nw
│ ├── window.nw
│ └── windows.nw
└── line-pragma.nw
├── support
├── automake
│ ├── guile.am
│ └── noweb.am
├── images
│ ├── child-window-diagram.graffle
│ ├── child-window-diagram.pdf
│ ├── emacsy-logo.pdf
│ ├── screenshot-small.png
│ ├── screenshot.png
│ ├── the-garden.pdf
│ ├── window-diagram.graffle
│ └── window-diagram.pdf
├── latex
│ ├── cleveref.sty
│ ├── commands.tex
│ ├── hello-emacsy.tex
│ ├── noweb.sty
│ └── nwmac.tex
├── m4
│ ├── ax_check_noweb.m4
│ ├── ax_check_open.m4
│ └── ax_lang_compiler_ms.m4
├── noweb
│ ├── boiler-plate.nw
│ ├── paper-footer.nw
│ ├── paper-header.nw
│ └── paper-wrapper.nw
├── pkg-config
│ ├── Makefile.am
│ ├── emacsy.pc.in
│ └── not-installed
│ │ └── emacsy.pc.in
└── scheme
│ ├── check.scm
│ ├── float-equality.scm
│ └── srfi
│ └── srfi-64.scm
├── test
├── Makefile.am
├── dummy.sh
├── minibuffer-test-dir
│ ├── bin
│ │ └── run-test
│ ├── empty-dir
│ │ └── .dummy
│ ├── exam
│ │ └── .dummy
│ ├── minibuffer-a
│ └── minibuffer-b
├── print-install.sh.in
└── works-without-noweb.sh.in
└── todo.org
/.gitignore:
--------------------------------------------------------------------------------
1 | Makefile
2 | Makefile.in
3 | *.go
4 | *.defs
5 | *.trs
6 | .libs*
7 | .deps*
8 | TAGS
9 | /aclocal.m4
10 | /autom4te.cache/*
11 | /bin/env
12 | /bin/namespace-markup
13 | /bin/runtests
14 | /bin/warn-notangle
15 | /src/stamp-h1
16 | /support/build-aux/config.guess
17 | /support/build-aux/config.sub
18 | /support/build-aux/depcomp
19 | /support/build-aux/install-sh
20 | /support/build-aux/ltmain.sh
21 | /support/build-aux/missing
22 | /support/build-aux/test-driver
23 | /support/m4/libtool.m4
24 | /support/m4/ltoptions.m4
25 | /support/m4/ltsugar.m4
26 | /support/m4/ltversion.m4
27 | /support/m4/lt~obsolete.m4
28 | /support/pkg-config/emacsy.pc
29 | /support/pkg-config/not-installed/emacsy.pc
30 | /test/print-install.sh
31 | /test/works-without-noweb.sh
32 | /src/config.h
33 | /src/config.h.in
34 | /src/emacsy/advice-test.scm
35 | /src/emacsy/advice.scm
36 | /src/emacsy/advice.tex
37 | /src/emacsy/block-test.scm
38 | /src/emacsy/block.scm
39 | /src/emacsy/block.tex
40 | /src/emacsy/buffer-test.scm
41 | /src/emacsy/buffer.scm
42 | /src/emacsy/buffer.tex
43 | /src/emacsy/command-test.scm
44 | /src/emacsy/command.scm
45 | /src/emacsy/command.tex
46 | /src/emacsy/core-test.scm
47 | /src/emacsy/core.scm
48 | /src/emacsy/core.tex
49 | /src/emacsy/emacsy-c-api.tex
50 | /src/emacsy/emacsy-test.scm
51 | /src/emacsy/emacsy.aux
52 | /src/emacsy/emacsy.c
53 | /src/emacsy/emacsy.h
54 | /src/emacsy/emacsy.lo
55 | /src/emacsy/emacsy.log
56 | /src/emacsy/emacsy.nwi
57 | /src/emacsy/emacsy.out
58 | /src/emacsy/emacsy.pdf
59 | /src/emacsy/emacsy.scm
60 | /src/emacsy/emacsy.tdo
61 | /src/emacsy/emacsy.tex
62 | /src/emacsy/emacsy.tex.log
63 | /src/emacsy/libemacsy.la
64 | /src/emacsy/emacsy.toc
65 | /src/emacsy/event-test.scm
66 | /src/emacsy/event.scm
67 | /src/emacsy/event.tex
68 | /src/emacsy/kbd-macro-test.scm
69 | /src/emacsy/kbd-macro.scm
70 | /src/emacsy/kbd-macro.tex
71 | /src/emacsy/keymap-test.scm
72 | /src/emacsy/keymap.scm
73 | /src/emacsy/keymap.tex
74 | /src/emacsy/klecl-test.scm
75 | /src/emacsy/klecl.scm
76 | /src/emacsy/klecl.tex
77 | /src/emacsy/minibuffer-test.scm
78 | /src/emacsy/minibuffer.scm
79 | /src/emacsy/minibuffer.tex
80 | /src/emacsy/texput.log
81 | /src/emacsy/util.scm
82 | /src/emacsy/util.tex
83 | /src/emacsy/windows.scm
84 | /src/emacsy/windows.tex
85 | /src/line-pragma.scm
86 | /src/line-pragma.tex
87 | /libtool
88 | /config.log
89 | /config.status
90 | /configure
91 |
92 | support/build-aux/compile
93 |
94 | *.log
95 | /src/emacsy/_windows.tex
96 | /src/emacsy/core.aux
97 | /src/emacsy/core.nwi
98 | /src/emacsy/emacsy.c.x
99 | /src/emacsy/help-test.scm
100 | /src/emacsy/help.scm
101 | /src/emacsy/util.aux
102 | /src/emacsy/util.nwi
103 | /src/emacsy/window-test.scm
104 | /src/emacsy/window.scm
105 |
--------------------------------------------------------------------------------
/Makefile.am:
--------------------------------------------------------------------------------
1 | ACLOCAL_AMFLAGS = -I support/m4
2 |
3 | SUBDIRS = src test bin example
4 |
5 | EXTRA_DIST = README.md support bin
6 |
7 | sign-dist: dist
8 | gpg -a --output $(distdir).tar.gz.sig --detach-sig $(distdir).tar.gz
9 |
10 | verify-sig: $(distdir).tar.gz.sig $(distdir).tar.gz
11 | gpg --verify $(distdir).tar.gz.sig
12 |
13 | # I am using the following guidelines for the clean targets.
14 | # http://ftp.gnu.org/old-gnu/Manuals/automake-1.7.2/html_chapter/automake_14.html
15 | MAINTAINERCLEANFILES = Makefile.in \
16 | libtool \
17 | configure \
18 | aclocal.m4 \
19 | support/build-aux/config.guess \
20 | support/build-aux/config.sub \
21 | support/build-aux/depcomp \
22 | support/build-aux/install-sh \
23 | support/build-aux/ltmain.sh \
24 | support/build-aux/missing \
25 | support/build-aux/test-driver \
26 | support/m4/libtool.m4 \
27 | support/m4/ltoptions.m4 \
28 | support/m4/ltsugar.m4 \
29 | support/m4/ltversion.m4 \
30 | support/m4/lt~obsolete.m4 \
31 | $(distdir) \
32 | $(distdir).tar.gz
33 |
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Emacsy
2 | ======
3 |
4 | Emacsy is an embeddable Emacs-like library for [GNU Guile
5 | Scheme](http://www.gnu.org/software/guile/). It was a [kickstarter
6 | project](http://www.kickstarter.com/projects/568774734/emacsy-an-embeddable-emacs/?ref=kicktraq).
7 | It is now a [Google Summer of Code 2013
8 | project](https://google-melange.appspot.com/gsoc/proposal/review/google/gsoc2013/shanecelis/1).
9 | I will be working with Ludovic Courtès from the [GNU
10 | Project](http://www.gnu.org/gnu/thegnuproject.html). Keep abreast of
11 | its development by watching this repository or following me on twitter
12 | [@shanecelis](https://twitter.com/shanecelis).
13 |
14 | WARNING
15 | -------
16 |
17 | This project is currently in development. It is as alpha as can be.
18 | Not meant for general consumption yet. Contributors, welcome.
19 |
20 | Dependencies
21 | ------------
22 |
23 | * [GNU Guile Scheme 2.0](http://www.gnu.org/software/guile/)
24 |
25 | * [guile-lib](http://www.nongnu.org/guile-lib/)
26 |
27 | * Only necessary if building from github
28 | * [Noweb](http://www.cs.tufts.edu/~nr/noweb/)
29 |
30 | * pdflatex
31 |
32 | * automake >= v1.14
33 |
34 | If you get an error saying that "warning: macro
35 | 'AM\_EXTRA\_RECURSIVE\_TARGETS' not found in library", this means
36 | your version of automake is too old. (Alternatively, you can
37 | comment out the AM\_EXTRA\_RECURSIVE\_TARGETS in configure.ac.)
38 |
39 | * autoconf >= v2.69
40 |
41 | Building from a release (easy)
42 | ------------------------------
43 |
44 | $ wget https://github.com/shanecelis/emacsy/releases/download/v0.1.1/emacsy-0.1.1.tar.gz
45 | $ tar xfz emacsy-0.1.1.tar.gz
46 | $ cd emacsy-0.1.1
47 | $ ./configure
48 | $ make
49 |
50 | Building from github (harder)
51 | -----------------------------
52 |
53 | $ git clone https://github.com/shanecelis/emacsy.git
54 | $ cd emacsy/example
55 | $ git clone https://github.com/shanecelis/hello-emacsy.git
56 | $ git clone https://github.com/shanecelis/emacsy-webkit-gtk.git
57 | $ cd ..
58 | $ ./autogen.sh
59 | $ ./configure
60 | $ make
61 |
62 |
63 | Running
64 | -------
65 |
66 | Run the [minimal example
67 | program](http://gnufoo.org/emacsy/minimal-emacsy-example.pdf) and the
68 | [barebones webkit
69 | browser](https://github.com/shanecelis/emacsy-webkit-gtk) example.
70 |
71 | $ make run
72 |
73 | ![minimal example screenshot]()
74 |
75 |
76 |
77 |
78 |
79 | Reading
80 | -------
81 |
82 | This is a literate program, so you can read it.
83 |
84 | $ make show-doc
85 |
86 | The literate documents are bundled in the distribution as `emacys.pdf`
87 | and `hello-emacsy-paper.pdf`.
88 |
89 | Running Tests
90 | -------------
91 |
92 | $ make check
93 |
94 | TODO
95 | ----
96 |
97 | Lots to do. See the `todo.org` file.
98 |
99 | License
100 | -------
101 |
102 | Emacsy is available under the GNU GPLv3+. See the bundled LICENSE file
103 | for details.
104 |
--------------------------------------------------------------------------------
/autogen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | autoreconf --force --install -I support/m4 -I/usr/local/share/aclocal
3 |
--------------------------------------------------------------------------------
/bin/Makefile.am:
--------------------------------------------------------------------------------
1 | EXTRA_DIST =
2 |
3 | MAINTAINERCLEANFILES = Makefile.in warn-notangle env
4 |
--------------------------------------------------------------------------------
/bin/count-lines:
--------------------------------------------------------------------------------
1 | #!/opt/local/bin/gawk -f
2 | # count-lines
3 | #
4 | # http://www.cs.tufts.edu/~nr/noweb/guide.ps
5 | BEGIN { bogus = "this is total bogosity";
6 | codecount[bogus] = -1; docscount[bogus] = -1
7 | code = 0
8 | }
9 | /^@file / { thisfile = $2; files[thisfile] = 0 }
10 | /^@begin code/ { code = 1 }
11 | /^@begin docs/ { code = 0 }
12 | /^@nl/ {
13 | if (code == 0)
14 | docscount[thisfile]++
15 | else
16 | codecount[thisfile]++
17 | }
18 | END {
19 | printf " Code Docs Both File\n"
20 | for (file in files) {
21 | printf "%5d %5d %5d %s\n",
22 | codecount[file], docscount[file],
23 | codecount[file]+docscount[file], file
24 | totalcode += codecount[file]
25 | totaldocs += docscount[file]
26 | }
27 | printf "%5d %5d %5d %s\n",
28 | totalcode, totaldocs, totalcode+totaldocs, "Total"
29 | }
30 |
--------------------------------------------------------------------------------
/bin/env.in:
--------------------------------------------------------------------------------
1 | #!@BASH@
2 | # env
3 | #
4 | # Allows one to run this command in this project's local environment.
5 |
6 | ABS_TOP_SRCDIR="@abs_top_srcdir@";
7 |
8 | if test -z "$GUILE_LOAD_PATH"; then
9 | # Must include both srcdir and builddir because of the way release builds and out of source builds work.
10 | GUILE_LOAD_PATH="@abs_top_srcdir@/src:@abs_top_builddir@/src:@abs_top_srcdir@/support/scheme";
11 | else
12 | GUILE_LOAD_PATH="@abs_top_srcdir@/src:@abs_top_builddir@/src:@abs_top_srcdir@/support/scheme:$GUILE_LOAD_PATH";
13 | fi
14 |
15 | if test -z "$GUILE_LOAD_COMPILED_PATH"; then
16 | GUILE_LOAD_COMPILED_PATH="@abs_top_builddir@/src";
17 | else
18 | GUILE_LOAD_COMPILED_PATH="@abs_top_builddir@/src:$GUILE_LOAD_COMPILED_PATH";
19 | fi
20 |
21 | if test -z "$DYLD_LIBRARY_PATH"; then
22 | DYLD_LIBRARY_PATH="@abs_top_builddir@/src/emacsy";
23 | else
24 | DYLD_LIBRARY_PATH="@abs_top_builddir@/src/emacsy:$DYLD_LIBRARY_PATH";
25 | fi
26 |
27 | export GUILE_LOAD_PATH GUILE_LOAD_COMPILED_PATH DYLD_LIBRARY_PATH ABS_TOP_SRCDIR;
28 |
29 | exec "$@";
30 |
--------------------------------------------------------------------------------
/bin/namespace-markup.in:
--------------------------------------------------------------------------------
1 | #!@PERL@ -w
2 | # namespace-markup.pl
3 |
4 | use strict;
5 | use Getopt::Long;
6 |
7 | my %options = ();
8 | my %namespaces = ();
9 | my $help;
10 | GetOptions ("namespace|n=s" => \%namespaces,
11 | "help" => \$help);
12 |
13 |
14 | if (@ARGV == 0 || $help) {
15 | print STDERR "usage: namespace-markup [-n basename=namespace] [-n ...] file ...\n";
16 | print STDERR << 'END';
17 |
18 | Creates a namespace in noweb files such that for a file named, say,
19 | 'A.nw' all references inside it like 'Function' will be re-labeled
20 | 'A:Function'. However, say I have an appendix file 'A-appendix.nw'
21 | that I want to still be in the same namespace so that when it
22 | references 'Function' it will reference 'A:Function' and not
23 | 'A-appendix:Function'. I can manually specify the namespace through
24 | the arguments of this command that the basename of the filename
25 | 'A-appendix' is actually in the 'A' namespace by using the following
26 | option -n 'A-appendix=A'.
27 |
28 | END
29 | exit(2);
30 | }
31 | my $file = 'not-defined';
32 | $namespaces{$file} = 'not-defined';
33 | local *INPUT;
34 | open(INPUT, "@markup@ " . join(' ', @ARGV) . " |") or die("noweb/markup failed.\n");
35 | while (my $line = ) {
36 | if ($line =~ /^\@file ([^\.:]+)[^:]*$/) {
37 | $file = $1;
38 | if (! (defined $namespaces{$file})) {
39 | $namespaces{$file} = $file;
40 | }
41 | }
42 | if ($line =~ /^\@(defn|use) ([^+][^:]*)$/) {
43 | # I keep getting warnings here about an uninitialized value
44 | # that I can't seem to sort out.
45 | # no warnings 'uninitialized';
46 | $line =~ s/^\@(defn|use) ([^+][^:]*)$/\@$1 $namespaces{$file}:$2/;
47 | }
48 | print $line;
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/bin/warn-notangle.in:
--------------------------------------------------------------------------------
1 | #!@BASH@
2 | # warn-notangle
3 | #
4 | # Exit with an error code if there is an error in the notangle process.
5 |
6 | function usage() {
7 | echo "usage: warn-notangle [-LW] [-o output] -- " >&2;
8 | echo " -W treat warnings from notangle as errors" >&2;
9 | echo " -L add a (use-modules (line-pragma)) header to the file" >&2;
10 | exit 2;
11 | }
12 | addPrefix=0;
13 | warningsAsErrors=0;
14 | output="";
15 | while getopts hWo:L opt; do
16 | case $opt in
17 | L) addPrefix=1;;
18 | W) warningsAsErrors=1;;
19 | h) usage;;
20 | o) output=$OPTARG;;
21 | *) echo "error: invalid option given." >&2; usage;;
22 | esac
23 | done
24 | shift $[ OPTIND - 1 ]
25 |
26 | function notangle() {
27 | if [ $addPrefix -eq 1 ]; then
28 | echo "(use-modules (line-pragma))";
29 | fi
30 | @notangle@ "$@";
31 | }
32 |
33 | if [ $# -eq 0 ]; then
34 | usage;
35 | fi
36 |
37 | if [ -z "$output" ]; then
38 | notangle "$@";
39 | ec=$?;
40 | else
41 | notangle "$@" | cpif "$output";
42 | ec=${PIPESTATUS[0]};
43 | fi
44 |
45 | if [ $warningsAsErrors -eq 1 ] && [ $ec -ne 0 ]; then
46 | [ ! -z "$output" ] && rm "$output";
47 | exit $ec;
48 | else
49 | exit 0;
50 | fi
51 |
--------------------------------------------------------------------------------
/bin/wrapper.in:
--------------------------------------------------------------------------------
1 | #!@BASH@
2 | # wrapper
3 | #
4 | # Wrap a given command's output with a header and footer.
5 |
6 | function usage() {
7 | echo "usage: wrapper [-H header-file] [-F footer-file] -- " >&2;
8 | exit 2;
9 | }
10 | header="";
11 | footer="";
12 | while getopts hH:F: opt; do
13 | case $opt in
14 | H) header=$OPTARG;;
15 | F) footer=$OPTARG;;
16 | h) usage;;
17 | *) echo "error: invalid option given." >&2; usage;;
18 | esac
19 | done
20 | shift $[ OPTIND - 1 ]
21 |
22 | if [ ! -z "$header" ]; then
23 | cat "$header";
24 | fi
25 | "$@";
26 | ec=$?;
27 | if [ ! -z "$footer" ]; then
28 | cat "$footer";
29 | fi
30 | exit $ec;
31 |
--------------------------------------------------------------------------------
/configure.ac:
--------------------------------------------------------------------------------
1 | # -*- Autoconf -*-
2 | # Process this file with autoconf to produce a configure script.
3 |
4 | AC_PREREQ([2.63])
5 | # We'll try to use semantic versioning. http://semver.org
6 | AC_INIT([emacsy], [0.1.2], [shane.celis@gmail.com])
7 | AC_CONFIG_AUX_DIR([support/build-aux])
8 | # Lies.
9 | #AM_INIT_GUILE_MODULE
10 |
11 | # We're going to use GNUMake and we're going to like it.
12 | AM_INIT_AUTOMAKE([-Wall foreign -Wno-portability parallel-tests])
13 | AM_EXTRA_RECURSIVE_TARGETS([run show-doc])
14 | AM_SILENT_RULES([yes])
15 | AC_CONFIG_MACRO_DIR([support/m4])
16 | # Checks for programs.
17 | AC_PROG_CC_C99
18 | AC_PROG_CC_C_O
19 | AC_PROG_LIBTOOL
20 | AX_CHECK_NOWEB
21 | AX_CHECK_OPEN
22 | GUILE_PROGS
23 | AC_PATH_PROG([PERL], [perl])
24 | AC_SUBST([PERL])
25 |
26 | AC_PATH_PROG([BASH], [bash])
27 | AC_SUBST([BASH])
28 |
29 | AC_PATH_PROG([guile_snarf], guile-snarf)
30 | AC_SUBST(guile_snarf)
31 |
32 | # Checks for libraries.
33 | PKG_CHECK_MODULES([GUILE], [guile-2.0])
34 |
35 | AM_CONDITIONAL([HELLO_EMACSY], [test -f example/hello-emacsy/README.md])
36 |
37 | AC_SUBST(DISTDIR, ["${PACKAGE_TARNAME}-${PACKAGE_VERSION}"])
38 |
39 | #PKG_CHECK_MODULES([EMACSY], [libemacsy])
40 | #AX_CHECK_GLUT
41 | # The autoconf macro does not prefer these frameworks over the
42 | # standard X11 locations.
43 |
44 | # Checks for header files.
45 | AC_HEADER_STDC
46 | AC_CHECK_HEADERS([stdio.h])
47 |
48 | dnl AC_CHECK_HEADERS([tap/basic.h], [], [])
49 |
50 | # Checks for typedefs, structures, and compiler characteristics.
51 |
52 | # Checks for library functions.
53 | dnl AC_CHECK_FUNCS([puts])
54 |
55 | #
56 | dnl AC_SUBST(EMACSY_CFLAGS, ['-I${top_srcdir}/../..'])
57 | dnl AC_SUBST(EMACSY_LIBS, ['${top_srcdir}/../../libemacsy.a'])
58 | AC_SUBST(latexdir, ['${top_srcdir}/support/latex'])
59 | AC_SUBST(imagesdir, ['${top_srcdir}/support/images'])
60 | AC_SUBST(automakedir, ['${top_srcdir}/support/automake'])
61 | AC_SUBST(helloemacsydir, ['${top_srcdir}/example/hello-emacsy/'])
62 | pdflatex="TEXINPUTS=${latexdir}:${imagesdir}:${helloemacsydir}/support/images: $pdflatex";
63 |
64 | AC_SUBST(ENV, ['${top_builddir}/bin/env'])
65 |
66 | # Output files.
67 | AC_CONFIG_HEADERS([src/config.h])
68 | AC_CONFIG_FILES([ Makefile
69 | bin/Makefile
70 | src/Makefile
71 | src/emacsy/Makefile
72 | test/Makefile
73 | test/works-without-noweb.sh
74 | test/print-install.sh
75 | support/pkg-config/emacsy.pc
76 | support/pkg-config/not-installed/emacsy.pc
77 | example/Makefile
78 | ], [])
79 |
80 | AC_CONFIG_FILES([bin/env], [chmod a+x bin/env])
81 | AC_CONFIG_FILES([bin/warn-notangle], [chmod a+x bin/warn-notangle])
82 | AC_CONFIG_FILES([bin/namespace-markup], [chmod a+x bin/namespace-markup])
83 | AC_CONFIG_FILES([bin/wrapper], [chmod a+x bin/wrapper])
84 |
85 |
86 | dnl We can point the example to the build directory.
87 | dnl export EMACSY_CFLAGS="-I${ac_abs_top_builddir}/src/emacsy"
88 | dnl export EMACSY_LIBS="-L${ac_abs_top_builddir}/src/emacsy/.libs"
89 |
90 | dnl Autotools are impossible to love.
91 | if test -f configure; then
92 | abs_top_srcdir="`pwd`";
93 | abs_top_builddir="`pwd`";
94 | elif test -f ../configure; then
95 | abs_top_srcdir="`pwd`/..";
96 | abs_top_builddir="`pwd`";
97 | else
98 | AC_MSG_ERROR([Can't detect src and build directories.])
99 | fi
100 |
101 | # Unfortunately, since we build the source files using noweb,
102 | # depending on whether it is built in the source tree or out of the
103 | # sourcetree, the header files may or may not be where they ought to
104 | # be in srcdir or builddir. Bleh.
105 | export EMACSY_CFLAGS="-I${abs_top_srcdir}/src/emacsy -I${abs_top_builddir}/src/emacsy"
106 | export EMACSY_LIBS="-L${abs_top_builddir}/src/emacsy/.libs -lemacsy"
107 | export EMACSY_MODULE="${abs_top_srcdir}/src"
108 | AC_CONFIG_SUBDIRS([example/hello-emacsy])
109 | AC_CONFIG_SUBDIRS([example/emacsy-webkit-gtk])
110 | AC_OUTPUT
111 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | Makefile
2 | Makefile.in
3 | *.go
4 | *.defs
5 | .libs*
6 | .deps*
7 | TAGS
8 | /aclocal.m4
9 | /autom4te.cache/*
10 | /support/build-aux/config.guess
11 | /support/build-aux/config.sub
12 | /support/build-aux/depcomp
13 | /support/build-aux/install-sh
14 | /support/build-aux/ltmain.sh
15 | /support/build-aux/missing
16 | /support/build-aux/test-driver
17 | /support/m4/libtool.m4
18 | /support/m4/ltoptions.m4
19 | /support/m4/ltsugar.m4
20 | /support/m4/ltversion.m4
21 | /support/m4/lt~obsolete.m4
22 | /test/works-without-noweb.sh
23 | /src/config.h
24 | /src/config.h.in
25 | /libtool
26 | /config.log
27 | /config.status
28 | /configure
29 |
--------------------------------------------------------------------------------
/example/Makefile.am:
--------------------------------------------------------------------------------
1 | SUBDIRS = . hello-emacsy emacsy-webkit-gtk
2 |
3 | MAINTAINERCLEANFILES = Makefile.in
4 |
--------------------------------------------------------------------------------
/src/Makefile.am:
--------------------------------------------------------------------------------
1 | SUBDIRS = emacsy
2 |
3 | MAINTAINERCLEANFILES = Makefile.in config.h.in
4 |
5 | NOWEB_FILES = line-pragma.nw
6 |
7 | NOWEB_PRODUCTS = line-pragma.scm
8 | CLEANFILES = $(NOWEB_CLEANFILES)
9 | BUILT_SOURCES = $(NOWEB_BUILT_FILES)
10 | EXTRA_DIST = convenience-lambda.scm cursor-list.scm $(NOWEB_EXTRA_DIST)
11 |
12 | guilemoduledir = $(prefix)/share/guile/site/
13 | dist_guilemodule_DATA = line-pragma.scm convenience-lambda.scm cursor-list.scm
14 |
15 | include $(top_srcdir)/support/automake/noweb.am
16 |
--------------------------------------------------------------------------------
/src/convenience-lambda.scm:
--------------------------------------------------------------------------------
1 | ;; convenience-lambda.scm
2 | ;;
3 | ;; This syntax was inspired by arc and Clojure's anonymous procedure
4 | ;; syntax.
5 | ;;
6 | ;; #.\ (+ %1 %2) -> (lambda (%1 %2) (+ %1 %2))
7 | ;; #.\ (+ % %%) -> (lambda (% %%) (+ % %%))
8 | ;;
9 | ;; The .\ is supposed to approximate the lowercase lambda character in
10 | ;; ascii.
11 | ;;
12 | ;; Shane Celis
13 |
14 | (define-module (convenience-lambda)
15 | #:use-module (ice-9 regex))
16 |
17 | (eval-when (compile load eval)
18 | (define (convenience-lambda char port)
19 | (let ((uses-numbers? 'unknown))
20 | (define (range a b)
21 | (if (> a b)
22 | '()
23 | (cons a (range (1+ a) b))))
24 | (define (scan-tree lst)
25 | "scan-tree :: tree -> number"
26 | (cond
27 | ((symbol? lst)
28 | (let ((str (symbol->string lst)))
29 | (if (char=? #\% (string-ref str 0))
30 | (if (string-match "^%[0-9]+" str)
31 | (if uses-numbers?
32 | (begin
33 | (set! uses-numbers? #t)
34 | (string->number (string-trim str #\%)))
35 | (error "Pick a convention. Use %, %% or %1, %2 not both."))
36 | (if (string-match "^%+" str)
37 | (if (or (eq? uses-numbers? 'unknown)
38 | (eq? uses-numbers? #f))
39 | (begin
40 | (set! uses-numbers? #f)
41 | (string-length str))
42 | (error "Pick a convention. Use %, %% or %1, %2 not both.")
43 | )
44 | (error "Expected %1 or % to name positional variables; found '~a' instead." str)))
45 | 0)))
46 | ((pair? lst)
47 | (max (scan-tree (car lst)) (scan-tree (cdr lst))))
48 | (else 0)))
49 | (if (char=? #\\ (read-char port))
50 | (let* ((content (read port))
51 | (arg-count (scan-tree content)))
52 | (display (number->string arg-count))
53 | `(lambda ,(map (lambda (x)
54 | (string->symbol
55 | (if uses-numbers?
56 | (string-concatenate (list "%" (number->string x)))
57 | (make-string x #\%))))
58 | (range 1 arg-count))
59 | ,content))
60 | (error "Expected form like #.\\ (+ %1 %2)"))))
61 |
62 | (read-hash-extend #\. convenience-lambda))
63 |
--------------------------------------------------------------------------------
/src/cursor-list.scm:
--------------------------------------------------------------------------------
1 | #|
2 | cursor-list.scm
3 |
4 | This module creates a list with a cursor, that is, a position
5 | within the list. It's represented by two lists. The "left" list is
6 | held in reverse order which has the preceding contents. The "right"
7 | list is held in the conventional order.
8 |
9 | |#
10 |
11 | (define-module (cursor-list)
12 | #:use-module (ice-9 optargs)
13 | #:use-module (srfi srfi-1)
14 | #:use-module (srfi srfi-9)
15 | #:use-module (srfi srfi-9 gnu)
16 | #:export (make-cursor-list
17 | cursor-right?
18 | cursor-right!
19 | cursor-left?
20 | cursor-left!
21 | cursor-right-insert!
22 | cursor-left-insert!
23 | cursor-right-delete!
24 | cursor-left-delete!
25 | cursor-right-ref
26 | cursor-right-set!
27 | cursor-left-ref
28 | cursor-left-set!
29 | cursor-list?
30 | cursor-list->list))
31 |
32 | (define-record-type
33 | (%make-cursor-list left right)
34 | cursor-list?
35 | (left left set-left!)
36 | (right right set-right!))
37 |
38 | (define* (make-cursor-list list #:optional (index 0))
39 | (%make-cursor-list (reverse (take list index)) (drop list index)))
40 |
41 | (define (cursor-right-ref clist)
42 | (car (right clist)))
43 |
44 | (define (cursor-left-ref clist)
45 | (car (left clist)))
46 |
47 | (define (cursor-right-set! clist item)
48 | (set-car! (right clist) item)
49 | *unspecified*)
50 |
51 | (define (cursor-left-set! clist item)
52 | (set-car! (left clist) item)
53 | *unspecified*)
54 |
55 | (define* (cursor-right? clist #:optional (count 1))
56 | (>= (length (right clist)) count))
57 |
58 | (define* (cursor-left? clist #:optional (count 1))
59 | (>= (length (left clist)) count))
60 |
61 | (define (cursor-right! clist)
62 | (when (cursor-right? clist)
63 | (set-left! clist (cons (cursor-right-ref clist) (left clist)))
64 | (set-right! clist (cdr (right clist))))
65 | *unspecified*)
66 |
67 | (define (cursor-left! clist)
68 | (when (cursor-left? clist)
69 | (set-right! clist (cons (cursor-left-ref clist) (right clist)))
70 | (set-left! clist (cdr (left clist))))
71 | *unspecified*)
72 |
73 | (define (cursor-right-insert! clist item)
74 | (set-right! clist (cons item (right clist)))
75 | *unspecified*)
76 |
77 | (define (cursor-left-insert! clist item)
78 | (set-left! clist (cons item (left clist)))
79 | *unspecified*)
80 |
81 | (define (cursor-right-delete! clist)
82 | (set-right! clist (cdr (right clist)))
83 | *unspecified*)
84 |
85 | (define (cursor-left-delete! clist)
86 | (set-left! clist (cdr (left clist)))
87 | *unspecified*)
88 |
89 | (define (cursor-list->list clist)
90 | (append (reverse (left clist)) (right clist)))
91 |
92 | (set-record-type-printer!
93 | (lambda (clist port)
94 | (format port "#"
95 | (reverse (left clist))
96 | (right clist))))
97 |
--------------------------------------------------------------------------------
/src/emacsy/Makefile.am:
--------------------------------------------------------------------------------
1 |
2 | # Stop make from deleting intermediate files.
3 | # http://darrendev.blogspot.com/2008/06/stopping-make-delete-intermediate-files.html
4 | .SECONDARY:
5 |
6 | NOWEB_DOCS = _emacsy.pdf
7 | # Any noweb document can be built by itself as a stand-alone document by
8 | # appending a -paper suffix to its name. So if you're working on just
9 | # the command.nw, you may want to change NOWEB_DOCS to only make its
10 | # PDF.
11 | #
12 | #NOWEB_DOCS = command-paper.pdf
13 |
14 | NOWEAVE_HEADER = $(top_srcdir)/support/noweb/paper-header.nw
15 | NOWEAVE_FOOTER = $(top_srcdir)/support/noweb/paper-footer.nw
16 |
17 | PARTIAL_TEX = _emacsy-c-api.tex _event.tex _keymap.tex _command.tex _block.tex _klecl.tex _advice.tex _buffer.tex _minibuffer.tex _core.tex _window.tex _util.tex
18 | _emacsy.pdf : $(PARTIAL_TEX)
19 |
20 | # XXX this rule that pulls something from one of the examples is not working.
21 | # I'm going to just copy the file over for the meantime. Ugh.
22 |
23 | # hello-emacsy.tex : $(helloemacsydir)/src/hello-emacsy.nw
24 | # $(MAKE) -C $(helloemacsydir) all
25 | # cp $(helloemacsydir)/src/hello-emacsy.tex hello-emacsy.tex
26 |
27 | EXTRA_DIST = $(SCHEME_TESTS) $(NOWEB_FILES)
28 |
29 | NOWEB_FILES = emacsy.nw emacsy-c-api.nw event.nw util.nw keymap.nw \
30 | command.nw buffer.nw block.nw klecl.nw kbd-macro.nw \
31 | minibuffer.nw core.nw advice.nw window.nw help.nw self-doc.nw mru-stack.nw
32 |
33 | NOWEB_SCHEME_TESTS = event-test.scm self-doc-test.scm keymap-test.scm \
34 | command-test.scm buffer-test.scm block-test.scm klecl-test.scm \
35 | kbd-macro-test.scm core-test.scm emacsy-test.scm advice-test.scm \
36 | minibuffer-test.scm window-test.scm help-test.scm mru-stack-test.scm
37 |
38 | NOWEB_SCHEME_FILES = event.scm util.scm self-doc.scm keymap.scm \
39 | command.scm buffer.scm block.scm klecl.scm kbd-macro.scm \
40 | minibuffer.scm core.scm emacsy.scm advice.scm window.scm help.scm mru-stack.scm
41 |
42 | SCHEME_FILES = $(NOWEB_SCHEME_FILES) coroutine.scm agenda.scm mode.scm job.scm
43 |
44 | SCHEME_TESTS = $(NOWEB_SCHEME_TESTS) job-test.scm
45 |
46 | GOBJECTS = $(SCHEME_FILES:%.scm=%.go)
47 |
48 | NOWEB_PRODUCTS = $(NOWEB_SCHEME_FILES) $(NOWEB_SCHEME_TESTS) emacsy.h emacsy.c
49 |
50 | NOWEB_INCLUDES = $(top_srcdir)/support/noweb/boiler-plate.nw
51 |
52 | BUILT_SOURCES = $(NOWEB_PRODUCTS)
53 |
54 | CLEANFILES = $(NOWEB_CLEANFILES) $(GOBJECTS)
55 |
56 | distdir-local:
57 | $(MAKE) all
58 |
59 | doc: $(NOWEB_DOCS)
60 |
61 | # The util file may have its chunks specified anywhere.
62 | util.scm : util.nw $(NOWEB_FILES)
63 |
64 | if LINE_PRAGMA
65 | LINE_PRAGMA_FLAGS = -L
66 | else
67 | LINE_PRAGMA_FLAGS =
68 | endif
69 |
70 | MARKUP_FLAGS = -markup "$(top_builddir)/bin/namespace-markup --"
71 | #WARN_NOTANGLE_FLAGS = -W # treat warnings as errors
72 | WARN_NOTANGLE_C_FLAGS =
73 | WARN_NOTANGLE_LISP_FLAGS = $(LINE_PRAGMA_FLAGS) # add a (use-modules (line-pragma)) header
74 | NOWEAVE_FLAGS = $(MARKUP_FLAGS)
75 | NOTANGLE_C_FLAGS = -c $(LINE_PRAGMA_FLAGS) $(MARKUP_FLAGS)
76 | NOTANGLE_LISP_FLAGS = $(LINE_PRAGMA_FLAGS) $(MARKUP_FLAGS)
77 | NOTANGLE_H_FLAGS = $(LINE_PRAGMA_FLAGS) $(MARKUP_FLAGS)
78 | lib_LTLIBRARIES = libemacsy.la
79 |
80 | AM_CFLAGS = $(GUILE_CFLAGS)
81 | libemacsy_la_LIBADD = $(GUILE_LIBS)
82 | libemacsy_la_SOURCES = emacsy.c
83 | include_HEADERS = emacsy.h
84 |
85 | DISTCLEANFILES = $(PARTIAL_TEX) _emacsy.tex
86 | MAINTAINERCLEANFILES = Makefile.in config.h.in
87 |
88 | guilemoduledir = $(prefix)/share/guile/site/$(PACKAGE_TARNAME)
89 | dist_guilemodule_DATA = $(SCHEME_FILES) $(GOBJECTS)
90 |
91 | AM_ETAGSFLAGS = -l scheme
92 | ETAGS_ARGS = $(SCHEME_FILES) $(SCHEME_TESTS)
93 | TAGS_DEPENDENCIES = $(SCHEME_FILES) $(SCHEME_TESTS)
94 |
95 | #TESTS_ENVIRONMENT = LOG_COMPILER
96 | TESTS = $(SCHEME_TESTS)
97 | TEST_EXTENSIONS = .scm
98 | SCM_LOG_COMPILER = $(top_builddir)/bin/env $(GUILE)
99 |
100 | include $(top_srcdir)/support/automake/noweb.am
101 | include $(top_srcdir)/support/automake/guile.am
102 |
103 | emacsy.h emacsy.c : emacsy-c-api.nw
104 |
105 | dist-hook: _emacsy.pdf
106 | cp $(builddir)/_emacsy.pdf $(top_distdir)/emacsy.pdf
107 |
108 |
109 |
--------------------------------------------------------------------------------
/src/emacsy/advice.nw:
--------------------------------------------------------------------------------
1 | \section{Advice}
2 |
3 | %\epigraph{Wise men don't need advice. Fools won't take it.}{Benjamin Franklin}
4 |
5 | %\epigraph{Nobody can give you wiser advice than yourself.}{Marcus Tullius Cicero}
6 |
7 | \epigraph{No enemy is worse than bad advice.}{Sophocles}
8 |
9 |
10 | Emacs has a facility to define ``advice'' these are pieces of code
11 | that run before, after, or around an already defined function. This
12 | \href{http://electricimage.net/cupboard/2013/05/04/on-defadvice/}{article}
13 | provides a good example.
14 |
15 | <>=
16 | (define-module (emacsy advice)
17 | #:use-module (srfi srfi-9)
18 | )
19 |
20 | <>
21 |
22 | <>
23 |
24 | <>
25 | @
26 |
27 | How will this work? Before we try to make the macro, let's focus on
28 | building up the functions. We want to have a function that we can
29 | substitute for the original function which will have a number of
30 | before, after, and around pieces of advice that can be attached to it.
31 |
32 | <>=
33 | (define-record-type
34 | (make-record-of-advice original before around after)
35 | record-of-advice?
36 | (original advice-original)
37 | (before advice-before set-advice-before!)
38 | (around advice-around set-advice-around!)
39 | (after advice-after set-advice-after!))
40 | @
41 |
42 | <>=
43 | (define-record-type
44 | (make-piece-of-advice procedure name class priority flag)
45 | piece-of-advice?
46 | (procedure poa-procedure)
47 | (name poa-name) ;; symbol not string
48 | (class poa-class set-poa-class!)
49 | (priority poa-priority set-poa-priority!)
50 | (flag poa-flag set-poa-flag!))
51 | @
52 |
53 | <>=
54 | (define (make-advising-function advice)
55 | (lambda args
56 | (let ((around-advices (append (advice-around advice)
57 | (list (make-piece-of-advice
58 | (advice-original
59 | advice)
60 | 'original
61 | 'bottom
62 | 0
63 | 'activate))))
64 | (result #f))
65 | (define (my-next-advice)
66 | (if (null? around-advices)
67 | (throw 'next-advices-drained)
68 | (let ((next-one-around (car around-advices)))
69 | (set! around-advices (cdr around-advices))
70 | (apply (poa-procedure next-one-around) args))))
71 | ;; This could be done more cleanly. For instance,
72 | ;; If one calls (next-advice) more than once,
73 | ;; they drain all the advice rather than calling
74 | ;; the same advice again, which is probably
75 | ;; the more correct behavior.
76 |
77 | (for-each (lambda (before)
78 | (apply (poa-procedure before) args))
79 | (advice-before advice))
80 |
81 | (set! result (with-fluid* next-advice-func my-next-advice
82 | (lambda ()
83 | (next-advice))))
84 | (for-each (lambda (after)
85 | (apply (poa-procedure after) result args))
86 | (advice-after advice))
87 | result)))
88 | @
89 |
90 | <>=
91 | (define (next-advice)
92 | (if (fluid-bound? next-advice-func)
93 | ((fluid-ref next-advice-func))
94 | (throw 'no-next-advice-bound)))
95 | @
96 |
97 | <>=
98 | (define next-advice-func (make-fluid))
99 | @
100 |
101 | To test this functionality, we're going to make some counter
102 | procedures.
103 |
104 | <>=
105 | (define (my-orig-func x)
106 | (+ x 1))
107 |
108 | (define (make-counter)
109 | (let ((x 0))
110 | (lambda args
111 | (if (and (= (length args) 1) (eq? (car args) 'count))
112 | x
113 | (begin (set! x (+ x 1))
114 | (car args))))))
115 |
116 | (define a-before (make-counter))
117 | @
118 |
119 | Let's make an identity advice procedure. It does nothing, but it does
120 | wrap around the function.
121 | <>=
122 | (define advice (make-record-of-advice my-orig-func '() '() '()))
123 |
124 | (define advised-func (make-advising-function advice))
125 | (check (a-before 'count) => 0)
126 | (check (my-orig-func 1) => 2)
127 | (check (advised-func 1) => 2)
128 | (check (a-before 'count) => 0)
129 | @
130 |
131 | Let's test this with the simple functionality of having a piece of
132 | before advice.
133 |
134 | <>=
135 | (define advice (make-record-of-advice my-orig-func (list (make-piece-of-advice a-before 'a-before 'before 0 'activate)) '() '()))
136 |
137 | (define advised-func (make-advising-function advice))
138 | (check (a-before 'count) => 0)
139 | (check (my-orig-func 1) => 2)
140 | (check (advised-func 1) => 2)
141 | (check (a-before 'count) => 1)
142 | @
143 |
144 | Let's check the after advice.
145 | <>=
146 | (define a-after (make-counter))
147 | (define advice (make-record-of-advice my-orig-func '() '()
148 | (list (make-piece-of-advice a-after 'a-after 'after 0 'activate))))
149 |
150 | (define advised-func (make-advising-function advice))
151 | (check (a-after 'count) => 0)
152 | (check (my-orig-func 1) => 2)
153 | (check (advised-func 1) => 2)
154 | (check (a-after 'count) => 1)
155 | @
156 |
157 | Let's check the after advice.
158 | <>=
159 | (define a-around (lambda args
160 | (next-advice)
161 | 1))
162 | (define advice (make-record-of-advice my-orig-func '() (list (make-piece-of-advice a-around 'a-around 'around 0 'activate)) '()))
163 |
164 | (define advised-func (make-advising-function advice))
165 | (check (my-orig-func 1) => 2)
166 | (check (advised-func 1) => 1)
167 | @
168 |
169 | So the rudimentary elements of our advice facility works. Now, we
170 | want to make it so that we can alter the advice of an existing
171 | function. We'll add a property to the procedure, such that we can
172 | tell if it is an ``advised'' procedure.
173 |
174 | <>=
175 | (define (advised? proc)
176 | (and (procedure? proc)
177 | (assq 'record-of-advice
178 | (procedure-properties proc))
179 | #t))
180 | @
181 |
182 | <>=
183 | (define (make-advising-function* proc record-of-advice)
184 | (let ((advised-proc (make-advising-function proc record-of-advice)))
185 | (set-procedure-property! proc 'record-of-advice record-of-advice)))
186 | @
187 |
188 | <>=
189 | (define (remove-advice! procedure name)
190 | (if (advised? procedure)
191 | (let ((roa (procedure-property procedure 'record-of-advice)))
192 | (for-each (lambda (class)
193 | (match (getter-and-setter-for-poa class)
194 | ((getter setter!)
195 |
196 | (setter! (delete! name (getter roa) (lambda (name elem)
197 | (eq? (poa-name elem))))))))
198 | '(before around after)))))
199 | @
200 |
201 | <>=
202 | (define (getter-and-setter-for-poa poa-class)
203 | (cond ((eq? poa-class 'before)
204 | (cons advice-before set-advice-before!)
205 | (eq? poa-class 'around)
206 | (cons advice-around set-advice-around!)
207 | (eq? poa-class 'after)
208 | (cons advice-after set-advice-after!)
209 | (else
210 | (throw 'invalid-advice-class)))))
211 | @
212 |
213 | <>=
214 | (define (add-advice! procedure piece-of-advice)
215 | "Add a piece-of-advice to the procedure. Returns the advised
216 | procedure."
217 | (define (sort-by! lst f)
218 | (sort! lst (lambda (a b)
219 | (< (f a) (f b)))))
220 | (if (not (advised? procedure))
221 | ;; Procedure has never been advised.
222 | (add-advice (make-advising-function* procedure (make-record-of-advice procedure '() '() '())) piece-of-advice)
223 | ;; Add a new piece of advice.
224 | (begin
225 | (remove-advice! procedure (poa-name piece-of-advice))
226 | (let ((roa (procedure-property procedure 'record-of-advice)))
227 | (match (getter-setter-for-poa (poa-class piece-of-advice))
228 | ((getter setter!)
229 | (setter!
230 | roa
231 | (sort-by! (cons piece-of-advice (getter roa))
232 | poa-priority)))))
233 | procedure)))
234 | @
235 |
236 | <>=
237 | (use-modules (emacsy advice)
238 | (emacsy event)
239 | (emacsy klecl)
240 | (oop goops)
241 | (srfi srfi-11))
242 |
243 | (eval-when (compile load eval)
244 | ;; Some trickery so we can test private procedures.
245 | (module-use! (current-module) (resolve-module '(emacsy advice))))
246 |
247 | <<+ Test Preamble>>
248 | <>
249 | <<+ Test Postscript>>
250 | @
251 |
--------------------------------------------------------------------------------
/src/emacsy/agenda.scm:
--------------------------------------------------------------------------------
1 | ;;; guile-2d
2 | ;;; Copyright (C) 2013 David Thompson
3 | ;;;
4 | ;;; Guile-2d is free software: you can redistribute it and/or modify it
5 | ;;; under the terms of the GNU Lesser General Public License as
6 | ;;; published by the Free Software Foundation, either version 3 of the
7 | ;;; License, or (at your option) any later version.
8 | ;;;
9 | ;;; Guile-2d is distributed in the hope that it will be useful, but
10 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
11 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 | ;;; Lesser General Public License for more details.
13 | ;;;
14 | ;;; You should have received a copy of the GNU Lesser General Public
15 | ;;; License along with this program. If not, see
16 | ;;; .
17 |
18 | ;;; Commentary:
19 | ;;
20 | ;; Deferred procedure scheduling.
21 | ;;
22 | ;;; Code:
23 |
24 | (define-module (emacsy agenda)
25 | #:use-module (ice-9 q)
26 | #:use-module (srfi srfi-1)
27 | #:use-module (srfi srfi-9)
28 | #:use-module (emacsy coroutine)
29 | #:export (make-agenda
30 | with-agenda
31 | agenda-schedule
32 | agenda-schedule-interval
33 | update-agenda
34 | clear-agenda
35 | wait))
36 |
37 | ;; This code is a modified version of the agenda implementation in
38 | ;; SICP. Thank you, SICP!
39 |
40 | ;;;
41 | ;;; Time segment
42 | ;;;
43 |
44 | (define-record-type
45 | (%make-time-segment time queue)
46 | time-segment?
47 | (time segment-time)
48 | (queue segment-queue))
49 |
50 | (define (make-time-segment time . callbacks)
51 | "Create a new time segment at TIME and enqueus everything in the
52 | list CALLBACKS."
53 | (let ((segment (%make-time-segment time (make-q))))
54 | ;; Enqueue all callbacks
55 | (for-each (lambda (c) (segment-enq segment c)) callbacks)
56 | segment))
57 |
58 | (define (segment-enq segment callback)
59 | "Add the CALLBACK procedure to SEGMENT's queue."
60 | (enq! (segment-queue segment) callback))
61 |
62 | ;;;
63 | ;;; Agenda
64 | ;;;
65 |
66 | (define-record-type
67 | (%make-agenda time segments)
68 | agenda?
69 | (time agenda-time set-agenda-time!)
70 | (segments agenda-segments set-agenda-segments!))
71 |
72 | (define (make-agenda)
73 | "Create a new, empty agenda."
74 | (%make-agenda 0 '()))
75 |
76 | ;; The global agenda that will be used when schedule is called outside
77 | ;; of a with-agenda form.
78 | (define global-agenda (make-agenda))
79 |
80 | (define *current-agenda* global-agenda)
81 |
82 | ;; emacs: (put 'with-agenda 'scheme-indent-function 1)
83 | (define-syntax-rule (with-agenda agenda body ...)
84 | (begin
85 | (set! *current-agenda* agenda)
86 | body
87 | ...
88 | (set! *current-agenda* global-agenda)))
89 |
90 | (define (agenda-empty? agenda)
91 | "Return #t if AGENDA has no scheduled procedures."
92 | (null? (agenda-segments agenda)))
93 |
94 | (define (first-segment agenda)
95 | "Return the first time segment in AGENDA."
96 | (car (agenda-segments agenda)))
97 |
98 | (define (rest-segments agenda)
99 | "Return everything but the first segment in AGENDA."
100 | (cdr (agenda-segments agenda)))
101 |
102 | (define (agenda-add-segment agenda time callback)
103 | "Add a new time segment to the beginning of AGENDA at the given TIME
104 | and enqueue CALLBACK."
105 | (set-agenda-segments! agenda
106 | (cons (make-time-segment time callback)
107 | (agenda-segments agenda))))
108 |
109 | (define (insert-segment segments time callback)
110 | "Insert a new time segment after the first segment in SEGMENTS."
111 | (set-cdr! segments
112 | (cons (make-time-segment time callback)
113 | (cdr segments))))
114 |
115 | (define (first-agenda-item agenda)
116 | "Return the first time segment queue in AGENDA."
117 | (if (agenda-empty? agenda)
118 | (error "Agenda is empty")
119 | (segment-queue (first-segment agenda))))
120 |
121 | (define (agenda-time-delay agenda dt)
122 | "Return the sum of the time delta, DT, and the current time of AGENDA."
123 | (+ (agenda-time agenda) (inexact->exact (round dt))))
124 |
125 | (define (%agenda-schedule agenda callback dt)
126 | "Schedule the procedure CALLBACK in AGENDA to be run DT updates from now."
127 | (let ((time (agenda-time-delay agenda dt)))
128 | (define (belongs-before? segments)
129 | (or (null? segments)
130 | (< time (segment-time (car segments)))))
131 |
132 | (define (add-to-segments segments)
133 | ;; Add to existing time segment if the times match
134 | (if (= (segment-time (car segments)) time)
135 | (segment-enq (car segments) callback)
136 | ;; Continue searching
137 | (if (belongs-before? (cdr segments))
138 | ;; Create new time segment and insert it where it belongs
139 | (insert-segment segments time callback)
140 | ;; Continue searching
141 | (add-to-segments (cdr segments)))))
142 |
143 | ;; Handle the case of inserting a new time segment at the
144 | ;; beginning of the segment list.
145 | (if (belongs-before? (agenda-segments agenda))
146 | ;; Add segment if it belongs at the beginning of the list...
147 | (agenda-add-segment agenda time callback)
148 | ;; ... Otherwise, search for the right place
149 | (add-to-segments (agenda-segments agenda)))
150 | *unspecified*))
151 |
152 | (define (flush-queue! q)
153 | "Dequeue and execute every member of Q."
154 | (unless (q-empty? q)
155 | ((deq! q)) ;; Execute scheduled procedure
156 | (flush-queue! q)))
157 |
158 | (define (%update-agenda agenda)
159 | "Move AGENDA forward in time and run scheduled procedures."
160 | (set-agenda-time! agenda (1+ (agenda-time agenda)))
161 | (let next-segment ()
162 | (unless (agenda-empty? agenda)
163 | (let ((segment (first-segment agenda)))
164 | ;; Process time segment if it is scheduled before or at the
165 | ;; current agenda time.
166 | (when (>= (agenda-time agenda) (segment-time segment))
167 | (flush-queue! (segment-queue segment))
168 | (set-agenda-segments! agenda (rest-segments agenda))
169 | (next-segment))))))
170 |
171 | (define (%clear-agenda agenda)
172 | "Remove all scheduled procedures from AGENDA."
173 | (set-agenda-segments! agenda '()))
174 |
175 | (define* (agenda-schedule thunk #:optional (delay 1))
176 | "Schedule THUNK in the current agenda to run after DELAY updates (1
177 | by default)."
178 | (%agenda-schedule *current-agenda* thunk delay))
179 |
180 | (define* (agenda-schedule-interval thunk #:optional (interval 1) (delay 1))
181 | "Schedule THUNK in the current agenda to run after DELAY updates and
182 | run every INTERVAL updates thereafter. Both DELAY and INTERVAL default
183 | to 1. Simply pass THUNK and nothing else to schedule THUNK to be run
184 | upon every update."
185 | (%agenda-schedule *current-agenda*
186 | (lambda ()
187 | (thunk)
188 | (agenda-schedule-interval thunk interval interval))
189 | delay))
190 |
191 | (define (update-agenda)
192 | "Update the current agenda."
193 | (%update-agenda *current-agenda*))
194 |
195 | (define (clear-agenda)
196 | "Clear the current agenda."
197 | (%clear-agenda *current-agenda*))
198 |
199 | (define* (wait #:optional (delay 1))
200 | "Yield coroutine and schdule the continuation to be run after DELAY
201 | ticks."
202 | (yield (lambda (resume) (agenda-schedule resume delay))))
203 |
--------------------------------------------------------------------------------
/src/emacsy/block.nw:
--------------------------------------------------------------------------------
1 | % -*- mode: Noweb; noweb-code-mode: scheme-mode -*-
2 | \section{Block Module}
3 |
4 | \epigraph{Wearied I fell asleep: But now lead on; In me is no delay; with thee to go, Is to stay here}{Paradise Lost \\John Milton}
5 |
6 | The [[block]] module handles blocking in Emacsy. When I prototyped
7 | Emacsy, I considered this the riskiest part of the project. If I
8 | couldn't get this to work, it wouldn't be worth trying to develop the
9 | idea further. To understand what I mean, one can try running the
10 | following in Emacs \verb|M-: (read-key)|. This will evaluate
11 | [[read-key]] and effectively block until there is another key press.
12 |
13 | Implementing ``blocking'' on a small set of bare functions can be done
14 | without too much trickery. However, what if you have computations
15 | that follow after these functions? For instance if you evaluate
16 | \verb|M-: (message "Got %s" (read-key))|, [[read-key]] must block
17 | until a key is pressed, then resume the computation that will call
18 | [[message]]. An Operating System must perform a similar operation
19 | whenever a system call is made, usually implemented using interrupts
20 | or traps. Without recourse to interrupts and bare stack manipulation,
21 | what can we do to achieve a similar feature?
22 |
23 | GNU Guile has a terrific feature called delimited continuations. Here
24 | is an example of a delimited continuation from the Guile Manual. This
25 | continuation [[cont]]
26 |
27 | \begin{verbatim}
28 | (define cont
29 | (call-with-prompt
30 | ;; tag
31 | 'foo
32 | ;; thunk
33 | (lambda ()
34 | (+ 34 (abort-to-prompt 'foo)))
35 | ;; handler
36 | (lambda (k) k)))
37 | \end{verbatim}
38 |
39 | \noindent could be rewritten as
40 |
41 | \begin{verbatim}
42 | (define cont
43 | (lambda (x)
44 | (+ 34 x)))
45 | \end{verbatim}.
46 |
47 | \noindent I had to read and re-read this example to let it sink in.
48 | What does it buy us? It allows us to abort a computation at any time
49 | and resume it later.\footnote{Lua's coroutines also seem like a good
50 | candidate for pulling off a trick like this. Python's generators,
51 | however, do not.} So if we were to implement [[read-key]], we abort
52 | the computation if there has been no key press. Our main loop in
53 | \verb|C| continues to run, redraw, wait for key presses. When a key
54 | press comes, we can resume that computation---that continuation.
55 | That's the idea. What's beautiful about this is that the user code
56 | has access to the same rich input services as the system code without
57 | any unnatural contortions. These ``system calls'' look like regular
58 | procedure calls much like the Unix call to [[open]] looks like a
59 | regular function call.
60 |
61 | One of the key features I figured one bought by embedding a
62 | higher-level language like Scheme was garbage collection. High-level
63 | blocking while still being low-level non-blocking is a huge boon.
64 | What we'll implement is a simple blocking system using Guile's
65 | delimited continuations, also called prompts.
66 |
67 | Let's start with the tests, so the usage is somewhat obvious.
68 |
69 | <>=
70 | (define done-blocking? #f)
71 | (define (i-block)
72 | (block-yield)
73 | (set! done-blocking? #t))
74 | @
75 |
76 | [[i-block]] will immediately yield. If it is not called with
77 | [[call-blockable]] then it will throw an error.
78 |
79 | <>=
80 | (check-throw (i-block) => 'misc-error)
81 | @
82 |
83 | <>=
84 | (define-public (block-yield)
85 | ;; I forgot why I'm running this thunk.
86 | (run-thunk (abort-to-prompt 'block 'block-until
87 | (const #t) #t)))
88 | @
89 |
90 | [[call-blockable]] will handle any aborts to the [['block]] prompt.
91 | If the thunk aborts, it adds an instance of the class
92 | [[]] to a list of such instances.
93 |
94 | <>=
95 | (define blocking-continuations '())
96 | @
97 |
98 | <>=
99 | (define-public (call-blockable thunk)
100 | (let ((bc #f))
101 | (call-with-prompt
102 | 'block
103 | thunk
104 | (lambda (cc kind . args)
105 | (case kind
106 | ((block-until)
107 | (let ((continue-command-loop? #t)
108 | (continue-wait? #t))
109 | (set! bc <>)
110 | ;; Remember this bc.
111 | (cons! bc blocking-continuations))))))
112 | bc))
113 | @
114 |
115 | <>=
116 | ;; I want to get rid of this state if I can.
117 | (define-public continue-command-loop? (make-unbound-fluid))
118 | @
119 |
120 | Let's add a little syntactic sugar [[with-blockable]].
121 |
122 | <>=
123 | (define-syntax-public with-blockable
124 | (syntax-rules ()
125 | ((with-blockable e ...)
126 | (call-blockable (lambda () e ...)))))
127 | @
128 |
129 | <>=
130 | (make
131 | #:tag 'block-until
132 | #:continuation cc
133 | #:loop-number 0
134 | #:continue-when? (car args)
135 | #:continue-now
136 | (lambda ()
137 | (set! continue-command-loop? #f)
138 | (if continue-wait?
139 | (call-blockable
140 | (lambda () (cc (lambda () #t))))))
141 | #:serial? (cadr args))
142 | @
143 |
144 | Now we can call [[i-block]] and capture its continuation.
145 |
146 | <>=
147 | (check-true (call-blockable (lambda () (i-block))))
148 | (check (length blocking-continuations) => 1)
149 | @
150 |
151 | To possibly resume these continuations, we're going to call
152 | [[block-tick]]. Additionally, continuations come in two flavors:
153 | serial and non-serial. The constraints on resuming are different. A
154 | non-serial block can be resumed whenever the
155 | [[continue-when?]]\todo{rename continue-now?} thunk return true. A
156 | serial block, however, will only be resumed after every other serial
157 | block that has a greater number, meaning more recent, has been
158 | resumed.
159 |
160 | <>=
161 | (define-public (block-tick)
162 | (set! blocking-continuations
163 | ;; Sort the continuations by the most recent ones.
164 | (sort! blocking-continuations (lambda (a b)
165 | (> (number a) (number b)))))
166 | (let ((ran-serial? #f))
167 | (for-each
168 | (lambda (bc)
169 | (if (not (serial? bc))
170 | ;; If it's not serial, we might run it.
171 | (maybe-continue bc)
172 | ;; If it's serial, we only run the top one.
173 | (if (and (not ran-serial?) (serial? bc))
174 | (begin
175 | (if (maybe-continue bc)
176 | (set! ran-serial? #t))))))
177 | blocking-continuations))
178 | ;; Keep everything that hasn't been run.
179 | (set! blocking-continuations
180 | (filter! (lambda (bc) (not (ran? bc)))
181 | blocking-continuations))
182 | ;(format #t "blocking-continuations #~a of ~a~%" (length blocking-continuations) (map number blocking-continuations))
183 | (when (or (null? blocking-continuations)
184 | (null? (filter serial? blocking-continuations)))
185 | (run-hook no-blocking-continuations-hook))
186 | #t)
187 | @
188 |
189 | \todo[inline]{Maybe get rid of no-blocking-continuations-hook and just have a
190 | predicate to test for whether any blocks exist?}
191 |
192 | <>=
193 | (define*-public (blocking?)
194 | (> (length blocking-continuations) 0))
195 | @
196 |
197 | <>=
198 | (define-method (maybe-continue (obj ))
199 | (if (and (not (ran? obj))
200 | ; (or run-serial? (serial? obj))
201 | ;; this line crashed.
202 | (run-thunk (slot-ref obj 'continue-when?)))
203 | (begin (set! (ran? obj) #t)
204 | (run-thunk (slot-ref obj 'continue-now))
205 | #t)
206 | #f))
207 | @
208 |
209 | If there are no blocking continuations, we run this hook.
210 |
211 | <>=
212 | (define-public no-blocking-continuations-hook (make-hook))
213 | @
214 |
215 | Now we should be able to resume [[i-block]] by running [[block-tick]].
216 |
217 | <>=
218 | (check done-blocking? => #f)
219 | (check (block-tick) => #t)
220 | (check done-blocking? => #t)
221 | (check (length blocking-continuations) => 0)
222 | @
223 |
224 | In addition to simply yielding we can block until a particular
225 | condition is met.
226 |
227 | <>=
228 | (define*-public (block-until condition-thunk #:optional (serial? #f))
229 | (if (not (run-thunk condition-thunk))
230 | (run-thunk (abort-to-prompt 'block 'block-until
231 | condition-thunk serial?))))
232 | @
233 |
234 | \noindent And if we have [[block-until]], it's easy to write
235 | [[block-while]].
236 |
237 | <>=
238 | (define*-public (block-while condition-thunk #:optional (serial? #f))
239 | (block-until (negate condition-thunk) serial?))
240 | @
241 |
242 | Let's exercise this [[block-until]] procedure.
243 | <>=
244 | (define continue-blocking? #t)
245 | (define (i-block-until)
246 | (block-until (lambda () (not continue-blocking?))))
247 | (check (length blocking-continuations) => 0)
248 | (call-blockable (lambda () (i-block-until)))
249 | (check (length blocking-continuations) => 1)
250 | @
251 |
252 | \noindent Now, even if we call [[block-tick]] it shouldn't be resumed.
253 |
254 | <>=
255 | (block-tick)
256 | (check (length blocking-continuations) => 1)
257 | @
258 |
259 | \noindent Let's change the condition for our blocking call.
260 |
261 | <>=
262 | (set! continue-blocking? #f)
263 | (check (length blocking-continuations) => 1)
264 | (block-tick)
265 | (check (length blocking-continuations) => 0)
266 | @
267 |
268 | Sometimes we may just want to kill a blocking continuation. One could
269 | just forget the reference and let it be garbage collected. Here,
270 | we're going to throw an exception such that whatever the continuation
271 | was doing can potentially be cleaned up.
272 |
273 | <>=
274 | (define-method-public (block-kill (obj ))
275 | (set! (ran? obj) #t)
276 | (call-blockable
277 | (lambda () ((slot-ref obj 'continuation)
278 | (lambda ()
279 | (throw 'block-killed obj)
280 | #f)))))
281 | @
282 |
283 | \noindent Let's exercise [[block-kill]].
284 | <>=
285 | (set! continue-blocking? #t)
286 | (let ((bc (call-blockable (lambda () (i-block-until)))))
287 | (check (length blocking-continuations) => 1)
288 | (block-tick)
289 | (check (length blocking-continuations) => 1)
290 | (check-throw (block-kill bc) => 'block-killed)
291 | ;; The killed block is not cleaned out immediately.
292 | (check (length blocking-continuations) => 1)
293 | (block-tick)
294 | (check (length blocking-continuations) => 0))
295 |
296 | @
297 |
298 | We're going to capture these blocking continuations into a class.
299 |
300 | <>=
301 | (define-class ()
302 | (number #:getter number #:init-thunk (let ((count -1))
303 | (lambda () (incr! count))))
304 | (loop-number #:getter loop-number #:init-keyword #:loop-number)
305 | (tag #:getter tag #:init-keyword #:tag)
306 | (continuation #:init-keyword #:continuation)
307 | (continue-when? #:init-keyword #:continue-when?)
308 | (continue-now #:init-keyword #:continue-now)
309 | ;; Has this ran and ready to be deleted?
310 | (ran? #:accessor ran? #:init-value #f)
311 | (serial? #:getter serial? #:init-keyword #:serial? #:init-value #t))
312 |
313 | (define-method (write (obj ) port)
314 | (write (string-concatenate
315 | (list "#string (tag obj))
316 | " " (number->string (number obj))
317 | " cl " (number->string (loop-number obj)) ">")) port))
318 | @
319 |
320 | \noindent There's a lot of information being
321 |
322 |
323 |
324 | \subsection*{Utilities}
325 | The [[incr!]] macro is just a little bit of syntactic sugar.
326 |
327 | <>=
328 | (define-syntax-public incr!
329 | (syntax-rules ()
330 | ((incr! variable inc)
331 | (begin
332 | (set! variable (+ variable inc))
333 | variable))
334 | ((incr! variable)
335 | (incr! variable 1))))
336 | @
337 |
338 | <>=
339 | (define-syntax-public decr!
340 | (syntax-rules ()
341 | ((decr! variable inc)
342 | (incr! variable (- inc)))
343 | ((decr! variable)
344 | (decr! variable 1))))
345 | @
346 |
347 | <>=
348 | (define-syntax-public cons!
349 | (syntax-rules ()
350 | ((cons! elm list)
351 | (begin
352 | (set! list (cons elm list))
353 | list))))
354 | @
355 | \subsection*{File Layout}
356 |
357 | <>=
358 | (define-module (emacsy block)
359 | #:use-module (ice-9 optargs)
360 | #:use-module (oop goops)
361 | #:use-module (emacsy util))
362 | <>
363 | <>
364 | <>
365 | <>
366 | <>
367 | @
368 |
369 | Layout for tests.
370 | <>=
371 | (use-modules (emacsy block)
372 | (oop goops))
373 |
374 | (eval-when (compile load eval)
375 | ;; Some trickery so we can test private procedures.
376 | (module-use! (current-module) (resolve-module '(emacsy block))))
377 |
378 | <<+ Test Preamble>>
379 | <>
380 | <<+ Test Postscript>>
381 | @
382 |
383 |
--------------------------------------------------------------------------------
/src/emacsy/command.nw:
--------------------------------------------------------------------------------
1 | % -*- mode: Noweb; noweb-code-mode: scheme-mode -*-
2 | \section{Command Module}
3 |
4 | \epigraph{If words of command are not clear and distinct, if orders are not thoroughly understood, then the general is to blame.}{Sun Tzu}
5 |
6 | The command module is responsible for a couple things. In Emacs one
7 | defines commands by using the special form [[(interactive)]] within
8 | the body of the procedure. Consider this simple command.
9 |
10 | \begin{verbatim}
11 | (defun hello-command ()
12 | (interactive)
13 | (message "Hello, Emacs!"))
14 | \end{verbatim}
15 |
16 | Emacsy uses a more Scheme-like means of defining commands as shown
17 | below.
18 |
19 | \begin{verbatim}
20 | (define-interactive (hello-command)
21 | (message "Hello, Emacsy!"))
22 | \end{verbatim}
23 |
24 | One deviation from Emacs I want to see within Emacsy is to have the
25 | commands be more context sensitive. To illustrate the problem when I
26 | hit \verb|M-x TAB TAB| it autocompletes all the available commands
27 | into a buffer. In my case that buffer contains 4,840 commands. This
28 | doesn't seem to hurt command usability, but it does hurt the command
29 | discoverability.
30 |
31 | I want Emacsy to have command sets that are analogous to keymaps.
32 | There will be a global command set [[global-cmdset]] similar to the
33 | global keymap [[global-map]]. And in the same way that major and
34 | minor modes may add keymaps to a particular buffer, so too may they
35 | add command maps.
36 |
37 | \todo[inline]{Figure out where to look up any given
38 | function/variable using this kind of code (apropos-internal
39 | "\^emacsy.*"). Refer to ice-9 readline package for an example of
40 | its usage.}
41 |
42 | The class holds the entries, a string completer for tab completion,
43 | and potentially a parent command map.
44 |
45 | \todo[inline]{Wouldn't this better be thought of as a command set
46 | rather than map. Also, having it as a map means there could be two
47 | different implementations of the command; the one referred to by the
48 | procedure, and the one referred to in the map. They could be become
49 | unsynchronized.}
50 |
51 | <>=
52 | (define-class-public ()
53 | (commands #:getter commands #:init-form (list))
54 | (completer #:getter completer #:init-form (make ))
55 | (parent #:accessor parent #:init-keyword #:parent #:init-value #f))
56 | (export commands completer)
57 | @
58 |
59 | We have accessors for adding, removing, and testing what's in the
60 | set. Note that the parent set is never mutated.
61 |
62 | <>=
63 | (define-method-public (command-contains? (cmap ) command-symbol)
64 | (or (memq command-symbol (commands cmap))
65 | (and (parent cmap) (command-contains? (parent cmap) command-symbol))))
66 |
67 | (define-method-public (command-add! (cmap ) command-symbol)
68 | (when (not (command-contains? cmap command-symbol))
69 | (add-strings! (completer cmap) (list (symbol->string command-symbol)))
70 | (slot-set! cmap 'commands (cons command-symbol (commands cmap)))))
71 |
72 | (define-method-public (command-remove! (cmap ) command-symbol)
73 | (when (command-contains? cmap command-symbol)
74 | (slot-set! cmap 'commands (delq! command-symbol (commands cmap)))
75 | ;; Must rebuild the completer.
76 | (let ((c (make )))
77 | (add-strings! c (map symbol->string (commands cmap)))
78 | (slot-set! cmap 'completer c))))
79 | @
80 |
81 | We define the global command map.
82 |
83 | <>=
84 | (define-public global-cmdset (make ))
85 | @
86 |
87 | \todo[inline]{Perhaps procedure-properties should be used to denote a
88 | procedure as a command?}
89 |
90 | <>=
91 | (define-public (module-command-interface mod)
92 | (unless (module-variable mod '%module-command-interface)
93 | (module-define! mod '%module-command-interface
94 | (let ((iface (make-module)))
95 | (set-module-name! iface (module-name mod))
96 | (set-module-version! iface (module-version mod))
97 | (set-module-kind! iface 'command)
98 | ;(module-use! iface (resolve-interface '(guile)))
99 | iface)))
100 | (module-ref mod '%module-command-interface))
101 |
102 |
103 |
104 | (define-public (module-export-command! m names)
105 | (let ((public-i (module-command-interface m)))
106 | ;; Add them to this module.
107 | (for-each (lambda (name)
108 | (let* ((internal-name (if (pair? name) (car name) name))
109 | (external-name (if (pair? name) (cdr name) name))
110 | (var (module-ensure-local-variable! m internal-name)))
111 | (module-add! public-i external-name var)))
112 | names)))
113 |
114 | (define-syntax-rule (export-command name ...)
115 | (eval-when (eval load compile expand)
116 | (call-with-deferred-observers
117 | (lambda ()
118 | (module-export-command! (current-module) '(name ...))))))
119 |
120 | (define-syntax-public define-interactive
121 | (syntax-rules ()
122 | ((define-interactive (name . args) . body)
123 | (begin (define-cmd global-cmdset (name . args)
124 | . body)
125 | (export-command name)))
126 | ((define-interactive name value)
127 | (begin (define-cmd global-cmdset name value)
128 | (export-command name))
129 | )))
130 | @
131 |
132 | \todo[inline]{Need to fix: define-cmd doesn't respect documentation strings.}
133 | <>=
134 | (define-syntax-public define-cmd
135 | (lambda (x)
136 | (syntax-case x ()
137 | ((define-cmd (name . args) e0)
138 | #'(begin
139 | (define* (name . args)
140 | (with-fluids ((in-what-command 'name))
141 | e0))
142 | (export name)
143 | (emacsy-kind-set!
144 | (module-variable (current-module) 'name)
145 | 'command)
146 | (set-command-properties! name 'name)))
147 | ((define-cmd (name . args) e0 e1 . body)
148 | (string? (syntax->datum #'e0))
149 | ;; Handle the case where there is a documentation string.
150 | #'(begin
151 | (define* (name . args)
152 | e0
153 | (with-fluids ((in-what-command 'name))
154 | (let ()
155 | e1 . body)))
156 | (export name)
157 | (emacsy-kind-set!
158 | (module-variable (current-module) 'name)
159 | 'command)
160 | (set-command-properties! name 'name)))
161 | ((define-cmd (name . args) e0 e1 . body)
162 | #'(begin
163 | (define* (name . args)
164 | (with-fluids ((in-what-command 'name))
165 | (let ()
166 | e0 e1 . body)))
167 | (export name)
168 | (emacsy-kind-set!
169 | (module-variable (current-module) 'name)
170 | 'command)
171 | (set-command-properties! name 'name)))
172 | ((define-cmd name value)
173 | #'(begin
174 | (define name #f)
175 | (let ((v value))
176 | (set! name (colambda args
177 | (with-fluids ((in-what-command 'name))
178 | (apply v args))))
179 | (export name)
180 | (emacsy-kind-set!
181 | (module-variable (current-module) 'name)
182 | 'command)
183 | (set-command-properties! name 'name))))
184 | ((define-cmd cmap (name . args) . body)
185 | #'(begin
186 | (define-cmd (name . args) . body)
187 | (command-add! cmap 'name)))
188 | ((define-cmd cmap name value)
189 | #'(begin
190 | (define-cmd name value)
191 | (command-add! cmap 'name))))))
192 | @
193 |
194 | <>=
195 | (define-public (register-interactive name proc)
196 | (command-add! global-cmdset name)
197 | (set-command-properties! proc name))
198 | @
199 |
200 | <>=
201 | (define-public (command->proc command)
202 | (cond
203 | ((thunk? command)
204 | command)
205 | (else
206 | (warn "command->proc not given a command: ~a" command)
207 | #f)))
208 | @
209 |
210 | <>=
211 | (define-public (command-name command)
212 | (procedure-name command))
213 | @
214 |
215 | <>=
216 | (define-public (command? object)
217 | (thunk? object))
218 | @
219 |
220 | \subsection{Determine Interactivity}
221 |
222 | We would like to be able to determine within the command procedure's
223 | body whether the command has been called interactively, by the user's
224 | key press, or by a keyboard macro or another procedure call. The best
225 | way I can think to do this is to have a means of answering the
226 | following questions: 1) What command am I in? 2) What is the current
227 | interactive command?
228 |
229 | Determining the current command is not that difficult. That's
230 | generally set by the [[this-command]] variable. However, determining
231 | what command I am in is a little troublesome. One can examine the
232 | stack and look for the first procedure that has some property
233 | associated with commands.
234 |
235 | <>=
236 | (define* (set-command-properties! proc #:optional (name #f))
237 | (let ((cname (or name (procedure-name proc) #f)))
238 | (set-procedure-property! proc 'command-name
239 | (if (eq? cname 'proc)
240 | #f
241 | cname))))
242 | @
243 |
244 | <>=
245 | (define in-what-command (make-fluid #f))
246 | @
247 |
248 | <>=
249 | (define-syntax-public lambda-cmd
250 | (syntax-rules ()
251 | ((lambda-cmd args . body)
252 | (let ((proc (lambda* args
253 | (with-fluids ((in-what-command #f))
254 | . body))))
255 | (set-command-properties! proc)
256 | proc))))
257 | @
258 |
259 | <>=
260 | (define test-cmd (lambda-cmd args 1))
261 | (define (test-cmd-2) 2)
262 | (define-cmd (test-cmd-3) 3)
263 | (check (procedure-documentation test-cmd-3) => #f)
264 | (check (test-cmd) => 1)
265 | (check-true (command? test-cmd))
266 | (check-true (command? test-cmd-2))
267 | (check-true (command? test-cmd-3))
268 | (check (assq-ref (procedure-properties test-cmd) 'command-name) => #f)
269 | (check (assq 'command-name (procedure-properties test-cmd-2)) => #f)
270 | (check (command-name test-cmd) => 'proc)
271 | (check (command-name test-cmd-2) => 'test-cmd-2)
272 | (check (command-name test-cmd-3) => 'test-cmd-3)
273 | @
274 |
275 | <>=
276 | (define-public (what-command-am-i?)
277 | (fluid-ref in-what-command))
278 | @
279 |
280 | <>=
281 | (define-cmd (test-who-am-i?)
282 | "test-who-am-i? documentation"
283 | (let ((w (what-command-am-i?)))
284 | 1
285 | w))
286 | (check (command-name test-who-am-i?) => 'test-who-am-i?)
287 | (check (test-who-am-i?) => 'test-who-am-i?)
288 | (check (procedure-documentation test-who-am-i?) => "test-who-am-i? documentation")
289 | @
290 |
291 | <>=
292 | (define-public (command-execute command . args)
293 | (if (command? command)
294 | (let ((cmd-proc (command->proc command))
295 | (cmd-name (command-name command)))
296 | (emacsy-log-info "Running command: ~a" cmd-name)
297 | (set! last-command this-command)
298 | (set! this-command cmd-name)
299 | (apply cmd-proc args))
300 | (error (emacsy-log-warning "command-execute not given a command: ~a" command))))
301 | @
302 |
303 | <>=
304 | (define-public (emacsy-log-info format-msg . args)
305 | (apply format (current-error-port) format-msg args)
306 | (newline (current-error-port)))
307 | @
308 |
309 | <>=
310 | (define-public this-command #f)
311 | (define-public last-command #f)
312 | @
313 |
314 | <>=
315 | (define-public kill-rogue-coroutine? #f)
316 | (define-public seconds-to-wait-for-yield 2)
317 | @
318 |
319 | <>=
320 | (define-public (call-interactively command . args)
321 | (dynamic-wind
322 | (lambda () (if kill-rogue-coroutine?
323 | (alarm seconds-to-wait-for-yield)))
324 | (lambda () (with-fluids ((this-interactive-command (command-name command)))
325 | (apply command-execute command args)))
326 | (lambda () (if kill-rogue-coroutine?
327 | (alarm 0)))))
328 | @
329 |
330 | <>=
331 | (define this-interactive-command (make-fluid))
332 | @
333 |
334 | <>=
335 | (define*-public (called-interactively? #:optional (kind 'any))
336 | (eq? (fluid-ref in-what-command) (fluid-ref this-interactive-command)))
337 | @
338 |
339 | <>=
340 | (define-cmd (foo)
341 | (if (called-interactively?)
342 | 'interactive
343 | 'non-interactive))
344 | (check (command? 'foo) => #f)
345 | (check (command? foo) => #t)
346 | (check (command-name foo) => 'foo)
347 | (check-true (command->proc foo))
348 |
349 | (check-throw (command-execute 'foo) => 'misc-error)
350 | (check (command-execute foo) => 'non-interactive)
351 | (check (call-interactively foo) => 'interactive)
352 | @
353 |
354 |
355 | \subsection*{File Layout}
356 |
357 | <>=
358 | (define-module (emacsy command)
359 | #:use-module (string completion)
360 | #:use-module (oop goops)
361 | #:use-module (ice-9 optargs)
362 | #:use-module (emacsy util)
363 | #:use-module (emacsy self-doc)
364 | #:use-module (emacsy coroutine)
365 |
366 | #:export-syntax (export-command))
367 | <>
368 | <>
369 | <>
370 | <>
371 | <>
372 | @
373 |
374 | Layout for tests.
375 | <>=
376 | (use-modules (emacsy command)
377 | (emacsy event)
378 | (oop goops))
379 |
380 | (eval-when (compile load eval)
381 | ;; Some trickery so we can test private procedures.
382 | (module-use! (current-module) (resolve-module '(emacsy command))))
383 |
384 | <<+ Test Preamble>>
385 | <>
386 | <<+ Test Postscript>>
387 | @
388 |
--------------------------------------------------------------------------------
/src/emacsy/coroutine-test.scm:
--------------------------------------------------------------------------------
1 | (use-modules (emacsy coroutine)
2 | (check))
3 |
4 | (define a (make-coroutine
5 | (lambda ()
6 | (couser-data)
7 | #;(yield (lambda (resume)
8 | (resume 'b)
9 | ))
10 | ;'a
11 | )
12 | 'a
13 | 'a-user-data))
14 |
15 | (check (a) => 'a-user-data)
16 |
--------------------------------------------------------------------------------
/src/emacsy/coroutine.scm:
--------------------------------------------------------------------------------
1 | ;; https://github.com/davexunit/guile-2d/blob/master/2d/coroutine.scm
2 | ;;
3 | ;; I'm Shane. I'm having a problem trying to fix codefine*. I wrote
4 | ;; the strip-optargs macro to try to fix it, but I can't get it right.
5 | ;; Help me, #guile!
6 |
7 | ;;; guile-2d
8 | ;;; Copyright (C) 2013 David Thompson
9 | ;;;
10 | ;;; Guile-2d is free software: you can redistribute it and/or modify it
11 | ;;; under the terms of the GNU Lesser General Public License as
12 | ;;; published by the Free Software Foundation, either version 3 of the
13 | ;;; License, or (at your option) any later version.
14 | ;;;
15 | ;;; Guile-2d is distributed in the hope that it will be useful, but
16 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 | ;;; Lesser General Public License for more details.
19 | ;;;
20 | ;;; You should have received a copy of the GNU Lesser General Public
21 | ;;; License along with this program. If not, see
22 | ;;; .
23 |
24 | ;;; Commentary:
25 | ;;
26 | ;; Cooperative multi-tasking.
27 | ;;
28 | ;;; Code:
29 |
30 | (define-module (emacsy coroutine)
31 | #:export (make-coroutine
32 | coroutine
33 | colambda
34 | codefine
35 | codefine*
36 | couser-data)
37 | #:replace (yield))
38 |
39 | (define cid-next 0)
40 |
41 | ;; could have a (make-coroutine thunk) => (cid . run-coroutine-thunk)
42 | (define* (make-coroutine thunk #:optional (name #f) (user-data #f))
43 | "Creates a procedure that can yield a continuation. (Does not execute thunk.)"
44 | (define cid cid-next)
45 | (define (handler cont key . args)
46 | (define (resume . args)
47 | (format #t "resuming ~a cid ~a~%" name cid)
48 | ;; Call continuation that resumes the procedure.
49 | (call-with-prompt 'coroutine-prompt
50 | (lambda () (apply cont args))
51 | handler))
52 | (when name
53 | (set-procedure-property! resume
54 | 'name (string->symbol
55 | (format #f "~a-resume-~a" name cid))))
56 | (case key
57 | ((callback)
58 | (when (procedure? (car args))
59 | (apply (car args) resume (cdr args))))
60 | ((user-data)
61 | (resume user-data))))
62 | (set! cid-next (1+ cid-next))
63 |
64 | (lambda () (call-with-prompt 'coroutine-prompt thunk handler)))
65 |
66 | (define* (coroutine thunk #:optional (name #f) (user-data #f))
67 | "Calls a procedure that can yield a continuation."
68 | ((make-coroutine thunk name user-data)))
69 |
70 | ;; emacs: (put 'colambda 'scheme-indent-function 0)
71 | (define-syntax-rule (colambda args body ...)
72 | "Syntacic sugar for a lambda that is run as a coroutine."
73 | (lambda args
74 | (coroutine
75 | (lambda () body ...))))
76 |
77 | ;; emacs: (put 'codefine 'scheme-indent-function 1)
78 | (define-syntax-rule (codefine (name . args) . body)
79 | "Syntactic sugar for defining a procedure that is run as a
80 | coroutine."
81 | (define (name . args)
82 | ;; Create an inner procedure with the same signature so that a
83 | ;; recursive procedure call does not create a new prompt.
84 | (define (name . args) . body)
85 | (coroutine
86 | (lambda () (name . args)) 'name)))
87 |
88 | ;; (strip-optargs #'(a #:optional (b 2) c)) =>~ (a b c)
89 | ;; TODO make it work with keyword arguments.
90 | (define strip-optargs
91 | (lambda (x)
92 | (syntax-case x ()
93 | (()
94 | #'())
95 | ;; optional values
96 | (((v e) e2 ...)
97 | (identifier? #'v)
98 | #`(v . #,(strip-optargs #'(e2 ...))))
99 | ;; #:optional keyword
100 | ((#:optional e2 ...)
101 | (strip-optargs #'(e2 ...)))
102 | ;; identifiers
103 | ((e1 e2 ...)
104 | (identifier? #'e1)
105 | #`(e1 . #,(strip-optargs #'(e2 ...)))))))
106 |
107 | ;; emacs: (put 'codefine* 'scheme-indent-function 1)
108 | "Syntactic sugar for defining a procedure with optional and
109 | keyword arguments that is run as a coroutine."
110 | #;(define-syntax codefine*
111 | (lambda (x)
112 | (syntax-case x ()
113 | ((codefine* (name . args) . body)
114 | (with-syntax ((callable-args (strip-optargs #'args)))
115 | #'(define* (name . args)
116 | ;; Create an inner procedure with the same signature so that a
117 | ;; recursive procedure call does not create a new prompt.
118 | (define* (name . args) . body)
119 | (coroutine
120 | (lambda () (name . callable-args)))))))))
121 |
122 | ;; emacs: (put 'codefine* 'scheme-indent-function 1)
123 | #;(define-syntax-rule (codefine* (name . formals) . body)
124 | "Syntactic sugar for defining a procedure that is run as a
125 | coroutine."
126 | (define (name . args)
127 | ;; Create an inner procedure with the same signature so that a
128 | ;; recursive procedure call does not create a new prompt.
129 | (define* (name . formals) . body)
130 | (coroutine
131 | (lambda () (apply name args)))))
132 |
133 | ;; emacs: (put 'codefine* 'scheme-indent-function 1)
134 | ;; Thank Mark Weaver for defining this little gem without
135 | ;; the crazy syntax-case I originally did. -SEC
136 | #;(define-syntax-rule (codefine* (name . formals) . body)
137 | "Syntactic sugar for defining a procedure that is run as a
138 | coroutine."
139 | (define (name . args)
140 | ;; Create an inner procedure with the same signature so that a
141 | ;; recursive procedure call does not create a new prompt.
142 | (define* (name . formals) . body)
143 | (coroutine
144 | (lambda () (apply name args)))))
145 |
146 | ;; Syntactic sugar for defining a procedure that is run as a
147 | ;; coroutine.
148 | (define-syntax codefine*
149 | (lambda (x)
150 | (syntax-case x ()
151 | ((_ (name . formals) e0)
152 | #'(define (name . args)
153 | ;; Create an inner procedure with the same signature so that a
154 | ;; recursive procedure call does not create a new prompt.
155 | (define* (name . formals) e0)
156 | (coroutine
157 | (lambda () (apply name args))
158 | 'name)))
159 | ;; Handle the case where there is a documentation string.
160 | ((_ (name . formals) e0 e1 . body)
161 | (string? (syntax->datum #'e0))
162 | #'(define (name . args)
163 | e0
164 | ;; Create an inner procedure with the same signature so that a
165 | ;; recursive procedure call does not create a new prompt.
166 | (define* (name . formals) e1 . body)
167 | (coroutine
168 | (lambda () (apply name args))
169 | 'name)))
170 | ((_ (name . formals) e0 e1 . body)
171 | #'(define (name . args)
172 | ;; Create an inner procedure with the same signature so that a
173 | ;; recursive procedure call does not create a new prompt.
174 | (define* (name . formals) e0 e1 . body)
175 | (coroutine
176 | (lambda () (apply name args))
177 | 'name))))))
178 |
179 | (define (yield callback)
180 | "Yield continuation to a CALLBACK procedure."
181 | (abort-to-prompt 'coroutine-prompt 'callback callback))
182 |
183 | (define (couser-data)
184 | "Return the user-data for this coroutine."
185 | (abort-to-prompt 'coroutine-prompt 'user-data))
186 |
--------------------------------------------------------------------------------
/src/emacsy/emacsy-c-api.nw:
--------------------------------------------------------------------------------
1 | % -*- mode: Noweb; noweb-code-mode: c-mode -*-
2 | \chapter{C API}
3 | \lstset{language=C}
4 |
5 | Emacsy provides a C API to ease integration with C and C++
6 | programs. The C API is given below.
7 |
8 | <>=
9 | /* Initialize Emacsy. */
10 | int emacsy_initialize(int init_flags);
11 |
12 | /* Enqueue a keyboard event. */
13 | void emacsy_key_event(int char_code,
14 | int modifier_key_flags);
15 |
16 | /* Enqueue a mouse event. */
17 | void emacsy_mouse_event(int x, int y,
18 | int state,
19 | int button,
20 | int modifier_key_flags);
21 |
22 | /* Run an iteration of Emacsy's event loop
23 | (will not block). */
24 | int emacsy_tick();
25 |
26 | /* Return the message or echo area. */
27 | char *emacsy_message_or_echo_area();
28 |
29 | /* Return the mode line. */
30 | char *emacsy_mode_line();
31 |
32 | /* Return the name of the current buffer. */
33 | char *emacsy_current_buffer();
34 |
35 | /* Run a hook. */
36 | int emacsy_run_hook_0(const char *hook_name);
37 |
38 | /* Return the minibuffer point. */
39 | int emacsy_minibuffer_point();
40 |
41 | /* Terminate Emacsy, runs termination hook. */
42 | int emacsy_terminate();
43 |
44 | /* Attempt to load a module. */
45 | int emacsy_load_module(const char *module_name);
46 |
47 | /* Load a file in the emacsy environment. */
48 | //int emacsy_load(const char *file_name);
49 |
50 | /* Convert the modifier_key_flags into a Scheme list of symbols. */
51 | // Do I want to include any Scheme objects or keep it strictly C?
52 | #include
53 | SCM modifier_key_flags_to_list(int modifier_key_flags);
54 | @
55 | <>=
56 | /* emacsy.h
57 |
58 | <<+ Copyright>>
59 |
60 | <<+ License>>
61 | */
62 |
63 | <>
64 |
65 | <>
66 |
67 | <>
68 |
69 | <>
70 |
71 | @ Here are the constants for the C API.
72 |
73 | <>=
74 | #define EMACSY_MODKEY_COUNT 6
75 |
76 | #define EMACSY_MODKEY_ALT 1 // A
77 | #define EMACSY_MODKEY_CONTROL 2 // C
78 | #define EMACSY_MODKEY_HYPER 4 // H
79 | #define EMACSY_MODKEY_META 8 // M
80 | #define EMACSY_MODKEY_SUPER 16 // s
81 | #define EMACSY_MODKEY_SHIFT 32 // S
82 |
83 | #define EMACSY_MOUSE_BUTTON_DOWN 0
84 | #define EMACSY_MOUSE_BUTTON_UP 1
85 | #define EMACSY_MOUSE_MOTION 2
86 |
87 | #define EMACSY_INTERACTIVE 1
88 | #define EMACSY_NON_INTERACTIVE 0
89 |
90 | @ Here are the return flags that may be returned by \verb|emacsy_tick|.
91 |
92 | <>=
93 | #define EMACSY_QUIT_APPLICATION_P 1
94 | #define EMACSY_ECHO_AREA_UPDATED_P 2
95 | #define EMACSY_MODELINE_UPDATED_P 4
96 | #define EMACSY_RAN_UNDEFINED_COMMAND_P 8
97 | @ The boilerplate guards so that a C++ program may include
98 | \verb|emacsy.h| are given below.
99 |
100 | <>=
101 | #ifdef __cplusplus
102 | extern "C" {
103 | #endif
104 | <>=
105 | #ifdef __cplusplus
106 | }
107 | #endif
108 | @ The implementation of the API calls similarly named Scheme procedures.
109 |
110 | \section{emacsy\_initialize}
111 |
112 | <>=
113 | int emacsy_initialize(int init_flags)
114 | {
115 | /* Load the (emacsy emacsy) module. */
116 | const char *module = "emacsy emacsy";
117 | int err = emacsy_load_module(module);
118 | if (err)
119 | return err;
120 |
121 | (void) scm_call_1(scm_c_public_ref("emacsy emacsy", "emacsy-initialize"),
122 | (init_flags & EMACSY_INTERACTIVE) ? SCM_BOOL_T : SCM_BOOL_F);
123 |
124 | return err;
125 | }
126 | @ The function [[scm_c_use_module]] throws an exception if it cannot
127 | find the module, so we have to split that functionality into a body
128 | function [[load_module_try]] and an error handler [[load_module_error]].
129 |
130 | <>=
131 | SCM load_module_try(void *data)
132 | {
133 | scm_c_use_module((const char *)data);
134 | return scm_list_1(SCM_BOOL_T);
135 | }
136 | @
137 |
138 | <>=
139 | SCM load_module_error(void *data, SCM key, SCM args)
140 | {
141 | //fprintf(stderr, "error: Unable to load module (%s).\n", (const char*) data);
142 | return scm_list_3(SCM_BOOL_F, key, args);
143 | }
144 | @
145 |
146 | Attempt to load a module. Returns 0 if no errors, and non-zero otherwise.
147 |
148 | <>=
149 | int emacsy_load_module(const char *module)
150 | {
151 | SCM result = scm_internal_catch(SCM_BOOL_T,
152 | load_module_try, (void *) module,
153 | load_module_error, (void *) module);
154 | if (scm_is_false(scm_car(result))) {
155 | fprintf(stderr, "error: Unable to load module (%s); got error to key %s with args %s. Try setting the "
156 | "GUILE_LOAD_PATH environment variable.\n", module,
157 | scm_to_locale_string(scm_car(scm_cdr(result))),
158 | scm_to_locale_string(scm_car(scm_cdr(scm_cdr(result))))
159 | );
160 | return 1; //EMACSY_ERR_NO_MODULE;
161 | }
162 | return 0;
163 | }
164 |
165 | @ \section{emacsy\_key\_event}
166 |
167 | <>=
168 | void emacsy_key_event(int char_code,
169 | int modifier_key_flags)
170 | {
171 | SCM i = scm_from_int(char_code);
172 | //fprintf(stderr, "i = %d\n", scm_to_int(i));
173 | SCM c = scm_integer_to_char(i);
174 | //fprintf(stderr, "c = %d\n", scm_to_int(scm_char_to_integer(c)));
175 |
176 | (void) scm_call_2(scm_c_public_ref("emacsy emacsy", "emacsy-key-event"),
177 | c,
178 | modifier_key_flags_to_list(modifier_key_flags));
179 | }
180 | @ \section{emacsy\_mouse\_event}
181 |
182 | <>=
183 | void emacsy_mouse_event(int x, int y,
184 | int state,
185 | int button,
186 | int modifier_key_flags)
187 | {
188 |
189 | SCM down_sym = scm_c_string_to_symbol("down");
190 | SCM up_sym = scm_c_string_to_symbol("up");
191 | SCM motion_sym = scm_c_string_to_symbol("motion");
192 | SCM state_sym;
193 | switch(state) {
194 | case EMACSY_MOUSE_BUTTON_UP: state_sym = up_sym; break;
195 | case EMACSY_MOUSE_BUTTON_DOWN: state_sym = down_sym; break;
196 | case EMACSY_MOUSE_MOTION: state_sym = motion_sym; break;
197 | default:
198 | fprintf(stderr, "warning: mouse event state received invalid input %d.\n",
199 | state);
200 | return;
201 | }
202 |
203 | (void) scm_call_3(scm_c_public_ref("emacsy emacsy", "emacsy-mouse-event"),
204 | scm_vector(scm_list_2(scm_from_int(x),
205 | scm_from_int(y))),
206 | scm_from_int(button),
207 | state_sym);
208 | }
209 | @ \section{emacsy\_tick}
210 |
211 | <>=
212 | int emacsy_tick()
213 | {
214 | int flags = 0;
215 | (void) scm_call_0(scm_c_public_ref("emacsy emacsy",
216 | "emacsy-tick"));
217 | if (scm_is_true(scm_c_public_ref("emacsy emacsy",
218 | "emacsy-quit-application?")))
219 | flags |= EMACSY_QUIT_APPLICATION_P;
220 | if (scm_is_true(scm_c_public_ref("emacsy emacsy",
221 | "emacsy-ran-undefined-command?")))
222 | flags |= EMACSY_RAN_UNDEFINED_COMMAND_P;
223 |
224 | return flags;
225 | }
226 |
227 | @ \section{emacsy\_message\_or\_echo\_area}
228 |
229 | <>=
230 | char *emacsy_message_or_echo_area()
231 | {
232 | return scm_to_locale_string(
233 | scm_call_0(scm_c_public_ref("emacsy emacsy",
234 | "emacsy-message-or-echo-area")));
235 | }
236 | @ %def emacsy_message_or_echo_area
237 |
238 | \section{emacsy\_current\_buffer}
239 |
240 | <>=
241 | char *emacsy_current_buffer()
242 | {
243 | return scm_to_locale_string(
244 | scm_call_1(scm_c_public_ref("emacsy emacsy", "buffer-name"),
245 | scm_call_0(scm_c_public_ref("emacsy emacsy",
246 | "current-buffer"))));
247 | }
248 | @ %def emacsy_message_or_echo_area
249 |
250 |
251 | \section{emacsy\_mode\_line}
252 |
253 | \todo[inline]{Keep name as modeline.}
254 | <>=
255 | char *emacsy_mode_line()
256 | {
257 | return scm_to_locale_string(
258 | scm_call_0(scm_c_public_ref("emacsy emacsy",
259 | "emacsy-mode-line")));
260 | }
261 | @ \section{emacsy\_terminate}
262 |
263 | <>=
264 | int emacsy_terminate()
265 | {
266 | SCM result;
267 | result = scm_call_0(scm_c_public_ref("emacsy emacsy",
268 | "emacsy-terminate"));
269 | return 0;
270 | }
271 | <>=
272 | #include "emacsy.h"
273 | #include
274 |
275 | <>
276 |
277 | <>
278 | @
279 | <>=
280 | int emacsy_run_hook_0(const char *hook_name)
281 | {
282 | /* This should be protected from all sorts of errors that the hooks
283 | could throw. */
284 | SCM result;
285 | result = scm_call_1(scm_c_public_ref("emacsy emacsy",
286 | "emacsy-run-hook"),
287 | scm_c_private_ref("guile-user",
288 | hook_name));
289 | return 0;
290 | }
291 | <>=
292 | int emacsy_minibuffer_point()
293 | {
294 | return scm_to_int(
295 | scm_call_0(scm_c_public_ref("emacsy emacsy",
296 | "emacsy-minibuffer-point")));
297 | }
298 | <>=
299 | SCM scm_c_string_to_symbol(const char* str) {
300 | return scm_string_to_symbol(scm_from_locale_string(str));
301 | }
302 |
303 | SCM modifier_key_flags_to_list(int modifier_key_flags)
304 | {
305 | const char* modifiers[] = { "alt", "control", "hyper", "meta", "super", "shift" };
306 | SCM list = SCM_EOL;
307 | for (int i = 0; i < EMACSY_MODKEY_COUNT; i++) {
308 | if (modifier_key_flags & (1 @<< i)) {
309 | list = scm_cons(scm_c_string_to_symbol(modifiers[i]), list);
310 | }
311 | }
312 |
313 | return list;
314 | }
315 |
316 | SCM_DEFINE(scm_modifier_key_flags_to_list, "modifier-key-flags->list",
317 | 1, 0, 0,
318 | (SCM flags),
319 | "Convert an integer of modifier key flags to a list of symbols.")
320 | {
321 | int modifier_key_flags = scm_to_int(flags);
322 | return modifier_key_flags_to_list(modifier_key_flags);
323 | }
324 | @
325 |
--------------------------------------------------------------------------------
/src/emacsy/event.nw:
--------------------------------------------------------------------------------
1 | % -*- mode: Noweb; noweb-code-mode: scheme-mode -*-
2 | \section{Event Module}
3 |
4 | Let's define an root event class. \todo{Rename time to event-time.}
5 |
6 | <>=
7 | (define-class-public ()
8 | (time #:getter time #:init-thunk (lambda () (emacsy-time)))
9 | (discrete-event? #:getter discrete-event? #:init-keyword #:discrete-event? #:init-value #t))
10 | (export time discrete-event?)
11 | @
12 |
13 | This defines the class [[]]. It relies on [[emacsy-time]] that
14 | I'm going to place in a utilities module, which will be mostly a
15 | bucket of miscellaneous things that any module might end up making use
16 | of.
17 |
18 | <>=
19 | (define-public (emacsy-time)
20 | (exact->inexact (/ (tms:clock (times)) internal-time-units-per-second)))
21 | @
22 |
23 | \subsection{Key Event}
24 | Now let's give ourselves an event that'll capture key strokes
25 | including the modifier keys.
26 |
27 | <>=
28 | (define-class-public ()
29 | (modifier-keys #:getter modifier-keys
30 | #:init-keyword #:modifier-keys
31 | #:init-value '()))
32 |
33 | (define-class-public ()
34 | (command-char #:getter command-char
35 | #:init-keyword #:command-char))
36 | (export modifier-keys command-char)
37 | @
38 |
39 | <>=
40 | (check-true (make #:command-char #\a))
41 | @
42 |
43 | One of the idioms we want to capture from Emacs is this.
44 |
45 | \begin{verbatim}
46 | (define-key global-map "M-f" 'some-command)
47 | \end{verbatim}
48 |
49 | They [[keymap]] and [[command]] module will deal with most of the
50 | above, except for the [[kbd]] procedure. That's something events will
51 | be concerned with. One may define a converter for a [[kbd-entry]] to
52 | an event of the proper type. Note that a [[kbd-string]] is broken
53 | into multiple [[kbd-entries]] on whitespace boundaries, e.g., ``C-x
54 | C-f'' is a [[kbd-string]] that when parsed becomes two [[kbd-entries]]
55 | ``C-x'' and ``C-f''.
56 |
57 |
58 |
59 | Let's write the converter for the [[]] class that will
60 | accept the same kind of strings that Emacs does. If the [[kbd-entry]]
61 | does not match the event-type, we return false [[#f]].
62 |
63 | <>=
64 | (define (kbd-entry->key-event kbd-entry)
65 | (match (strip-off-modifier-keys kbd-entry)
66 | ((mod-keys kbd-entry)
67 | (let ((regex "^([^ ]|RET|DEL|ESC|TAB|SPC)$"))
68 | (let ((match (string-match regex kbd-entry)))
69 | (if match
70 | (let* ((char (string->command-char (match:substring match 1))))
71 | (make #:command-char char #:modifier-keys mod-keys))
72 | #f))))))
73 |
74 | (define (get-modifier-keys str)
75 | (if str
76 | (map modifier-char->symbol
77 | (filter (lambda (x) (not (char=? x #\-))) (string->list str)))
78 | '()))
79 |
80 | (define-public (strip-off-modifier-keys kbd-entry)
81 | "Parse the kbd-entry and strip off the modifier-keys and return the kbd-entry
82 | and a list of modifier keys."
83 | (let ((regex "^(([ACHMsS]-)*)(.*)$"))
84 | (let ((match (string-match regex kbd-entry)))
85 | (if match
86 | (let ((mod-keys (get-modifier-keys (match:substring match 1))))
87 | (list mod-keys (match:substring match 3)))
88 | (list '() kbd-entry)))))
89 | @
90 |
91 | <>=
92 | (check (strip-off-modifier-keys "C-a") => '((control) "a"))
93 | (check (strip-off-modifier-keys "a") => '(() "a"))
94 | (check (strip-off-modifier-keys "asdf") => '(() "asdf"))
95 | @
96 |
97 | For the modifier keys, we are going to emulate Emacs to a fault.
98 |
99 | <>=
100 | (define-public (modifier-char->symbol char)
101 | (case char
102 | ((#\A) 'alt)
103 | ((#\C) 'control)
104 | ((#\H) 'hyper)
105 | ((#\M) 'meta)
106 | ((#\s) 'super)
107 | ((#\S) 'shift)
108 | (else (warn (format #f "Invalid character for modifier key: ~a" char))
109 | #f)))
110 | @
111 |
112 | <>=
113 | (check (modifier-char->symbol #\S) => 'shift)
114 | (check (modifier-char->symbol #\X) => #f)
115 | @
116 |
117 | <>=
118 | (define (string->command-char str)
119 | (if (= (string-length str) 1)
120 | ;; One character string, return first character; simple!
121 | (string-ref str 0)
122 | (string-case str
123 | ("RET" #\cr)
124 | ("DEL" #\del)
125 | ("ESC" #\esc)
126 | ("TAB" #\tab)
127 | ("SPC" #\space)
128 | (else (warn (format #f "Invalid command character: ~a" str)) ))))
129 | @
130 |
131 | Now we have the function [[kbd-entry->key-event]]. [[kbd]] needs to
132 | know about this and any other converter function. So let's register it.
133 |
134 | <>=
135 | (define kbd-converter-functions '())
136 | @
137 |
138 | <>=
139 | (define-public (register-kbd-converter function-name function)
140 | (set! kbd-converter-functions
141 | (assq-set! kbd-converter-functions function-name function)))
142 | @
143 |
144 | Now we can register it.
145 |
146 | <>=
147 | (register-kbd-converter 'kbd-entry->key-event kbd-entry->key-event)
148 | @
149 |
150 | Rather than doing this for every given converter, let's just write a
151 | macro.
152 |
153 | <>=
154 | (define-syntax-public define-kbd-converter
155 | (syntax-rules ()
156 | ((define-kbd-converter (name args ...) expr ...)
157 | (begin (define* (name args ...)
158 | expr ...)
159 | (register-kbd-converter 'name name)))
160 | ((define-kbd-converter name value)
161 | (begin (define* name value)
162 | (register-kbd-converter 'name name)))))
163 | @
164 |
165 | <>=
166 | (check-true (memq 'kbd-entry->key-event (alist-keys kbd-converter-functions)))
167 | @
168 |
169 | One issue we have with the above is the following:
170 |
171 | <>=
172 | (check (modifier-keys (kbd-entry->key-event "C-C-C-x")) => '(control control control))
173 | @
174 |
175 | Our code doesn't account for duplicate modifier keys. For the keymap,
176 | we want a unique identifier of an event. Rather than massaging the
177 | conversion while in its string form, it seems reasonable to convert
178 | the [[kbd-entry]] into an event, then make the event canonical, then
179 | convert back into a string. [[kbd]] will look like this:
180 |
181 | <>=
182 | (define*-public (kbd key-string #:optional (canonical? #t))
183 | (if canonical?
184 | (map event->kbd (map canonize-event! (kbd->events key-string)))
185 | (map event->kbd (kbd->events key-string))))
186 | @
187 |
188 | <>=
189 | (define (kbd-entry->event kbd-entry)
190 | (or (find-first (lambda (f) (f kbd-entry))
191 | (alist-values kbd-converter-functions))
192 | (throw 'invalid-kbd-entry kbd-entry)))
193 | @
194 |
195 | The procedure [[find-first]] is similar to [[find]]; however,
196 | [[(find-first f (x . xs))]] returns the first [[(f x)]] that not false
197 | rather than the first [[x]] for which [[(f x)]] is true.
198 |
199 | <>=
200 | (define-public (find-first f lst)
201 | "Return the first result f which is not false and #f if no such result is found."
202 | (if (null? lst)
203 | #f
204 | (or (f (car lst))
205 | (find-first f (cdr lst)))))
206 | @
207 |
208 | <>=
209 | (define-public (alist-values alist)
210 | (map cdr alist))
211 |
212 | (define-public (alist-keys alist)
213 | (map car alist))
214 | @
215 |
216 |
217 | <>=
218 | (define-public (kbd->events kbd-string)
219 | (let ((kbd-entries (string-tokenize kbd-string)))
220 | (map kbd-entry->event kbd-entries)))
221 | @
222 |
223 | <>=
224 | (define-method-public (canonize-event! (event ))
225 | <>
226 | (let ((mod-keys (modifier-keys event)))
227 | ;; Put them in alphabetical order: ACHMsS.
228 | (slot-set! event 'modifier-keys
229 | (intersect-order mod-keys
230 | '(alt control hyper meta super shift))))
231 | event)
232 | @
233 |
234 | <>=
235 | (define-public (intersect-order list ordered-list )
236 | "Returns the intersection of the two lists ordered according to the
237 | second argument."
238 | (filter (lambda (x) (memq x list))
239 | ordered-list))
240 | @
241 |
242 | <>=
243 | (if (memq 'shift (modifier-keys event))
244 | (if (char-set-contains? char-set:requires-shift-key (command-char event))
245 | ;; Remove extraneous shift.
246 | (slot-set! event 'modifier-keys (delq 'shift (modifier-keys event)))
247 | ;; No shift required, but there is a shift in the kbd-entry.
248 | (if (char-lower-case? (command-char event))
249 | (begin
250 | ;; Change the character to uppercase.
251 | (slot-set! event 'command-char (char-upcase (command-char event)))
252 | ;; Get rid of the shift.
253 | (slot-set! event 'modifier-keys (delq 'shift (modifier-keys event)))))))
254 | @
255 |
256 | Let's test our canonization of a properly formed but non-canonical event.
257 |
258 | <>=
259 | (let ((key-event (kbd-entry->event "S-C-C-S-a")))
260 | (check (modifier-keys key-event) => '(shift control control shift))
261 | (check (command-char key-event) => #\a)
262 | (canonize-event! key-event)
263 | (check (modifier-keys key-event) => '(control))
264 | (check (command-char key-event) => #\A))
265 | @
266 |
267 | <>=
268 | (check (kbd "S-C-C-S-a") => '("C-A"))
269 | (check (kbd "S-C-C-S-A") => '("C-A"))
270 | @
271 |
272 |
273 | <>=
274 | (define char-set:requires-shift-key (char-set-union
275 | char-set:symbol
276 | char-set:upper-case
277 | (char-set-delete char-set:punctuation
278 | ;punctuation = !"#%&'()*,-./:;?@[\\]_{}
279 | #\. #\; #\[ #\] #\, #\' #\\)))
280 | @
281 |
282 | Now we convert the [[]] back to a [[kbd-entry]].
283 |
284 | <>=
285 | (define-method-public (event->kbd (event ))
286 | (let ((mods (next-method))
287 | (cmd-char (command-char->string (command-char event))))
288 | (format #f "~a~a" mods cmd-char)))
289 |
290 | (define-method-public (event->kbd (event ))
291 | (let ((mods (map string (map modifier-symbol->char (modifier-keys event)))))
292 | (string-join `(,@mods "") "-")))
293 | @
294 |
295 | <>=
296 | (check (event->kbd (make #:command-char #\a)) => "a")
297 | @
298 |
299 | Instead of using [[define-generic]] I've written a convenience macro
300 | [[define-generic-public]] that exports the symbol to the current
301 | module. This mimics the functionality of [[define-public]]. In
302 | general, any *-public macro will export the symbol or syntax to the
303 | current module.
304 |
305 | <>=
306 | (define-public (modifier-symbol->char sym)
307 | (case sym
308 | ((alt) #\A)
309 | ((control) #\C)
310 | ((hyper) #\H)
311 | ((meta) #\M)
312 | ((super) #\s)
313 | ((shift) #\S)
314 | (else (error "Bad modifier symbol " sym))))
315 | @
316 |
317 | <>=
318 | (define (command-char->string c)
319 | (case c
320 | ((#\cr #\newline) "RET")
321 | ((#\del) "DEL")
322 | ((#\esc) "ESC")
323 | ((#\tab) "TAB")
324 | ((#\space) "SPC")
325 | (else (string c))))
326 | @
327 |
328 | <>=
329 | (check (event->kbd (make #:command-char #\a
330 | #:modifier-keys '(control))) => "C-a")
331 | @
332 |
333 | Now we can display the [[]] in a nice way.
334 |
335 | <>=
336 | (define-method (write (obj ) port)
337 | (display "#kbd obj) port)
339 | (display ">" port))
340 | @
341 |
342 | A few procedures to determine whether what kind of objects is nice.
343 |
344 | <>=
345 | (define-public (event? obj)
346 | (is-a? obj ))
347 |
348 | (define-public (key-event? obj)
349 | (is-a? obj ))
350 | @
351 |
352 | \subsection{Mouse Event}
353 |
354 | We also want to be able to deal with mouse events captured by the
355 | class [[]].
356 |
357 | <>=
358 | (define-class-public ()
359 | (modifier-keys #:getter modifier-keys #:init-keyword #:modifier-keys #:init-value '())
360 | (position #:getter position #:init-keyword #:position)
361 | (button #:getter button #:init-keyword #:button)
362 | (state #:getter state #:init-keyword #:state))
363 | (export modifier-keys position button state)
364 | @
365 |
366 | Mouse drags require a little bit of extra information, for that we
367 | have the [[]] class.
368 |
369 | <>=
370 | (define-class-public ()
371 | (rect #:getter rect #:init-keyword #:rect))
372 | @
373 |
374 | <>=
375 | (define-method (canonize-event! (event ))
376 | (let ((mod-keys (modifier-keys event)))
377 | ;; Put them in alphabetical order: ACHMsS.
378 | (slot-set! event 'modifier-keys
379 | (intersect-order mod-keys
380 | '(alt control hyper meta super shift)))
381 | event))
382 | @
383 |
384 | The [[kbd-entry]] for mouse events is similar to key events. The
385 | regular expression is
386 | \verb|^(([ACHMsS]-)*)((up-|down-|drag-)?mouse-([123]))\$|.
387 |
388 | <>=
389 | (define-kbd-converter (kbd-entry->mouse-event kbd-entry)
390 | (let* ((regex "^(([ACHMsS]-)*)((up-|down-|drag-)?mouse-([123]))$")
391 | (match (string-match regex kbd-entry)))
392 | (if match
393 | (let* ((symbol (string->symbol (match:substring match 3)))
394 | (modifier-keys (get-modifier-keys (match:substring match 1))))
395 | ;; Warning that symbol is not used; squelch with this noop ref.
396 | symbol
397 | <>)
398 | ;; It doesn't specify a mouse event; return false.
399 | #f)))
400 | @
401 |
402 | <>=
403 | (make #:position #f
404 | #:button (string->number (match:substring match 5))
405 | #:state (let ((state-string (match:substring match 4)))
406 | (if state-string
407 | (string->symbol
408 | (string-trim-right state-string #\-))
409 | 'click))
410 | #:modifier-keys modifier-keys)
411 | @
412 |
413 | <>=
414 | (define-method (event->kbd (event ))
415 | (define (state->list state)
416 | (case state
417 | ((up down drag)
418 | (list (symbol->string state)))
419 | ((click)
420 | '())
421 | (else
422 | (error "Bad state state for mouse event " state))))
423 | (let ((mods (map string (map modifier-symbol->char (modifier-keys event))))
424 | (state-list (state->list (state event))))
425 | (string-join
426 | `(,@mods ,@state-list "mouse" ,(number->string (button event)))
427 | "-")))
428 | @
429 |
430 | <>=
431 | (check (kbd "mouse-1") => '("mouse-1"))
432 | (check (kbd "S-S-mouse-1") => '("S-mouse-1"))
433 | @
434 |
435 | Finally, let's add some interrogative procedures that mirror Emacs'.
436 |
437 | <>=
438 | (define*-public (mouse-event? obj #:optional (of-state #f))
439 | (and (is-a? obj )
440 | (if of-state
441 | (eq? of-state (state obj))
442 | #t)))
443 |
444 | (define-public (up-mouse-event? e)
445 | (mouse-event? e 'up))
446 |
447 | (define-public (down-mouse-event? e)
448 | (mouse-event? e 'down))
449 |
450 | (define-public (drag-mouse-event? e)
451 | (mouse-event? e 'drag))
452 |
453 | (define-public (click-mouse-event? e)
454 | (mouse-event? e 'click))
455 |
456 | (define-public (motion-mouse-event? e)
457 | (mouse-event? e 'motion))
458 | @
459 |
460 | \subsection{Dummy Event}
461 |
462 | Finally, have a dummy event, which is useful to record when used
463 | temporal macros.
464 |
465 | \todo[inline]{This should probably be placed in the [[kbd-macro]] module.}
466 |
467 | <>=
468 | (define-class-public ())
469 | @
470 |
471 | <>=
472 | (define-method (canonize-event! (event ))
473 | event)
474 |
475 | (define-method (event->kbd (event ))
476 | #f)
477 | @
478 |
479 | \subsection*{File Layout}
480 |
481 | <>=
482 | (define-module (emacsy event)
483 | #:use-module (ice-9 q)
484 | #:use-module (ice-9 regex)
485 | #:use-module (ice-9 optargs)
486 | ; #:use-module (srfi srfi-1)
487 | #:use-module (ice-9 match)
488 | #:use-module (oop goops)
489 | #:use-module (emacsy util)
490 | )
491 | <>
492 | <>
493 | <>
494 | <>
495 | <>
496 | @
497 |
498 | Layout for tests.
499 | <>=
500 | (use-modules (emacsy event)
501 | (oop goops)
502 | )
503 |
504 | (eval-when (compile load eval)
505 | ;; Some trickery so we can test private procedures.
506 | (module-use! (current-module) (resolve-module '(emacsy event))))
507 |
508 | <<+ Test Preamble>>
509 | <>
510 | <<+ Test Postscript>>
511 | @
512 |
--------------------------------------------------------------------------------
/src/emacsy/help.nw:
--------------------------------------------------------------------------------
1 | % -*- mode: Noweb; noweb-code-mode: scheme-mode -*-
2 | \section{Help}
3 |
4 |
5 |
6 | <>=
7 | (define-module (emacsy help)
8 | #:use-module (emacsy self-doc)
9 | #:use-module (emacsy keymap)
10 | #:use-module (emacsy klecl)
11 | #:use-module (emacsy command)
12 | #:use-module (emacsy minibuffer)
13 | #:use-module (emacsy core))
14 |
15 | <>
16 | <>
17 | <>
18 | <>
19 | @
20 |
21 | <>=
22 | (define-interactive
23 | (describe-variable
24 | #:optional
25 | (symbol (completing-read
26 | "Describe variable: "
27 | (emacsy-collect-kind (current-module) 'variable 1)
28 | #:to-string symbol->string)))
29 | #;(message "Describing variable ~a: ~a" symbol (variable-documentation symbol))
30 | (message "~a" (variable-documentation symbol)))
31 |
32 | (define-interactive
33 | (describe-command
34 | #:optional
35 | (symbol (completing-read
36 | "Describe command: "
37 | (emacsy-collect-kind (current-module) 'command 1)
38 | #:to-string symbol->string)))
39 | #;(message "Describing variable ~a: ~a" symbol (variable-documentation symbol))
40 | (message "~a" (procedure-documentation (module-ref (current-module) symbol))))
41 | @
42 |
43 | <>=
44 |
45 | (define-key global-map "C-h v" 'describe-variable)
46 | (define-key global-map "C-h c" 'describe-command)
47 | @
48 |
49 | <>=
50 | (use-modules (check)
51 | (emacsy help))
52 | (use-private-modules (emacsy help))
53 |
54 | <>
55 |
56 | (check-exit)
57 | @
58 |
--------------------------------------------------------------------------------
/src/emacsy/job-test.scm:
--------------------------------------------------------------------------------
1 | (use-modules (emacsy coroutine)
2 | (emacsy agenda)
3 | (emacsy job)
4 | (ice-9 receive)
5 | (check))
6 |
7 | (use-private-modules (emacsy job))
8 |
9 | (set! next-job-id 1)
10 |
11 | (check *current-job-list* => '())
12 |
13 | (define a (make-job (lambda ()
14 | (get-job-id))))
15 |
16 |
17 | (check *current-job-list* => (list (%make-job 1 'baby #f #f)))
18 |
19 | (check (a) => 1)
20 |
21 | (check *current-job-list* => (list (%make-job 1 'zombie 1 #f)))
22 |
23 | (check-throw (a) => 'job-already-started)
24 |
25 | (check *current-job-list* => (list (%make-job 1 'zombie 1 #f)))
26 |
27 | (set! *current-job-list* '())
28 |
29 | (define b (make-job (lambda ()
30 | 1
31 | (wait)
32 | 2)))
33 |
34 | (check *current-job-list* => (list (%make-job 2 'baby #f #f)))
35 | (check (b) => *unspecified*)
36 | (check *current-job-list* => (list (%make-job 2 'running #f #f)))
37 | (check-throw (b) => 'job-already-started)
38 | (check *current-job-list* => (list (%make-job 2 'running #f #f)))
39 | (update-agenda)
40 | (check *current-job-list* => (list (%make-job 2 'zombie 2 #f)))
41 |
42 | (set! *current-job-list* '())
43 |
44 | (define c (make-job (lambda ()
45 | 1
46 | (wait)
47 | 2)))
48 |
49 | (check *current-job-list* => (list (%make-job 3 'baby #f #f)))
50 | (check (c) => *unspecified*)
51 | (check *current-job-list* => (list (%make-job 3 'running #f #f)))
52 | (check-throw (c) => 'job-already-started)
53 | (check *current-job-list* => (list (%make-job 3 'running #f #f)))
54 | (suspend-job (car *current-job-list*))
55 | (check *current-job-list* => (list (%make-job 3 'suspended #f #f)))
56 | (update-agenda)
57 | (check (job-state (car *current-job-list*)) => 'suspended)
58 | (continue-job (car *current-job-list*))
59 | (check *current-job-list* => (list (%make-job 3 'running #f #f)))
60 | (update-agenda)
61 | (check *current-job-list* => (list (%make-job 3 'zombie 2 #f)))
62 |
63 | (set! *current-job-list* '())
64 |
65 | (define d (make-job (lambda ()
66 | 1
67 | (job-exit 3)
68 | 2)))
69 |
70 | (check *current-job-list* => (list (%make-job 4 'baby #f #f)))
71 | (check (d) => 3)
72 | (check *current-job-list* => (list (%make-job 4 'zombie 3 #f)))
73 |
74 | (set! *current-job-list* '())
75 |
76 | (define e (make-job
77 | (lambda ()
78 | (receive (proc job)
79 | (make-job (lambda ()
80 | (wait)
81 | 'f))
82 | (wait)
83 | (proc)
84 | (wait-for-job job)))))
85 |
86 |
87 | (check *current-job-list* => (list (%make-job 5 'baby #f #f)))
88 | (e)
89 | (check *current-job-list* => (list (%make-job 6 'baby #f #f)
90 | (%make-job 5 'running #f #f)))
91 | (update-agenda)
92 | (check *current-job-list* => (list (%make-job 6 'running #f #f)
93 | (%make-job 5 'running #f #f)))
94 | (update-agenda)
95 | (check *current-job-list* => (list (%make-job 6 'zombie 'f #f)
96 | (%make-job 5 'zombie 'f #f)))
97 |
98 | (check (format #f "~a" (car *current-job-list*)) => "#")
99 |
100 | (check-report)
101 | (check-exit)
102 |
--------------------------------------------------------------------------------
/src/emacsy/job.scm:
--------------------------------------------------------------------------------
1 | (define-module (emacsy job)
2 | #:use-module (srfi srfi-1)
3 | #:use-module (srfi srfi-9)
4 | #:use-module (srfi srfi-9 gnu)
5 | #:use-module (emacsy coroutine)
6 | #:use-module (emacsy agenda)
7 | #:export (
8 | job?
9 | make-job
10 | get-job-id
11 | suspend-job
12 | continue-job))
13 |
14 | (define-record-type
15 | (%make-job job-id job-state job-exit-value job-cont)
16 | job?
17 | (job-id job-id)
18 | (job-state job-state set-job-state!)
19 | (job-exit-value job-exit-value set-job-exit-value!)
20 | (job-cont job-cont set-job-cont!))
21 |
22 | (set-record-type-printer!
23 | (lambda (job port)
24 | (format port "# port)))
34 |
35 |
36 |
37 | (define *current-job-list* '())
38 |
39 | (define next-job-id 1)
40 |
41 | (define (job-id->job jid)
42 | (find (lambda (job)
43 | (eq? (job-id job) jid))
44 | *current-job-list*))
45 |
46 | (define (coerce-to-job job-or-jid)
47 | (if (job? job-or-jid)
48 | job-or-jid
49 | (job-id->job job-or-jid)))
50 |
51 | (define* (make-job thunk)
52 | "Creates a coroutine that has some job control."
53 | (let ((job (%make-job next-job-id 'baby #f #f)))
54 | (define (handler cont key . args)
55 | (define (resume . args)
56 | #;(format #t "resuming job ~a~%" (job-id job))
57 | ;; Call continuation that resumes the procedure.
58 | (call-with-prompt 'coroutine-prompt
59 | (lambda () (apply cont args))
60 | handler))
61 | (define (job-resume . args)
62 | (if (eq? (job-state job) 'running)
63 | (apply resume args)
64 | (begin
65 | (set-job-cont! job job-resume)
66 | #;(format #t "job ~a unable to resume because it is ~a~%"
67 | (job-id job)
68 | (job-state job)))))
69 | (case key
70 | ((callback)
71 | (when (procedure? (car args))
72 | (apply (car args) job-resume (cdr args))))
73 | ((user-data)
74 | (resume job))))
75 | (set! next-job-id (1+ next-job-id))
76 | (set! *current-job-list* (cons job *current-job-list*))
77 | (values
78 | (lambda ()
79 | (if (eq? (job-state job) 'baby)
80 | (begin
81 | (set-job-state! job 'running)
82 | #;(format #t "starting job ~a~%" (job-id job))
83 | (call-with-prompt 'coroutine-prompt
84 | (lambda () (job-exit (thunk)))
85 | handler))
86 | (throw 'job-already-started)))
87 | job)))
88 |
89 | (define (suspend-job job-or-jid)
90 | "Suspend a job that is currently running."
91 | (set-job-state! (coerce-to-job job-or-jid) 'suspended))
92 |
93 | (define (continue-job job-or-jid)
94 | "Continue a suspended job and schedule it to be run."
95 | (let ((job (coerce-to-job job-or-jid)))
96 | (when (eq? (job-state job) 'suspended)
97 | (set-job-state! job 'running)
98 | (agenda-schedule (job-cont job))
99 | (set-job-cont! job #f))))
100 |
101 | (define (wait-for-job job-or-jid)
102 | "Waits for a job to complete."
103 | ;; XXX This should be smarter than polling.
104 | (let ((job (coerce-to-job job-or-jid)))
105 | (while (not (eq? (job-state job) 'zombie))
106 | (wait))
107 | (job-exit-value job)))
108 |
109 | (define (job-exit return-value)
110 | "Exit the job with the given return-value."
111 | (let ((job (couser-data)))
112 | (set-job-state! job 'zombie)
113 | (set-job-exit-value! job return-value))
114 | (yield (lambda (resume)
115 | return-value)))
116 |
117 | (define (get-job-id)
118 | "Returns the job-id within the job."
119 | (job-id (couser-data)))
120 |
--------------------------------------------------------------------------------
/src/emacsy/kbd-macro.nw:
--------------------------------------------------------------------------------
1 | % -*- mode: Noweb; noweb-code-mode: scheme-mode -*-
2 | \section{Keyboard Macro Module}
3 |
4 |
5 | \epigraph{...}{...}
6 |
7 | We will now add a keyboard macro facility familiar to Emacs users. We
8 | hook into the [[read-event]] procedure using a hook.
9 |
10 | <>=
11 | ;; XXX This also may record the key event that stops the keyboard
12 | ;; macro, which it shouldn't.
13 | (define (kbd-read-event-hook event)
14 | (when defining-kbd-macro?
15 | (message "RECORDING ~a" event)
16 | (cons! event last-kbd-macro)))
17 | @
18 |
19 | <>=
20 | ;; How do I ensure this only happens once?
21 | (add-hook! read-event-hook kbd-read-event-hook)
22 | @
23 |
24 | \subsection{kmacro-start-macro}
25 |
26 | <>=
27 | (define-interactive (kmacro-start-macro)
28 | (set! last-kbd-macro '())
29 | (set! defining-kbd-macro? #t))
30 | @
31 |
32 | <>=
33 | (define-public defining-kbd-macro? #f)
34 | (define-public last-kbd-macro '())
35 | @
36 |
37 | \subsection{kmacro-end-macro}
38 |
39 | <>=
40 | (define-interactive (kmacro-end-macro)
41 | (set! defining-kbd-macro? #f))
42 | @
43 |
44 | \subsection{kmacro-end-and-call-macro}
45 | <>=
46 | (define-interactive (kmacro-end-and-call-macro)
47 | (if defining-kbd-macro?
48 | (kmacro-end-macro))
49 | (execute-kbd-macro last-kbd-macro))
50 | @
51 |
52 | \subsection{execute-kbd-macro}
53 |
54 | <>=
55 | (define-interactive
56 | (execute-kbd-macro #:optional
57 | (kbd-macro last-kbd-macro)
58 | (count 1) (loopfunc #f))
59 | (let ((orig-event-queue event-queue)
60 | (new-event-queue (make-q)))
61 | (for-each (lambda (x)
62 | (enq! new-event-queue x))
63 | (reverse kbd-macro))
64 | (in-out-guard
65 | (lambda ()
66 | (set! event-queue new-event-queue)
67 | (set! executing-kbd-macro? #t))
68 | (lambda ()
69 | (command-loop (lambda args (not (q-empty? event-queue)))))
70 | ;; Turn off the executing-kbd-macro?.
71 | (lambda ()
72 | (set! executing-kbd-macro? #f)
73 | (set! event-queue orig-event-queue)
74 | (run-hook kbd-macro-termination-hook)))))
75 | @
76 |
77 | <>=
78 | (define-public executing-kbd-macro? #f)
79 | (define-public kbd-macro-termination-hook (make-hook))
80 | @
81 |
82 | Let's set up a command to test our functionality with.
83 |
84 | <>=
85 | (define test-command-called 0)
86 | (define test-keymap (make-keymap))
87 | (define-interactive (test-command)
88 | (incr! test-command-called))
89 |
90 | (define-key test-keymap (kbd "a") 'test-command)
91 | (set! default-klecl-maps (lambda () (list test-keymap)))
92 |
93 | (check test-command-called => 0)
94 | (kmacro-start-macro)
95 | (emacsy-key-event #\a)
96 | (emacsy-key-event #\b) ;; this executes no command.
97 | (primitive-command-loop (lambda args #f))
98 | (primitive-command-loop (lambda args #f))
99 | (kmacro-end-macro)
100 | (check test-command-called => 1)
101 | (check (map command-char last-kbd-macro) => '(#\b #\a))
102 | (execute-kbd-macro last-kbd-macro)
103 | (check test-command-called => 2)
104 | @
105 |
106 | \subsection{execute-temporal-kbd-macro}
107 |
108 | In addition to regular keyboard macros, Emacsy can execute keyboard
109 | macros such that they reproduce the keys at the same pace as they were
110 | recorded.
111 |
112 | <>=
113 | (define-interactive
114 | (execute-temporal-kbd-macro #:optional (kbd-macro last-kbd-macro))
115 | (in-out
116 | (set! executing-kbd-macro? #t)
117 | (let* ((start-time (emacsy-time))
118 | (macro-start-time (time (last kbd-macro))))
119 | (let loop ((macro (reverse kbd-macro)))
120 | (when (not (null? macro))
121 | (block-until (lambda ()
122 | (let ((duration (- (emacsy-time) start-time) ))
123 | (run-hook executing-temporal-kbd-macro-hook duration)
124 | (>= duration
125 | (- (time (car macro)) macro-start-time)))))
126 | (emacsy-event (car macro))
127 | (loop (cdr macro)))))
128 | (begin
129 | (set! executing-kbd-macro? #f)
130 | (run-hook kbd-macro-termination-hook))))
131 | @
132 |
133 | <>=
134 | (define-public executing-temporal-kbd-macro-hook (make-hook 1))
135 | @
136 |
137 | <>=
138 | (check test-command-called => 2)
139 | (execute-temporal-kbd-macro last-kbd-macro)
140 | (primitive-command-loop (lambda args #f))
141 | (check test-command-called => 3)
142 | @
143 |
144 |
145 | \subsection*{File Layout}
146 |
147 | <>=
148 | (define-module (emacsy kbd-macro)
149 | #:use-module (ice-9 q)
150 | #:use-module (srfi srfi-1)
151 | #:use-module (emacsy util)
152 | #:use-module (emacsy event)
153 | #:use-module (emacsy command)
154 | #:use-module (emacsy keymap)
155 | #:use-module (emacsy klecl)
156 | #:use-module (emacsy block))
157 | <