diff options
author | Andrzej Rybczak <electricityispower@gmail.com> | 2012-09-20 04:32:51 +0200 |
---|---|---|
committer | Andrzej Rybczak <electricityispower@gmail.com> | 2012-09-20 04:32:51 +0200 |
commit | 07fc58015ea6e322b13968e782f96cde63a64f3a (patch) | |
tree | 8f65fab4f350538b67e2f43f70973580b04ea3d2 | |
parent | ba0a47668a1939414281fa74c3beb4202318815c (diff) |
bindings: add support for defining and executing commands
-rw-r--r-- | doc/bindings | 21 | ||||
-rw-r--r-- | src/actions.cpp | 22 | ||||
-rw-r--r-- | src/actions.h | 10 | ||||
-rw-r--r-- | src/bindings.cpp | 98 | ||||
-rw-r--r-- | src/bindings.h | 43 | ||||
-rw-r--r-- | src/help.cpp | 1 | ||||
-rw-r--r-- | src/ncmpcpp.cpp | 17 | ||||
-rw-r--r-- | src/statusbar.cpp | 21 | ||||
-rw-r--r-- | src/statusbar.h | 12 | ||||
-rw-r--r-- | src/window.cpp | 5 | ||||
-rw-r--r-- | src/window.h | 2 |
11 files changed, 208 insertions, 44 deletions
diff --git a/doc/bindings b/doc/bindings index 631b66ae..ae443964 100644 --- a/doc/bindings +++ b/doc/bindings @@ -95,6 +95,24 @@ ## selected_items_adder, server_info, song_info, ## sort_playlist_dialog, tiny_tag_editor. ## +## 5) In addition to binding to a key, you can also bind actions +## or chains of actions to a command. If it comes to commands, +## syntax is very similar to defining keys. Here goes example +## definition of a command: +## +## def_command "quit" [deferred] +## stop +## quit +## +## If you execute the above command (which can be done by +## invoking action execute_command, typing 'quit' and pressing +## enter), ncmpcpp will stop the player and then quit. Note the +## presence of word 'deferred' enclosed in square brackets. It +## tells ncmpcpp to wait for confirmation (ie. pressing enter) +## after you typed quit. Instead of 'deferred', 'immediate' +## could be used. Then ncmpcpp will not wait for confirmation +## (enter) and will execute the command the moment it sees it. +## ## Note: Both 'backspace' and 'backspace_2' are used because some ## terminals interpret backspace using keycode of 'backspace' ## and some the one of 'backspace_2'. You can get away with @@ -191,6 +209,9 @@ #def_key "-" # volume_down # +#def_key ":" +# execute_command +# #def_key "tab" # next_screen # diff --git a/src/actions.cpp b/src/actions.cpp index 739630f6..16e5178e 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -972,6 +972,27 @@ void Stop::Run() Mpd.Stop(); } +void ExecuteCommand::Run() +{ + using Global::wFooter; + Statusbar::lock(); + Statusbar::put() << NC::fmtBold << ":" << NC::fmtBoldEnd; + wFooter->setGetStringHelper(Statusbar::Helpers::TryExecuteImmediateCommand()); + std::string name = wFooter->getString(); + wFooter->setGetStringHelper(Statusbar::Helpers::getString); + Statusbar::unlock(); + if (name.empty()) + return; + auto cmd = Bindings.findCommand(name); + if (cmd) + { + Statusbar::msg(1, "Executing %s...", name.c_str()); + cmd->binding().execute(); + } + else + Statusbar::msg("No command named \"%s\"", name.c_str()); +} + bool MoveSortOrderUp::canBeRun() const { return myScreen == mySortPlaylistDialog; @@ -2576,6 +2597,7 @@ void populateActions() insertAction(new NextSong()); insertAction(new Pause()); insertAction(new Stop()); + insertAction(new ExecuteCommand()); insertAction(new SavePlaylist()); insertAction(new MoveSortOrderUp()); insertAction(new MoveSortOrderDown()); diff --git a/src/actions.h b/src/actions.h index 6f3ce64f..92b1ad8d 100644 --- a/src/actions.h +++ b/src/actions.h @@ -31,7 +31,7 @@ enum ActionType aDummy, aMouseEvent, aScrollUp, aScrollDown, aScrollUpArtist, aScrollUpAlbum, aScrollDownArtist, aScrollDownAlbum, aPageUp, aPageDown, aMoveHome, aMoveEnd, aToggleInterface, aJumpToParentDirectory, aPressEnter, aPressSpace, aPreviousColumn, aNextColumn, aMasterScreen, aSlaveScreen, aVolumeUp, - aVolumeDown, aDeletePlaylistItems, aDeleteStoredPlaylist, aDeleteBrowserItems, aReplaySong, aPrevious, aNext, aPause, aStop, aSavePlaylist, + aVolumeDown, aDeletePlaylistItems, aDeleteStoredPlaylist, aDeleteBrowserItems, aReplaySong, aPrevious, aNext, aPause, aStop, aExecuteCommand, aSavePlaylist, aMoveSortOrderUp, aMoveSortOrderDown, aMoveSelectedItemsUp, aMoveSelectedItemsDown, aMoveSelectedItemsTo, aAdd, aSeekForward, aSeekBackward, aToggleDisplayMode, aToggleSeparatorsBetweenAlbums, aToggleLyricsFetcher, aToggleFetchingLyricsInBackground, aTogglePlayingSongCentering, aUpdateDatabase, @@ -361,6 +361,14 @@ protected: virtual void Run(); }; +struct ExecuteCommand : public Action +{ + ExecuteCommand() : Action(aExecuteCommand, "execute_command") { } + +protected: + virtual void Run(); +}; + struct SavePlaylist : public Action { SavePlaylist() : Action(aSavePlaylist, "save_playlist") { } diff --git a/src/bindings.cpp b/src/bindings.cpp index 78ad69cb..1f089d10 100644 --- a/src/bindings.cpp +++ b/src/bindings.cpp @@ -189,53 +189,110 @@ Key Key::read(NC::Window &w) bool BindingsConfiguration::read(const std::string &file) { + enum class InProgress { None, Command, Key }; + bool result = true; std::ifstream f(file); if (!f.is_open()) return result; + // shared variables + InProgress in_progress = InProgress::None; size_t line_no = 0; - bool key_def_in_progress = false; - Key key = Key::noOp; + std::string line; Binding::ActionChain actions; - std::string line, strkey; + + // def_key specific variables + Key key = Key::noOp; + std::string strkey; + + // def_command specific variables + bool cmd_immediate = false; + std::string cmd_name; auto error = [&]() -> std::ostream & { - std::cerr << file << ":" << line_no << ": "; - key_def_in_progress = false; + std::cerr << file << ":" << line_no << ": error: "; + in_progress = InProgress::None; result = false; return std::cerr; }; - auto bind_key_def = [&]() -> bool { - if (!actions.empty()) + auto bind_in_progress = [&]() -> bool { + if (in_progress == InProgress::Command) { - bind(key, actions); - actions.clear(); - return true; + if (!actions.empty()) + { + m_commands.insert(std::make_pair(cmd_name, Command(actions, cmd_immediate))); + actions.clear(); + return true; + } + else + { + error() << "definition of command '" << cmd_name << "' cannot be empty\n"; + return false; + } } - else + else if (in_progress == InProgress::Key) { - error() << "definition of key '" << strkey << "' cannot be empty.\n"; - return false; + if (!actions.empty()) + { + bind(key, actions); + actions.clear(); + return true; + } + else + { + error() << "definition of key '" << strkey << "' cannot be empty\n"; + return false; + } } + return true; }; + const char def_command[] = "def_command"; + const char def_key[] = "def_key"; + while (!f.eof() && ++line_no) { getline(f, line); if (line.empty() || line[0] == '#') continue; - if (!line.compare(0, 7, "def_key")) // beginning of key definition + // beginning of command definition + if (!line.compare(0, const_strlen(def_command), def_command)) { - if (key_def_in_progress) + if (!bind_in_progress()) + break; + in_progress = InProgress::Command; + cmd_name = getEnclosedString(line, '"', '"', 0); + if (cmd_name.empty()) + { + error() << "command must have non-empty name\n"; + break; + } + if (m_commands.find(cmd_name) != m_commands.end()) + { + error() << "redefinition of command '" << cmd_name << "'\n"; + break; + } + std::string cmd_type = getEnclosedString(line, '[', ']', 0); + if (cmd_type == "immediate") + cmd_immediate = true; + else if (cmd_type == "deferred") + cmd_immediate = false; + else { - if (!bind_key_def()) - break; + error() << "invalid type of command: '" << cmd_type << "'\n"; + break; } - key_def_in_progress = true; + } + // beginning of key definition + else if (!line.compare(0, const_strlen(def_key), def_key)) + { + if (!bind_in_progress()) + break; + in_progress = InProgress::Key; strkey = getEnclosedString(line, '"', '"', 0); key = stringToKey(strkey); if (key == Key::noOp) @@ -262,8 +319,7 @@ bool BindingsConfiguration::read(const std::string &file) break; } } - if (key_def_in_progress) - bind_key_def(); + bind_in_progress(); f.close(); return result; } @@ -318,6 +374,8 @@ void BindingsConfiguration::generateDefaults() } if (notBound(k = stringToKey("-"))) bind(k, aVolumeDown); + if (notBound(k = stringToKey(":"))) + bind(k, aExecuteCommand); if (notBound(k = stringToKey("tab"))) bind(k, aNextScreen); if (notBound(k = stringToKey("shift_tab"))) diff --git a/src/bindings.h b/src/bindings.h index c6ee340d..c62784bb 100644 --- a/src/bindings.h +++ b/src/bindings.h @@ -22,6 +22,7 @@ #define _BINDINGS_H #include <cassert> +#include <unordered_map> #include "actions.h" #include "macro_utilities.h" @@ -85,6 +86,20 @@ struct Binding } } + bool execute() const { + bool result = false; + if (m_is_single) { + assert(m_action); + result = m_action->Execute(); + } else { + for (auto it = m_chain->begin(); it != m_chain->end(); ++it) + if (!(*it)->Execute()) + break; + result = true; + } + return result; + } + bool isSingle() const { return m_is_single; } @@ -105,16 +120,41 @@ private: }; }; +/// Represents executable command +struct Command +{ + Command(const Binding &binding_, bool immediate_) + : m_binding(binding_), m_immediate(immediate_) { } + + const Binding &binding() const { return m_binding; } + bool immediate() const { return m_immediate; } + +private: + Binding m_binding; + bool m_immediate; +}; + /// Keybindings configuration -struct BindingsConfiguration +class BindingsConfiguration { + typedef std::unordered_map<std::string, Command> CommandsSet; typedef std::multimap<Key, Binding> BindingsMap; + +public: typedef BindingsMap::iterator BindingIterator; typedef BindingsMap::const_iterator ConstBindingIterator; bool read(const std::string &file); void generateDefaults(); + const Command *findCommand(const std::string &name) { + const Command *ptr = 0; + auto it = m_commands.find(name); + if (it != m_commands.end()) + ptr = &it->second; + return ptr; + } + std::pair<BindingIterator, BindingIterator> get(const Key &k) { return m_bindings.equal_range(k); } @@ -132,6 +172,7 @@ private: } BindingsMap m_bindings; + CommandsSet m_commands; }; extern BindingsConfiguration Bindings; diff --git a/src/help.cpp b/src/help.cpp index fe68ad4d..9685da71 100644 --- a/src/help.cpp +++ b/src/help.cpp @@ -238,6 +238,7 @@ void Help::GetKeybindings() KeyDesc(aSetCrossfade, "Set crossfade"); KeyDesc(aUpdateDatabase, "Start music database update"); w << '\n'; + KeyDesc(aExecuteCommand, "Execute command"); KeyDesc(aApplyFilter, "Apply filter"); KeyDesc(aFindItemForward, "Find item forward"); KeyDesc(aFindItemBackward, "Find item backward"); diff --git a/src/ncmpcpp.cpp b/src/ncmpcpp.cpp index c297f45f..92c5e1f4 100644 --- a/src/ncmpcpp.cpp +++ b/src/ncmpcpp.cpp @@ -237,7 +237,6 @@ int main(int argc, char **argv) drawHeader(); past = Timer; } - // header stuff end if (input != Key::noOp) @@ -249,22 +248,8 @@ int main(int argc, char **argv) auto k = Bindings.get(input); for (; k.first != k.second; ++k.first) - { - Binding &b = k.first->second; - if (b.isSingle()) - { - if (b.action()->Execute()) - break; - } - else - { - auto chain = b.chain(); - for (auto it = chain->begin(); it != chain->end(); ++it) - if (!(*it)->Execute()) - break; + if (k.first->second.execute()) break; - } - } if (myScreen == myPlaylist) myPlaylist->EnableHighlighting(); diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 90379f9c..df6882ac 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -22,6 +22,7 @@ #include "settings.h" #include "status.h" #include "statusbar.h" +#include "bindings.h" using Global::wFooter; @@ -186,12 +187,13 @@ void Statusbar::Helpers::mpd() Mpd.OrderDataFetching(); } -void Statusbar::Helpers::getString(const std::wstring &) +bool Statusbar::Helpers::getString(const std::wstring &) { Status::trace(); + return true; } -void Statusbar::Helpers::ApplyFilterImmediately::operator()(const std::wstring &ws) +bool Statusbar::Helpers::ApplyFilterImmediately::operator()(const std::wstring &ws) { using Global::myScreen; // if input queue is not empty, we don't want to update filter since next @@ -209,4 +211,19 @@ void Statusbar::Helpers::ApplyFilterImmediately::operator()(const std::wstring & } Status::trace(); } + return true; +} + +bool Statusbar::Helpers::TryExecuteImmediateCommand::operator()(const std::wstring &ws) +{ + bool continue_ = true; + if (m_ws != ws) + { + m_ws = ws; + auto cmd = Bindings.findCommand(ToString(m_ws)); + if (cmd && cmd->immediate()) + continue_ = false; + } + Status::trace(); + return continue_; } diff --git a/src/statusbar.h b/src/statusbar.h index e56db09c..a0e39f0c 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -71,7 +71,7 @@ namespace Helpers {// void mpd(); /// called each time user types another character while inside Window::getString -void getString(const std::wstring &); +bool getString(const std::wstring &); /// called each time user changes current filter (while being inside Window::getString) struct ApplyFilterImmediately @@ -79,13 +79,21 @@ struct ApplyFilterImmediately ApplyFilterImmediately(Filterable *f, const std::wstring &filter) : m_f(f), m_ws(filter) { } - void operator()(const std::wstring &ws); + bool operator()(const std::wstring &ws); private: Filterable *m_f; std::wstring m_ws; }; +struct TryExecuteImmediateCommand +{ + bool operator()(const std::wstring &ws); + +private: + std::wstring m_ws; +}; + } } diff --git a/src/window.cpp b/src/window.cpp index 367b31de..0d1ae3d1 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -552,7 +552,10 @@ std::string Window::getString(const std::string &base, size_t length_, size_t wi mvwhline(m_window, y, minx, '*', maxx-minx); if (m_get_string_helper) - m_get_string_helper(*tmp); + { + if (!m_get_string_helper(*tmp)) + break; + } wmove(m_window, y, x); prefresh(m_window, 0, 0, m_start_y, m_start_x, m_start_y+m_height-1, m_start_x+m_width-1); diff --git a/src/window.h b/src/window.h index 603b3841..7893e86c 100644 --- a/src/window.h +++ b/src/window.h @@ -132,7 +132,7 @@ enum Where { wUp, wDown, wPageUp, wPageDown, wHome, wEnd }; /// Helper function that is invoked each time one will want /// to obtain string from Window::getString() function /// @see Window::getString() -typedef std::function<void(const std::wstring &)> GetStringHelper; +typedef std::function<bool(const std::wstring &)> GetStringHelper; /// Initializes curses screen and sets some additional attributes /// @param window_title title of the window (has an effect only if pdcurses lib is used) |