<commit_before>#include "window.hh"
#include "buffer.hh"
#include "file.hh"
#include "command_manager.hh"
#include "buffer_manager.hh"
#include "register_manager.hh"
#include "selectors.hh"
#include "assert.hh"
#include "debug.hh"
#include "highlighters.hh"
#include "highlighter_registry.hh"
#include "filters.hh"
#include "filter_registry.hh"
#include "hooks_manager.hh"
#include "context.hh"
#include "keys.hh"

#include <unordered_map>
#include <map>
#include <sstream>
#include <boost/regex.hpp>
#include <ncurses.h>
#include <sys/types.h>
#include <sys/wait.h>

#if defined(__APPLE__)
#include <mach-o/dyld.h>
#endif

#define CTRL(x) x - 'a' + 1

using namespace Kakoune;
using namespace std::placeholders;

void set_attribute(int attribute, bool on)
{
    if (on)
        attron(attribute);
    else
        attroff(attribute);
}

int nc_color(Color color)
{
    switch (color)
    {
    case Color::Black:   return COLOR_BLACK;
    case Color::Red:     return COLOR_RED;
    case Color::Green:   return COLOR_GREEN;
    case Color::Yellow:  return COLOR_YELLOW;
    case Color::Blue:    return COLOR_BLUE;
    case Color::Magenta: return COLOR_MAGENTA;
    case Color::Cyan:    return COLOR_CYAN;
    case Color::White:   return COLOR_WHITE;

    case Color::Default:
    default:
        return COLOR_BLACK;
    }
}

void set_color(Color fg_color, Color bg_color)
{
    static std::map<std::pair<Color, Color>, int> colorpairs;
    static int current_pair = -1;
    static int next_pair = 1;

    if (current_pair != -1)
        attroff(COLOR_PAIR(current_pair));

    if (fg_color == Color::Default and bg_color == Color::Default)
        return;

    std::pair<Color, Color> colorpair(fg_color, bg_color);
    auto it = colorpairs.find(colorpair);
      if (it != colorpairs.end())
    {
        current_pair = it->second;
        attron(COLOR_PAIR(it->second));
    }
    else
    {
        init_pair(next_pair, nc_color(fg_color), nc_color(bg_color));
        colorpairs[colorpair] = next_pair;
        current_pair = next_pair;
        attron(COLOR_PAIR(next_pair));
        ++next_pair;
    }
}

void draw_window(Window& window)
{
    int max_x,max_y;
    getmaxyx(stdscr, max_y, max_x);
    max_y -= 1;

    window.set_dimensions(DisplayCoord(max_y, max_x));
    window.update_display_buffer();

    DisplayCoord position;
    for (const DisplayAtom& atom : window.display_buffer())
    {
        assert(position == atom.coord());
        const std::string content = atom.content();

        set_attribute(A_UNDERLINE, atom.attribute() & Underline);
        set_attribute(A_REVERSE, atom.attribute() & Reverse);
        set_attribute(A_BLINK, atom.attribute() & Blink);
        set_attribute(A_BOLD, atom.attribute() & Bold);

        set_color(atom.fg_color(), atom.bg_color());

        size_t pos = 0;
        size_t end;
        while (true)
        {
            move(position.line, position.column);
            clrtoeol();
            end = content.find_first_of('\n', pos);
            std::string line = content.substr(pos, end - pos);
            addstr(line.c_str());

            if (end != std::string::npos)
            {
                addch(' ');
                position.line = position.line + 1;
                position.column = 0;
                pos = end + 1;

                if (position.line >= max_y)
                    break;
            }
            else
            {
                position.column += line.length();
                break;
            }
        }
        if (position.line >= max_y)
            break;
    }

    set_attribute(A_UNDERLINE, 0);
    set_attribute(A_REVERSE, 0);
    set_attribute(A_BLINK, 0);
    set_attribute(A_BOLD, 0);
    set_color(Color::Blue, Color::Black);
    while (++position.line < max_y)
    {
        move(position.line, 0);
        clrtoeol();
        addch('~');
    }

    set_color(Color::Cyan, Color::Black);
    std::string status_line = window.status_line();
    static int last_status_length = 0;
    move(max_y, max_x - last_status_length);
    clrtoeol();
    move(max_y, max_x - status_line.length());
    addstr(status_line.c_str());
    last_status_length = status_line.length();

    const DisplayCoord& cursor_position = window.cursor_position();
    move(cursor_position.line, cursor_position.column);
}

void draw_editor_ifn(Editor& editor)
{
    Window* window = dynamic_cast<Window*>(&editor);
    if (window)
        draw_window(*window);
}

