From: g0dil Date: Thu, 8 Jan 2009 20:14:30 +0000 (+0000) Subject: Utils/Termlib: Implement LineEditor auxiliary display support X-Git-Url: http://g0dil.de/git?a=commitdiff_plain;h=0461eef93912cb9d454d726b4a7b4ccf50ed31bd;p=senf.git Utils/Termlib: Implement LineEditor auxiliary display support Utils/Termlib: Implement generic completion support Utils/Console: Provide access to path parsing in CommandParser Utils/Console: Implement command name completion in interactive console git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@1048 270642c3-0616-0410-b53a-bc976706d245 --- diff --git a/Utils/Console/LineEditor.cc b/Utils/Console/LineEditor.cc index ab2f548..95f1153 100644 --- a/Utils/Console/LineEditor.cc +++ b/Utils/Console/LineEditor.cc @@ -28,6 +28,7 @@ // Custom includes #include "../Logger/SenfLog.hh" +#include "../../Utils/range.hh" //#include "LineEditor.mpp" #define prefix_ @@ -76,6 +77,10 @@ LineEditorClientReader(Client & client, LineEditorSwitcher & switcher) 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() @@ -122,6 +127,77 @@ senf::console::detail::LineEditorClientReader::deleteCharOrExit(term::LineEditor term::bindings::deleteChar(editor); } +prefix_ void senf::console::detail::LineEditorClientReader:: +completePath(term::LineEditor & editor, unsigned b, unsigned e, + std::vector & 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(node)->follow(); + if (!node->isDirectory()) + return; + dir = static_cast(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" diff --git a/Utils/Console/LineEditor.hh b/Utils/Console/LineEditor.hh index 840ce5a..39e13b6 100644 --- a/Utils/Console/LineEditor.hh +++ b/Utils/Console/LineEditor.hh @@ -74,6 +74,8 @@ namespace detail { // Editor callbacks void executeLine(std::string const & text); void deleteCharOrExit(term::LineEditor & editor); + void completePath(term::LineEditor & editor, unsigned b, unsigned e, + std::vector & completions); term::LineEditor editor_; LineEditorSwitcher * switcher_; diff --git a/Utils/Console/Parse.cc b/Utils/Console/Parse.cc index dc1dbce..e4e1e66 100644 --- a/Utils/Console/Parse.cc +++ b/Utils/Console/Parse.cc @@ -349,6 +349,29 @@ prefix_ void senf::console::CommandParser::parseArguments(std::string const & ar } } +prefix_ void senf::console::CommandParser::parsePath(std::string const & path, + ParseCommandInfo & info) +{ + typedef boost::spirit::position_iterator PositionIterator; + PositionIterator b (path.begin(), path.end(), std::string("")); + PositionIterator e (path.end(), path.end(), std::string("")); + detail::ParseDispatcher::BindInfo bind (impl().dispatcher, info); + boost::spirit::parse_info result; + try { + result = boost::spirit::parse( b, e, + impl().grammar.use_parser(), + impl().grammar.use_parser() ); + } + catch (boost::spirit::parser_error & 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) { diff --git a/Utils/Console/Parse.hh b/Utils/Console/Parse.hh index cfa871b..5e8b0ac 100644 --- a/Utils/Console/Parse.hh +++ b/Utils/Console/Parse.hh @@ -599,6 +599,11 @@ namespace console { 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 diff --git a/Utils/Console/Parse.ih b/Utils/Console/Parse.ih index afb4b78..8d3d35d 100644 --- a/Utils/Console/Parse.ih +++ b/Utils/Console/Parse.ih @@ -28,6 +28,7 @@ // Custom includes #include +#include "../../config.hh" #include #include #include @@ -51,7 +52,7 @@ namespace detail { /////////////////////////////////////////////////////////////////////////// // Start rules - enum { CommandParser, SkipParser, ArgumentsParser }; + enum { CommandParser, SkipParser, ArgumentsParser, PathParser }; /////////////////////////////////////////////////////////////////////////// // The parse context (variables needed while parsing) @@ -97,12 +98,13 @@ namespace detail { struct definition : public boost::spirit::grammar_def< boost::spirit::rule, boost::spirit::rule, + boost::spirit::rule, boost::spirit::rule > { boost::spirit::rule 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; @@ -260,6 +262,11 @@ namespace detail { [ token_ = construct_(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_) ] @@ -349,7 +356,8 @@ namespace detail { start_parsers( command, // CommandParser skip, // SkipParser - arguments // ArgumentsParser + arguments, // ArgumentsParser + opt_path // PathParser ); BOOST_SPIRIT_DEBUG_TRACE_RULE(command,1); diff --git a/Utils/Console/Server.cci b/Utils/Console/Server.cci index 9f3f4e6..fb6e82d 100644 --- a/Utils/Console/Server.cci +++ b/Utils/Console/Server.cci @@ -139,6 +139,12 @@ prefix_ senf::console::DirectoryNode & senf::console::Client::root() 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 { diff --git a/Utils/Console/Server.hh b/Utils/Console/Server.hh index 4fac812..431221b 100644 --- a/Utils/Console/Server.hh +++ b/Utils/Console/Server.hh @@ -173,6 +173,7 @@ namespace console { std::ostream & stream(); std::string promptString() const; DirectoryNode & root() const; + DirectoryNode & cwd() const; Server::Mode mode() const; void write(std::string const & data) const; diff --git a/Utils/Termlib/Editor.cc b/Utils/Termlib/Editor.cc index d8bd15e..f268efb 100644 --- a/Utils/Termlib/Editor.cc +++ b/Utils/Termlib/Editor.cc @@ -39,14 +39,15 @@ prefix_ senf::term::BaseEditor::BaseEditor(AbstractTerminal & terminal) 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; } @@ -130,12 +131,71 @@ prefix_ void senf::term::BaseEditor::maybeClrScr() 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 { @@ -197,6 +257,11 @@ prefix_ unsigned senf::term::BaseEditor::width() return terminal_->width(); } +prefix_ unsigned senf::term::BaseEditor::height() +{ + return terminal_->height(); +} + prefix_ void senf::term::BaseEditor::write(char ch) { terminal_->write(ch); @@ -394,6 +459,23 @@ prefix_ void senf::term::LineEditor::nextHistory() } } +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_; @@ -436,6 +518,7 @@ prefix_ bool senf::term::LineEditor::cb_init() prefix_ void senf::term::LineEditor::cb_windowSizeChanged() { BaseEditor::cb_windowSizeChanged(); + clearAuxDisplay(); prompt(prompt_); gotoChar(point_); forceRedisplay(); @@ -445,14 +528,19 @@ prefix_ void senf::term::LineEditor::v_keyReceived(keycode_t key) { 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); } /////////////////////////////////////////////////////////////////////////// @@ -534,6 +622,76 @@ prefix_ void senf::term::bindings::clearScreen(LineEditor & editor) editor.forceRedisplay(); } +prefix_ void senf::term::bindings::complete(LineEditor & editor, Completer completer) +{ + typedef std::vector 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" diff --git a/Utils/Termlib/Editor.hh b/Utils/Termlib/Editor.hh index d654a35..d2d091c 100644 --- a/Utils/Termlib/Editor.hh +++ b/Utils/Termlib/Editor.hh @@ -54,14 +54,19 @@ namespace term { 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(); @@ -85,6 +90,8 @@ namespace term { ClockService::clock_type keyTimeout_; scheduler::TimerEvent timer_; unsigned column_; + unsigned displayHeight_; + unsigned line_; }; class LineEditor @@ -130,6 +137,11 @@ namespace term { 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(); @@ -179,6 +191,9 @@ namespace bindings { void nextHistory (LineEditor & editor); void clearScreen (LineEditor & editor); + typedef boost::function &)> Completer; + void complete (LineEditor & editor, Completer completer); + } }} diff --git a/config.hh b/config.hh index a746626..12e24b5 100644 --- a/config.hh +++ b/config.hh @@ -93,6 +93,10 @@ namespace config { # define SENF_CONSOLE_MAX_COMMAND_ARITY 6 # endif # +# ifndef PHOENIX_LIMIT +# define PHOENIX_LIMIT 6 +# endif +# # ///////////////////////////////hh.e//////////////////////////////////////// # endif