Public Access
Shell: Tab completes every word of a command, and * and ? stand for several files (#67)
Tab used to complete a command's first word only. It now follows the help text word by word: `lora st` gives `lora status`, `gnss track ` lists `start stop`. The words are read from the help text as written, so a new command completes with no table to keep; the Shell's own words are added in the same notation. `*` and `?` in the last part of a path, for ls, du, rm, cp and mv, from the Shell and both consoles. The command runs once for each name matched, lined up and run by the main loop as each finishes; `cancel` empties the line-up. 64 matches at most, refused whole past that. In the Shell, rm with a pattern asks once, with the count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
+20
-15
@@ -25,14 +25,8 @@ void ShellApp::onEnter() {
|
||||
ringPos_ = 0;
|
||||
scroll_ = 0;
|
||||
confirm_.reset();
|
||||
words_ = commandWords(helpText_);
|
||||
for (auto* app : apps_.visibleApps()) { // and the Apps, by their names with a capital
|
||||
std::string name = AppManager::commandFor(app->id);
|
||||
if (std::find(words_.begin(), words_.end(), name) == words_.end()) words_.push_back(name);
|
||||
}
|
||||
words_.push_back("help");
|
||||
words_.push_back("clear"); // the Shell's own
|
||||
words_.push_back("quit");
|
||||
// What Tab completes besides the firmware's own commands, written as `help` writes them.
|
||||
ownHelp_ = "help | clear | quit | exit\nkey up|down|left|right|select|back|home|del|tab|space|help\n";
|
||||
log_.clear();
|
||||
log_.add(open_ ? "The console's commands. `help` lists them." : "No memory for the Shell: leave an App, or stop IRC.");
|
||||
}
|
||||
@@ -43,7 +37,7 @@ void ShellApp::onExit() {
|
||||
console.shellShowsAll(false);
|
||||
open_ = false;
|
||||
log_.clear();
|
||||
std::vector<std::string>().swap(words_);
|
||||
std::string().swap(ownHelp_);
|
||||
confirm_.reset();
|
||||
}
|
||||
|
||||
@@ -78,11 +72,20 @@ void ShellApp::enter(const std::string& line) {
|
||||
// to. An empty folder goes without a word; anything `rm` would refuse anyway, it refuses itself.
|
||||
if (startsWith(line, "rm ")) {
|
||||
files::RmArgs args = files::parseRm(line.substr(3));
|
||||
Target target = args.force || args.path.empty() ? Target::Missing : probe_(args.path);
|
||||
bool ask = target == Target::File || (target == Target::FullFolder && args.recursive);
|
||||
bool ask = false;
|
||||
if (args.force || args.path.empty()) {
|
||||
} else if (files::hasGlob(args.path)) { // a pattern: one question for all it matches
|
||||
bool more = false;
|
||||
int n = count_(args.path, more);
|
||||
ask = n > 0 && !more; // none, or too many: `rm` says so itself
|
||||
question_ = "The " + std::to_string(n) + " that match " + args.path + (args.recursive ? ", folders and what's in them too" : "") + ". It can't be undone.";
|
||||
} else {
|
||||
Target target = probe_(args.path);
|
||||
ask = target == Target::File || (target == Target::FullFolder && args.recursive);
|
||||
question_ = target == Target::File ? args.path + ". It can't be undone." : args.path + " and everything in it. It can't be undone.";
|
||||
}
|
||||
if (ask) {
|
||||
pending_ = line;
|
||||
question_ = target == Target::File ? args.path + ". It can't be undone." : args.path + " and everything in it. It can't be undone.";
|
||||
confirm_.reset(new DialogModel({"Cancel", "Delete"}));
|
||||
return;
|
||||
}
|
||||
@@ -123,12 +126,14 @@ bool ShellApp::onKey(const KeyEvent& e) {
|
||||
case Key::Down:
|
||||
if (history_.down(recalled)) input_.setText(recalled);
|
||||
break;
|
||||
case Key::Tab: { // the command's name, or past it a path on the card
|
||||
case Key::Tab: { // the command, every word of it; where its words end, a path on the card
|
||||
std::vector<std::string> matches;
|
||||
PathToComplete path;
|
||||
bool more = false;
|
||||
if (splitForPath(input_.text(), path)) input_.setText(completePath(path, list_(path.folder, path.prefix, more), matches));
|
||||
else input_.setText(completeCommand(input_.text(), words_, matches));
|
||||
const std::string typed = input_.text();
|
||||
std::string done = completeWords(typed, (std::string(helpText_) + ownHelp_).c_str(), matches);
|
||||
if (done == typed && matches.empty() && splitForPath(typed, path)) done = completePath(path, list_(path.folder, path.prefix, more), matches);
|
||||
input_.setText(done);
|
||||
if (matches.size() > 1) {
|
||||
std::string all;
|
||||
for (auto& m : matches) all += (all.empty() ? "" : " ") + m;
|
||||
|
||||
@@ -30,9 +30,11 @@ class ShellApp : public App {
|
||||
// A folder's entries that start with `prefix`, whatever their case, a folder's with a slash at its
|
||||
// end: what Tab completes a path from. `more` when there were too many to give them all.
|
||||
using List = std::function<std::vector<std::string>(const std::string& folder, const std::string& prefix, bool& more)>;
|
||||
// How many names a pattern matches (/notes/*.txt), for the question `rm` asks; `more` past the limit.
|
||||
using Count = std::function<int(const std::string& pattern, bool& more)>;
|
||||
|
||||
ShellApp(Run run, Probe probe, List list, const char* helpText, AppManager& apps)
|
||||
: run_(std::move(run)), probe_(std::move(probe)), list_(std::move(list)), helpText_(helpText), apps_(apps) {}
|
||||
ShellApp(Run run, Probe probe, List list, Count count, const char* helpText, AppManager& apps)
|
||||
: run_(std::move(run)), probe_(std::move(probe)), list_(std::move(list)), count_(std::move(count)), helpText_(helpText), apps_(apps) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
@@ -49,10 +51,11 @@ class ShellApp : public App {
|
||||
Run run_;
|
||||
Probe probe_;
|
||||
List list_;
|
||||
Count count_;
|
||||
const char* helpText_;
|
||||
AppManager& apps_;
|
||||
ShellLog log_;
|
||||
std::vector<std::string> words_; // the commands Tab completes: built on entering, from `help`
|
||||
std::string ownHelp_; // the Shell's own commands and the keys `key` takes, in the form of `help`'s text
|
||||
LineEditor input_{240};
|
||||
InputHistory history_{16};
|
||||
std::unique_ptr<DialogModel> confirm_;
|
||||
|
||||
Reference in New Issue
Block a user