-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcompletion.cpp
127 lines (104 loc) · 3.94 KB
/
completion.cpp
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
#include "completion.h"
#include "utils.h"
#include <glob.h>
#include "Global.h"
#include <string_view>
#include "Tokenizer.h"
#include "Token.h"
#include "builtins.h"
using replxx::Replxx;
namespace completion {
static void push_prefixed_completion(std::vector<Replxx::Completion> &out, std::string &&completed_word) {
if(completed_word.empty())
return;
std::size_t slash = completed_word.rfind('/', completed_word.size() - 1);
if(slash != std::string::npos)
completed_word.erase(completed_word.begin(), completed_word.begin() + slash + 1);
out.emplace_back(std::move(completed_word));
}
static void complete_word_path(std::vector<Replxx::Completion> &out, std::string_view word, bool getRidOfFulPathSlashes = false) {
std::string pattern(word);
pattern.push_back('*');
glob_t globbuf;
if(glob(pattern.c_str(), 0, nullptr, &globbuf) != 0) {
return;
}
for(std::size_t i = 0; i < globbuf.gl_pathc; i++) {
std::string completion(globbuf.gl_pathv[i]);
if(getRidOfFulPathSlashes) {
push_prefixed_completion(out, std::move(completion));
} else {
out.emplace_back(std::move(completion));
}
}
globfree(&globbuf);
}
static void add_compl_if_matches(std::vector<Replxx::Completion> &out, std::string_view input, const std::string &word) {
if(word.starts_with(input)) {
out.emplace_back(word);
}
}
static void complete_command_name_path(std::vector<Replxx::Completion> &out, std::string_view word) {
if(std::optional<std::string> path = g.get_variable("PATH")) {
utils::Splitter(path.value()).delim(':').for_each([&] (const std::string &dir) -> utils::Splitter::ShouldContinue {
std::string glob_path = dir.empty() ? "." : dir;
glob_path.push_back('/');
glob_path.append(word);
complete_word_path(out, glob_path, true);
return utils::Splitter::CONTINUE_LOOP;
});
}
for(const auto &builtin : *get_builtins()) {
add_compl_if_matches(out, word, builtin.first);
}
for(const auto &function : g.functions) {
add_compl_if_matches(out, word, function.first);
}
}
std::vector<Replxx::Completion> completion_callback(Replxx &, std::string const &input, int &contextLen) {
std::vector<Token> tokens;
try {
tokens = Tokenizer(input).dontThrowOnIncompleteInput().tokenize();
} catch(const Tokenizer::SyntaxError &) {
return {};
}
if(tokens.empty()) {
return {};
}
// TODO: word expansion on tab completion?
std::string_view word = tokens.back().value;
bool isCommandName = true;
if(utils::utf8_codepoint_len(input) > tokens.back().positionEndUtf8Codepoint) {
word = "";
isCommandName = true;
} else if(tokens.back().type == Token::Type::OPERATOR) {
word = "";
isCommandName = false;
} else if(word.find('/') != word.npos) {
isCommandName = false;
} else if(tokens.size() >= 2) {
if(tokens.at(tokens.size() - 2).type == Token::Type::WORD) {
std::string_view previous_word = tokens.at(tokens.size() - 2).value;
if(previous_word != "then" &&
previous_word != "{" &&
previous_word != "(" &&
previous_word != "do")
isCommandName = false;
}
}
contextLen = utils::utf8_codepoint_len(word);
std::vector<Replxx::Completion> completions;
if(isCommandName) {
complete_command_name_path(completions, word);
} else {
complete_word_path(completions, word);
}
std::sort(completions.begin(), completions.end(), [](const auto &a, const auto &b) {
return a.text() < b.text();
});
completions.erase(std::unique(completions.begin(), completions.end(), [](const auto &a, const auto &b) {
return a.text() == b.text();
}), completions.end());
return completions;
}
} // namespace completion