CursesでReadlineを使う

boostを若干濫用気味にCursesのメインループを書いてみた。
さらにreadlineでのプロンプトも実験。

// g++ main.cpp -lreadline -lcurses
#include <curses.h>
#include <termios.h>
#include <errno.h>
#include <stdlib.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <string>
#include <list>
#include <map>
#include <algorithm>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

namespace Ncurses
{
  struct Point { int x; int y; };
  struct Size { int w; int h; };
  struct Rect { Point point; Size size; };

  class Window
  {
    WINDOW *window_;
    Rect rect_;
    Point cursor_;

    public:
    Window(const Rect &rect)
      : rect_(rect)
    {
      cursor_.x=0;
      cursor_.y=0;
      window_=newwin(rect.size.h, rect.size.w
          , rect.point.y, rect.point.x);
    }

    void update()
    {
      wmove(window_, rect_.size.h-1, 0);
      wprintw(window_, "line: %d/%d - col: %d/%d"
          , cursor_.y+1, rect_.size.h-1
          , cursor_.x+1, rect_.size.w);
      wclrtoeol(window_);
      wmove(window_, cursor_.y, cursor_.x);
      wrefresh(window_);
    }

    void left() { --cursor_.x; clamp_(); }
    void right() { ++cursor_.x; clamp_(); } 
    void down() { ++cursor_.y; clamp_(); } 
    void up() { --cursor_.y; clamp_(); } 
    void head(){ cursor_.x=0; }
    void tail(){ cursor_.x=rect_.size.w-1; }
    void begin(){ cursor_.y=0; }
    void end(){ cursor_.y=rect_.size.h-2; }
    void clear() { wclear(window_); }
    int getChar() { return wgetch(window_); }

    void lastline()
    {
      wmove(window_, rect_.size.h-1, 0);
      wclrtoeol(window_);
      wrefresh(window_);
    }

    private:
    void clamp_(){
      cursor_.x=std::max(0, std::min(cursor_.x, rect_.size.w-1));
      cursor_.y=std::max(0, std::min(cursor_.y, rect_.size.h-2));
    }
  };
  typedef boost::shared_ptr<Window> WindowPtr;

  class App
  {
    typedef boost::function<void(void)> Action;
    typedef std::map<int,Action> ActionMap;
    ActionMap actionMap_;
    std::list <WindowPtr> windowStack_;
    bool doLoop_;

    public:
    App()
      : doLoop_(true)
    {
      initscr();
      noecho();
      cbreak();
    }

    ~App()
    {
      endwin();
    }

    WindowPtr createWindow(const Rect &rect){
      windowStack_.push_back(WindowPtr(new Window(rect)));
      return windowStack_.back();
    }

    void loop()
    {
      while(doLoop_)
      {
        windowStack_.back()->update();
        int key=windowStack_.back()->getChar();
        //int key=getch();
        ActionMap::iterator action=actionMap_.find(key);
        if(action==actionMap_.end()){
          // no action
          continue;
        }

        action->second();
      }
    }

    template<typename Callback>
      void registAction(int key, Callback callback)
      {
        actionMap_[key]=callback;
      }

    bool promptQuit(const std::string &input)
    {
      if(input=="y" || input=="Y"){
        doLoop_=false;
      }
      return true;
    }
  };

  struct Readline
  {
    struct termios keep_term;

    Readline()
    {
      // setup term mode
      struct termios temp_term;
      errno = 0;
      if(tcgetattr(fileno(stdin), &keep_term) == -1){
        perror("tcgetattr failure");
        exit(EXIT_FAILURE);
      }

      temp_term = keep_term;
      temp_term.c_lflag |= ECHO;

      errno = 0;
      if(tcsetattr(fileno(stdin), TCSANOW, &temp_term) == -1){
        perror("tcsetattr(temp_term) failure");
        exit(EXIT_FAILURE);
      }
    }

    ~Readline()
    {
      // restore term mode
      errno = 0;
      if(tcsetattr(fileno(stdin), TCSANOW, &keep_term) == -1){
        perror("tcsetattr(keep_term) failure");
        exit(EXIT_FAILURE);
      }
    }

    std::string operator()(const std::string &prompt)
    {
      char *input=readline(prompt.c_str());
      std::string line(input);
      free(input);
      return line;
    }  
  };

  struct Prompt
  {
    boost::function<void(void)> preFunc_;
    std::string prompt_;
    boost::function<void(const char *)> callback_;
    boost::function<void(void)> postFunc_;

    template<typename PreFunc, typename Callback, typename PostFunc>
      Prompt(PreFunc preFunc, const std::string &prompt, Callback callback, PostFunc postFunc)
      : preFunc_(preFunc), prompt_(prompt), callback_(callback), postFunc_(postFunc)
      {}

    void operator()()
    {
      preFunc_();
      Readline rl;
      std::string input=rl(prompt_);
      callback_(input.c_str());
      postFunc_();
    }
  };

} // namespace

int main()
{
  Ncurses::App app;
  Ncurses::Rect rect={{0, 0}, {COLS, LINES}};
  Ncurses::WindowPtr window=app.createWindow(rect);

  app.registAction('h', boost::bind(&Ncurses::Window::left, window));
  app.registAction('j', boost::bind(&Ncurses::Window::down, window));
  app.registAction('k', boost::bind(&Ncurses::Window::up, window));
  app.registAction('l', boost::bind(&Ncurses::Window::right, window));
  app.registAction('0', boost::bind(&Ncurses::Window::head, window));
  app.registAction('$', boost::bind(&Ncurses::Window::tail, window));
  app.registAction('g', boost::bind(&Ncurses::Window::begin, window));
  app.registAction('G', boost::bind(&Ncurses::Window::end, window));

  app.registAction('q', Ncurses::Prompt(
        boost::bind(&Ncurses::Window::lastline, window)
        , "quit?(y/n): "
        , boost::bind(&Ncurses::App::promptQuit, &app, _1)
        , boost::bind(&Ncurses::Window::clear, window)
        ));

  app.loop();

  return 0;
}

この上にイベントキューを実装して、
各種シグナル(SIGCHLD、SIGALRM、SIGPIPE、SIGWINCH辺り)を捌ける
仕組みを作るのが目的。