Key ncurses_get_key()
{
    char c = getch();

    Key::Modifiers modifiers = Key::Modifiers::None;
    if (c > 0 and c < 27)
    {
        modifiers = Key::Modifiers::Control;
        c = c - 1 + 'a';
    }
    else if (c == 27)
    {
        timeout(0);
        char new_c = getch();
        timeout(-1);
        if (new_c != ERR)
        {
            c = new_c;
            modifiers = Key::Modifiers::Alt;
        }
    }
    return Key(modifiers, c);
}

void init_ncurses()
{
    initscr();
    cbreak();
    noecho();
    nonl();
    intrflush(stdscr, false);
    keypad(stdscr, true);
    curs_set(0);
    start_color();
    ESCDELAY=25;
}

void deinit_ncurses()
{
    endwin();
}

struct prompt_aborted {};

std::string ncurses_prompt(const std::string& text, Completer completer = complete_nothing)
{
    curs_set(2);
    auto restore_cursor = on_scope_end([]() { curs_set(0); });

    int max_x, max_y;
    getmaxyx(stdscr, max_y, max_x);
    move(max_y-1, 0);
    addstr(text.c_str());
    clrtoeol();

    size_t cursor_pos = 0;

    Completions completions;
    int current_completion = -1;
    std::string text_before_completion;

    std::string result;
    std::string saved_result;

    static std::unordered_map<std::string, std::vector<std::string>> history_per_prompt;
    std::vector<std::string>& history = history_per_prompt[text];
    auto history_it = history.end();

    while (true)
    {
        int c = getch();
        switch (c)
        {
        case '\r':
        {
            std::vector<std::string>::iterator it;
            while ((it = find(history, result)) != history.end())
                history.erase(it);

            history.push_back(result);
            return result;
        }
        case KEY_UP:
            if (history_it != history.begin())
            {
                if (history_it == history.end())
                   saved_result = result;
                --history_it;
                result = *history_it;
                cursor_pos = result.length();
            }
            break;
        case KEY_DOWN:
            if (history_it != history.end())
            {
                ++history_it;
                if (history_it != history.end())
                    result = *history_it;
                else
                    result = saved_result;
                cursor_pos = result.length();
            }
            break;
        case KEY_LEFT:
            if (cursor_pos > 0)
                --cursor_pos;
            break;
        case KEY_RIGHT:
            if (cursor_pos < result.length())
                ++cursor_pos;
            break;
        case KEY_BACKSPACE:
            if (cursor_pos != 0)
            {
                result = result.substr(0, cursor_pos - 1)
                       + result.substr(cursor_pos, std::string::npos);

                --cursor_pos;
            }

            current_completion = -1;
            break;
        case CTRL('r'):
            {
                c = getch();
                std::string reg = RegisterManager::instance()[c].get();
                current_completion = -1;
                result = result.substr(0, cursor_pos) + reg + result.substr(cursor_pos, std::string::npos);
                cursor_pos += reg.length();
            }
            break;
        case 27:
            throw prompt_aborted();
        case '\t':
        {
            if (current_completion == -1)
            {
                completions = completer(result, cursor_pos);
                if (completions.candidates.empty())
                    break;

                text_before_completion = result.substr(completions.start,
                                                       completions.end - completions.start);
            }
            ++current_completion;

            std::string completion;
            if (current_completion >= completions.candidates.size())
            {
                if (current_completion == completions.candidates.size() and
                    std::find(completions.candidates.begin(), completions.candidates.end(), text_before_completion) == completions.candidates.end())
                    completion = text_before_completion;
                else
                {
                    current_completion = 0;
                    completion = completions.candidates[0];
                }
            }
            else
                completion = completions.candidates[current_completion];

            move(max_y-1, text.length());
            result = result.substr(0, completions.start) + completion;
            cursor_pos = completions.start + completion.length();
            break;
        }
        default:
            current_completion = -1;
            result = result.substr(0, cursor_pos) + (char)c + result.substr(cursor_pos, std::string::npos);
            ++cursor_pos;
        }

        move(max_y - 1, text.length());
        clrtoeol();
        addstr(result.c_str());
        move(max_y - 1, text.length() + cursor_pos);
        refresh();
    }
    return result;
}

static std::function<std::string (const std::string&, Completer)> prompt_func  = ncurses_prompt;

std::string prompt(const std::string& text, Completer completer = complete_nothing)
{
    return prompt_func(text, completer);
}

static std::function<Key ()> get_key_func = ncurses_get_key;

Key get_key()
{
    return get_key_func();
}


void print_status(const std::string& status)
{
    int x,y;
    getmaxyx(stdscr, y, x);
    move(y-1, 0);
    clrtoeol();
    addstr(status.c_str());
}

struct InsertSequence
{
    IncrementalInserter::Mode mode;
    std::vector<Key>          keys;

