diff --git a/Utils/Console/LineEditor.cc b/Utils/Console/LineEditor.cc index df9d0652fd0a20b6964ccc032ad3b8e51108427e..512ab6ff9afe51fe33562eaa266ef3cb844eb96a 100644 --- a/Utils/Console/LineEditor.cc +++ b/Utils/Console/LineEditor.cc @@ -87,6 +87,7 @@ LineEditorClientReader(Client & client, LineEditorSwitcher & switcher) boost::bind(&term::bindings::complete, _1, senf::membind(&LineEditorClientReader::completePath, this))); + editor_.defineKey(senf::term::KeyParser::Return, &senf::term::bindings::acceptWithRepeat); } prefix_ void senf::console::detail::LineEditorClientReader::v_setupFailed() @@ -140,10 +141,27 @@ senf::console::detail::LineEditorClientReader::deleteCharOrExit(term::LineEditor } prefix_ void senf::console::detail::LineEditorClientReader:: -completePath(term::LineEditor & editor, unsigned b, unsigned e, +completePath(term::LineEditor & editor, unsigned & b, unsigned & e, std::string & prefix, std::vector<std::string> & completions) { - std::string base (editor.text().substr(b,e)); + std::string const & t (editor.text()); + // Search backward from e finding the longest valid path. This does *not* accept all valid + // path's, only those without empedded white-space. However, this is only for completion so + // it's ok. + if (b<e) { + unsigned bb (e-1); + for (;;) { + if (! CommandParser::isWordChar(t[bb]) && t[bb] != '/') { + ++bb; + break; + } + if (bb == b) + break; + --bb; + } + b = bb; + } + std::string base (t.substr(b,e)); CommandParser parser; ParseCommandInfo cmd; try { @@ -204,9 +222,9 @@ completePath(term::LineEditor & editor, unsigned b, unsigned e, } DirectoryNode::ChildrenRange cs (dir->completions(i->value())); + prefix = basePath; for (DirectoryNode::ChildrenRange::iterator j (cs.begin()); j != cs.end(); ++j) - completions.push_back(basePath + j->first - + (j->second->followLink().isDirectory() ? "/" : " ")); + completions.push_back(j->first + (j->second->followLink().isDirectory() ? "/" : " ")); } ///////////////////////////////cc.e//////////////////////////////////////// diff --git a/Utils/Console/LineEditor.hh b/Utils/Console/LineEditor.hh index 6fe3ea33bb1bbc432739c790cdee1ee7fce70f3a..0360cd9c5d26fb7f721d3862c385e0db0f8e418f 100644 --- a/Utils/Console/LineEditor.hh +++ b/Utils/Console/LineEditor.hh @@ -89,11 +89,12 @@ 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<std::string> & completions); + void completePath(term::LineEditor & editor, unsigned & b, unsigned & e, + std::string & prefix, std::vector<std::string> & completions); term::LineEditor editor_; LineEditorSwitcher * switcher_; + std::string default_; }; }}} diff --git a/Utils/Console/Server.cc b/Utils/Console/Server.cc index 913a271089299b93d59a94b383ffe7b5e9049985..66564bb019487f2e28c5bbf1dc60de831f5ccb3c 100644 --- a/Utils/Console/Server.cc +++ b/Utils/Console/Server.cc @@ -284,12 +284,6 @@ prefix_ void senf::console::Client::setNoninteractive() prefix_ std::string::size_type senf::console::Client::handleInput(std::string data, bool incremental) { - if (data.empty() && ! incremental) { - data = lastCommand_; - stream() << "repeat: " << data << std::endl; - } else - lastCommand_ = data; - std::string::size_type n (data.size()); try { diff --git a/Utils/Console/Server.hh b/Utils/Console/Server.hh index 9e9471a4540115817a2d95e0666b21ea28fb20d3..53aa9697eb0c0844cf69dbb99bf79bf96a3b36f0 100644 --- a/Utils/Console/Server.hh +++ b/Utils/Console/Server.hh @@ -234,7 +234,6 @@ namespace console { CommandParser parser_; Executor executor_; std::string name_; - std::string lastCommand_; boost::scoped_ptr<detail::ClientReader> reader_; Server::Mode mode_; std::string backtrace_; diff --git a/Utils/Termlib/Editor.cc b/Utils/Termlib/Editor.cc index 0122904da71794f0b8950ed9f9f80ef82207a455..cceb4d87745a666da2471ff986f86ca1f73bd867 100644 --- a/Utils/Termlib/Editor.cc +++ b/Utils/Termlib/Editor.cc @@ -582,6 +582,15 @@ prefix_ void senf::term::bindings::accept(LineEditor & editor) editor.accept(); } +prefix_ void senf::term::bindings::acceptWithRepeat(LineEditor & editor) +{ + if (editor.text().empty()) { + editor.prevHistory(); + editor.forceRedisplay(); + } + editor.accept(); +} + prefix_ void senf::term::bindings::backwardDeleteChar(LineEditor & editor) { unsigned p (editor.point()); @@ -639,10 +648,18 @@ prefix_ void senf::term::bindings::complete(LineEditor & editor, Completer compl { typedef std::vector<std::string> Completions; + std::string text (editor.text()); Completions completions; - completer(editor, 0, editor.point(), completions); + unsigned b (0); + unsigned e (editor.point()); + std::string prefix; + completer(editor, b, e, prefix, completions); if (completions.empty()) return; + if (e > text.size()) + e = text.size(); + if (b > e) + b = e; // Find common start string of all completions unsigned commonStart (completions[0].size()); @@ -657,17 +674,16 @@ prefix_ void senf::term::bindings::complete(LineEditor & editor, Completer compl } // 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)); + std::string completion (prefix+completions[0].substr(0, commonStart)); bool didComplete (false); - if (text.substr(0, editor.point()) != completion) { - text.erase(0, editor.point()); - text.insert(0, completion); + if (text.substr(b, e) != completion) { + text.erase(b, e); + text.insert(b, completion); didComplete = true; } // Otherwise place cursor directly after the (possibly partial) completion - editor.set(text, commonStart); + editor.set(text, b+prefix.size()+commonStart); if (didComplete || completions.size() == 1) return; @@ -683,14 +699,12 @@ prefix_ void senf::term::bindings::complete(LineEditor & editor, Completer compl 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) { + for (unsigned column (0); column < nColumns && i != completions.end(); ++column, ++i) { 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()); + std::copy(i->begin(), + i->size() > colWidth-2 ? i->begin()+colWidth-2 : i->end(), + entry.begin()); line += entry; - ++i; } editor.auxDisplay(row, line); } diff --git a/Utils/Termlib/Editor.hh b/Utils/Termlib/Editor.hh index 952291faec36e41086b6717e414c9f8bd1d668b1..d7cadde9c1334ec630bd0e5c4e93990d509e6d52 100644 --- a/Utils/Termlib/Editor.hh +++ b/Utils/Termlib/Editor.hh @@ -163,8 +163,8 @@ namespace term { bindings::complete(): \code - void myCompleter(senf::term::LineEditor & editor, unsigned b, unsigned e, - std::vector<std::string> & completions) + void myCompleter(senf::term::LineEditor & editor, unsigned & b, unsigned & e, + std::string & prefix, std::vector<std::string> & completions) { // Get text to complete std::string text (editor.text().substr(b, e-b)); @@ -180,6 +180,19 @@ namespace term { When \c myCompleter is a class member, use senf::membind() and pass this instead of \c &myCompleter to \c boost::bind() and thus to senf::term::bindings::complete. + + The completion protocol is as follows: When completion is desired, the completer function is + called. \a b and \a e are set to 0 and <tt>editor.point()</tt> respectively. \a prefix and + \a completions are empty. + + \li the completer may restrict the to-be-completed string to any subrange by changing \a b + and \a e accordingly. + \li If there is an initial substring which applies to \e all completions but should not be + listed in the list of completions, assign this value to \a prefix. + \li Add all possible completions to the \a completions vector not including the \a prefix. + \li The completion result is taken from the size of the \a completions vector \e only: If + this vector is empty, completion failed (even if \a prefix is set), a single entry in \a + completions (even if it is the empty string) signals a unique completion. \section editor_auxarea The aux display area @@ -325,6 +338,7 @@ namespace bindings { void forwardChar (LineEditor & editor); ///< Move one char forward void backwardChar (LineEditor & editor); ///< Move one char backwards void accept (LineEditor & editor); ///< Accept input line + void acceptWithRepeat (LineEditor & editor); ///< Accept, possibly repeat last history entry void backwardDeleteChar (LineEditor & editor); ///< Delete char before cursor void deleteChar (LineEditor & editor); ///< Delete char at cursor void beginningOfLine (LineEditor & editor); ///< Move to beginning of line @@ -335,13 +349,14 @@ namespace bindings { void nextHistory (LineEditor & editor); ///< Move to next history entry void clearScreen (LineEditor & editor); ///< Clear screen and redisplay editor - typedef boost::function<void (LineEditor &, unsigned b, unsigned e, std::vector<std::string> &)> Completer; + typedef boost::function<void (LineEditor &, unsigned & b, unsigned & e, + std::string & prefix, std::vector<std::string> &)> Completer; void complete (LineEditor & editor, Completer completer); ///< Complete text at cursor /**< This function calls \a completer to find the list of possible completions for the text between \a b and \a e (as passed to the completer). The completer must add - all possible completions to the completions vector. + all possible completions to the \a completions vector. \see \ref editor_complete */