bindings: add support for defining and executing commands

This commit is contained in:
Andrzej Rybczak
2012-09-20 04:32:51 +02:00
parent ba0a47668a
commit 07fc58015e
11 changed files with 209 additions and 45 deletions

View File

@@ -95,6 +95,24 @@
## selected_items_adder, server_info, song_info, ## selected_items_adder, server_info, song_info,
## sort_playlist_dialog, tiny_tag_editor. ## 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 ## Note: Both 'backspace' and 'backspace_2' are used because some
## terminals interpret backspace using keycode of 'backspace' ## terminals interpret backspace using keycode of 'backspace'
## and some the one of 'backspace_2'. You can get away with ## and some the one of 'backspace_2'. You can get away with
@@ -191,6 +209,9 @@
#def_key "-" #def_key "-"
# volume_down # volume_down
# #
#def_key ":"
# execute_command
#
#def_key "tab" #def_key "tab"
# next_screen # next_screen
# #

View File

@@ -972,6 +972,27 @@ void Stop::Run()
Mpd.Stop(); 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 bool MoveSortOrderUp::canBeRun() const
{ {
return myScreen == mySortPlaylistDialog; return myScreen == mySortPlaylistDialog;
@@ -2576,6 +2597,7 @@ void populateActions()
insertAction(new NextSong()); insertAction(new NextSong());
insertAction(new Pause()); insertAction(new Pause());
insertAction(new Stop()); insertAction(new Stop());
insertAction(new ExecuteCommand());
insertAction(new SavePlaylist()); insertAction(new SavePlaylist());
insertAction(new MoveSortOrderUp()); insertAction(new MoveSortOrderUp());
insertAction(new MoveSortOrderDown()); insertAction(new MoveSortOrderDown());

View File

@@ -31,7 +31,7 @@ enum ActionType
aDummy, aMouseEvent, aScrollUp, aScrollDown, aScrollUpArtist, aScrollUpAlbum, aScrollDownArtist, aDummy, aMouseEvent, aScrollUp, aScrollDown, aScrollUpArtist, aScrollUpAlbum, aScrollDownArtist,
aScrollDownAlbum, aPageUp, aPageDown, aMoveHome, aMoveEnd, aToggleInterface, aJumpToParentDirectory, aScrollDownAlbum, aPageUp, aPageDown, aMoveHome, aMoveEnd, aToggleInterface, aJumpToParentDirectory,
aPressEnter, aPressSpace, aPreviousColumn, aNextColumn, aMasterScreen, aSlaveScreen, aVolumeUp, 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, aMoveSortOrderUp, aMoveSortOrderDown, aMoveSelectedItemsUp, aMoveSelectedItemsDown,
aMoveSelectedItemsTo, aAdd, aSeekForward, aSeekBackward, aToggleDisplayMode, aToggleSeparatorsBetweenAlbums, aMoveSelectedItemsTo, aAdd, aSeekForward, aSeekBackward, aToggleDisplayMode, aToggleSeparatorsBetweenAlbums,
aToggleLyricsFetcher, aToggleFetchingLyricsInBackground, aTogglePlayingSongCentering, aUpdateDatabase, aToggleLyricsFetcher, aToggleFetchingLyricsInBackground, aTogglePlayingSongCentering, aUpdateDatabase,
@@ -361,6 +361,14 @@ protected:
virtual void Run(); virtual void Run();
}; };
struct ExecuteCommand : public Action
{
ExecuteCommand() : Action(aExecuteCommand, "execute_command") { }
protected:
virtual void Run();
};
struct SavePlaylist : public Action struct SavePlaylist : public Action
{ {
SavePlaylist() : Action(aSavePlaylist, "save_playlist") { } SavePlaylist() : Action(aSavePlaylist, "save_playlist") { }

View File

@@ -189,53 +189,110 @@ Key Key::read(NC::Window &w)
bool BindingsConfiguration::read(const std::string &file) bool BindingsConfiguration::read(const std::string &file)
{ {
enum class InProgress { None, Command, Key };
bool result = true; bool result = true;
std::ifstream f(file); std::ifstream f(file);
if (!f.is_open()) if (!f.is_open())
return result; return result;
// shared variables
InProgress in_progress = InProgress::None;
size_t line_no = 0; size_t line_no = 0;
bool key_def_in_progress = false; std::string line;
Key key = Key::noOp;
Binding::ActionChain actions; 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 & { auto error = [&]() -> std::ostream & {
std::cerr << file << ":" << line_no << ": "; std::cerr << file << ":" << line_no << ": error: ";
key_def_in_progress = false; in_progress = InProgress::None;
result = false; result = false;
return std::cerr; return std::cerr;
}; };
auto bind_key_def = [&]() -> bool { auto bind_in_progress = [&]() -> bool {
if (!actions.empty()) if (in_progress == InProgress::Command)
{ {
bind(key, actions); if (!actions.empty())
actions.clear(); {
return true; 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"; if (!actions.empty())
return false; {
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) while (!f.eof() && ++line_no)
{ {
getline(f, line); getline(f, line);
if (line.empty() || line[0] == '#') if (line.empty() || line[0] == '#')
continue; 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())
{ {
if (!bind_key_def()) error() << "command must have non-empty name\n";
break; break;
} }
key_def_in_progress = true; 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
{
error() << "invalid type of command: '" << cmd_type << "'\n";
break;
}
}
// 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); strkey = getEnclosedString(line, '"', '"', 0);
key = stringToKey(strkey); key = stringToKey(strkey);
if (key == Key::noOp) if (key == Key::noOp)
@@ -262,8 +319,7 @@ bool BindingsConfiguration::read(const std::string &file)
break; break;
} }
} }
if (key_def_in_progress) bind_in_progress();
bind_key_def();
f.close(); f.close();
return result; return result;
} }
@@ -318,6 +374,8 @@ void BindingsConfiguration::generateDefaults()
} }
if (notBound(k = stringToKey("-"))) if (notBound(k = stringToKey("-")))
bind(k, aVolumeDown); bind(k, aVolumeDown);
if (notBound(k = stringToKey(":")))
bind(k, aExecuteCommand);
if (notBound(k = stringToKey("tab"))) if (notBound(k = stringToKey("tab")))
bind(k, aNextScreen); bind(k, aNextScreen);
if (notBound(k = stringToKey("shift_tab"))) if (notBound(k = stringToKey("shift_tab")))

View File

@@ -22,6 +22,7 @@
#define _BINDINGS_H #define _BINDINGS_H
#include <cassert> #include <cassert>
#include <unordered_map>
#include "actions.h" #include "actions.h"
#include "macro_utilities.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 { bool isSingle() const {
return m_is_single; return m_is_single;
} }
@@ -105,16 +120,41 @@ private:
}; };
}; };
/// Keybindings configuration /// Represents executable command
struct BindingsConfiguration 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
class BindingsConfiguration
{
typedef std::unordered_map<std::string, Command> CommandsSet;
typedef std::multimap<Key, Binding> BindingsMap; typedef std::multimap<Key, Binding> BindingsMap;
public:
typedef BindingsMap::iterator BindingIterator; typedef BindingsMap::iterator BindingIterator;
typedef BindingsMap::const_iterator ConstBindingIterator; typedef BindingsMap::const_iterator ConstBindingIterator;
bool read(const std::string &file); bool read(const std::string &file);
void generateDefaults(); 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) { std::pair<BindingIterator, BindingIterator> get(const Key &k) {
return m_bindings.equal_range(k); return m_bindings.equal_range(k);
} }
@@ -132,6 +172,7 @@ private:
} }
BindingsMap m_bindings; BindingsMap m_bindings;
CommandsSet m_commands;
}; };
extern BindingsConfiguration Bindings; extern BindingsConfiguration Bindings;

View File

@@ -238,6 +238,7 @@ void Help::GetKeybindings()
KeyDesc(aSetCrossfade, "Set crossfade"); KeyDesc(aSetCrossfade, "Set crossfade");
KeyDesc(aUpdateDatabase, "Start music database update"); KeyDesc(aUpdateDatabase, "Start music database update");
w << '\n'; w << '\n';
KeyDesc(aExecuteCommand, "Execute command");
KeyDesc(aApplyFilter, "Apply filter"); KeyDesc(aApplyFilter, "Apply filter");
KeyDesc(aFindItemForward, "Find item forward"); KeyDesc(aFindItemForward, "Find item forward");
KeyDesc(aFindItemBackward, "Find item backward"); KeyDesc(aFindItemBackward, "Find item backward");

View File

@@ -237,7 +237,6 @@ int main(int argc, char **argv)
drawHeader(); drawHeader();
past = Timer; past = Timer;
} }
// header stuff end // header stuff end
if (input != Key::noOp) if (input != Key::noOp)
@@ -249,22 +248,8 @@ int main(int argc, char **argv)
auto k = Bindings.get(input); auto k = Bindings.get(input);
for (; k.first != k.second; ++k.first) for (; k.first != k.second; ++k.first)
{ if (k.first->second.execute())
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;
break; break;
}
}
if (myScreen == myPlaylist) if (myScreen == myPlaylist)
myPlaylist->EnableHighlighting(); myPlaylist->EnableHighlighting();

View File

@@ -22,6 +22,7 @@
#include "settings.h" #include "settings.h"
#include "status.h" #include "status.h"
#include "statusbar.h" #include "statusbar.h"
#include "bindings.h"
using Global::wFooter; using Global::wFooter;
@@ -186,12 +187,13 @@ void Statusbar::Helpers::mpd()
Mpd.OrderDataFetching(); Mpd.OrderDataFetching();
} }
void Statusbar::Helpers::getString(const std::wstring &) bool Statusbar::Helpers::getString(const std::wstring &)
{ {
Status::trace(); 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; using Global::myScreen;
// if input queue is not empty, we don't want to update filter since next // 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(); 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_;
} }

View File

@@ -71,7 +71,7 @@ namespace Helpers {//
void mpd(); void mpd();
/// called each time user types another character while inside Window::getString /// 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) /// called each time user changes current filter (while being inside Window::getString)
struct ApplyFilterImmediately struct ApplyFilterImmediately
@@ -79,13 +79,21 @@ struct ApplyFilterImmediately
ApplyFilterImmediately(Filterable *f, const std::wstring &filter) ApplyFilterImmediately(Filterable *f, const std::wstring &filter)
: m_f(f), m_ws(filter) { } : m_f(f), m_ws(filter) { }
void operator()(const std::wstring &ws); bool operator()(const std::wstring &ws);
private: private:
Filterable *m_f; Filterable *m_f;
std::wstring m_ws; std::wstring m_ws;
}; };
struct TryExecuteImmediateCommand
{
bool operator()(const std::wstring &ws);
private:
std::wstring m_ws;
};
} }
} }

View File

@@ -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); mvwhline(m_window, y, minx, '*', maxx-minx);
if (m_get_string_helper) if (m_get_string_helper)
m_get_string_helper(*tmp); {
if (!m_get_string_helper(*tmp))
break;
}
wmove(m_window, y, x); 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); prefresh(m_window, 0, 0, m_start_y, m_start_x, m_start_y+m_height-1, m_start_x+m_width-1);

View File

@@ -132,7 +132,7 @@ enum Where { wUp, wDown, wPageUp, wPageDown, wHome, wEnd };
/// Helper function that is invoked each time one will want /// Helper function that is invoked each time one will want
/// to obtain string from Window::getString() function /// to obtain string from Window::getString() function
/// @see Window::getString() /// @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 /// Initializes curses screen and sets some additional attributes
/// @param window_title title of the window (has an effect only if pdcurses lib is used) /// @param window_title title of the window (has an effect only if pdcurses lib is used)