= Minibuffer.read Walkthrough =
This article is a practical walkthrough of coroutine usage. To help understand how to use coroutines, and also how reading from the minibuffer works, we are going to make use of the simple example from [[WritingCommands]], the `echo-message` command. We'll step through what happens from the point that the user calls `echo-message`. Here is the source code of that command.
{{{
function echo_message (window, message) {
window.minibuffer.message(message);
}
interactive("echo-message",
"echo a user-supplied message in the minibuffer",
function (I) {
echo_message(
I.window,
(yield I.minibuffer.read($prompt = "message: ")));
});
}}}
== An event occurs. ==
In a simple event-driven program, events come in from devices like keyboard and mouse to an underlying platform (in our case Xulrunner), which calls ''event handlers'' in the application, which in turn do interesting stuff. Event handlers run in the same order as the events that came in to trigger them, and one event handler cannot start until the previous one has completed.
Conkeror's coroutine library lets us put a small twist on this scenario. We can ''suspend'' a handler in mid-computation, and process other events, then ''resume'' the computation we suspended at a later time, from the handler of a ''different event''.
To get ourselves in the right frame of mind for this, think of a complete computation, as triggered by a single event, as a "thread". These are not real threads in the sense of multi-threaded programming, but they are an emulation of that effect. So let us begin to step through the computation of a thread, kicked off by a keypress that runs our `echo-message` command.
An event occurs, which causes Xulrunner to call Conkeror's `keypress_handler`, which looks up what command to run, and calls `call_interactively` with that command name. If the handler of that command, or anything that it calls in turn uses the javascript keyword `yield`, that means that the handler is a ''generator''. `call_interactively` uses Conkeror's coroutine library to run that generator in a special loop that gives us the power to ''suspend execution'' (cue creepy theremin effects). Any `yield` expression throughout the entire computation, unless dealt with locally, will fall through to the special loop in the coroutine library, which will in turn take action based on the ''thing yielded''.
You can see how `call_interactively` uses `co_call` to wrap the generator in the special loop.
{{{
// modules/interactive.js: call_interactively
...
var result = handler(I);
if (is_coroutine(result)) {
co_call(function() {
try {
yield result;
} catch (e) {
handle_interactive_error(window, e);
}
}());
}
...
}}}
If we strip away all the error-handling in the code above, we would have something very simple:
{{{
co_call(handler(I));
}}}
The handler for `echo-message` will now run until any part of it requests to be suspended.
{{{
// command-handler of echo-message
function (I) {
echo_message(
I.window,
(yield I.minibuffer.read($prompt = "message: ")));
}
}}}
The first things that happen in our handler are the computation of the arguments to pass to `echo_message`. The interesting one looks like this:
{{{
(yield I.minibuffer.read($prompt = "message: "))
}}}
`I.minibuffer.read` is another coroutine. This syntax is how to have one coroutine (our command handler) call another coroutine ''synchronously''. Our command handler will be paused until `I.minibuffer.read` returns a value. So we have finally traced evaluation to the point where something is about to happen in the minibuffer!
`minibuffer.read` looks like this:
{{{
// modules/minibuffer-read.js
function () {
var s = new text_entry_minibuffer_state((yield CONTINUATION),
forward_keywords(arguments));
this.push_state(s);
var result = yield SUSPEND;
yield co_return(result);
};
}}}
You can ignore the `forward_keywords(arguments)` for now—suffice it to say that it sends our `$prompt = "message: "` into the `new text_entry_minibuffer_state` so that deeper levels of minibuffer code can put up the prompt.
There is something interesting going on with that `(yield CONTINUATION)` and the `yield SUSPEND`. These two expressions can be viewed as a pair. We are going to tell the ''special loop'' to suspend this thread of computation. But first we need a way to resume the thread later. `(yield CONTINUATION)` is a request to the special loop to send us a kind of handle for the current thread—in common parlance, a ''continuation''. We need to stash this handle away somewhere so that we can use it later, from a different thread, to resume the computation. We pass this continuation into the constructor of `text_entry_minibuffer_state`, which stashes it away.
Ever notice how in Conkeror you can be in a minibuffer prompt, say after hitting `C-x C-f` (`find-url-new-buffer`), and you can then hit `C-h k` to find out what some key does—the minibuffer changes to a prompt for a keypress, and after you hit a key, it goes back to the prompt you were in before, asking for an URL. This kind of recursive use of the minibuffer is possible because the minibuffer keeps track of a '''stack of states'''. The prompt that `C-x C-f` put up was one state, and then when you hit `C-h k`, another state got pushed onto the stack. When that state was finished collecting a keypress, it was disposed, and the previous state was restored: back to that URL prompt.
In the code listing above, we see that `minibuffer.read` creates a new `text_entry_minibuffer_state`, and then ''pushes'' that state onto the ''stack of states'' I mentioned. (The variable `this` refers to the minibuffer.) So the minibuffer is now in a state displaying a prompt, and a text field ready to be typed into. But as long as our javascript code is busy doing something, no events, such as keypresses can be processed. Once `minibuffer.read` has set up the minibuffer for input, it needs to step aside so that lower levels of code in Xulrunner can respond to key presses.
Which brings us to the very next line:
{{{
yield SUSPEND;
}}}
When we yield the value `SUSPEND` to the special loop, the special loop performs a `yield` of its own, which suspends the special loop so that the platform (Xulrunner) can process more events. Our computation still exists in memory, and we were clever enough to stash a reference to it with `(yield CONTINUATION)`, but it isn't doing anything. The thread that began with that initial event is now in a deep sleep. But Conkeror is now in a state with a minibuffer input window open and focused. And Xulrunner can listen to events, and send them to Conkeror's keyboard system for interactive processing.
== Typing occurs. ==
The user types some text, and then hits `return`. In the minibuffer, `return` is bound to the command `exit-minibuffer`. We'll put an ellided version of the command's source code here so you can see the interesting bits:
{{{
// modules/minibuffer-read.js
function exit_minibuffer (window) {
var m = window.minibuffer;
var s = m.current_state;
// ...
var val = m._input_text;
// ...
var cont = s.continuation;
delete s.continuation;
m.pop_state();
if (cont) {
// ...
cont(val);
}
}
interactive("exit-minibuffer", null,
function (I) { exit_minibuffer(I.window); });
}}}
First, `exit_minibuffer` gets the current `state` object from the minibuffer, and `val`, the text that has been typed into the minibuffer. That `s.continuation` is where `text_entry_minibuffer_state` stashed the object we got earlier from `(yield CONTINUATION)`. This is our handle to ressurect the command that we were in process of running. The line `delete s.continuation` is a bit of manual garbage-collecting necessary when working with coroutine references in javascript. The line that brings our command back to life is this one:
{{{
cont(val);
}}}
This resumes the coroutine thread of our command. So we are back to:
{{{
// modules/minibuffer-read.js
function () {
var s = new text_entry_minibuffer_state((yield CONTINUATION),
forward_keywords(arguments));
this.push_state(s);
var result = yield SUSPEND;
yield co_return(result);
};
}}}
The `yield SUSPEND` now returns, evaluating to the value passed to `cont` in `exit_minibuffer`. That value is the text that the user entered into the minibuffer.
The last line of minibuffer.read is `yield co_return(result)`. `co_return` creates an object of a particular type, that, when the special loop sees one of, it closes the coroutine and sends the given value to the previous coroutine. Remember how we made a ''synchronous call'' to `minibuffer.read`? `minibuffer.read` has just told the special loop that it's done, and to send the `result` to the thing that called `minibuffer.read`. This brings us back to our command handler for `echo-message`, which has been paused at a `yield` expression all this time. But that `yield` expression now evaluates to the thing that `minibuffer.read` sent back via `co_return`, with the special loop acting as the go-between.
{{{
interactive("echo-message",
"echo a user-supplied message in the minibuffer",
function (I) {
echo_message(
I.window,
(yield I.minibuffer.read($prompt = "message: ")));
});
}}}
We now have all the information needed to call `echo_message`.
== Summary ==
In summary, to understand minibuffer interaction, it helps to remember that Conkeror is an event-driven program. There was an original event at the beginning of this walk-through that resulted in a call to our `echo-message` command. The `echo-message` command handler made a synchronous call to the coroutine `minibuffer.read`, which stashed a handle to the current thread (`(yield CONTINUATION)`) and then suspended that thread (`yield SUSPEND`). One event-handler thus having run to completion, we arranged for a later event to call `exit-minibuffer`, which resumed our sleeping thread with the value it was waiting for (text from the minibuffer input field), thus allowing it to run to completion.