summaryrefslogtreecommitdiff
path: root/src/command_manager.cc
blob: b1722f3f5a7f5bedd1286772a1bd1f7e291453d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#include "command_manager.hh"

#include "utils.hh"
#include "assert.hh"

#include <algorithm>

namespace Kakoune
{

void CommandManager::register_command(const std::string& command_name, Command command,
                                      const CommandCompleter& completer)
{
    m_commands[command_name] = CommandAndCompleter { command, completer };
}

void CommandManager::register_command(const std::vector<std::string>& command_names, Command command,
                                      const CommandCompleter& completer)
{
    for (auto command_name : command_names)
        register_command(command_name, command, completer);
}

typedef std::vector<std::pair<size_t, size_t>> TokenList;
static TokenList split(const std::string& line)
{
    TokenList result;

    size_t pos = 0;
    while (pos != line.length())
    {
        while(line[pos] == ' ' and pos != line.length())
            ++pos;

        size_t token_start = pos;

        while((line[pos] != ' ' or line[pos-1] == '\\') and pos != line.length())
            ++pos;

        result.push_back(std::make_pair(token_start, pos));
    }
    return result;
}

struct command_not_found : runtime_error
{
    command_not_found(const std::string& command)
        : runtime_error(command + " : no such command") {}
};

void CommandManager::execute(const std::string& command_line)
{
    TokenList tokens = split(command_line);
    if (tokens.empty())
        return;

    std::string command_name =
        command_line.substr(tokens[0].first,
                            tokens[0].second - tokens[0].first);

    auto command_it = m_commands.find(command_name);
    if (command_it == m_commands.end())
        throw command_not_found(command_name);

    CommandParameters params;
    for (auto it = tokens.begin() + 1; it != tokens.end(); ++it)
    {
        params.push_back(command_line.substr(it->first,
                                             it->second - it->first));
    }

    command_it->second.command(params);
}

Completions CommandManager::complete(const std::string& command_line, size_t cursor_pos)
{
    TokenList tokens = split(command_line);

    size_t token_to_complete = -1;
    for (size_t i = 0; i < tokens.size(); ++i)
    {
        if (tokens[i].first <= cursor_pos and tokens[i].second >= cursor_pos)
        {
            token_to_complete = i;
            break;
        }
    }

    if (token_to_complete == 0 or tokens.empty()) // command name completion
    {
        size_t cmd_start = tokens.empty() ? 0 : tokens[0].first;
        Completions result(cmd_start, cursor_pos);
        std::string prefix = command_line.substr(cmd_start,
                                                 cursor_pos - cmd_start);

        for (auto& command : m_commands)
        {
            if (command.first.substr(0, prefix.length()) == prefix)
                result.candidates.push_back(command.first);
        }

        return result;
    }

    assert(not tokens.empty());
    std::string command_name =
        command_line.substr(tokens[0].first,
                            tokens[0].second - tokens[0].first);

    auto command_it = m_commands.find(command_name);
    if (command_it == m_commands.end() or not command_it->second.completer)
        return Completions();

    CommandParameters params;
    for (auto it = tokens.begin() + 1; it != tokens.end(); ++it)
    {
        params.push_back(command_line.substr(it->first,
                                             it->second - it->first));
    }
    Completions result(tokens[token_to_complete].first, cursor_pos);
    size_t cursor_pos_in_token = cursor_pos - tokens[token_to_complete].first;

    result.candidates = command_it->second.completer(params,
                                                     token_to_complete - 1,
                                                     cursor_pos_in_token);
    return result;
}

CandidateList PerArgumentCommandCompleter::operator()(const CommandParameters& params,
                                                      size_t token_to_complete,
                                                      size_t pos_in_token) const
{
    if (token_to_complete >= m_completers.size())
        return CandidateList();

    // it is possible to try to complete a new argument
    assert(token_to_complete <= params.size());

    const std::string& argument = token_to_complete < params.size() ?
                                  params[token_to_complete] : std::string();
    return m_completers[token_to_complete](argument, pos_in_token);
}

}