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,
## 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
#

View File

@@ -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());

View File

@@ -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") { }

View File

@@ -189,26 +189,52 @@ 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 {
auto bind_in_progress = [&]() -> bool {
if (in_progress == InProgress::Command)
{
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 if (in_progress == InProgress::Key)
{
if (!actions.empty())
{
bind(key, actions);
@@ -217,25 +243,56 @@ bool BindingsConfiguration::read(const std::string &file)
}
else
{
error() << "definition of key '" << strkey << "' cannot be empty.\n";
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())
{
if (!bind_key_def())
error() << "command must have non-empty name\n";
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);
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")))

View File

@@ -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:
};
};
/// Keybindings configuration
struct BindingsConfiguration
/// 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
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;

View File

@@ -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");

View File

@@ -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())
if (k.first->second.execute())
break;
}
else
{
auto chain = b.chain();
for (auto it = chain->begin(); it != chain->end(); ++it)
if (!(*it)->Execute())
break;
break;
}
}
if (myScreen == myPlaylist)
myPlaylist->EnableHighlighting();

View File

@@ -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_;
}

View File

@@ -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;
};
}
}

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);
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);

View File

@@ -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)