| 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 |
|
| 79 |
return; |
| 80 |
} |
| 81 |
if (matches.size() == 1) { |
| 82 |
|
| 83 |
auto& m = matches.front(); |
| 84 |
str = m.text; |
| 85 |
if (!m.partial) { |
| 86 |
tokens.emplace_back(); |
| 87 |
} |
| 88 |
return; |
| 89 |
} |
| 90 |
|
| 91 |
|
| 92 |
|
| 93 |
|
| 94 |
|
| 95 |
|
| 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 |
} |