// Custom includes
#include "../Logger/SenfLog.hh"
+#include "../../Utils/range.hh"
//#include "LineEditor.mpp"
#define prefix_
editor_.prompt(promptString());
editor_.defineKey(senf::term::KeyParser::Ctrl('D'),
senf::membind(&LineEditorClientReader::deleteCharOrExit, this));
+ editor_.defineKey(senf::term::KeyParser::Tab,
+ boost::bind(&term::bindings::complete,
+ _1,
+ senf::membind(&LineEditorClientReader::completePath, this)));
}
prefix_ void senf::console::detail::LineEditorClientReader::v_setupFailed()
term::bindings::deleteChar(editor);
}
+prefix_ void senf::console::detail::LineEditorClientReader::
+completePath(term::LineEditor & editor, unsigned b, unsigned e,
+ std::vector<std::string> & completions)
+{
+ std::string base (editor.text().substr(b,e));
+ CommandParser parser;
+ ParseCommandInfo cmd;
+ try {
+ parser.parsePath(base, cmd);
+ }
+ catch (CommandParser::ParserErrorException & ex) {
+ return;
+ }
+
+ ParseCommandInfo::TokensRange path (cmd.commandPath());
+ if (path.empty()) {
+ DirectoryNode::ChildrenRange cs (client().cwd().children());
+ for (DirectoryNode::ChildrenRange::iterator i (cs.begin()); i != cs.end(); ++i) {
+ completions.push_back(i->first);
+ if (i->second->isDirectory())
+ completions.push_back(i->first + "/");
+ }
+ return;
+ }
+
+ ParseCommandInfo::TokensRange::const_iterator i (path.begin());
+ ParseCommandInfo::TokensRange::const_iterator const i_end (boost::prior(path.end()));
+ DirectoryNode * dir (& client().cwd());
+ std::string basePath;
+ for (; i != i_end; ++i)
+ if (*i == NoneToken()) {
+ if (i == path.begin()) {
+ dir = & client().root();
+ basePath = "/";
+ }
+ }
+ else if (*i == WordToken("..")) {
+ DirectoryNode * parent (dir->parent().get());
+ if (parent) dir = parent;
+ basePath += "../";
+ }
+ else if (*i == WordToken("."))
+ ;
+ else {
+ if (dir->hasChild(i->value())) {
+ dir = & dir->getDirectory(i->value());
+ basePath += i->value() + "/";
+ } else {
+ DirectoryNode::ChildrenRange cs (dir->completions(i->value()));
+ if (has_one_elt(cs)) {
+ GenericNode * node (cs.begin()->second.get());
+ if (node->isLink())
+ node = & static_cast<LinkNode*>(node)->follow();
+ if (!node->isDirectory())
+ return;
+ dir = static_cast<DirectoryNode*>(node);
+ basePath += cs.begin()->first + "/";
+ }
+ else
+ return;
+ }
+ }
+
+ DirectoryNode::ChildrenRange cs (dir->completions(i->value()));
+ for (DirectoryNode::ChildrenRange::iterator j (cs.begin()); j != cs.end(); ++j) {
+ completions.push_back(basePath + j->first);
+ if (j->second->isDirectory())
+ completions.push_back(basePath + j->first + "/");
+ }
+}
+
///////////////////////////////cc.e////////////////////////////////////////
#undef prefix_
//#include "LineEditor.mpp"
// Editor callbacks
void executeLine(std::string const & text);
void deleteCharOrExit(term::LineEditor & editor);
+ void completePath(term::LineEditor & editor, unsigned b, unsigned e,
+ std::vector<std::string> & completions);
term::LineEditor editor_;
LineEditorSwitcher * switcher_;
}
}
+prefix_ void senf::console::CommandParser::parsePath(std::string const & path,
+ ParseCommandInfo & info)
+{
+ typedef boost::spirit::position_iterator<std::string::const_iterator> PositionIterator;
+ PositionIterator b (path.begin(), path.end(), std::string("<unknown>"));
+ PositionIterator e (path.end(), path.end(), std::string("<unknown>"));
+ detail::ParseDispatcher::BindInfo bind (impl().dispatcher, info);
+ boost::spirit::parse_info<PositionIterator> result;
+ try {
+ result = boost::spirit::parse( b, e,
+ impl().grammar.use_parser<Impl::Grammar::PathParser>(),
+ impl().grammar.use_parser<Impl::Grammar::SkipParser>() );
+ }
+ catch (boost::spirit::parser_error<Impl::Grammar::Errors, PositionIterator> & ex) {
+ throwParserError(ex);
+ }
+ if (! result.full) {
+ boost::spirit::file_position pos (result.stop.get_position());
+ throw ParserErrorException("path expected")
+ << "\nat " << pos.file << ":" << pos.line << ":" << pos.column;
+ }
+}
+
struct senf::console::CommandParser::SetIncremental
{
SetIncremental(CommandParser & parser) : parser_ (parser) {
of the command). The argument tokens are written into
\a info. */
+ void parsePath(std::string const & path, ParseCommandInfo & info);
+ ///< Parse \a path
+ /**< parsePath() parses the string \a path as an arbitrary
+ command path. The result is written into \a info. */
+
std::string::size_type parseIncremental(std::string const & commands, Callback cb);
///< Incremental parse
/**< An incremental parse will parse all complete statements
// Custom includes
#include <vector>
+#include "../../config.hh"
#include <boost/spirit.hpp>
#include <boost/spirit/utility/grammar_def.hpp>
#include <boost/spirit/dynamic.hpp>
///////////////////////////////////////////////////////////////////////////
// Start rules
- enum { CommandParser, SkipParser, ArgumentsParser };
+ enum { CommandParser, SkipParser, ArgumentsParser, PathParser };
///////////////////////////////////////////////////////////////////////////
// The parse context (variables needed while parsing)
struct definition
: public boost::spirit::grammar_def< boost::spirit::rule<Scanner>,
boost::spirit::rule<Scanner>,
+ boost::spirit::rule<Scanner>,
boost::spirit::rule<Scanner> >
{
boost::spirit::rule<Scanner> command, path, argument, word, string, hexstring, token,
punctuation, hexbyte, balanced_tokens, simple_argument, complex_argument, builtin,
skip, statement, relpath, abspath, arguments, group_start, group_close,
- statement_end;
+ statement_end, opt_path;
boost::spirit::chset<> special_p, punctuation_p, space_p, invalid_p, word_p;
boost::spirit::distinct_parser<> keyword_p;
[ token_ = construct_<Token>(Token::HexString,
str_) ]
;
+
+ opt_path
+ = ! path [ bind(&PD::beginCommand)(d_, path_) ]
+ [ bind(&PD::endCommand)(d_) ]
+ ;
path // Returns value in context.path
= eps_p [ clear(path_) ]
start_parsers(
command, // CommandParser
skip, // SkipParser
- arguments // ArgumentsParser
+ arguments, // ArgumentsParser
+ opt_path // PathParser
);
BOOST_SPIRIT_DEBUG_TRACE_RULE(command,1);
return server_.root();
}
+prefix_ senf::console::DirectoryNode & senf::console::Client::cwd()
+ const
+{
+ return executor_.cwd();
+}
+
prefix_ senf::console::Server::Mode senf::console::Client::mode()
const
{
std::ostream & stream();
std::string promptString() const;
DirectoryNode & root() const;
+ DirectoryNode & cwd() const;
Server::Mode mode() const;
void write(std::string const & data) const;
keyTimeout_ (senf::ClockService::milliseconds(DEFAULT_KEY_TIMEOUT_MS)),
timer_ ("senf::term::BaseEditor::keySequenceTimeout",
senf::membind(&BaseEditor::keySequenceTimeout, this)),
- column_ (0u)
+ column_ (0u), displayHeight_ (1u), line_ (0u)
{
terminal_->setCallbacks(*this);
}
prefix_ void senf::term::BaseEditor::newline()
{
- write("\r\n");
+ reset();
+ write("\n");
write(tifo_.getString(Terminfo::properties::ClrEol));
column_ = 0;
}
write(tifo_.getString(Terminfo::properties::ClearScreen));
}
+prefix_ void senf::term::BaseEditor::toLine(unsigned l)
+{
+ if (l >= height())
+ l = height() - 1;
+ unsigned ll (l);
+ if (ll >= displayHeight_)
+ ll = displayHeight_-1;
+ if (ll > line_) {
+ if (tifo_.hasProperty(Terminfo::properties::ParmDownCursor)) {
+ write(tifo_.formatString(Terminfo::properties::ParmDownCursor, ll - line_));
+ line_ = ll;
+ }
+ else {
+ char const * cud1 (tifo_.getString(Terminfo::properties::CursorDown));
+ while (ll > line_) {
+ write(cud1);
+ ++line_;
+ }
+ }
+ }
+ else if (ll < line_) {
+ if (tifo_.hasProperty(Terminfo::properties::ParmUpCursor)) {
+ write(tifo_.formatString(Terminfo::properties::ParmUpCursor, line_ - ll));
+ line_ = ll;
+ }
+ else {
+ char const * cuu1 (tifo_.getString(Terminfo::properties::CursorUp));
+ while (ll < line_) {
+ write(cuu1);
+ --line_;
+ }
+ }
+ }
+ while (line_ < l) {
+ write("\n");
+ write(tifo_.getString(Terminfo::properties::ClrEol));
+ ++displayHeight_;
+ ++line_;
+ }
+ write('\r');
+ column_ = 0;
+}
+
+prefix_ void senf::term::BaseEditor::reset()
+{
+ for (unsigned i (1); i < displayHeight_; ++i) {
+ toLine(i);
+ clearLine();
+ }
+ toLine(0);
+ displayHeight_ = 1;
+}
+
prefix_ unsigned senf::term::BaseEditor::currentColumn()
const
{
return column_;
}
+prefix_ unsigned senf::term::BaseEditor::currentLine()
+ const
+{
+ return line_;
+}
+
prefix_ bool senf::term::BaseEditor::cb_init()
{
try {
return terminal_->width();
}
+prefix_ unsigned senf::term::BaseEditor::height()
+{
+ return terminal_->height();
+}
+
prefix_ void senf::term::BaseEditor::write(char ch)
{
terminal_->write(ch);
}
}
+prefix_ void senf::term::LineEditor::auxDisplay(int line, std::string const & text)
+{
+ toLine(line+1);
+ clearLine();
+ put(text);
+}
+
+prefix_ unsigned senf::term::LineEditor::maxAuxDisplayHeight()
+{
+ return height()-1;
+}
+
+prefix_ void senf::term::LineEditor::clearAuxDisplay()
+{
+ reset();
+}
+
prefix_ std::string const & senf::term::LineEditor::text()
{
return text_;
prefix_ void senf::term::LineEditor::cb_windowSizeChanged()
{
BaseEditor::cb_windowSizeChanged();
+ clearAuxDisplay();
prompt(prompt_);
gotoChar(point_);
forceRedisplay();
{
if (! enabled_)
return;
+ clearAuxDisplay();
lastKey_ = key;
KeyMap::iterator i (bindings_.find(key));
if (i != bindings_.end())
i->second(*this);
else if (key >= ' ' && key < 256)
insert(char(key));
+ if (currentLine() != 0)
+ toLine(0);
if (redisplayNeeded_)
forceRedisplay();
+ else
+ toColumn(point_ - displayPos_ + promptWidth_ + 1);
}
///////////////////////////////////////////////////////////////////////////
editor.forceRedisplay();
}
+prefix_ void senf::term::bindings::complete(LineEditor & editor, Completer completer)
+{
+ typedef std::vector<std::string> Completions;
+
+ Completions completions;
+ completer(editor, 0, editor.point(), completions);
+ if (completions.empty())
+ return;
+
+ // Find common start string of all completions
+ unsigned commonStart (completions[0].size());
+ unsigned maxLen (commonStart);
+ for (Completions::const_iterator i (boost::next(completions.begin()));
+ i != completions.end(); ++i) {
+ if (i->size() > maxLen)
+ maxLen = i->size();
+ unsigned n (0u);
+ for (; n < commonStart && n < i->size() && completions[0][n] == (*i)[n]; ++n) ;
+ commonStart = n;
+ }
+
+ // Replace to-be-completed string with the common start string shared by all completions
+ std::string text (editor.text());
+ std::string completion (completions[0].substr(0, commonStart));
+ bool didComplete (false);
+ if (text.substr(0, editor.point()) != completion) {
+ text.erase(0, editor.point());
+ text.insert(0, completion);
+ didComplete = true;
+ }
+
+ // If completion is already unique, make sure completion is followed by a space and place cursor
+ // after that space
+ if (completions.size() == 1u) {
+ if (text.size() <= commonStart || text[commonStart] != ' ')
+ text.insert(commonStart, " ");
+ editor.set(text, commonStart + 1);
+ return;
+ }
+
+ // Otherwise place cursor directly after the partial completion
+ editor.set(text, commonStart);
+ if (didComplete)
+ return;
+
+ // Text was not changed, show list of possible completions
+ unsigned colWidth (maxLen+2);
+ unsigned nColumns ((editor.width()-1) / colWidth);
+ if (nColumns < 1) nColumns = 1;
+ unsigned nRows ((completions.size()+nColumns-1) / nColumns);
+ if (nRows > editor.maxAuxDisplayHeight()) {
+ editor.auxDisplay(0, "(too many completions)");
+ return;
+ }
+ Completions::iterator i (completions.begin());
+ for (unsigned row (0); row < nRows; ++row) {
+ std::string line;
+ for (unsigned column (0); column < nColumns && i != completions.end(); ++column) {
+ std::string entry (colWidth, ' ');
+ if (i->size() > colWidth-2)
+ std::copy(i->begin(), i->begin()+colWidth-2, entry.begin());
+ else
+ std::copy(i->begin(), i->end(), entry.begin());
+ line += entry;
+ ++i;
+ }
+ editor.auxDisplay(row, line);
+ }
+}
+
///////////////////////////////cc.e////////////////////////////////////////
#undef prefix_
//#include "Editor.mpp"
void newline(); ///< Move to beginning of a new, empty line
void toColumn(unsigned c); ///< Move cursor to column \p c
void put(char ch); ///< Write \p ch at current column
- void put(std::string const & text);
+ void put(std::string const & text); ///< Write \a text starting at current column
void clearLine(); ///< Clear current line and move cursor to first column
void setBold(); ///< Set bold char display
void setNormal(); ///< Set normal char display
void maybeClrScr(); ///< Clear screen if possible
+ void toLine(unsigned l); ///< Move to relative display line \a l
+ void reset(); ///< Reset display area to single line
+
unsigned currentColumn() const; ///< Return number of current column
- unsigned width();
+ unsigned currentLine() const; ///< Return number of current relative line
+ unsigned width(); ///< Return current screen width
+ unsigned height(); ///< Return current screen height
protected:
virtual bool cb_init();
ClockService::clock_type keyTimeout_;
scheduler::TimerEvent timer_;
unsigned column_;
+ unsigned displayHeight_;
+ unsigned line_;
};
class LineEditor
void prevHistory();
void nextHistory();
+ // Aux Display
+ void auxDisplay(int line, std::string const & text);
+ unsigned maxAuxDisplayHeight();
+ void clearAuxDisplay();
+
// Get information
std::string const & text();
unsigned point();
void nextHistory (LineEditor & editor);
void clearScreen (LineEditor & editor);
+ typedef boost::function<void (LineEditor &, unsigned b, unsigned e, std::vector<std::string> &)> Completer;
+ void complete (LineEditor & editor, Completer completer);
+
}
}}
# define SENF_CONSOLE_MAX_COMMAND_ARITY 6
# endif
#
+# ifndef PHOENIX_LIMIT
+# define PHOENIX_LIMIT 6
+# endif
+#
# ///////////////////////////////hh.e////////////////////////////////////////
# endif