Files
roro9stack/src/apps/shell_app.h
T
twislaandClaude Opus 5.5 0fdbb5b1ed
CI / build (pull_request) Successful in 1m38s
Site / build (pull_request) Successful in 9s
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
2026-10-07 12:47:10 +02:00

69 lines
2.8 KiB
C++

#pragma once
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "app.h"
#include "app_manager.h"
#include "dialog_model.h"
#include "input_history.h"
#include "line_editor.h"
#include "shell_log.h"
namespace roro {
// The Shell (issue #67): the console's commands on the device's own screen and keyboard. A third
// place to type them, after USB serial and the Debug Console, and trusted like the first: whoever
// holds the device can do all of it in Settings anyway (Q205).
//
// It shows the replies to its own commands, and only those unless asked otherwise (Q206): the
// console knows who each line was printed for (Console::Origin) and fills a ring that exists only
// while the App is open. Nothing is kept once it is left.
class ShellApp : public App {
public:
using Run = std::function<void(const std::string& line)>;
// What `rm` is pointed at: it decides whether the Shell asks first.
enum class Target { Missing, File, EmptyFolder, FullFolder };
using Probe = std::function<Target(const std::string& path)>;
// 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, 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;
bool onKey(const KeyEvent& e) override;
bool textEntryActive() const override { return !confirm_; }
void update(uint32_t nowMs) override;
void draw(Canvas& c) override;
void help(std::vector<KeyHelp>& out) const override;
private:
void enter(const std::string& line);
void runNow(const std::string& line);
Run run_;
Probe probe_;
List list_;
Count count_;
const char* helpText_;
AppManager& apps_;
ShellLog log_;
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_;
std::string pending_, question_; // the `rm` being asked about, and what's asked
uint32_t ringPos_ = 0, seenRevision_ = 0;
int scroll_ = 0; // wrapped lines scrolled back from the bottom
bool open_ = false;
};
} // namespace roro