Skip to content
Snippets Groups Projects
Commit 206fb34c authored by g0dil's avatar g0dil
Browse files

Utils/Console: Replace Readline with LineEditor

parent 7554c469
No related branches found
No related tags found
No related merge requests found
......@@ -118,7 +118,7 @@ INLINE_OPTS = [ '-finline-limit=5000' ]
env.Append(
CPPPATH = [ '#/include' ],
CXXFLAGS = [ '-Wall', '-Woverloaded-virtual', '-Wno-long-long' ] + INLINE_OPTS,
LIBS = [ 'readline', 'rt', '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB',
LIBS = [ 'rt', '$BOOSTREGEXLIB', '$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB',
'$BOOSTFSLIB' ],
TEST_EXTRA_LIBS = [ ],
DOXY_XREF_TYPES = [ 'bug', 'fixme', 'todo', 'idea' ],
......
// $Id$
//
// Copyright (C) 2008
// Copyright (C) 2009
// Fraunhofer Institute for Open Communication Systems (FOKUS)
// Competence Center NETwork research (NET), St. Augustin, GERMANY
// Stefan Bund <g0dil@berlios.de>
......@@ -21,70 +21,110 @@
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
/** \file
\brief Readline inline non-template implementation */
\brief LineEditor non-inline non-template implementation */
//#include "Readline.ih"
#include "LineEditor.hh"
//#include "LineEditor.ih"
// Custom includes
#include "../Logger/SenfLog.hh"
#define prefix_ inline
///////////////////////////////cci.p///////////////////////////////////////
//#include "LineEditor.mpp"
#define prefix_
///////////////////////////////cc.p////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// senf::console::detail::ReadlineClientReader
// senf::console::detail::LineEditorSwitcher
prefix_ bool senf::console::detail::ReadlineClientReader::active()
prefix_ senf::console::detail::LineEditorSwitcher::LineEditorSwitcher(Client & client)
: ClientReader(client),
reader_ (new LineEditorClientReader(client, *this))
{}
prefix_ void senf::console::detail::LineEditorSwitcher::editorSetupFailed()
{
// We need to delete the old reader *before* creating the new one such that all old scheduler
// events are removed before installing the new ones.
reader_.reset(0);
reader_.reset(new DumbClientReader(client()));
}
prefix_ void senf::console::detail::LineEditorSwitcher::v_disablePrompt()
{
return instance_ != 0;
reader_->disablePrompt();
}
prefix_ senf::console::detail::ReadlineClientReader &
senf::console::detail::ReadlineClientReader::instance()
prefix_ void senf::console::detail::LineEditorSwitcher::v_enablePrompt()
{
return *instance_;
reader_->enablePrompt();
}
prefix_ int senf::console::detail::ReadlineClientReader::getc()
prefix_ void senf::console::detail::LineEditorSwitcher::v_write(std::string const & data)
{
char ch (ch_);
ch_ = -1;
return ch;
reader_->write(data);
}
prefix_ void senf::console::detail::ReadlineClientReader::write(std::string text)
///////////////////////////////////////////////////////////////////////////
// senf::console::detail::LineEditorClientReader
prefix_ senf::console::detail::LineEditorClientReader::
LineEditorClientReader(Client & client, LineEditorSwitcher & switcher)
: term::BaseTelnetProtocol(client.handle()), ClientReader(client),
editor_ (*this, senf::membind(&LineEditorClientReader::executeLine, this)),
switcher_ (&switcher)
{
try {
translate(text);
handle().write(text);
} catch (SystemException &) {
;
}
editor_.prompt(promptString());
editor_.defineKey(senf::term::KeyParser::Ctrl('D'),
senf::membind(&LineEditorClientReader::deleteCharOrExit, this));
}
prefix_ void senf::console::detail::ReadlineClientReader::terminate()
prefix_ void senf::console::detail::LineEditorClientReader::v_setupFailed()
{
terminate_ = true;
// Commits suicide
switcher_->editorSetupFailed();
}
prefix_ void senf::console::detail::ReadlineClientReader::eof()
prefix_ void senf::console::detail::LineEditorClientReader::v_eof()
{
stream() << '\n' << std::flush;
stopClient();
}
///////////////////////////////////////////////////////////////////////////
// senf::console::detail::SafeReadlineClientReader
prefix_
senf::console::detail::SafeReadlineClientReader::SafeReadlineClientReader(Client & client)
: ClientReader (client),
reader_ ( ReadlineClientReader::active()
? static_cast<ClientReader*>(new DumbClientReader(client))
: static_cast<ClientReader*>(new ReadlineClientReader(client)) )
{}
prefix_ void senf::console::detail::LineEditorClientReader::v_disablePrompt()
{
editor_.hide();
}
prefix_ void senf::console::detail::LineEditorClientReader::v_enablePrompt()
{
editor_.show();
}
prefix_ void senf::console::detail::LineEditorClientReader::v_write(std::string const & data)
{
BaseTelnetProtocol::write(data);
}
prefix_ void
senf::console::detail::LineEditorClientReader::executeLine(std::string const & text)
{
handleInput(text);
stream() << std::flush;
editor_.prompt(promptString());
editor_.show();
}
prefix_ void
senf::console::detail::LineEditorClientReader::deleteCharOrExit(term::LineEditor & editor)
{
if (editor.text().empty())
ClientReader::handle().facet<TCPSocketProtocol>().shutdown(TCPSocketProtocol::ShutRD);
else
term::bindings::deleteChar(editor);
}
///////////////////////////////cci.e///////////////////////////////////////
///////////////////////////////cc.e////////////////////////////////////////
#undef prefix_
//#include "LineEditor.mpp"
// Local Variables:
......
// $Id$
//
// Copyright (C) 2008
// Copyright (C) 2009
// Fraunhofer Institute for Open Communication Systems (FOKUS)
// Competence Center NETwork research (NET), St. Augustin, GERMANY
// Stefan Bund <g0dil@berlios.de>
......@@ -21,101 +21,70 @@
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
/** \file
\brief Readline public header */
\brief LineEditor public header */
#ifndef HH_SENF_Scheduler_Console_Readline_
#define HH_SENF_Scheduler_Console_Readline_ 1
#ifndef HH_SENF_Utils_Console_LineEditor_
#define HH_SENF_Utils_Console_LineEditor_ 1
// Custom includes
#include <boost/scoped_ptr.hpp>
#include "../Termlib/TelnetTerminal.hh"
#include "../Termlib/Editor.hh"
#include "Server.hh"
#include "../../Utils/Exception.hh"
#include "../../Scheduler/Scheduler.hh"
//#include "Readline.mpp"
//#include "LineEditor.mpp"
///////////////////////////////hh.p////////////////////////////////////////
namespace senf {
namespace console {
namespace detail {
#ifndef DOXYGEN
/** \brief Internal: GNU readline based ClientReader implementation
This implementation of the ClientReader interface uses GNU readline library to provide a
rich editing environment for console sessions. Since an application can only use readline
once, only one ReadlineReader can be allocated at any time.
*/
class ReadlineClientReader
class LineEditorSwitcher
: public ClientReader
{
public:
ReadlineClientReader(Client & client);
~ReadlineClientReader();
explicit LineEditorSwitcher(Client & client);
static bool active();
static ReadlineClientReader & instance();
void editorSetupFailed();
struct DuplicateReaderException : public Exception
{ DuplicateReaderException() : Exception("duplicate readline instantiation") {} };
int getc();
void callback(std::string line);
void write(std::string text);
void terminate();
void eof();
private:
virtual void v_disablePrompt();
virtual void v_enablePrompt();
virtual void v_translate(std::string & data);
void charEvent(int event);
static ReadlineClientReader * instance_;
int ch_;
unsigned skipChars_;
char nameBuffer_[256];
char promptBuffer_[1024];
scheduler::FdEvent readevent_;
bool terminate_;
char * savedLineBuffer_;
int savedPoint_;
int savedEnd_;
int savedMark_;
virtual void v_write(std::string const & data);
boost::scoped_ptr<ClientReader> reader_;
};
/** \brief Internal: Safe GNU readline based ClientReader implementation
This implementation of the ClientReader interface forwards all functionality to either
ReadlineClientReader or DumbClientReader. A RadlineClientReader is used, if none is active,
otherwise a DumbClientReader is allocated. Using this ClientReader implementation, the first
console client will have readline functionality enabled, all further will have it disabled.
*/
class SafeReadlineClientReader
: public ClientReader
class LineEditorClientReader
: public ClientReader, public term::TelnetTerminal
{
public:
SafeReadlineClientReader(Client & client);
explicit LineEditorClientReader(Client & client, LineEditorSwitcher & switcher);
private:
// TelnetTerminal API implementation
virtual void v_setupFailed();
virtual void v_eof();
// ClientReader API implementation
virtual void v_disablePrompt();
virtual void v_enablePrompt();
virtual void v_translate(std::string & data);
virtual void v_write(std::string const & data);
boost::scoped_ptr<ClientReader> reader_;
};
// Editor callbacks
void executeLine(std::string const & text);
void deleteCharOrExit(term::LineEditor & editor);
#endif
term::LineEditor editor_;
LineEditorSwitcher * switcher_;
};
}}}
///////////////////////////////hh.e////////////////////////////////////////
#include "Readline.cci"
//#include "Readline.ct"
//#include "Readline.cti"
//#include "LineEditor.cci"
//#include "LineEditor.ct"
//#include "LineEditor.cti"
#endif
......
// $Id$
//
// Copyright (C) 2008
// Fraunhofer Institute for Open Communication Systems (FOKUS)
// Competence Center NETwork research (NET), St. Augustin, GERMANY
// Stefan Bund <g0dil@berlios.de>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the
// Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
/** \file
\brief Readline non-inline non-template implementation */
#include "Readline.hh"
//#include "Readline.ih"
// Custom includes
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/replace.hpp>
#include "../../Utils/membind.hh"
//#include "Readline.mpp"
#define prefix_
///////////////////////////////cc.p////////////////////////////////////////
//
// Readline integration is a bit awkward. There are several things to it:
//
// - Readline uses global variables for all state. Therefore, we can use readline only for one
// console.
// - We need to make readline to read from the socket handle instead of some input stream. We can
// do this by setting a custom rl_getc_function.
// - We need to make readline to write to the NonblockingSocketOStream. This is possible in glibc
// by using a 'cookie stream'.
// - We need to correctly handle the terminal mode settings. Currently we unconditionally
// initialize the remote telnet by sending a fixed telnet option string and ignoring any otpions
// sent back to us.
// - We need to make sure, readline never uses stderr -> we must disable beeping
// - There are places, where readline calls read_key unconditionally even when NOT prompted by the
// callback that another key is available. One such place is completion. (The 'show all
// completions (y/n)?' question). For now, we disable completion support.
//
///////////////////////////////////////////////////////////////////////////
// senf::console::detail::ReadlineClientReader
extern "C" {
extern int readline_echoing_p;
extern int _rl_bell_preference;
void _rl_erase_entire_line();
}
namespace {
int readline_getc_function(FILE *)
{
if (senf::console::detail::ReadlineClientReader::active())
return senf::console::detail::ReadlineClientReader::instance().getc();
else
return -1;
}
void readline_callback(char * input)
{
if (senf::console::detail::ReadlineClientReader::active()) {
if (input)
return senf::console::detail::ReadlineClientReader::instance().callback(
std::string(input) );
else // input == 0 -> EOF (or Ctrl-D)
senf::console::detail::ReadlineClientReader::instance().eof();
}
}
ssize_t readline_cookie_write_function(void * cookie, char const * buffer, size_t size)
{
if (senf::console::detail::ReadlineClientReader::active() && buffer)
senf::console::detail::ReadlineClientReader::instance().write(
std::string(buffer, size));
return size;
}
void readline_prep_term(int meta)
{
readline_echoing_p = 1;
}
void readline_deprep_term()
{}
int restart_line(int count, int key)
{
rl_kill_full_line(count, key);
rl_crlf();
rl_forced_update_display();
return 0;
}
}
prefix_ senf::console::detail::ReadlineClientReader::ReadlineClientReader(Client & client)
: ClientReader(client), ch_ (-1), skipChars_ (0),
readevent_ ( "senf::console::detail::ReadlineClientReader", senf::membind(&ReadlineClientReader::charEvent, this),
client.handle(), scheduler::FdEvent::EV_READ, false ),
terminate_ (false)
{
if (instance_ != 0)
throw DuplicateReaderException();
instance_ = this;
cookie_io_functions_t cookie_io = { 0, &readline_cookie_write_function, 0, 0 };
rl_outstream = fopencookie(0, "a", cookie_io);
if (rl_outstream == 0)
SENF_THROW_SYSTEM_EXCEPTION("");
if (setvbuf(rl_outstream, 0, _IONBF, BUFSIZ) < 0)
SENF_THROW_SYSTEM_EXCEPTION("");
rl_instream = rl_outstream;
rl_terminal_name = "vt100";
strncpy(nameBuffer_, client.name().c_str(), 128);
nameBuffer_[127] = 0;
rl_readline_name = nameBuffer_;
rl_prep_term_function = &readline_prep_term;
rl_deprep_term_function = &readline_deprep_term;
rl_getc_function = &readline_getc_function;
rl_bind_key('\t', &rl_insert);
rl_bind_key('\x03', &restart_line);
using_history();
// Don't ask me, where I found this ...
static char options[] = { 0xFF, 0xFB, 0x01, // IAC WILL ECHO
0xFF, 0xFE, 0x22, // IAC DONT LINEMODE
0xFF, 0xFB, 0x03, // IAC WILL SGA
0x00 };
handle().write(options, options+sizeof(options));
handle().write(std::string("(readline support enabled)\r\n"));
strncpy(promptBuffer_, promptString().c_str(), 1024);
promptBuffer_[1023] = 0;
rl_callback_handler_install(promptBuffer_, &readline_callback);
_rl_bell_preference = 0; // Set this *after* the config file has been read
readevent_.enable();
}
prefix_ senf::console::detail::ReadlineClientReader::~ReadlineClientReader()
{
rl_callback_handler_remove();
fclose(rl_outstream);
rl_outstream = 0;
rl_instream = 0;
instance_ = 0;
}
prefix_ void senf::console::detail::ReadlineClientReader::callback(std::string line)
{
boost::trim(line);
if (!line.empty())
add_history(line.c_str());
handleInput(line);
stream() << std::flush;
strncpy(promptBuffer_, promptString().c_str(), 1024);
promptBuffer_[1023] = 0;
rl_set_prompt(promptBuffer_);
}
prefix_ void senf::console::detail::ReadlineClientReader::v_disablePrompt()
{
_rl_erase_entire_line();
}
prefix_ void senf::console::detail::ReadlineClientReader::v_enablePrompt()
{
rl_forced_update_display();
}
prefix_ void senf::console::detail::ReadlineClientReader::v_translate(std::string & data)
{
boost::replace_all(data, "\n", "\r\n");
boost::replace_all(data, "\r", "\r\0");
boost::replace_all(data, "\xff", "\xff\xff");
}
prefix_ void senf::console::detail::ReadlineClientReader::charEvent(int event)
{
char ch;
if (event != scheduler::FdEvent::EV_READ || handle().read(&ch, &ch+1) <= &ch) {
stopClient();
return;
}
ch_ = static_cast<unsigned char>(ch);
if (skipChars_ > 0)
--skipChars_;
else if (ch_ == 0xff)
skipChars_ = 2;
else if (ch_ != 0)
rl_callback_read_char();
if (terminate_)
stopClient();
}
senf::console::detail::ReadlineClientReader *
senf::console::detail::ReadlineClientReader::instance_ (0);
///////////////////////////////////////////////////////////////////////////
// senf::console::detail::SafeReadlineClientReader
prefix_ void senf::console::detail::SafeReadlineClientReader::v_disablePrompt()
{
reader_->disablePrompt();
}
prefix_ void senf::console::detail::SafeReadlineClientReader::v_enablePrompt()
{
reader_->enablePrompt();
}
prefix_ void senf::console::detail::SafeReadlineClientReader::v_translate(std::string & data)
{
reader_->translate(data);
}
///////////////////////////////cc.e////////////////////////////////////////
#undef prefix_
//#include "Readline.mpp"
// Local Variables:
// mode: c++
// fill-column: 100
// comment-column: 40
// c-file-style: "senf"
// indent-tabs-mode: nil
// ispell-local-dictionary: "american"
// compile-command: "scons -u test"
// End:
......@@ -35,7 +35,7 @@
#include "../../Utils/senfassert.hh"
#include "../../Utils/membind.hh"
#include "../../Utils/Logger/SenfLog.hh"
#include "Readline.hh"
#include "LineEditor.hh"
//#include "Server.mpp"
#define prefix_
......@@ -50,8 +50,7 @@ prefix_ std::streamsize senf::console::detail::NonblockingSocketSink::write(cons
try {
if (client_.handle().writeable()) {
std::string data (s, n);
client_.translate(data);
client_.handle().write( data );
client_.write(data);
}
}
catch (SystemException & ex) {
......@@ -149,6 +148,7 @@ senf::console::detail::DumbClientReader::clientData(senf::ReadHelper<ClientHandl
prefix_ void senf::console::detail::DumbClientReader::showPrompt()
{
std::string prompt (promptString());
prompt += " ";
stream() << std::flush;
handle().write(prompt);
......@@ -170,8 +170,10 @@ prefix_ void senf::console::detail::DumbClientReader::v_enablePrompt()
showPrompt();
}
prefix_ void senf::console::detail::DumbClientReader::v_translate(std::string & data)
{}
prefix_ void senf::console::detail::DumbClientReader::v_write(std::string const & data)
{
handle().write(data);
}
///////////////////////////////////////////////////////////////////////////
// senf::console::detail::NoninteractiveClientReader
......@@ -190,8 +192,10 @@ prefix_ void senf::console::detail::NoninteractiveClientReader::v_disablePrompt(
prefix_ void senf::console::detail::NoninteractiveClientReader::v_enablePrompt()
{}
prefix_ void senf::console::detail::NoninteractiveClientReader::v_translate(std::string & data)
{}
prefix_ void senf::console::detail::NoninteractiveClientReader::v_write(std::string const & data)
{
handle().write(data);
}
prefix_ void
senf::console::detail::NoninteractiveClientReader::newData(int event)
......@@ -247,7 +251,7 @@ prefix_ void senf::console::Client::setInteractive()
readevent_.disable();
timer_.disable();
mode_ = Server::Interactive;
reader_.reset(new detail::SafeReadlineClientReader (*this));
reader_.reset(new detail::LineEditorSwitcher (*this));
executor_.autocd(true).autocomplete(true);
}
......@@ -259,11 +263,6 @@ prefix_ void senf::console::Client::setNoninteractive()
reader_.reset(new detail::NoninteractiveClientReader(*this));
}
prefix_ void senf::console::Client::translate(std::string & data)
{
reader_->translate(data);
}
prefix_ std::string::size_type senf::console::Client::handleInput(std::string data,
bool incremental)
{
......
......@@ -130,7 +130,7 @@ prefix_ std::string const & senf::console::Client::name()
prefix_ std::string senf::console::Client::promptString()
const
{
return name_ + ":" + executor_.cwdPath() + "$ ";
return name_ + ":" + executor_.cwdPath() + "$";
}
prefix_ senf::console::DirectoryNode & senf::console::Client::root()
......@@ -145,6 +145,12 @@ prefix_ senf::console::Server::Mode senf::console::Client::mode()
return mode_;
}
prefix_ void senf::console::Client::write(std::string const & data)
const
{
reader_->write(data);
}
prefix_ senf::console::Client & senf::console::Client::get(std::ostream & os)
{
return dynamic_cast<detail::NonblockingSocketOStream&>(os)->client();
......@@ -213,9 +219,9 @@ prefix_ void senf::console::detail::ClientReader::enablePrompt()
v_enablePrompt();
}
prefix_ void senf::console::detail::ClientReader::translate(std::string & data)
prefix_ void senf::console::detail::ClientReader::write(std::string const & data)
{
v_translate(data);
v_write(data);
}
prefix_ senf::console::detail::ClientReader::ClientReader(Client & client)
......
......@@ -174,6 +174,7 @@ namespace console {
std::string promptString() const;
DirectoryNode & root() const;
Server::Mode mode() const;
void write(std::string const & data) const;
static Client & get(std::ostream & os);
......@@ -185,7 +186,6 @@ namespace console {
void setInteractive();
void setNoninteractive();
void translate(std::string & data);
size_t handleInput(std::string input, bool incremental = false);
virtual void v_write(senf::log::time_type timestamp, std::string const & stream,
std::string const & area, unsigned level,
......
......@@ -118,7 +118,7 @@ namespace detail {
void disablePrompt();
void enablePrompt();
void translate(std::string & data);
void write(std::string const & data);
protected:
ClientReader(Client & client);
......@@ -126,7 +126,7 @@ namespace detail {
private:
virtual void v_disablePrompt() = 0;
virtual void v_enablePrompt() = 0;
virtual void v_translate(std::string & data) = 0;
virtual void v_write(std::string const & data) = 0;
Client & client_;
};
......@@ -145,7 +145,7 @@ namespace detail {
private:
virtual void v_disablePrompt();
virtual void v_enablePrompt();
virtual void v_translate(std::string & data);
virtual void v_write(std::string const & data);
void clientData(senf::ReadHelper<ClientHandle>::ptr helper);
void showPrompt();
......@@ -169,7 +169,7 @@ namespace detail {
private:
virtual void v_disablePrompt();
virtual void v_enablePrompt();
virtual void v_translate(std::string & data);
virtual void v_write(std::string const & data);
void newData(int event);
......
......@@ -80,7 +80,7 @@ namespace {
delete this;
}
virtual void executeLine(std::string const & text)
void executeLine(std::string const & text)
{
SENF_LOG(("Execute line: " << text));
editor_.show();
......
......@@ -8,7 +8,7 @@ from SCons.Script import *
# c) check for a local SENF, set options accordingly and update that SENF if needed
def SetupForSENF(env):
env.Append( LIBS = [ 'senf', 'readline', 'rt', '$BOOSTREGEXLIB',
env.Append( LIBS = [ 'senf', 'rt', '$BOOSTREGEXLIB',
'$BOOSTIOSTREAMSLIB', '$BOOSTSIGNALSLIB',
'$BOOSTFSLIB' ],
BOOSTREGEXLIB = 'boost_regex',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment