OXYGENHUB
◆ main openmsx / src / commands / Completer.cc
5.9 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "Completer.hh"
2
3
#include "Interpreter.hh"
4
#include "InterpreterOutput.hh"
5
#include "TclObject.hh"
6
7
#include "FileContext.hh"
8
#include "FileOperations.hh"
9
#include "foreach_file.hh"
10
11
#include "ranges.hh"
12
#include "stl.hh"
13
#include "strCat.hh"
14
#include "stringsp.hh"
15
#include "utf8_unchecked.hh"
16
#include "xrange.hh"
17
18
#include <algorithm>
19
#include <array>
20
#include <memory>
21
22
namespace openmsx {
23
24
static bool formatHelper(std::span<const std::string_view> input, size_t columnLimit,
25
                         std::vector<std::string>& result)
26
{
27
	size_t column = 0;
28
	auto it = begin(input);
29
	do {
30
		size_t maxColumn = column;
31
		for (size_t i = 0; (i < result.size()) && (it != end(input));
32
		     ++i, ++it) {
33
			auto curSize = utf8::unchecked::size(result[i]);
34
			strAppend(result[i], spaces(column - curSize), *it);
35
			maxColumn = std::max(maxColumn,
36
			                     utf8::unchecked::size(result[i]));
37
			if (maxColumn > columnLimit) return false;
38
		}
39
		column = maxColumn + 2;
40
	} while (it != end(input));
41
	return true;
42
}
43
44
static std::vector<std::string> format(std::span<const std::string_view> input, size_t columnLimit)
45
{
46
	std::vector<std::string> result;
47
	for (auto lines : xrange(1u, input.size())) {
48
		result.assign(lines, std::string());
49
		if (formatHelper(input, columnLimit, result)) {
50
			return result;
51
		}
52
	}
53
	append(result, input);
54
	return result;
55
}
56
57
std::vector<std::string> Completer::formatListInColumns(std::span<const std::string_view> input)
58
{
59
	return format(input, output->getOutputColumns() - 1);
60
}
61
62
bool Completer::equalHead(std::string_view s, const CompletionCandidate& c)
63
{
64
	if (c.text.size() < s.size()) return false;
65
	if (c.caseSensitive) {
66
		return std::ranges::equal(s, subspan(c.text, 0, s.size()));
67
	} else {
68
		return strncasecmp(s.data(), c.text.data(), s.size()) == 0;
69
	}
70
}
71
72
void Completer::completeImpl(std::vector<std::string>& tokens, std::vector<CompletionCandidate> matches)
73
{
74
	auto& str = tokens.back();
75
	assert(std::ranges::all_of(matches, [&](auto& m) { return equalHead(str, m); }));
76
77
	if (matches.empty()) {
78
		// no matching values
79
		return;
80
	}
81
	if (matches.size() == 1) {
82
		// only one match
83
		auto& m = matches.front();
84
		str = m.text;
85
		if (!m.partial) {
86
			tokens.emplace_back();
87
		}
88
		return;
89
	}
90
91
	// Sort and remove duplicates.
92
	//  For efficiency it's best if the list doesn't contain duplicates to
93
	//  start with. Though sometimes this is hard to avoid. E.g. when doing
94
	//  filename completion + some extra allowed strings and one of these
95
	//  extra strings is the same as one of the filenames.
96
	std::ranges::sort(matches, {}, &CompletionCandidate::text);
97
	auto u = std::ranges::unique(matches, {}, &CompletionCandidate::text);
98
	matches.erase(u.begin(), u.end());
99
100
	auto minsize_of_matches = std::ranges::min(
101
		matches, {}, [](const auto& m) { return m.text.size(); }).text.size();
102
103
	bool expanded = false;
104
	while (str.size() < minsize_of_matches) {
105
		auto it = begin(matches);
106
		auto b = begin(it->text);
107
		auto e = b + str.size();
108
		utf8::unchecked::next(e);
109
		std::string_view test_str(b, e);
110
		if (!std::ranges::all_of(matches, [&](auto& val) { return equalHead(test_str, val); })) {
111
			break;
112
		}
113
		str = test_str;
114
		expanded = true;
115
	}
116
	if (!expanded && output) {
117
		output->setCompletions(matches);
118
	}
119
}
120
121
void Completer::completeFileNameImpl(std::vector<std::string>& tokens,
122
                                     const FileContext& context,
123
                                     std::vector<CompletionCandidate> matches)
124
{
125
	std::string& filename = tokens.back();
126
	filename = FileOperations::expandTilde(std::move(filename));
127
	filename = FileOperations::expandCurrentDirFromDrive(std::move(filename));
128
	std::string_view dirname1 = FileOperations::getDirName(filename);
129
130
	std::span<const std::string> paths;
131
	if (FileOperations::isAbsolutePath(filename)) {
132
		static const std::array<std::string, 1> EMPTY = {""};
133
		paths = EMPTY;
134
	} else {
135
		paths = context.getPaths();
136
	}
137
138
	for (const auto& p : paths) {
139
		auto pLen = p.size();
140
		if (!p.empty() && (p.back() != '/')) ++pLen;
141
142
		auto fileAction = [&](std::string_view path, bool isDir = false) {
143
			const auto& nm = FileOperations::getConventionalPath(
144
				std::string(path.substr(pLen)));
145
			CompletionCandidate c = {.text = nm};
146
			if (isDir) c.text += '/';
147
			if (equalHead(filename, c)) {
148
				c.display_ = FileOperations::getFilename(nm);
149
				if (isDir) {
150
					c.display_ += '/';
151
					c.partial = true;
152
				}
153
				matches.push_back(std::move(c));
154
			}
155
		};
156
		auto dirAction = [&](std::string& path) {
157
			fileAction(path, true);
158
		};
159
160
		foreach_file_and_directory(
161
			FileOperations::join(p, dirname1),
162
			fileAction, dirAction);
163
	}
164
	completeImpl(tokens, std::move(matches));
165
}
166
167
void Completer::checkNumArgs(std::span<const TclObject> tokens, unsigned exactly, const char* errMessage) const
168
{
169
	checkNumArgs(tokens, exactly, Prefix{exactly - 1}, errMessage);
170
}
171
172
void Completer::checkNumArgs(std::span<const TclObject> tokens, AtLeast atLeast, const char* errMessage) const
173
{
174
	checkNumArgs(tokens, atLeast, Prefix{atLeast.min - 1}, errMessage);
175
}
176
177
void Completer::checkNumArgs(std::span<const TclObject> tokens, Between between, const char* errMessage) const
178
{
179
	checkNumArgs(tokens, between, Prefix{between.min - 1}, errMessage);
180
}
181
182
void Completer::checkNumArgs(std::span<const TclObject> tokens, unsigned exactly, Prefix prefix, const char* errMessage) const
183
{
184
	if (tokens.size() == exactly) return;
185
	getInterpreter().wrongNumArgs(prefix.n, tokens, errMessage);
186
}
187
188
void Completer::checkNumArgs(std::span<const TclObject> tokens, AtLeast atLeast, Prefix prefix, const char* errMessage) const
189
{
190
	if (tokens.size() >= atLeast.min) return;
191
	getInterpreter().wrongNumArgs(prefix.n, tokens, errMessage);
192
}
193
194
void Completer::checkNumArgs(std::span<const TclObject> tokens, Between between, Prefix prefix, const char* errMessage) const
195
{
196
	if (tokens.size() >= between.min && tokens.size() <= between.max) return;
197
	getInterpreter().wrongNumArgs(prefix.n, tokens, errMessage);
198
}
199
200
} // namespace openmsx