summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrzej Rybczak <electricityispower@gmail.com>2012-09-20 04:32:51 +0200
committerAndrzej Rybczak <electricityispower@gmail.com>2012-09-20 04:32:51 +0200
commit07fc58015ea6e322b13968e782f96cde63a64f3a (patch)
tree8f65fab4f350538b67e2f43f70973580b04ea3d2
parentba0a47668a1939414281fa74c3beb4202318815c (diff)
bindings: add support for defining and executing commands
-rw-r--r--doc/bindings21
-rw-r--r--src/actions.cpp22
-rw-r--r--src/actions.h10
-rw-r--r--src/bindings.cpp98
-rw-r--r--src/bindings.h43
-rw-r--r--src/help.cpp1
-rw-r--r--src/ncmpcpp.cpp17
-rw-r--r--src/statusbar.cpp21
-rw-r--r--src/statusbar.h12
-rw-r--r--src/window.cpp5
-rw-r--r--src/window.h2
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)