    InsertSequence() : mode(IncrementalInserter::Mode::Insert) {}
};

InsertSequence last_insert_sequence;

bool insert_char(IncrementalInserter& inserter, const Key& key)
{
    switch (key.modifiers)
    {
    case Key::Modifiers::None:
        switch (key.key)
        {
        case 27:
            return false;
        default:
            inserter.insert(std::string() + key.key);
        }
        break;
    case Key::Modifiers::Control:
        switch (key.key)
        {
        case 'r':
        {
            Key next_key = get_key();
            last_insert_sequence.keys.push_back(next_key);
            if (next_key.modifiers == Key::Modifiers::None)
            {
                switch (next_key.key)
                {
                case '%':
                    inserter.insert(inserter.buffer().name());
                    break;
                default:
                    inserter.insert(RegisterManager::instance()[next_key.key]);
                }
            }
            break;
        }
        case 'm':
            inserter.insert(std::string() + '\n');
            break;
        case 'd':
            inserter.move_cursors({0, -1});
            break;
        case 'e':
            inserter.move_cursors({0,  1});
            break;
        case 'g':
            inserter.erase();
            break;
        }
        break;
    }
    return true;
}

void do_insert(Editor& editor, IncrementalInserter::Mode mode)
{
    last_insert_sequence.mode = mode;
    last_insert_sequence.keys.clear();
    IncrementalInserter inserter(editor, mode);
    draw_editor_ifn(editor);
    while(true)
    {
        Key key = get_key();

        if (not insert_char(inserter, key))
            break;

        last_insert_sequence.keys.push_back(key);
        draw_editor_ifn(editor);
    }
}

void do_repeat_insert(Editor& editor, int count)
{
    IncrementalInserter inserter(editor, last_insert_sequence.mode);
    for (const Key& key : last_insert_sequence.keys)
    {
        insert_char(inserter, key);
    }
    draw_editor_ifn(editor);
}


template<bool append>
void do_go(Editor& editor, int count)
{
    if (count != 0)
    {
        BufferIterator target =
            editor.buffer().iterator_at(BufferCoord(count-1, 0));

        editor.select(target);
    }
    else
    {
        Key key = get_key();
        if (key.modifiers != Key::Modifiers::None)
            return;

        switch (key.key)
        {
        case 'g':
        case 't':
        {
            BufferIterator target =
                editor.buffer().iterator_at(BufferCoord(0,0));
            editor.select(target);
            break;
        }
        case 'l':
        case 'L':
            editor.select(select_to_eol, append);
            break;
        case 'h':
        case 'H':
            editor.select(select_to_eol_reverse, append);
            break;
        case 'b':
        {
            BufferIterator target = editor.buffer().iterator_at(
                BufferCoord(editor.buffer().line_count() - 1, 0));
            editor.select(target);
            break;
        }
        }
    }
}

Context main_context;

Buffer* open_or_create(const std::string& filename)
{
    Buffer* buffer = NULL;
    try
    {
        buffer = create_buffer_from_file(filename);
    }
    catch (file_not_found& what)
    {
        print_status("new file " + filename);
        buffer = new Buffer(filename, Buffer::Type::NewFile);
    }
    return buffer;
}

template<bool force_reload>
void edit(const CommandParameters& params, const Context& context)
{
    if (params.size() == 0 or params.size() > 3)
        throw wrong_argument_count();

    std::string filename = params[0];

    Buffer* buffer = nullptr;
    if (not force_reload)
        buffer = BufferManager::instance().get_buffer(filename);
    if (not buffer)
        buffer = open_or_create(filename);

    Window& window = *buffer->get_or_create_window();

    if (params.size() > 1)
    {
        int line = std::max(0, atoi(params[1].c_str()) - 1);
        int column = params.size() > 2 ?
                         std::max(0, atoi(params[2].c_str()) - 1) : 0;

        window.select(window.buffer().iterator_at({line, column}));
    }

    main_context = Context(window);
}

void write_buffer(const CommandParameters& params, const Context& context)
{
    if (params.size() > 1)
        throw wrong_argument_count();

    Buffer& buffer = context.window().buffer();
    std::string filename = params.empty() ? buffer.name()
                                          : parse_filename(params[0]);

    write_buffer_to_file(buffer, filename);
    buffer.notify_saved();
}

bool quit_requested = false;

template<bool force>
void quit(const CommandParameters& params, const Context& context)
{
    if (params.size() != 0)
        throw wrong_argument_count();

    if (not force)
    {
        for (auto& buffer : BufferManager::instance())
        {
            if (buffer.type() != Buffer::Type::Scratch and buffer.is_modified())
            {
                print_status("modified buffer remaining");
                return;
            }
        }
    }
    quit_requested = true;
}

