Public Access
Shell: Tab completes a path on the SD card (#67)
Past the command's name, Tab completes the word being typed as a path: a folder keeps its slash to go on from, a file completed whole gets a space, several candidates are listed. Any case typed, the name's own is taken. After a file command the first slash is understood (`cat no` is /no). The folder is read on the storage task, bounded to 400 entries looked at and 24 candidates. 489 host tests (2 new). Checked on the device: a folder, a file inside it, several candidates, no slash, another case, nothing matching. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -88,4 +88,61 @@ std::string completeCommand(const std::string& typed, const std::vector<std::str
|
||||
return common;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
char lower(char c) { return c >= 'A' && c <= 'Z' ? static_cast<char>(c - 'A' + 'a') : c; }
|
||||
|
||||
bool startsWithNoCase(const std::string& name, const std::string& prefix) {
|
||||
if (name.size() < prefix.size()) return false;
|
||||
for (size_t i = 0; i < prefix.size(); i++)
|
||||
if (lower(name[i]) != lower(prefix[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool takesAPath(const std::string& command) {
|
||||
static const char* const kCommands[] = {"ls", "du", "mkdir", "rm", "cp", "mv", "cat", "install"};
|
||||
for (auto c : kCommands)
|
||||
if (command == c) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool splitForPath(const std::string& typed, PathToComplete& out) {
|
||||
size_t space = typed.rfind(' ');
|
||||
if (space == std::string::npos) return false; // still the command's own word
|
||||
std::string word = typed.substr(space + 1);
|
||||
if (!word.empty() && word[0] == '-') return false; // a switch
|
||||
if (word.empty() || word[0] != '/') {
|
||||
if (!takesAPath(typed.substr(0, typed.find(' ')))) return false;
|
||||
word = "/" + word;
|
||||
}
|
||||
size_t slash = word.rfind('/');
|
||||
out.head = typed.substr(0, space + 1);
|
||||
out.folder = word.substr(0, slash + 1);
|
||||
out.prefix = word.substr(slash + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string completePath(const PathToComplete& what, const std::vector<std::string>& names, std::vector<std::string>& matches) {
|
||||
matches.clear();
|
||||
for (auto& n : names)
|
||||
if (startsWithNoCase(n, what.prefix)) matches.push_back(n);
|
||||
if (matches.empty()) return what.head + what.folder + what.prefix;
|
||||
std::string common = matches[0];
|
||||
for (auto& m : matches) {
|
||||
size_t n = 0;
|
||||
while (n < common.size() && n < m.size() && lower(common[n]) == lower(m[n])) n++;
|
||||
common.resize(n);
|
||||
}
|
||||
if (matches.size() == 1) {
|
||||
bool folder = !common.empty() && common.back() == '/';
|
||||
matches.clear();
|
||||
return what.head + what.folder + common + (folder ? "" : " ");
|
||||
}
|
||||
// Several: never shorter than what was typed (cases may differ past the prefix).
|
||||
if (common.size() < what.prefix.size()) common = what.prefix;
|
||||
return what.head + what.folder + common;
|
||||
}
|
||||
|
||||
} // namespace roro
|
||||
|
||||
@@ -43,4 +43,20 @@ std::vector<std::string> commandWords(const char* helpText);
|
||||
// there is more than one.
|
||||
std::string completeCommand(const std::string& typed, const std::vector<std::string>& words, std::vector<std::string>& matches);
|
||||
|
||||
// Tab on a later word: a path on the SD card (issue #67). What is being completed, taken apart:
|
||||
// `rm -r /notes/sh` is head "rm -r ", folder "/notes/", prefix "sh". False when the cursor is still
|
||||
// on the first word, when the word is a switch (-r), or when it isn't a path and the command doesn't
|
||||
// take one. After a file command a path may be started without its slash: `cat no` is /no.
|
||||
struct PathToComplete {
|
||||
std::string head, folder, prefix;
|
||||
};
|
||||
bool splitForPath(const std::string& typed, PathToComplete& out);
|
||||
|
||||
// `names` are the folder's entries, a folder's with a slash at its end. The line with the path
|
||||
// completed as far as the entries that start with the prefix agree, whatever their case (the card
|
||||
// doesn't tell cases apart, so the name's own case is taken). A file completed whole gets a space
|
||||
// after it; a folder keeps its slash, to go on from. `matches` gets the candidates when there are
|
||||
// several. Unchanged when nothing matches.
|
||||
std::string completePath(const PathToComplete& what, const std::vector<std::string>& names, std::vector<std::string>& matches);
|
||||
|
||||
} // namespace roro
|
||||
|
||||
Reference in New Issue
Block a user