Implement more sophisticated readline-mode
This commit is contained in:
@@ -10,9 +10,8 @@ Channel::Channel(DrivenEngine *de, int chid, int port, const std::string &target
|
|||||||
closed_ = false;
|
closed_ = false;
|
||||||
target_ = target;
|
target_ = target;
|
||||||
readline_enabled_ = (chid == 0);
|
readline_enabled_ = (chid == 0);
|
||||||
readline_len_ = 0;
|
|
||||||
readline_lastc_ = 0;
|
readline_lastc_ = 0;
|
||||||
echo_len_ = 0;
|
desired_prompt_ = "] ";
|
||||||
assert(driven_->channels_[chid_] == nullptr);
|
assert(driven_->channels_[chid_] == nullptr);
|
||||||
driven_->channels_[chid_] = this;
|
driven_->channels_[chid_] = this;
|
||||||
}
|
}
|
||||||
@@ -24,6 +23,47 @@ Channel::~Channel() {
|
|||||||
driven_->channels_[chid_] = nullptr;
|
driven_->channels_[chid_] = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Channel::show_command() {
|
||||||
|
// If the prompt has changed, erase everything and start over.
|
||||||
|
if (desired_prompt_ != current_prompt_) {
|
||||||
|
int ccsize = current_prompt_.size() + current_command_.size();
|
||||||
|
readline_echo_ += util::repeat_string("\b \b", ccsize);
|
||||||
|
readline_echo_ += desired_prompt_;
|
||||||
|
current_command_ = "";
|
||||||
|
current_prompt_ = desired_prompt_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find out how much of the command matches.
|
||||||
|
int match = util::common_prefix_length(current_command_, desired_command_);
|
||||||
|
|
||||||
|
// Echo backspaces to remove the non-matching part.
|
||||||
|
int remove = current_command_.size() - match;
|
||||||
|
if (remove > 0) {
|
||||||
|
readline_echo_ += util::repeat_string("\b \b", remove);
|
||||||
|
current_command_ = current_command_.substr(0, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Echo the new part.
|
||||||
|
std::string newpart = desired_command_.substr(current_command_.size());
|
||||||
|
if (newpart != "") {
|
||||||
|
readline_echo_ += newpart;
|
||||||
|
current_command_ = desired_command_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::hide_command() {
|
||||||
|
int ccsize = current_command_.size() + current_prompt_.size();
|
||||||
|
if (ccsize > 0) {
|
||||||
|
readline_echo_ += util::repeat_string("\b \b", ccsize);
|
||||||
|
current_command_ = "";
|
||||||
|
current_prompt_ = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::set_prompt(const std::string &p) {
|
||||||
|
desired_prompt_ = p;
|
||||||
|
if (readline_enabled_) show_command();
|
||||||
|
}
|
||||||
|
|
||||||
void Channel::feed_readline(int nbytes, const char *bytes) {
|
void Channel::feed_readline(int nbytes, const char *bytes) {
|
||||||
for (int i = 0; i < nbytes; i++) {
|
for (int i = 0; i < nbytes; i++) {
|
||||||
@@ -32,39 +72,61 @@ void Channel::feed_readline(int nbytes, const char *bytes) {
|
|||||||
// Ignore newline immediately after carriage return.
|
// Ignore newline immediately after carriage return.
|
||||||
// Otherwise, crlf produces two newlines.
|
// Otherwise, crlf produces two newlines.
|
||||||
} else if ((c == '\r') || (c == '\n')) {
|
} else if ((c == '\r') || (c == '\n')) {
|
||||||
if ((echo_space() >= 3) && (readline_space() >= 1)) {
|
show_command();
|
||||||
echo_buf_[echo_len_++] = ' ';
|
readline_echo_ = readline_echo_ + " \n";
|
||||||
echo_buf_[echo_len_++] = '\r';
|
sb_in_->write_bytes(desired_command_);
|
||||||
echo_buf_[echo_len_++] = '\n';
|
sb_in_->write_uint8('\n');
|
||||||
readline_buf_[readline_len_++] = '\n';
|
desired_command_ = "";
|
||||||
sb_in_->write_bytes(readline_buf_, readline_len_);
|
current_prompt_ = "";
|
||||||
readline_len_ = 0;
|
current_command_ = "";
|
||||||
}
|
|
||||||
} else if (c == '\b') {
|
} else if (c == '\b') {
|
||||||
if ((readline_len_ >= 1) && (echo_space() >= 3)) {
|
int len = desired_command_.size();
|
||||||
echo_buf_[echo_len_++] = '\b';
|
if (len > 0) {
|
||||||
echo_buf_[echo_len_++] = ' ';
|
desired_command_ = desired_command_.substr(0, len-1);
|
||||||
echo_buf_[echo_len_++] = '\b';
|
|
||||||
readline_len_ -= 1;
|
|
||||||
}
|
}
|
||||||
} else if (c >= 32) {
|
} else if (c >= 32) {
|
||||||
// Don't use up the last character in the readline buffer: save
|
int len = desired_command_.size();
|
||||||
// it for the newline.
|
if (len < READLINE_MAX) {
|
||||||
if ((readline_space() >= 2) && (echo_space() >= 1)) {
|
desired_command_ = desired_command_ + c;
|
||||||
echo_buf_[echo_len_++] = c;
|
|
||||||
readline_buf_[readline_len_++] = c;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
readline_lastc_ = c;
|
readline_lastc_ = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Channel::peek_outgoing(int *nbytes, const char **bytes) {
|
||||||
|
if (sb_out_->fill() > 0) {
|
||||||
|
hide_command();
|
||||||
|
} else if (readline_enabled_) {
|
||||||
|
show_command();
|
||||||
|
}
|
||||||
|
if (readline_echo_.size() > 0) {
|
||||||
|
*nbytes = readline_echo_.size();
|
||||||
|
*bytes = readline_echo_.c_str();
|
||||||
|
} else {
|
||||||
|
*nbytes = sb_out_->fill();
|
||||||
|
*bytes = sb_out_->data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::sent_outgoing(int nbytes) {
|
||||||
|
if (nbytes > 0) {
|
||||||
|
if (readline_echo_.size() > 0) {
|
||||||
|
if (nbytes < int(readline_echo_.size())) {
|
||||||
|
readline_echo_ = readline_echo_.substr(nbytes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nbytes -= readline_echo_.size();
|
||||||
|
readline_echo_ = "";
|
||||||
|
}
|
||||||
|
sb_out_->read_bytes(nbytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Channel::set_readline(bool e) {
|
void Channel::set_readline(bool e) {
|
||||||
if (e != readline_enabled_) {
|
if (e != readline_enabled_) {
|
||||||
readline_enabled_ = e;
|
readline_enabled_ = e;
|
||||||
readline_len_ = 0;
|
desired_command_ = "";
|
||||||
readline_lastc_ = 0;
|
|
||||||
echo_len_ = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,31 +210,11 @@ bool DrivenEngine::drv_outgoing_empty(int chid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DrivenEngine::drv_peek_outgoing(int chid, int *nbytes, const char **bytes) {
|
void DrivenEngine::drv_peek_outgoing(int chid, int *nbytes, const char **bytes) {
|
||||||
Channel *ch = get_chid(chid);
|
return get_chid(chid)->peek_outgoing(nbytes, bytes);
|
||||||
if (ch->echo_len_ > 0) {
|
|
||||||
*nbytes = ch->echo_len_;
|
|
||||||
*bytes = ch->echo_buf_;
|
|
||||||
} else {
|
|
||||||
*nbytes = ch->sb_out_->fill();
|
|
||||||
*bytes = ch->sb_out_->data();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrivenEngine::drv_sent_outgoing(int chid, int nbytes) {
|
void DrivenEngine::drv_sent_outgoing(int chid, int nbytes) {
|
||||||
Channel *ch = get_chid(chid);
|
return get_chid(chid)->sent_outgoing(nbytes);
|
||||||
if (nbytes > 0) {
|
|
||||||
if (ch->echo_len_ > 0) {
|
|
||||||
if (nbytes >= ch->echo_len_) {
|
|
||||||
ch->sb_out_->read_bytes(nbytes - ch->echo_len_);
|
|
||||||
ch->echo_len_ = 0;
|
|
||||||
} else {
|
|
||||||
ch->echo_len_ -= nbytes;
|
|
||||||
memmove(ch->echo_buf_, ch->echo_buf_ + nbytes, ch->echo_len_);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ch->sb_out_->read_bytes(nbytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrivenEngine::drv_recv_incoming(int chid, int nbytes, const char *bytes) {
|
void DrivenEngine::drv_recv_incoming(int chid, int nbytes, const char *bytes) {
|
||||||
|
|||||||
@@ -144,6 +144,10 @@ public:
|
|||||||
//
|
//
|
||||||
void set_readline(bool enabled);
|
void set_readline(bool enabled);
|
||||||
|
|
||||||
|
// Set the prompt for readline mode.
|
||||||
|
//
|
||||||
|
void set_prompt(const std::string &prompt);
|
||||||
|
|
||||||
// You may delete any channel except for stdio. This closes
|
// You may delete any channel except for stdio. This closes
|
||||||
// the channel.
|
// the channel.
|
||||||
//
|
//
|
||||||
@@ -155,10 +159,11 @@ private:
|
|||||||
//
|
//
|
||||||
Channel(DrivenEngine *de, int chid, int port, const std::string &target);
|
Channel(DrivenEngine *de, int chid, int port, const std::string &target);
|
||||||
|
|
||||||
int readline_space() { return READLINE_MAX - readline_len_; }
|
|
||||||
int echo_space() { return READLINE_MAX - echo_len_; }
|
|
||||||
|
|
||||||
void feed_readline(int nbytes, const char *bytes);
|
void feed_readline(int nbytes, const char *bytes);
|
||||||
|
void peek_outgoing(int *nbytes, const char **bytes);
|
||||||
|
void sent_outgoing(int nbytes);
|
||||||
|
void show_command();
|
||||||
|
void hide_command();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int READLINE_MAX=512;
|
static const int READLINE_MAX=512;
|
||||||
@@ -170,12 +175,16 @@ private:
|
|||||||
bool closed_;
|
bool closed_;
|
||||||
std::string error_;
|
std::string error_;
|
||||||
std::string target_;
|
std::string target_;
|
||||||
char readline_buf_[READLINE_MAX];
|
|
||||||
int readline_len_;
|
// Readline stuff.
|
||||||
|
std::string desired_command_;
|
||||||
|
std::string current_command_;
|
||||||
|
std::string desired_prompt_;
|
||||||
|
std::string current_prompt_;
|
||||||
|
std::string readline_echo_;
|
||||||
char readline_lastc_;
|
char readline_lastc_;
|
||||||
char echo_buf_[READLINE_MAX];
|
|
||||||
int echo_len_;
|
|
||||||
bool readline_enabled_;
|
bool readline_enabled_;
|
||||||
|
|
||||||
friend class DrivenEngine;
|
friend class DrivenEngine;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -176,8 +176,9 @@ public:
|
|||||||
|
|
||||||
void handle_console_output() {
|
void handle_console_output() {
|
||||||
int nbytes; const char *bytes;
|
int nbytes; const char *bytes;
|
||||||
driven_->drv_peek_outgoing(0, &nbytes, &bytes);
|
while (true) {
|
||||||
if (nbytes > 0) {
|
driven_->drv_peek_outgoing(0, &nbytes, &bytes);
|
||||||
|
if (nbytes == 0) break;
|
||||||
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
assert(hstdout != INVALID_HANDLE_VALUE);
|
assert(hstdout != INVALID_HANDLE_VALUE);
|
||||||
DWORD nwrote;
|
DWORD nwrote;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "drivertests.hpp"
|
#include "drivertests.hpp"
|
||||||
#include "drivenengine.hpp"
|
#include "drivenengine.hpp"
|
||||||
#include <iostream>
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
|
||||||
static void write_closed_message(Channel *ch, StreamBuffer *out) {
|
static void write_closed_message(Channel *ch, StreamBuffer *out) {
|
||||||
@@ -107,17 +106,23 @@ public:
|
|||||||
// This test just prints the time.
|
// This test just prints the time.
|
||||||
class DriverPrintClockTest : public DrivenEngine {
|
class DriverPrintClockTest : public DrivenEngine {
|
||||||
public:
|
public:
|
||||||
int count;
|
int count_;
|
||||||
|
double last_clock_;
|
||||||
virtual void event_init(int argc, char *argv[]) {
|
virtual void event_init(int argc, char *argv[]) {
|
||||||
count = 0;
|
count_ = 0;
|
||||||
|
last_clock_ = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void event_update() {
|
virtual void event_update() {
|
||||||
std::cerr << std::fixed << std::setprecision(2) << get_clock() << " ";
|
double clock = get_clock();
|
||||||
count++;
|
if (clock > last_clock_ + 0.5) {
|
||||||
if (count == 10) {
|
stdostream() << std::fixed << std::setprecision(2) << clock << " ";
|
||||||
std::cerr << std::endl;
|
count_++;
|
||||||
count = 0;
|
last_clock_ = clock;
|
||||||
|
}
|
||||||
|
if (count_ == 10) {
|
||||||
|
stdostream() << std::endl;
|
||||||
|
count_ = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ void TextGame::event_init(int argc, char *argv[])
|
|||||||
actor_id_ = world_->create_login_actor();
|
actor_id_ = world_->create_login_actor();
|
||||||
printbuffer_line_ = 0;
|
printbuffer_line_ = 0;
|
||||||
stdostream() << "Login actor ID: " << actor_id_ << std::endl;
|
stdostream() << "Login actor ID: " << actor_id_ << std::endl;
|
||||||
get_stdio_channel()->out()->write_bytes(console_.get_prompt());
|
get_stdio_channel()->set_prompt(console_.get_prompt());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextGame::event_update()
|
void TextGame::event_update()
|
||||||
@@ -231,7 +231,7 @@ void TextGame::event_update()
|
|||||||
stop_driver();
|
stop_driver();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
get_stdio_channel()->out()->write_bytes(console_.get_prompt());
|
get_stdio_channel()->set_prompt(console_.get_prompt());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -141,6 +142,25 @@ StringVec split(const std::string &s, char sep) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string repeat_string(const std::string &a, int n) {
|
||||||
|
int len = a.size();
|
||||||
|
std::string result(len * n, ' ');
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
for (int j = 0; j < len; j++) {
|
||||||
|
result[i*len + j] = a[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int common_prefix_length(const std::string &a, const std::string &b) {
|
||||||
|
int minlen = std::min(a.size(), b.size());
|
||||||
|
for (int i = 0; i < minlen; i++) {
|
||||||
|
if (a[i] != b[i]) return i;
|
||||||
|
}
|
||||||
|
return minlen;
|
||||||
|
}
|
||||||
|
|
||||||
std::string tolower(std::string input) {
|
std::string tolower(std::string input) {
|
||||||
for (int i = 0; i < int(input.size()); i++) {
|
for (int i = 0; i < int(input.size()); i++) {
|
||||||
input[i] = std::tolower(input[i]);
|
input[i] = std::tolower(input[i]);
|
||||||
@@ -305,6 +325,9 @@ LuaDefine(unittests_util, "c") {
|
|||||||
LuaAssert(L, sv2[2]=="");
|
LuaAssert(L, sv2[2]=="");
|
||||||
LuaAssert(L, sv2[3]=="bar");
|
LuaAssert(L, sv2[3]=="bar");
|
||||||
|
|
||||||
|
// Test the repeat string routine.
|
||||||
|
LuaAssertStrEq(L, util::repeat_string("abc", 3), "abcabcabc");
|
||||||
|
|
||||||
// test toupper and tolower
|
// test toupper and tolower
|
||||||
LuaAssert(L, util::toupper("fooBar") == "FOOBAR");
|
LuaAssert(L, util::toupper("fooBar") == "FOOBAR");
|
||||||
LuaAssert(L, util::tolower("fooBar") == "foobar");
|
LuaAssert(L, util::tolower("fooBar") == "foobar");
|
||||||
|
|||||||
@@ -56,6 +56,12 @@ std::string hash_to_hex(const HashValue &hash);
|
|||||||
// Split a string into multiple strings
|
// Split a string into multiple strings
|
||||||
StringVec split(const std::string &s, char sep);
|
StringVec split(const std::string &s, char sep);
|
||||||
|
|
||||||
|
// Return N repetitions of string A
|
||||||
|
std::string repeat_string(const std::string &a, int n);
|
||||||
|
|
||||||
|
// Return the length of the common prefix of A and B.
|
||||||
|
int common_prefix_length(const std::string &a, const std::string &b);
|
||||||
|
|
||||||
// String to lowercase/uppercase. Ascii only, no unicode.
|
// String to lowercase/uppercase. Ascii only, no unicode.
|
||||||
std::string tolower(std::string input);
|
std::string tolower(std::string input);
|
||||||
std::string toupper(std::string input);
|
std::string toupper(std::string input);
|
||||||
|
|||||||
Reference in New Issue
Block a user