blob: 97a0bd2d4163be5d4c61af60960f33b4df98d2ee [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/lib/line_input/modal_line_input.h"
#include <lib/syslog/cpp/macros.h>
#include <locale>
namespace line_input {
namespace {
// State associated with running a modal options prompt.
class ModalOptionState {
public:
ModalOptionState(ModalLineInput* input, ModalPromptOptions opts,
ModalLineInput::ModalCompletionCallback cb)
: input_(input), options_(std::move(opts)), on_complete_(std::move(cb)) {}
void OnAccept(const std::string& line) { CheckAccept(line, true); }
void OnChanged(const std::string& line) {
if (options_.require_enter)
return; // Nothing to do.
if (CheckAccept(line, false)) {
// When the user has typed valid input and we don't require enter, synthesize an enter to
// invoke the normal accept codepath. We could close the input now, but skipping tne
// enter will erase the current line in normal console mode.
//
// This will cause OnAccept() above to be called which will then signal completion.
input_->OnInput('\r');
}
}
void OnCancel() {
if (!options_.cancel_option.empty()) {
input_->EndModal();
on_complete_(options_.cancel_option);
}
}
private:
// Checks whether the current line is a valid option. On success, returns true and optionally
// signals completion (which closes the modal prompt).
bool CheckAccept(const std::string& line, bool signal_complete) {
// Optionally check case-insensitively.
std::string to_check(line);
if (!options_.case_sensitive) {
for (size_t i = 0; i < to_check.size(); i++)
to_check[i] = std::tolower(to_check[i]);
}
for (const auto& opt : options_.options) {
if (to_check == opt) {
if (signal_complete) {
input_->EndModal();
on_complete_(to_check);
}
return true;
}
}
return false;
}
ModalLineInput* input_; // Non-owning (it owns us).
ModalPromptOptions options_;
ModalLineInput::ModalCompletionCallback on_complete_;
};
} // namespace
void ModalLineInput::Init(AcceptCallback accept_cb, const std::string& prompt) {
FX_DCHECK(!normal_input_) << "Calling Init() twice.";
normal_input_ = MakeAndSetupLineInput(std::move(accept_cb), prompt);
current_input_ = normal_input_.get();
}
void ModalLineInput::SetAutocompleteCallback(AutocompleteCallback cb) {
FX_DCHECK(normal_input_) << "Need to call Init() first.";
// Autocomplete only works for the non-modal input.
normal_input_->SetAutocompleteCallback(std::move(cb));
}
void ModalLineInput::SetChangeCallback(ChangeCallback cb) {
// Change callbacks only go to the non-modal input. Our model interface handles changes on the
// modal one.
normal_input_->SetChangeCallback(std::move(cb));
}
void ModalLineInput::SetCancelCallback(CancelCallback cb) {
// The cancel callback goes only to the regular input. Modal prompts have special handling.
normal_input_->SetCancelCallback(std::move(cb));
}
void ModalLineInput::SetEofCallback(EofCallback cb) { eof_callback_ = std::move(cb); }
void ModalLineInput::SetMaxCols(size_t max) {
FX_DCHECK(normal_input_) << "Need to call Init() first.";
max_cols_ = max;
normal_input_->SetMaxCols(max);
if (modal_input_)
modal_input_->SetMaxCols(max);
}
const std::string& ModalLineInput::GetLine() const { return current_input_->GetLine(); }
const std::deque<std::string>& ModalLineInput::GetHistory() const {
// History always comes from the regular one. The modal input has no history.
FX_DCHECK(normal_input_) << "Need to call Init() first.";
return normal_input_->GetHistory();
}
void ModalLineInput::OnInput(char c) {
if (to_delete_)
to_delete_.reset();
current_input_->OnInput(c);
}
void ModalLineInput::AddToHistory(const std::string& line) {
// History always goes to the normal input.
normal_input_->AddToHistory(line);
}
void ModalLineInput::Hide() {
hidden_ = true;
current_input_->Hide();
}
void ModalLineInput::Show() {
hidden_ = false;
current_input_->Show();
}
void ModalLineInput::ModalGetOption(const ModalPromptOptions& options, const std::string& prompt,
ModalCompletionCallback cb, WillShowModalCallback will_show) {
auto state = std::make_shared<ModalOptionState>(this, options, std::move(cb));
// This will show callback registers our changed callback on the new modal input class and
// calls the user will_show callback if provided. |this| can be captured here because this
// owns the callback.
auto do_will_show = [this, state, will_show = std::move(will_show)]() mutable {
modal_input_->SetChangeCallback([state](const std::string& line) { state->OnChanged(line); });
modal_input_->SetCancelCallback([state]() { state->OnCancel(); });
if (will_show)
will_show();
};
BeginModal(
prompt, [state](const std::string& line) { state->OnAccept(line); }, std::move(do_will_show));
}
void ModalLineInput::BeginModal(const std::string& prompt, ModalCompletionCallback cb,
WillShowModalCallback will_show) {
auto& record = modal_callbacks_.emplace_back();
record.prompt = prompt;
record.complete = std::move(cb);
record.will_show = std::move(will_show);
if (!modal_input_) {
// Not showing a modal input already, switch to it.
if (!hidden_)
normal_input_->Hide();
ShowNextModal();
}
// Otherwise we're already showing a modal input. This new one will be automatically shown in
// time.
}
void ModalLineInput::EndModal() {
FX_DCHECK(modal_input_) << "Not in a modal input.";
FX_DCHECK(!modal_callbacks_.empty());
modal_callbacks_.pop_front();
if (!hidden_)
modal_input_->Hide();
// Schedule the modal input to be deleted in next OnInput() call to prevent reentrancy.
FX_DCHECK(!to_delete_);
to_delete_ = std::move(modal_input_);
current_input_ = normal_input_.get();
if (modal_callbacks_.empty()) {
// Go back to normal mode.
if (!hidden_)
normal_input_->Show();
} else {
ShowNextModal();
}
}
void ModalLineInput::ShowNextModal() {
FX_DCHECK(!modal_callbacks_.empty());
FX_DCHECK(!modal_input_);
auto& record = modal_callbacks_.front();
modal_input_ = MakeAndSetupLineInput(std::move(record.complete), record.prompt);
current_input_ = modal_input_.get();
if (record.will_show)
record.will_show();
if (!hidden_)
current_input_->Show();
}
std::unique_ptr<LineInput> ModalLineInput::MakeAndSetupLineInput(AcceptCallback accept_cb,
const std::string& prompt) {
auto input = MakeLineInput(std::move(accept_cb), prompt);
if (max_cols_)
input->SetMaxCols(max_cols_);
input->SetEofCallback([this]() {
if (eof_callback_)
eof_callback_();
});
return input;
}
} // namespace line_input