template<bool force>
void write_and_quit(const CommandParameters& params, const Context& context)
{
    write_buffer(params, context);
    quit<force>(CommandParameters(), context);
}

void show_buffer(const CommandParameters& params, const Context& context)
{
    if (params.size() != 1)
        throw wrong_argument_count();

    Buffer* buffer = BufferManager::instance().get_buffer(params[0]);
    if (not buffer)
        print_status("buffer " + params[0] + " does not exists");
    else
        main_context = Context(*buffer->get_or_create_window());
}

void add_highlighter(const CommandParameters& params, const Context& context)
{
    if (params.size() < 1)
        throw wrong_argument_count();

    try
    {
        HighlighterRegistry& registry = HighlighterRegistry::instance();
        HighlighterParameters highlighter_params(params.begin()+1, params.end());
        registry.add_highlighter_to_window(context.window(), params[0],
                                           highlighter_params);
    }
    catch (runtime_error& err)
    {
        print_status("error: " + err.description());
    }
}

void add_group_highlighter(const CommandParameters& params, const Context& context)
{
    if (params.size() < 2)
        throw wrong_argument_count();

    try
    {
        HighlighterRegistry& registry = HighlighterRegistry::instance();

        HighlighterGroup& group = context.window().highlighters().get_group(params[0]);
        HighlighterParameters highlighter_params(params.begin()+2, params.end());
        registry.add_highlighter_to_group(context.window(), group,
                                          params[1], highlighter_params);
    }
    catch (runtime_error& err)
    {
        print_status("error: " + err.description());
    }
}

void rm_highlighter(const CommandParameters& params, const Context& context)
{
    if (params.size() != 1)
        throw wrong_argument_count();

    context.window().highlighters().remove(params[0]);
}

void rm_group_highlighter(const CommandParameters& params, const Context& context)
{
    if (params.size() != 2)
        throw wrong_argument_count();

    try
    {
        HighlighterGroup& group = context.window().highlighters().get_group(params[0]);
        group.remove(params[1]);
    }
    catch (runtime_error& err)
    {
        print_status("error: " + err.description());
    }
}
void add_filter(const CommandParameters& params, const Context& context)
{
    if (params.size() < 1)
        throw wrong_argument_count();

    try
    {
        FilterRegistry& registry = FilterRegistry::instance();
        FilterParameters filter_params(params.begin()+1, params.end());
        registry.add_filter_to_window(context.window(), params[0],
                                      filter_params);
    }
    catch (runtime_error& err)
    {
        print_status("error: " + err.description());
    }
}

void rm_filter(const CommandParameters& params, const Context& context)
{
    if (params.size() != 1)
        throw wrong_argument_count();

    context.window().remove_filter(params[0]);
}

void add_hook(const CommandParameters& params, const Context& context)
{
    if (params.size() < 4)
        throw wrong_argument_count();

    std::string regex = params[2];
    std::vector<std::string> hook_params(params.begin()+3, params.end());

    auto hook_func = [=](const std::string& param, const Context& context) {
        if (boost::regex_match(param, boost::regex(regex)))
            CommandManager::instance().execute(hook_params, context);
    };

    if (params[0] == "global")
        GlobalHooksManager::instance().add_hook(params[1], hook_func);
    else if (params[0] == "window")
        context.window().hooks_manager().add_hook(params[1], hook_func);
    else
        print_status("error: no such hook container " + params[0]);
}

void define_command(const CommandParameters& params, const Context& context)
{
    if (params.size() < 2)
        throw wrong_argument_count();

    std::vector<std::string> cmd_params(params.begin()+1, params.end());
    auto func = [=](const CommandParameters& params, const Context& context) {
        CommandManager::instance().execute(cmd_params, context);
    };
    CommandManager::instance().register_command(params[0], func);
}

