Instantize the character event queue

Wrap this stuff up in structs so it's no longer global.
This commit is contained in:
ridiculousfish 2019-06-02 15:41:23 -07:00
parent 6c1d29e14d
commit a48dbf23b8
9 changed files with 177 additions and 138 deletions

View file

@ -326,9 +326,8 @@ int builtin_commandline(parser_t &parser, io_streams_t &streams, wchar_t **argv)
for (i = w.woptind; i < argc; i++) {
if (auto mc = input_function_get_code(argv[i])) {
// input_unreadch inserts the specified keypress or readline function at the back of
// the queue of unused keypresses.
input_queue_ch(*mc);
// Inserts the readline function at the back of the queue.
reader_queue_ch(*mc);
} else {
streams.err.append_format(_(L"%ls: Unknown input function '%ls'"), cmd, argv[i]);
builtin_print_error_trailer(parser, streams.err, cmd);

View file

@ -201,6 +201,7 @@ static double output_elapsed_time(double prev_tstamp, bool first_char_seen) {
static void process_input(bool continuous_mode) {
bool first_char_seen = false;
double prev_tstamp = 0.0;
input_event_queue_t queue;
std::vector<wchar_t> bind_chars;
std::fwprintf(stderr, L"Press a key\n\n");
@ -209,7 +210,7 @@ static void process_input(bool continuous_mode) {
if (reader_test_and_clear_interrupted()) {
evt = char_event_t{shell_modes.c_cc[VINTR]};
} else {
evt = input_common_readch_timed(true);
evt = queue.readch_timed(true);
}
if (!evt.is_char()) {
output_bind_command(bind_chars);

View file

@ -3003,6 +3003,7 @@ static bool history_contains(const std::unique_ptr<history_t> &history, const wc
static void test_input() {
say(L"Testing input");
inputter_t input{};
// Ensure sequences are order independent. Here we add two bindings where the first is a prefix
// of the second, and then emit the second key list. The second binding should be invoked, not
// the first!
@ -3013,11 +3014,11 @@ static void test_input() {
// Push the desired binding to the queue.
for (size_t idx = 0; idx < desired_binding.size(); idx++) {
input_queue_ch(desired_binding.at(idx));
input.queue_ch(desired_binding.at(idx));
}
// Now test.
auto evt = input_readch();
auto evt = input.readch();
if (!evt.is_readline()) {
err(L"Event is not a readline");
} else if (evt.get_readline() != readline_cmd_t::down_line) {

View file

@ -160,10 +160,6 @@ static latch_t<std::vector<terminfo_mapping_t>> s_terminfo_mappings;
/// \return the input terminfo.
static std::vector<terminfo_mapping_t> create_input_terminfo();
static wchar_t input_function_args[MAX_INPUT_FUNCTION_ARGS];
static bool input_function_status;
static int input_function_args_index = 0;
/// Return the current bind mode.
wcstring input_get_bind_mode(const environment_t &vars) {
auto mode = vars.get(FISH_BIND_MODE_VAR);
@ -195,9 +191,6 @@ static int input_function_arity(readline_cmd_t function) {
}
}
/// Sets the return status of the most recently executed input function.
void input_function_set_status(bool status) { input_function_status = status; }
/// Helper function to compare the lengths of sequences.
static bool length_is_greater_than(const input_mapping_t &m1, const input_mapping_t &m2) {
return m1.seq.size() > m2.seq.size();
@ -294,13 +287,16 @@ void init_input() {
}
}
void input_function_push_arg(wchar_t arg) {
input_function_args[input_function_args_index++] = arg;
void inputter_t::function_push_arg(wchar_t arg) { input_function_args_.push_back(arg); }
wchar_t inputter_t::function_pop_arg() {
assert(!input_function_args_.empty() && "function_pop_arg underflow");
auto result = input_function_args_.back();
input_function_args_.pop_back();
return result;
}
wchar_t input_function_pop_arg() { return input_function_args[--input_function_args_index]; }
void input_function_push_args(readline_cmd_t code) {
void inputter_t::function_push_args(readline_cmd_t code) {
int arity = input_function_arity(code);
std::vector<char_event_t> skipped;
@ -308,26 +304,26 @@ void input_function_push_args(readline_cmd_t code) {
// Skip and queue up any function codes. See issue #2357.
wchar_t arg{};
for (;;) {
auto evt = input_common_readch();
auto evt = event_queue_.readch();
if (evt.is_char()) {
arg = evt.get_char();
break;
}
skipped.push_back(evt);
}
input_function_push_arg(arg);
function_push_arg(arg);
}
// Push the function codes back into the input stream.
size_t idx = skipped.size();
while (idx--) {
input_common_next_ch(skipped.at(idx));
event_queue_.push_front(skipped.at(idx));
}
}
/// Perform the action of the specified binding. allow_commands controls whether fish commands
/// should be executed, or should be deferred until later.
static void input_mapping_execute(const input_mapping_t &m, bool allow_commands) {
void inputter_t::mapping_execute(const input_mapping_t &m, bool allow_commands) {
// has_functions: there are functions that need to be put on the input queue
// has_commands: there are shell commands that need to be evaluated
bool has_commands = false, has_functions = false;
@ -349,9 +345,9 @@ static void input_mapping_execute(const input_mapping_t &m, bool allow_commands)
// We don't want to run commands yet. Put the characters back and return check_exit.
for (wcstring::const_reverse_iterator it = m.seq.rbegin(), end = m.seq.rend(); it != end;
++it) {
input_common_next_ch(*it);
event_queue_.push_front(*it);
}
input_common_next_ch(char_event_type_t::check_exit);
event_queue_.push_front(char_event_type_t::check_exit);
return; // skip the input_set_bind_mode
} else if (has_functions && !has_commands) {
// Functions are added at the head of the input queue.
@ -359,8 +355,8 @@ static void input_mapping_execute(const input_mapping_t &m, bool allow_commands)
end = m.commands.rend();
it != end; ++it) {
readline_cmd_t code = input_function_get_code(*it).value();
input_function_push_args(code);
input_common_next_ch(code);
function_push_args(code);
event_queue_.push_front(code);
}
} else if (has_commands && !has_functions) {
// Execute all commands.
@ -373,11 +369,11 @@ static void input_mapping_execute(const input_mapping_t &m, bool allow_commands)
parser.eval(cmd, io_chain_t(), TOP);
}
parser.set_last_statuses(std::move(last_statuses));
input_common_next_ch(char_event_type_t::check_exit);
event_queue_.push_front(char_event_type_t::check_exit);
} else {
// Invalid binding, mixed commands and functions. We would need to execute these one by
// one.
input_common_next_ch(char_event_type_t::check_exit);
event_queue_.push_front(char_event_type_t::check_exit);
}
// Empty bind mode indicates to not reset the mode (#2871)
@ -385,20 +381,20 @@ static void input_mapping_execute(const input_mapping_t &m, bool allow_commands)
}
/// Try reading the specified function mapping.
static bool input_mapping_is_match(const input_mapping_t &m) {
bool inputter_t::mapping_is_match(const input_mapping_t &m) {
const wcstring &str = m.seq;
assert(str.size() > 0 && "zero-length input string passed to input_mapping_is_match!");
bool timed = false;
for (size_t i = 0; i < str.size(); ++i) {
auto evt = timed ? input_common_readch_timed() : input_common_readch();
auto evt = timed ? event_queue_.readch_timed() : event_queue_.readch();
if (!evt.is_char() || evt.get_char() != str[i]) {
// We didn't match the bind sequence/input mapping, (it timed out or they entered
// something else) Undo consumption of the read characters since we didn't match the
// bind sequence and abort.
input_common_next_ch(evt);
while (i--) input_common_next_ch(str[i]);
event_queue_.push_front(evt);
while (i--) event_queue_.push_front(str[i]);
return false;
}
@ -410,11 +406,13 @@ static bool input_mapping_is_match(const input_mapping_t &m) {
return true;
}
void input_queue_ch(char_event_t ch) { input_common_queue_ch(ch); }
void inputter_t::queue_ch(char_event_t ch) { event_queue_.push_back(ch); }
void inputter_t::push_front(char_event_t ch) { event_queue_.push_front(ch); }
/// \return the first mapping that matches, walking first over the user's mapping list, then the
/// preset list. \return null if nothing matches.
static const input_mapping_t *find_mapping() {
const input_mapping_t *inputter_t::find_mapping() {
const input_mapping_t *generic = NULL;
const auto &vars = parser_t::principal_parser().vars();
const wcstring bind_mode = input_get_bind_mode(vars);
@ -429,7 +427,7 @@ static const input_mapping_t *find_mapping() {
if (m.is_generic()) {
if (!generic) generic = &m;
} else if (input_mapping_is_match(m)) {
} else if (mapping_is_match(m)) {
return &m;
}
}
@ -437,26 +435,26 @@ static const input_mapping_t *find_mapping() {
return generic;
}
static void input_mapping_execute_matching_or_generic(bool allow_commands) {
void inputter_t::mapping_execute_matching_or_generic(bool allow_commands) {
const input_mapping_t *mapping = find_mapping();
if (mapping) {
input_mapping_execute(*mapping, allow_commands);
mapping_execute(*mapping, allow_commands);
} else {
debug(2, L"no generic found, ignoring char...");
auto evt = input_common_readch();
auto evt = event_queue_.readch();
if (evt.is_eof()) {
input_common_next_ch(evt);
event_queue_.push_front(evt);
}
}
}
/// Helper function. Picks through the queue of incoming characters until we get to one that's not a
/// readline function.
static char_event_t input_read_characters_no_readline() {
char_event_t inputter_t::read_characters_no_readline() {
std::vector<char_event_t> saved_events;
char_event_t evt_to_return{0};
for (;;) {
auto evt = input_common_readch();
auto evt = event_queue_.readch();
if (evt.is_readline()) {
saved_events.push_back(evt);
} else {
@ -466,34 +464,34 @@ static char_event_t input_read_characters_no_readline() {
}
// Restore any readline functions, in reverse to preserve their original order.
for (auto iter = saved_events.rbegin(); iter != saved_events.rend(); ++iter) {
input_common_next_ch(*iter);
event_queue_.push_front(*iter);
}
return evt_to_return;
}
char_event_t input_readch(bool allow_commands) {
char_event_t inputter_t::readch(bool allow_commands) {
// Clear the interrupted flag.
reader_reset_interrupted();
// Search for sequence in mapping tables.
while (true) {
auto evt = input_common_readch();
auto evt = event_queue_.readch();
if (evt.is_readline()) {
switch (evt.get_readline()) {
case readline_cmd_t::self_insert: {
// Issue #1595: ensure we only insert characters, not readline functions. The
// common case is that this will be empty.
return input_read_characters_no_readline();
return read_characters_no_readline();
}
case readline_cmd_t::func_and: {
if (input_function_status) {
return input_readch();
if (function_status_) {
return readch();
}
do {
evt = input_common_readch();
evt = event_queue_.readch();
} while (evt.is_readline());
input_common_next_ch(evt);
return input_readch();
event_queue_.push_front(evt);
return readch();
}
default: {
return evt;
@ -504,8 +502,8 @@ char_event_t input_readch(bool allow_commands) {
// There's no need to go through the input functions.
return evt;
} else {
input_common_next_ch(evt);
input_mapping_execute_matching_or_generic(allow_commands);
event_queue_.push_front(evt);
mapping_execute_matching_or_generic(allow_commands);
// Regarding allow_commands, we're in a loop, but if a fish command is executed,
// check_exit is unread, so the next pass through the loop we'll break out and return
// it.

View file

@ -21,23 +21,50 @@ wcstring describe_char(wint_t c);
/// initializations for our input subsystem.
void init_input();
/// Read a character from fd 0. Try to convert some escape sequences into character constants, but
/// do not permanently block the escape character.
///
/// This is performed in the same way vim does it, i.e. if an escape character is read, wait for
/// more input for a short time (a few milliseconds). If more input is avaialable, it is assumed to
/// be an escape sequence for a special character (such as an arrow key), and readch attempts to
/// parse it. If no more input follows after the escape key, it is assumed to be an actual escape
/// key press, and is returned as such.
///
/// The argument determines whether fish commands are allowed to be run as bindings. If false, when
/// a character is encountered that would invoke a fish command, it is unread and
/// char_event_type_t::check_exit is returned.
char_event_t input_readch(bool allow_commands = true);
struct input_mapping_t;
class inputter_t {
input_event_queue_t event_queue_{};
std::vector<wchar_t> input_function_args_{};
bool function_status_{false};
/// Enqueue a character or a readline function to the queue of unread characters that input_readch
/// will return before actually reading from fd 0.
void input_queue_ch(char_event_t ch);
void function_push_arg(wchar_t arg);
void function_push_args(readline_cmd_t code);
void mapping_execute(const input_mapping_t &m, bool allow_commands);
void mapping_execute_matching_or_generic(bool allow_commands);
bool mapping_is_match(const input_mapping_t &m);
const input_mapping_t *find_mapping();
char_event_t read_characters_no_readline();
public:
inputter_t() = default;
/// Read a character from fd 0. Try to convert some escape sequences into character constants,
/// but do not permanently block the escape character.
///
/// This is performed in the same way vim does it, i.e. if an escape character is read, wait for
/// more input for a short time (a few milliseconds). If more input is avaialable, it is assumed
/// to be an escape sequence for a special character (such as an arrow key), and readch attempts
/// to parse it. If no more input follows after the escape key, it is assumed to be an actual
/// escape key press, and is returned as such.
///
/// The argument determines whether fish commands are allowed to be run as bindings. If false,
/// when a character is encountered that would invoke a fish command, it is unread and
/// char_event_type_t::check_exit is returned.
char_event_t readch(bool allow_commands = true);
/// Enqueue a char event to the queue of unread characters that input_readch will return before
/// actually reading from fd 0.
void queue_ch(char_event_t ch);
/// Enqueue a char event to the front of the queue; this will be the next event returned.
void push_front(char_event_t ch);
/// Sets the return status of the most recently executed input function.
void function_set_status(bool status) { function_status_ = status; }
/// Pop an argument from the function argument stack.
wchar_t function_pop_arg();
};
/// Add a key mapping from the specified sequence to the specified command.
///
@ -77,8 +104,6 @@ wcstring input_get_bind_mode(const environment_t &vars);
/// Set the current bind mode.
void input_set_bind_mode(const wcstring &bind_mode);
wchar_t input_function_pop_arg();
/// Sets the return status of the most recently executed input function.
void input_function_set_status(bool status);

View file

@ -34,36 +34,6 @@
#define WAIT_ON_ESCAPE_DEFAULT 30
static int wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT;
struct input_lookahead_t {
/// Events which have been read and returned by the sequence matching code.
std::deque<char_event_t> lookahead_list;
bool has_lookahead() const { return !lookahead_list.empty(); }
char_event_t pop() {
auto result = lookahead_list.front();
lookahead_list.pop_front();
return result;
}
/// \return the next lookahead char, or none if none. Discards timeouts.
maybe_t<char_event_t> pop_evt() {
while (has_lookahead()) {
auto evt = pop();
if (!evt.is_timeout()) {
return evt;
}
}
return none();
}
void push_back(char_event_t c) { lookahead_list.push_back(c); }
void push_front(char_event_t c) { lookahead_list.push_front(c); }
};
static mainthread_t<input_lookahead_t> s_lookahead;
/// Callback function for handling interrupts on reading.
static interrupt_func_t interrupt_handler;
@ -71,7 +41,7 @@ void input_common_init(interrupt_func_t func) { interrupt_handler = func; }
/// Internal function used by input_common_readch to read one byte from fd 0. This function should
/// only be called by input_common_readch().
static char_event_t readb() {
char_event_t input_event_queue_t::readb() {
for (;;) {
fd_set fdset;
int fd_max = 0;
@ -110,7 +80,7 @@ static char_event_t readb() {
if (interrupt_handler) {
if (auto interrupt_evt = interrupt_handler()) {
return *interrupt_evt;
} else if (auto mc = s_lookahead->pop_evt()) {
} else if (auto mc = pop_discard_timeouts()) {
return *mc;
}
}
@ -128,7 +98,7 @@ static char_event_t readb() {
if (barrier_from_poll || barrier_from_readability) {
if (env_universal_barrier()) {
// A variable change may have triggered a repaint, etc.
if (auto mc = s_lookahead->pop_evt()) {
if (auto mc = pop_discard_timeouts()) {
return *mc;
}
}
@ -136,7 +106,7 @@ static char_event_t readb() {
if (ioport > 0 && FD_ISSET(ioport, &fdset)) {
iothread_service_completion();
if (auto mc = s_lookahead->pop_evt()) {
if (auto mc = pop_discard_timeouts()) {
return *mc;
}
}
@ -175,9 +145,25 @@ void update_wait_on_escape_ms(const environment_t &vars) {
}
}
char_event_t input_common_readch() {
char_event_t input_event_queue_t::pop() {
auto result = queue_.front();
queue_.pop_front();
return result;
}
maybe_t<char_event_t> input_event_queue_t::pop_discard_timeouts() {
while (has_lookahead()) {
auto evt = pop();
if (!evt.is_timeout()) {
return evt;
}
}
return none();
}
char_event_t input_event_queue_t::readch() {
ASSERT_IS_MAIN_THREAD();
if (auto mc = s_lookahead->pop_evt()) {
if (auto mc = pop_discard_timeouts()) {
return *mc;
}
wchar_t res;
@ -215,27 +201,27 @@ char_event_t input_common_readch() {
}
}
char_event_t input_common_readch_timed(bool dequeue_timeouts) {
char_event_t input_event_queue_t::readch_timed(bool dequeue_timeouts) {
char_event_t result{char_event_type_t::timeout};
if (s_lookahead->has_lookahead()) {
result = s_lookahead->pop();
if (has_lookahead()) {
result = pop();
} else {
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
struct timeval tm = {wait_on_escape_ms / 1000, 1000 * (wait_on_escape_ms % 1000)};
if (select(1, &fds, 0, 0, &tm) > 0) {
result = input_common_readch();
result = readch();
}
}
// If we got a timeout, either through dequeuing or creating, ensure it stays on the queue.
if (result.is_timeout()) {
if (!dequeue_timeouts) s_lookahead->push_front(char_event_type_t::timeout);
if (!dequeue_timeouts) queue_.push_front(char_event_type_t::timeout);
return char_event_type_t::timeout;
}
return result;
}
void input_common_queue_ch(char_event_t ch) { s_lookahead->push_back(ch); }
void input_event_queue_t::push_back(char_event_t ch) { queue_.push_back(ch); }
void input_common_next_ch(char_event_t ch) { s_lookahead->push_front(ch); }
void input_event_queue_t::push_front(char_event_t ch) { queue_.push_front(ch); }

View file

@ -6,6 +6,7 @@
#include "maybe.h"
#include <stddef.h>
#include <queue>
enum class readline_cmd_t {
beginning_of_line,
@ -150,24 +151,40 @@ void input_common_init(interrupt_func_t func);
class environment_t;
void update_wait_on_escape_ms(const environment_t &vars);
/// Function used by input_readch to read bytes from stdin until enough bytes have been read to
/// convert them to a wchar_t. Conversion is done using mbrtowc. If a character has previously been
/// read and then 'unread' using \c input_common_unreadch, that character is returned.
/// This function never returns a timeout.
char_event_t input_common_readch();
/// A class which knows how to produce a stream of input events.
class input_event_queue_t {
std::deque<char_event_t> queue_;
/// Like input_common_readch(), except it will wait at most WAIT_ON_ESCAPE milliseconds for a
/// character to be available for reading.
/// If \p dequeue_timeouts is set, remove any timeout from the queue; otherwise retain them.
char_event_t input_common_readch_timed(bool dequeue_timeouts = false);
/// \return if we have any lookahead.
bool has_lookahead() { return !queue_.empty(); }
/// Enqueue a character or a readline function to the queue of unread characters that input_readch
/// will return before actually reading from fd 0.
void input_common_queue_ch(char_event_t ch);
/// \return the next event in the queue.
char_event_t pop();
/// Add a character or a readline function to the front of the queue of unread characters. This
/// will be the first character returned by input_readch (unless this function is called more than
/// once).
void input_common_next_ch(char_event_t ch);
/// \return the next event in the queue, discarding timeouts.
maybe_t<char_event_t> pop_discard_timeouts();
char_event_t readb();
public:
/// Function used by input_readch to read bytes from stdin until enough bytes have been read to
/// convert them to a wchar_t. Conversion is done using mbrtowc. If a character has previously
/// been read and then 'unread' using \c input_common_unreadch, that character is returned. This
/// function never returns a timeout.
char_event_t readch();
/// Like readch(), except it will wait at most WAIT_ON_ESCAPE milliseconds for a
/// character to be available for reading.
/// If \p dequeue_timeouts is set, remove any timeout from the queue; otherwise retain them.
char_event_t readch_timed(bool dequeue_timeouts = false);
/// Enqueue a character or a readline function to the queue of unread characters that
/// readch will return before actually reading from fd 0.
void push_back(char_event_t ch);
/// Add a character or a readline function to the front of the queue of unread characters. This
/// will be the next character returned by readch.
void push_front(char_event_t ch);
};
#endif

View file

@ -345,6 +345,8 @@ class reader_data_t : public std::enable_shared_from_this<reader_data_t> {
bool silent{false};
/// The representation of the current screen contents.
screen_t screen;
/// The source of input events.
inputter_t inputter;
/// The history.
history_t *history{nullptr};
/// The history search.
@ -2371,7 +2373,7 @@ struct readline_loop_state_t {
/// Read normal characters, inserting them into the command line.
/// \return the next unhandled event.
maybe_t<char_event_t> reader_data_t::read_normal_chars(readline_loop_state_t &rls) {
maybe_t<char_event_t> event_needing_handling = input_readch();
maybe_t<char_event_t> event_needing_handling = inputter.readch();
if (!event_is_normal_char(*event_needing_handling) || !can_read(STDIN_FILENO))
return event_needing_handling;
@ -2391,7 +2393,7 @@ maybe_t<char_event_t> reader_data_t::read_normal_chars(readline_loop_state_t &rl
// Only allow commands on the first key; otherwise, we might have data we
// need to insert on the commandline that the commmand might need to be able
// to see.
auto next_event = input_readch(false);
auto next_event = inputter.readch(false);
if (event_is_normal_char(next_event)) {
arr[i] = next_event.get_char();
} else {
@ -3101,10 +3103,10 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
? jump_precision_t::to
: jump_precision_t::till;
editable_line_t *el = active_edit_line();
wchar_t target = input_function_pop_arg();
wchar_t target = inputter.function_pop_arg();
bool success = jump(direction, precision, el, target);
input_function_set_status(success);
inputter.function_set_status(success);
reader_repaint_needed();
break;
}
@ -3116,7 +3118,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
success = jump(last_jump_direction, last_jump_precision, el, last_jump_target);
}
input_function_set_status(success);
inputter.function_set_status(success);
reader_repaint_needed();
break;
}
@ -3138,7 +3140,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
last_jump_direction = original_dir;
input_function_set_status(success);
inputter.function_set_status(success);
reader_repaint_needed();
break;
}
@ -3147,9 +3149,9 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
if (expand_abbreviation_as_necessary(1)) {
super_highlight_me_plenty();
mark_repaint_needed();
input_function_set_status(true);
inputter.function_set_status(true);
} else {
input_function_set_status(false);
inputter.function_set_status(false);
}
break;
}
@ -3394,6 +3396,12 @@ void reader_repaint_if_needed() {
}
}
void reader_queue_ch(const char_event_t &ch) {
if (reader_data_t *data = current_data_or_null()) {
data->inputter.queue_ch(ch);
}
}
void reader_react_to_color_change() {
reader_data_t *data = current_data_or_null();
if (!data) return;
@ -3401,7 +3409,7 @@ void reader_react_to_color_change() {
if (!data->repaint_needed || !data->screen_reset_needed) {
data->repaint_needed = true;
data->screen_reset_needed = true;
input_common_queue_ch(readline_cmd_t::repaint);
data->inputter.queue_ch(readline_cmd_t::repaint);
}
}

View file

@ -85,6 +85,10 @@ void reader_react_to_color_change();
/// Repaint immediately if needed.
void reader_repaint_if_needed();
/// Enqueue an event to the back of the reader's input queue.
class char_event_t;
void reader_queue_ch(const char_event_t &ch);
/// Run the specified command with the correct terminal modes, and while taking care to perform job
/// notification, set the title, etc.
void reader_run_command(const wcstring &buff);