window: use readline for handling line input

This commit is contained in:
Andrzej Rybczak
2013-12-25 23:31:27 +01:00
parent 819d8baebd
commit 70945596ef
8 changed files with 203 additions and 272 deletions

View File

@@ -181,6 +181,16 @@ PKG_CHECK_MODULES([libmpdclient], [libmpdclient >= 2.8], [
AC_MSG_ERROR([libmpdclient >= 2.8 is required!])
)
dnl =========================
dnl = checking for readline =
dnl =========================
AC_CHECK_HEADERS([readline/readline.h readline/history.h],
AC_CHECK_LIB(readline, rl_initialize, LDFLAGS="$LDFLAGS -lreadline",
AC_MSG_ERROR([readline headers found but there is no readline library to make use of])
),
)
dnl ========================
dnl = checking for pthread =
dnl ========================

View File

@@ -27,6 +27,7 @@
#include <boost/lexical_cast.hpp>
#include <algorithm>
#include <iostream>
#include <readline/readline.h>
#include "actions.h"
#include "charset.h"
@@ -191,6 +192,7 @@ void resizeScreen(bool reload_main_window)
// update internal screen dimensions
if (reload_main_window)
{
rl_resize_terminal();
endwin();
refresh();
}
@@ -1189,7 +1191,7 @@ void SetCrossfade::run()
Statusbar::lock();
Statusbar::put() << "Set crossfade to: ";
std::string crossfade = wFooter->getString(3);
std::string crossfade = wFooter->getString();
Statusbar::unlock();
int cf = fromString<unsigned>(crossfade);
lowerBoundCheck(cf, 1);
@@ -1203,7 +1205,7 @@ void SetVolume::run()
Statusbar::lock();
Statusbar::put() << "Set volume to: ";
std::string strvolume = wFooter->getString(3);
std::string strvolume = wFooter->getString();
Statusbar::unlock();
int volume = fromString<unsigned>(strvolume);
boundsCheck(volume, 0, 100);
@@ -1765,7 +1767,7 @@ void ApplyFilter::run()
Statusbar::lock();
Statusbar::put() << NC::Format::Bold << "Apply filter: " << NC::Format::NoBold;
wFooter->setGetStringHelper(Statusbar::Helpers::ApplyFilterImmediately(f, ToWString(filter)));
wFooter->setGetStringHelper(Statusbar::Helpers::ApplyFilterImmediately(f, filter));
wFooter->getString(filter);
wFooter->setGetStringHelper(Statusbar::Helpers::getString);
Statusbar::unlock();

View File

@@ -53,6 +53,7 @@ namespace
{
std::ofstream errorlog;
std::streambuf *cerr_buffer;
bool run_resize_screen = false;
# if !defined(WIN32)
void sighandler(int signal)
@@ -63,7 +64,7 @@ namespace
}
else if (signal == SIGWINCH)
{
Actions::resizeScreen(true);
run_resize_screen = true;
}
}
# endif // !WIN32
@@ -153,7 +154,6 @@ int main(int argc, char **argv)
wFooter = new NC::Window(0, Actions::FooterStartY, COLS, Actions::FooterHeight, "", Config.statusbar_color, NC::Border::None);
wFooter->setTimeout(500);
wFooter->setGetStringHelper(Statusbar::Helpers::getString);
wFooter->createHistory();
// initialize global timer
gettimeofday(&Timer, 0);
@@ -225,6 +225,12 @@ int main(int argc, char **argv)
}
Status::trace();
if (run_resize_screen)
{
Actions::resizeScreen(true);
run_resize_screen = false;
}
// header stuff
if (((Timer.tv_sec == past.tv_sec && Timer.tv_usec >= past.tv_usec+500000) || Timer.tv_sec > past.tv_sec)

View File

@@ -118,7 +118,7 @@ void Status::handleServerError(MPD::ServerError &e)
{
wFooter->setGetStringHelper(nullptr);
Statusbar::put() << "Password: ";
Mpd.SetPassword(wFooter->getString(-1, 0, 1));
Mpd.SetPassword(wFooter->getString(0, true));
Mpd.SendPassword();
Statusbar::msg("Password accepted");
wFooter->setGetStringHelper(Statusbar::Helpers::getString);

View File

@@ -189,13 +189,13 @@ void Statusbar::Helpers::mpd()
Status::update(Mpd.noidle());
}
bool Statusbar::Helpers::getString(const std::wstring &)
bool Statusbar::Helpers::getString(const char *)
{
Status::trace();
return true;
}
bool Statusbar::Helpers::ApplyFilterImmediately::operator()(const std::wstring &ws)
bool Statusbar::Helpers::ApplyFilterImmediately::operator()(const char *s)
{
using Global::myScreen;
// if input queue is not empty, we don't want to update filter since next
@@ -205,10 +205,10 @@ bool Statusbar::Helpers::ApplyFilterImmediately::operator()(const std::wstring &
// is next in queue, so its effects will be seen.
if (wFooter->inputQueue().empty() || wFooter->inputQueue().front() == KEY_ENTER)
{
if (m_ws != ws)
if (m_s != s)
{
m_ws = ws;
m_f->applyFilter(ToString(m_ws));
m_s = s;
m_f->applyFilter(m_s);
myScreen->refreshWindow();
}
Status::trace();
@@ -216,13 +216,13 @@ bool Statusbar::Helpers::ApplyFilterImmediately::operator()(const std::wstring &
return true;
}
bool Statusbar::Helpers::TryExecuteImmediateCommand::operator()(const std::wstring &ws)
bool Statusbar::Helpers::TryExecuteImmediateCommand::operator()(const char *s)
{
bool continue_ = true;
if (m_ws != ws)
if (m_s != s)
{
m_ws = ws;
auto cmd = Bindings.findCommand(ToString(m_ws));
m_s = s;
auto cmd = Bindings.findCommand(m_s);
if (cmd && cmd->immediate())
continue_ = false;
}

View File

@@ -71,27 +71,28 @@ namespace Helpers {//
void mpd();
/// called each time user types another character while inside Window::getString
bool getString(const std::wstring &);
bool getString(const char *);
/// called each time user changes current filter (while being inside Window::getString)
struct ApplyFilterImmediately
{
ApplyFilterImmediately(Filterable *f, const std::wstring &filter)
: m_f(f), m_ws(filter) { }
template <typename StringT>
ApplyFilterImmediately(Filterable *f, StringT &&filter)
: m_f(f), m_s(std::forward<StringT>(filter)) { }
bool operator()(const std::wstring &ws);
bool operator()(const char *s);
private:
Filterable *m_f;
std::wstring m_ws;
std::string m_s;
};
struct TryExecuteImmediateCommand
{
bool operator()(const std::wstring &ws);
bool operator()(const char *s);
private:
std::wstring m_ws;
std::string m_s;
};
}

View File

@@ -18,8 +18,12 @@
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include <cassert>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <readline/history.h>
#include <readline/readline.h>
#ifdef WIN32
# include <winsock.h>
@@ -33,6 +37,111 @@
#include "utility/wide_string.h"
#include "window.h"
namespace {
namespace rl {
NC::Window *w;
size_t start_x;
size_t start_y;
size_t width;
bool encrypted;
const char *base;
int read_key(FILE *)
{
int result;
do
{
w->runGetStringHelper(rl_line_buffer);
w->refresh();
result = w->readKey();
}
while (result == ERR);
return result;
}
void display_string()
{
auto print_char = [](wchar_t wc) {
if (encrypted)
*w << '*';
else
*w << wc;
};
auto print_string = [](wchar_t *ws, size_t len) {
if (encrypted)
for (size_t i = 0; i < len; ++i)
*w << '*';
else
*w << ws;
};
char pt = rl_line_buffer[rl_point];
rl_line_buffer[rl_point] = 0;
wchar_t pre_pos[rl_point+1];
pre_pos[mbstowcs(pre_pos, rl_line_buffer, rl_point)] = 0;
rl_line_buffer[rl_point] = pt;
int pos = wcswidth(pre_pos, rl_point);
assert(pos >= 0);
if (pos < 0)
pos = rl_point;
mvwhline(w->raw(), start_y, start_x, ' ', width+1);
w->goToXY(start_x, start_y);
if (size_t(pos) <= width)
{
print_string(pre_pos, pos);
wchar_t post_pos[rl_end-rl_point+1];
post_pos[mbstowcs(post_pos, rl_line_buffer+rl_point, rl_end-rl_point)] = 0;
size_t cpos = pos;
for (wchar_t *c = post_pos; *c != 0; ++c)
{
int n = wcwidth(*c);
if (n < 0)
{
print_char(L'.');
++cpos;
}
else
{
if (cpos+n > width)
break;
cpos += n;
print_char(*c);
}
}
}
else
{
wchar_t *mod_pre_pos = pre_pos;
while (*mod_pre_pos != 0)
{
++mod_pre_pos;
int n = wcwidth(*mod_pre_pos);
if (n < 0)
--pos;
else
pos -= n;
if (size_t(pos) <= width)
break;
}
print_string(mod_pre_pos, pos);
}
w->goToXY(start_x+pos, start_y);
}
int add_base()
{
rl_insert_text(base);
return 0;
}
}
}
namespace NC {//
void initScreen(GNUC_UNUSED const char *window_title, bool enable_colors)
@@ -64,6 +173,12 @@ void initScreen(GNUC_UNUSED const char *window_title, bool enable_colors)
noecho();
cbreak();
curs_set(0);
rl_initialize();
// overwrite readline callbacks
rl_getc_function = rl::read_key;
rl_redisplay_function = rl::display_string;
rl_startup_hook = rl::add_base;
}
void destroyScreen()
@@ -93,7 +208,6 @@ Window::Window(size_t startx,
m_border(border),
m_get_string_helper(0),
m_title(title),
m_history(0),
m_bold_counter(0),
m_underline_counter(0),
m_reverse_counter(0),
@@ -145,7 +259,6 @@ Window::Window(const Window &rhs)
, m_color_stack(rhs.m_color_stack)
, m_input_queue(rhs.m_input_queue)
, m_fds(rhs.m_fds)
, m_history(rhs.m_history ? new std::list<std::wstring>(*rhs.m_history) : 0)
, m_bold_counter(rhs.m_bold_counter)
, m_underline_counter(rhs.m_underline_counter)
, m_reverse_counter(rhs.m_reverse_counter)
@@ -171,7 +284,6 @@ Window::Window(Window &&rhs)
, m_color_stack(std::move(rhs.m_color_stack))
, m_input_queue(std::move(rhs.m_input_queue))
, m_fds(std::move(rhs.m_fds))
, m_history(rhs.m_history)
, m_bold_counter(rhs.m_bold_counter)
, m_underline_counter(rhs.m_underline_counter)
, m_reverse_counter(rhs.m_reverse_counter)
@@ -179,7 +291,6 @@ Window::Window(Window &&rhs)
{
rhs.m_window = 0;
rhs.m_border_window = 0;
rhs.m_history = 0;
}
Window &Window::operator=(Window rhs)
@@ -201,7 +312,6 @@ Window &Window::operator=(Window rhs)
std::swap(m_color_stack, rhs.m_color_stack);
std::swap(m_input_queue, rhs.m_input_queue);
std::swap(m_fds, rhs.m_fds);
std::swap(m_history, rhs.m_history);
std::swap(m_bold_counter, rhs.m_bold_counter);
std::swap(m_underline_counter, rhs.m_underline_counter);
std::swap(m_reverse_counter, rhs.m_reverse_counter);
@@ -213,7 +323,6 @@ Window::~Window()
{
delwin(m_window);
delwin(m_border_window);
delete m_history;
}
void Window::setColor(Color fg, Color bg)
@@ -286,18 +395,6 @@ void Window::setTitle(const std::string &new_title)
m_title = new_title;
}
void Window::createHistory()
{
if (!m_history)
m_history = new std::list<std::wstring>;
}
void Window::deleteHistory()
{
delete m_history;
m_history = 0;
}
void Window::recreate(size_t width, size_t height)
{
delwin(m_window);
@@ -481,229 +578,39 @@ void Window::pushChar(int ch)
m_input_queue.push(ch);
}
std::string Window::getString(const std::string &base, size_t length_, size_t width, bool encrypted)
std::string Window::getString(const std::string &base, size_t width, bool encrypted)
{
int input;
size_t beginning, maxbeginning, minx, x, real_x, y, maxx, real_maxx;
getyx(m_window, y, x);
minx = real_maxx = maxx = real_x = x;
rl::w = this;
getyx(m_window, rl::start_y, rl::start_x);
rl::width = width;
rl::encrypted = encrypted;
rl::base = base.c_str();
width--;
if (width == size_t(-1))
width = m_width-x-1;
rl::width = m_width-rl::start_x-1;
else
rl::width = width;
mmask_t oldmask;
std::string result;
curs_set(1);
std::wstring wbase = ToWString(base);
std::wstring *tmp = &wbase;
std::list<std::wstring>::iterator history_it = m_history->end();
std::string tmp_in;
wchar_t wc_in;
bool gotoend = 1;
bool block_scrolling = 0;
// disable scrolling if wide chars are used
for (std::wstring::const_iterator it = tmp->begin(); it != tmp->end(); ++it)
if (wcwidth(*it) > 1)
block_scrolling = 1;
beginning = -1;
do
{
if (tmp->empty())
block_scrolling = 0;
maxbeginning = block_scrolling ? 0 : (tmp->length() < width ? 0 : tmp->length()-width);
maxx = minx + (wideLength(*tmp) < width ? wideLength(*tmp) : width);
real_maxx = minx + (tmp->length() < width ? tmp->length() : width);
if (beginning > maxbeginning)
beginning = maxbeginning;
if (gotoend)
{
size_t real_real_maxx = minx;
size_t biggest_x = minx+width;
if (block_scrolling && maxx >= biggest_x)
{
size_t i = 0;
for (std::wstring::const_iterator it = tmp->begin(); i < width; ++it, ++real_real_maxx)
i += wcwidth(*it);
}
else
real_real_maxx = real_maxx;
real_x = real_real_maxx;
x = block_scrolling ? (maxx > biggest_x ? biggest_x : maxx) : maxx;
beginning = maxbeginning;
gotoend = 0;
}
mvwhline(m_window, y, minx, ' ', width+1);
if (!encrypted)
mvwprintw(m_window, y, minx, "%ls", tmp->substr(beginning, width+1).c_str());
else
mvwhline(m_window, y, minx, '*', maxx-minx);
if (m_get_string_helper)
{
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);
input = readKey();
switch (input)
{
case ERR:
case KEY_MOUSE:
break;
case KEY_UP:
if (m_history && !encrypted && history_it != m_history->begin())
{
while (--history_it != m_history->begin())
if (!history_it->empty())
break;
tmp = &*history_it;
gotoend = 1;
}
break;
case KEY_DOWN:
if (m_history && !encrypted && history_it != m_history->end())
{
while (++history_it != m_history->end())
if (!history_it->empty())
break;
tmp = &(history_it == m_history->end() ? wbase : *history_it);
gotoend = 1;
}
break;
case KEY_RIGHT:
{
if (x < maxx)
{
real_x++;
x += std::max(wcwidth((*tmp)[beginning+real_x-minx-1]), 1);
}
else if (beginning < maxbeginning)
beginning++;
break;
}
case KEY_CTRL_H:
case KEY_BACKSPACE:
case KEY_BACKSPACE_2:
{
if (x <= minx && !beginning)
break;
}
case KEY_LEFT:
{
if (x > minx)
{
real_x--;
x -= std::max(wcwidth((*tmp)[beginning+real_x-minx]), 1);
}
else if (beginning > 0)
beginning--;
if (input != KEY_CTRL_H && input != KEY_BACKSPACE && input != KEY_BACKSPACE_2)
break; // backspace = left & delete.
}
case KEY_DC:
{
if ((real_x-minx)+beginning == tmp->length())
break;
tmp->erase(tmp->begin()+(real_x-minx)+beginning);
if (beginning && beginning == maxbeginning && real_x < maxx)
{
real_x++;
x++;
}
break;
}
case KEY_HOME:
{
real_x = x = minx;
beginning = 0;
break;
}
case KEY_END:
{
gotoend = 1;
break;
}
case KEY_ENTER:
break;
case KEY_CTRL_U:
tmp->clear();
real_maxx = maxx = real_x = x = minx;
maxbeginning = beginning = 0;
break;
default:
{
if (tmp->length() >= length_)
break;
tmp_in += input;
if (int(mbrtowc(&wc_in, tmp_in.c_str(), MB_CUR_MAX, 0)) < 0)
break;
int wcwidth_res = wcwidth(wc_in);
if (wcwidth_res > 1)
block_scrolling = 1;
if (wcwidth_res > 0) // is char printable? we want to ignore things like Ctrl-?, Fx etc.
{
if ((real_x-minx)+beginning >= tmp->length())
{
tmp->push_back(wc_in);
if (!beginning)
{
real_x++;
x += wcwidth(wc_in);
}
beginning++;
gotoend = 1;
}
else
{
tmp->insert(tmp->begin()+(real_x-minx)+beginning, wc_in);
if (x < maxx)
{
real_x++;
x += wcwidth(wc_in);
}
else if (beginning < maxbeginning)
beginning++;
}
}
tmp_in.clear();
}
}
}
while (input != KEY_ENTER);
keypad(m_window, 0);
mousemask(0, &oldmask);
char *input = readline(nullptr);
mousemask(oldmask, nullptr);
keypad(m_window, 1);
curs_set(0);
if (m_history && !encrypted)
if (input != nullptr)
{
if (history_it != m_history->end())
{
m_history->push_back(*history_it);
tmp = &m_history->back();
m_history->erase(history_it);
}
else
m_history->push_back(*tmp);
if (input[0] != 0)
add_history(input);
result = input;
free(input);
}
return ToString(*tmp);
return result;
}
void Window::goToXY(int x, int y)
@@ -739,6 +646,17 @@ bool Window::hasCoords(int &x, int &y)
# endif
}
bool Window::runGetStringHelper(const char *arg) const
{
if (m_get_string_helper)
{
m_get_string_helper(arg);
return true;
}
else
return false;
}
size_t Window::getWidth() const
{
if (m_border != Border::None)

View File

@@ -132,7 +132,7 @@ enum class Scroll { Up, Down, PageUp, PageDown, Home, End };
/// Helper function that is invoked each time one will want
/// to obtain string from Window::getString() function
/// @see Window::getString()
typedef std::function<bool(const std::wstring &)> GetStringHelper;
typedef std::function<bool(const char *)> 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)
@@ -163,7 +163,7 @@ struct XY
/// Main class of NCurses namespace, used as base for other specialized windows
struct Window
{
Window() : m_window(0), m_border_window(0), m_history(0) { }
Window() : m_window(0), m_border_window(0) { }
/// Constructs an empty window with given parameters
/// @param startx X position of left upper corner of constructed window
@@ -229,14 +229,13 @@ struct Window
/// @see setGetStringHelper()
/// @see SetTimeout()
/// @see CreateHistory()
std::string getString(const std::string &base, size_t length_ = -1,
size_t width = 0, bool encrypted = 0);
std::string getString(const std::string &base, size_t width = 0, bool encrypted = 0);
/// Wrapper for above function that doesn't take base string (it will be empty).
/// Taken parameters are the same as for above.
std::string getString(size_t length_ = -1, size_t width = 0, bool encrypted = 0)
std::string getString(size_t width = 0, bool encrypted = 0)
{
return getString("", length_, width, encrypted);
return getString("", width, encrypted);
}
/// Moves cursor to given coordinates
@@ -262,6 +261,11 @@ struct Window
/// @param helper pointer to function that matches getStringHelper prototype
/// @see getString()
void setGetStringHelper(GetStringHelper helper) { m_get_string_helper = helper; }
/// Run current GetString helper function (if defined).
/// @see getString()
/// @return true if helper was run, false otherwise
bool runGetStringHelper(const char *arg) const;
/// Sets window's base color
/// @param fg foregound base color
@@ -280,13 +284,6 @@ struct Window
/// @param new_title new title for window
void setTitle(const std::string &new_title);
/// Creates internal container that stores all previous
/// strings that were edited using this window.
void createHistory();
/// Deletes container with all previous history entries
void deleteHistory();
/// Refreshed whole window and its border
/// @see refresh()
void display();
@@ -509,9 +506,6 @@ private:
typedef std::vector< std::pair<int, void (*)()> > FDCallbacks;
FDCallbacks m_fds;
/// pointer to container used as history
std::list<std::wstring> *m_history;
/// counters for format flags
int m_bold_counter;
int m_underline_counter;