void exec_commands_in_file(const CommandParameters& params,
                           const Context& context)
{
    if (params.size() != 1)
        throw wrong_argument_count();<commit_msg>def command takes an optional argument for parameter policy<commit_after>#include "window.hh"
#include "buffer.hh"
#include "file.hh"
#include "command_manager.hh"
#include "buffer_manager.hh"
#include "register_manager.hh"
#include "selectors.hh"
#include "assert.hh"
#include "debug.hh"
#include "highlighters.hh"
#include "highlighter_registry.hh"
#include "filters.hh"
#include "filter_registry.hh"
#include "hooks_manager.hh"
#include "context.hh"
#include "keys.hh"

#include <unordered_map>
#include <map>
#include <sstream>
#include <boost/regex.hpp>
#include <ncurses.h>
#include <sys/types.h>
#include <sys/wait.h>

#if defined(__APPLE__)
#include <mach-o/dyld.h>
#endif

#define CTRL(x) x - 'a' + 1

using namespace Kakoune;
using namespace std::placeholders;

void set_attribute(int attribute, bool on)
{
    if (on)
        attron(attribute);
    else
        attroff(attribute);
}

int nc_color(Color color)
{
    switch (color)
    {
    case Color::Black:   return COLOR_BLACK;
    case Color::Red:     return COLOR_RED;
    case Color::Green:   return COLOR_GREEN;
    case Color::Yellow:  return COLOR_YELLOW;
    case Color::Blue:    return COLOR_BLUE;
    case Color::Magenta: return COLOR_MAGENTA;
    case Color::Cyan:    return COLOR_CYAN;
    case Color::White:   return COLOR_WHITE;

    case Color::Default:
    default:
        return COLOR_BLACK;
    }
}

void set_color(Color fg_color, Color bg_color)
{
    static std::map<std::pair<Color, Color>, int> colorpairs;
    static int current_pair = -1;
    static int next_pair = 1;

    if (current_pair != -1)
        attroff(COLOR_PAIR(current_pair));

    if (fg_color == Color::Default and bg_color == Color::Default)
        return;

    std::pair<Color, Color> colorpair(fg_color, bg_color);
    auto it = colorpairs.find(colorpair);
      if (it != colorpairs.end())
    {
        current_pair = it->second;
        attron(COLOR_PAIR(it->second));
    }
    else
    {
        init_pair(next_pair, nc_color(fg_color), nc_color(bg_color));
        colorpairs[colorpair] = next_pair;
        current_pair = next_pair;
        attron(COLOR_PAIR(next_pair));
        ++next_pair;
    }
}

void draw_window(Window& window)
{
    int max_x,max_y;
    getmaxyx(stdscr, max_y, max_x);
    max_y -= 1;

    window.set_dimensions(DisplayCoord(max_y, max_x));
    window.update_display_buffer();

    DisplayCoord position;
    for (const DisplayAtom& atom : window.display_buffer())
    {
        assert(position == atom.coord());
        const std::string content = atom.content();

        set_attribute(A_UNDERLINE, atom.attribute() & Underline);
        set_attribute(A_REVERSE, atom.attribute() & Reverse);
        set_attribute(A_BLINK, atom.attribute() & Blink);
        set_attribute(A_BOLD, atom.attribute() & Bold);

        set_color(atom.fg_color(), atom.bg_color());

        size_t pos = 0;
        size_t end;
        while (true)
        {
            move(position.line, position.column);
            clrtoeol();
            end = content.find_first_of('\n', pos);
            std::string line = content.substr(pos, end - pos);
            addstr(line.c_str());

            if (end != std::string::npos)
            {
                addch(' ');
                position.line = position.line + 1;
                position.column = 0;
                pos = end + 1;

                if (position.line >= max_y)
                    break;
            }
            else
            {
                position.column += line.length();
                break;
            }
        }
        if (position.line >= max_y)
            break;
    }

    set_attribute(A_UNDERLINE, 0);
    set_attribute(A_REVERSE, 0);
    set_attribute(A_BLINK, 0);
    set_attribute(A_BOLD, 0);
    set_color(Color::Blue, Color::Black);
    while (++position.line < max_y)
    {
        move(position.line, 0);
        clrtoeol();
        addch('~');
    }

    set_color(Color::Cyan, Color::Black);
    std::string status_line = window.status_line();
    static int last_status_length = 0;
    move(max_y, max_x - last_status_length);
    clrtoeol();
    move(max_y, max_x - status_line.length());
    addstr(status_line.c_str());
    last_status_length = status_line.length();

    const DisplayCoord& cursor_position = window.cursor_position();
    move(cursor_position.line, cursor_position.column);
}

void draw_editor_ifn(Editor& editor)
{
    Window* window = dynamic_cast<Window*>(&editor);
    if (window)
        draw_window(*window);
}

Key ncurses_get_key()
{
    char c = getch();

    Key::Modifiers modifiers = Key::Modifiers::None;
    if (c > 0 and c < 27)
    {
        modifiers = Key::Modifiers::Control;
        c = c - 1 + 'a';
    }
    else if (c == 27)
    {
        timeout(0);
        char new_c = getch();
        timeout(-1);
        if (new_c != ERR)
        {
            c = new_c;
            modifiers = Key::Modifiers::Alt;
        }
    }
    return Key(modifiers, c);
}

void init_ncurses()
{
    initscr();
    cbreak();
    noecho();
    nonl();
    intrflush(stdscr, false);
    keypad(stdscr, true);
    curs_set(0);
    start_color();
    ESCDELAY=25;
}

void deinit_ncurses()
{
    endwin();
}

struct prompt_aborted {};

std::string ncurses_prompt(const std::string& text, Completer completer = complete_nothing)
{
    curs_set(2);
    auto restore_cursor = on_scope_end([]() { curs_set(0); });

    int max_x, max_y;
    getmaxyx(stdscr, max_y, max_x);
    move(max_y-1, 0);
    addstr(text.c_str());
    clrtoeol();

    size_t cursor_pos = 0;

    Completions completions;
    int current_completion = -1;
    std::string text_before_completion;

    std::string result;
    std::string saved_result;

    static std::unordered_map<std::string, std::vector<std::string>> history_per_prompt;
    std::vector<std::string>& history = history_per_prompt[text];
    auto history_it = history.end();

    while (true)
    {
        int c = getch();
        switch (c)
        {
        case '\r':
        {
            std::vector<std::string>::iterator it;
            while ((it = find(history, result)) != history.end())
                history.erase(it);

            history.push_back(result);
            return result;
        }
        case KEY_UP:
            if (history_it != history.begin())
            {
                if (history_it == history.end())
                   saved_result = result;
                --history_it;
                result = *history_it;
                cursor_pos = result.length();
            }
            break;
        case KEY_DOWN:
            if (history_it != history.end())
            {
                ++history_it;
                if (history_it != history.end())
                    result = *history_it;
                else
                    result = saved_result;
                cursor_pos = result.length();
            }
            break;
        case KEY_LEFT:
            if (cursor_pos > 0)
                --cursor_pos;
            break;
        case KEY_RIGHT:
            if (cursor_pos < result.length())
                ++cursor_pos;
            break;
        case KEY_BACKSPACE:
            if (cursor_pos != 0)
            {
                result = result.substr(0, cursor_pos - 1)
                       + result.substr(cursor_pos, std::string::npos);

                --cursor_pos;
            }

            current_completion = -1;
            break;
        case CTRL('r'):
            {
                c = getch();
                std::string reg = RegisterManager::instance()[c].get();
                current_completion = -1;
                result = result.substr(0, cursor_pos) + reg + result.substr(cursor_pos, std::string::npos);
                cursor_pos += reg.length();
            }
            break;
        case 27:
            throw prompt_aborted();
        case '\t':
        {
            if (current_completion == -1)
            {
                completions = completer(result, cursor_pos);
                if (completions.candidates.empty())
                    break;

                text_before_completion = result.substr(completions.start,
                                                       completions.end - completions.start);
            }
            ++current_completion;

            std::string completion;
            if (current_completion >= completions.candidates.size())
            {
                if (current_completion == completions.candidates.size() and
                    std::find(completions.candidates.begin(), completions.candidates.end(), text_before_completion) == completions.candidates.end())
                    completion = text_before_completion;
                else
                {
                    current_completion = 0;
                    completion = completions.candidates[0];
                }
            }
            else
                completion = completions.candidates[current_completion];

            move(max_y-1, text.length());
            result = result.substr(0, completions.start) + completion;
            cursor_pos = completions.start + completion.length();
            break;
        }
        default:
            current_completion = -1;
            result = result.substr(0, cursor_pos) + (char)c + result.substr(cursor_pos, std::string::npos);
            ++cursor_pos;
        }

        move(max_y - 1, text.length());
        clrtoeol();
        addstr(result.c_str());
        move(max_y - 1, text.length() + cursor_pos);
        refresh();
    }
    return result;
}

static std::function<std::string (const std::string&, Completer)> prompt_func  = ncurses_prompt;

std::string prompt(const std::string& text, Completer completer = complete_nothing)
{
    return prompt_func(text, completer);
}

static std::function<Key ()> get_key_func = ncurses_get_key;

Key get_key()
{
    return get_key_func();
}


void print_status(const std::string& status)
{
    int x,y;
    getmaxyx(stdscr, y, x);
    move(y-1, 0);
    clrtoeol();
    addstr(status.c_str());
}

struct InsertSequence
{
    IncrementalInserter::Mode mode;
    std::vector<Key>          keys;

    InsertSequence() : mode(IncrementalInserter::Mode::Insert) {}
};

InsertSequence last_insert_sequence;

bool insert_char(IncrementalInserter& inserter, const Key& key)
{
    switch (key.modifiers)
    {
    case Key::Modifiers::None:
        switch (key.key)
        {
        case 27:
            return false;
        default:
            inserter.insert(std::string() + key.key);
        }
        break;
    case Key::Modifiers::Control:
        switch (key.key)
        {
        case 'r':
        {
            Key next_key = get_key();
            last_insert_sequence.keys.push_back(next_key);
            if (next_key.modifiers == Key::Modifiers::None)
            {
                switch (next_key.key)
                {
                case '%':
                    inserter.insert(inserter.buffer().name());
                    break;
                default:
                    inserter.insert(RegisterManager::instance()[next_key.key]);
                }
            }
            break;
        }
        case 'm':
            inserter.insert(std::string() + '\n');
            break;
        case 'd':
            inserter.move_cursors({0, -1});
            break;
        case 'e':
            inserter.move_cursors({0,  1});
            break;
        case 'g':
            inserter.erase();
            break;
        }
        break;
    }
    return true;
}

void do_insert(Editor& editor, IncrementalInserter::Mode mode)
{
    last_insert_sequence.mode = mode;
    last_insert_sequence.keys.clear();
    IncrementalInserter inserter(editor, mode);
    draw_editor_ifn(editor);
    while(true)
    {
        Key key = get_key();

        if (not insert_char(inserter, key))
            break;

        last_insert_sequence.keys.push_back(key);
        draw_editor_ifn(editor);
    }
}

void do_repeat_insert(Editor& editor, int count)
{
    IncrementalInserter inserter(editor, last_insert_sequence.mode);
    for (const Key& key : last_insert_sequence.keys)
    {
        insert_char(inserter, key);
    }
    draw_editor_ifn(editor);
}


template<bool append>
void do_go(Editor& editor, int count)
{
    if (count != 0)
    {
        BufferIterator target =
            editor.buffer().iterator_at(BufferCoord(count-1, 0));

        editor.select(target);
    }
    else
    {
        Key key = get_key();
        if (key.modifiers != Key::Modifiers::None)
            return;

        switch (key.key)
        {
        case 'g':
        case 't':
        {
            BufferIterator target =
                editor.buffer().iterator_at(BufferCoord(0,0));
            editor.select(target);
            break;
        }
        case 'l':
        case 'L':
            editor.select(select_to_eol, append);
            break;
        case 'h':
        case 'H':
            editor.select(select_to_eol_reverse, append);
            break;
        case 'b':
        {
            BufferIterator target = editor.buffer().iterator_at(
                BufferCoord(editor.buffer().line_count() - 1, 0));
            editor.select(target);
            break;
        }
        }
    }
}

Context main_context;

Buffer* open_or_create(const std::string& filename)
{
    Buffer* buffer = NULL;
    try
    {
        buffer = create_buffer_from_file(filename);
    }
    catch (file_not_found& what)
    {
        print_status("new file " + filename);
        buffer = new Buffer(filename, Buffer::Type::NewFile);
    }
    return buffer;
}

template<bool force_reload>
void edit(const CommandParameters& params, const Context& context)
{
    if (params.size() == 0 or params.size() > 3)
        throw wrong_argument_count();

    std::string filename = params[0];

    Buffer* buffer = nullptr;
    if (not force_reload)
        buffer = BufferManager::instance().get_buffer(filename);
    if (not buffer)
        buffer = open_or_create(filename);

    Window& window = *buffer->get_or_create_window();

    if (params.size() > 1)
    {
        int line = std::max(0, atoi(params[1].c_str()) - 1);
        int column = params.size() > 2 ?
                         std::max(0, atoi(params[2].c_str()) - 1) : 0;

        window.select(window.buffer().iterator_at({line, column}));
    }

    main_context = Context(window);
}

void write_buffer(const CommandParameters& params, const Context& context)
{
    if (params.size() > 1)
        throw wrong_argument_count();

    Buffer& buffer = context.window().buffer();
    std::string filename = params.empty() ? buffer.name()
                                          : parse_filename(params[0]);

    write_buffer_to_file(buffer, filename);
    buffer.notify_saved();
}

bool quit_requested = false;

template<bool force>
void quit(const CommandParameters& params, const Context& context)
{
    if (params.size() != 0)
        throw wrong_argument_count();

    if (not force)
    {
        for (auto& buffer : BufferManager::instance())
        {
            if (buffer.type() != Buffer::Type::Scratch and buffer.is_modified())
            {
                print_status("modified buffer remaining");
                return;
            }
        }
    }
    quit_requested = true;
}

template<bool force>
void write_and_quit(const CommandParameters& params, const Context& context)
{
    write_buffer(params, context);
    quit<force>(CommandParameters(), context);
}

void show_buffer(const CommandParameters& params, const Context& context)
{
    if (params.size() != 1)
        throw wrong_argument_count();

    Buffer* buffer = BufferManager::instance().get_buffer(params[0]);
    if (not buffer)
        print_status("buffer " + params[0] + " does not exists");
    else
        main_context = Context(*buffer->get_or_create_window());
}

void add_highlighter(const CommandParameters& params, const Context& context)
{
    if (params.size() < 1)
        throw wrong_argument_count();

    try
    {
        HighlighterRegistry& registry = HighlighterRegistry::instance();
        HighlighterParameters highlighter_params(params.begin()+1, params.end());
        registry.add_highlighter_to_window(context.window(), params[0],
                                           highlighter_params);
    }
    catch (runtime_error& err)
    {
        print_status("error: " + err.description());
    }
}

void add_group_highlighter(const CommandParameters& params, const Context& context)
{
    if (params.size() < 2)
        throw wrong_argument_count();

    try
    {
        HighlighterRegistry& registry = HighlighterRegistry::instance();

        HighlighterGroup& group = context.window().highlighters().get_group(params[0]);
        HighlighterParameters highlighter_params(params.begin()+2, params.end());
        registry.add_highlighter_to_group(context.window(), group,
                                          params[1], highlighter_params);
    }
    catch (runtime_error& err)
    {
        print_status("error: " + err.description());
    }
}

void rm_highlighter(const CommandParameters& params, const Context& context)
{
    if (params.size() != 1)
        throw wrong_argument_count();

    context.window().highlighters().remove(params[0]);
}

void rm_group_highlighter(const CommandParameters& params, const Context& context)
{
    if (params.size() != 2)
        throw wrong_argument_count();

    try
    {
        HighlighterGroup& group = context.window().highlighters().get_group(params[0]);
        group.remove(params[1]);
    }
    catch (runtime_error& err)
    {
        print_status("error: " + err.description());
    }
}
void add_filter(const CommandParameters& params, const Context& context)
{
    if (params.size() < 1)
        throw wrong_argument_count();

    try
    {
        FilterRegistry& registry = FilterRegistry::instance();
        FilterParameters filter_params(params.begin()+1, params.end());
        registry.add_filter_to_window(context.window(), params[0],
                                      filter_params);
    }
    catch (runtime_error& err)
    {
        print_status("error: " + err.description());
    }
}

void rm_filter(const CommandParameters& params, const Context& context)
{
    if (params.size() != 1)
        throw wrong_argument_count();

    context.window().remove_filter(params[0]);
}

void add_hook(const CommandParameters& params, const Context& context)
{
    if (params.size() < 4)
        throw wrong_argument_count();

    std::string regex = params[2];
    std::vector<std::string> hook_params(params.begin()+3, params.end());

    auto hook_func = [=](const std::string& param, const Context& context) {
        if (boost::regex_match(param, boost::regex(regex)))
            CommandManager::instance().execute(hook_params, context);
    };

    if (params[0] == "global")
        GlobalHooksManager::instance().add_hook(params[1], hook_func);
    else if (params[0] == "window")
        context.window().hooks_manager().add_hook(params[1], hook_func);
    else
        print_status("error: no such hook container " + params[0]);
}

void define_command(const CommandParameters& params, const Context& context)
{
    if (params.size() < 2)
        throw wrong_argument_count();

    if (params[0] == "-env-params")
    {
        std::vector<std::string> cmd_params(params.begin() + 2, params.end());
        CommandManager::instance().register_command(params[1],
             [=](const CommandParameters& params, const Context& context) {
                char param_name[] = "kak_param0";
                for (size_t i = 0; i < 10; ++i)
                {
                    param_name[sizeof(param_name) - 2] = '0' + i;
                    if (params.size() > i)
                        setenv(param_name, params[i].c_str(), 1);
                    else
                        unsetenv(param_name);
                }
                CommandManager::instance().execute(cmd_params, context);
            });
    }
    else if (params[0] == "-append-params")
    {
        std::vector<std::string> cmd_params(params.begin() + 2, params.end());
        CommandManager::instance().register_command(params[1],
             [=](const CommandParameters& params, const Context& context) {
                std::vector<std::string> merged_params = cmd_params;
                for (auto& param : params)
                    merged_params.push_back(param);
                CommandManager::instance().execute(merged_params, context);
            });
    }
    else
    {
        std::vector<std::string> cmd_params(params.begin() + 1, params.end());
        CommandManager::instance().register_command(params[0],
             [=](const CommandParameters& params, const Context& context) {
                 if (not params.empty())
                     throw wrong_argument_count();
                CommandManager::instance().execute(cmd_params, context);
            });
    }
}

void exec_commands_in_file(const CommandParameters& params,
                           const Context& context)
{
    if (params.size() != 1)
        throw wrong_argument_count();
{'fraction_non_alphanumeric': 0.12522943638188003, 'fraction_numerical': 0.005800151733927217, 'mean_word_length': 2.147346530077794, 'pattern_counts': {'":': 0, '<': 75, '<?xml version=': 0, '>': 85, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '559206', 'n_tokens_mistral': 12562, 'n_tokens_neox': 12113, 'n_words': 2442}