Implement TUI colon command mode
This commit is contained in:
@@ -78,6 +78,15 @@ fn render_content(frame: &mut Frame, app: &App, area: Rect) {
|
||||
}
|
||||
|
||||
if app.mode() == Mode::Help {
|
||||
if let Some(help) = app.command_help() {
|
||||
frame.render_widget(
|
||||
Paragraph::new(help)
|
||||
.block(Block::bordered().title("Command help"))
|
||||
.wrap(Wrap { trim: false }),
|
||||
area,
|
||||
);
|
||||
return;
|
||||
}
|
||||
let lines = help_actions(app.help_context_mode()).map(|spec| {
|
||||
let bindings = spec
|
||||
.bindings
|
||||
@@ -231,6 +240,10 @@ fn main_text(app: &App) -> String {
|
||||
Mode::Dialog if app.discard_confirmation() => {
|
||||
"Discard all unsaved edits? Press y to discard, n or Esc to keep editing.".to_owned()
|
||||
}
|
||||
Mode::Dialog if app.command_confirmation_message().is_some() => app
|
||||
.command_confirmation_message()
|
||||
.unwrap_or_default()
|
||||
.to_owned(),
|
||||
Mode::Dialog => "Complete or cancel the active dialog.".to_owned(),
|
||||
Mode::Command => "Enter a command on the bottom line.".to_owned(),
|
||||
Mode::Help | Mode::Locked => String::new(),
|
||||
@@ -418,7 +431,11 @@ fn context_line(app: &App) -> Paragraph<'static> {
|
||||
|
||||
fn prompt_line(app: &App) -> Paragraph<'static> {
|
||||
match app.mode() {
|
||||
Mode::Command => Paragraph::new(":").style(Style::default().fg(Color::Yellow)),
|
||||
Mode::Command => Paragraph::new(format!(":{}", app.command_line().display()))
|
||||
.style(Style::default().fg(Color::Yellow)),
|
||||
Mode::Dialog if app.command_confirmation_message().is_some() => {
|
||||
Paragraph::new("confirm> y / n / Esc").style(Style::default().fg(Color::Yellow))
|
||||
}
|
||||
Mode::Dialog => Paragraph::new("dialog> ").style(Style::default().fg(Color::Yellow)),
|
||||
Mode::Editor if app.editor().is_some_and(EntryEditor::is_input_active) => {
|
||||
Paragraph::new("-- INSERT -- Esc stops input; Tab changes field; C-s saves")
|
||||
@@ -513,6 +530,51 @@ mod tests {
|
||||
assert!(!viewer_help.contains("insert entry"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_prompt_help_and_confirmation_are_rendered_without_secrets() {
|
||||
let mut app = App::new();
|
||||
app.dispatch(crate::action::Action::Command);
|
||||
for character in "otp validate otpauth://totp/test?secret=NEVER-SHOW".chars() {
|
||||
app.handle_command_input(
|
||||
crossterm::event::KeyCode::Char(character),
|
||||
crossterm::event::KeyModifiers::NONE,
|
||||
);
|
||||
}
|
||||
let prompt = render(120, 24, &app);
|
||||
assert!(prompt.contains("hidden secret-bearing arguments"));
|
||||
assert!(!prompt.contains("NEVER-SHOW"));
|
||||
|
||||
app.dispatch(crate::action::Action::Cancel);
|
||||
app.dispatch(crate::action::Action::Command);
|
||||
for character in "help git".chars() {
|
||||
app.handle_command_input(
|
||||
crossterm::event::KeyCode::Char(character),
|
||||
crossterm::event::KeyModifiers::NONE,
|
||||
);
|
||||
}
|
||||
app.handle_command_input(
|
||||
crossterm::event::KeyCode::Enter,
|
||||
crossterm::event::KeyModifiers::NONE,
|
||||
);
|
||||
assert!(render(120, 30, &app).contains("Usage: git"));
|
||||
|
||||
app.dispatch(crate::action::Action::Cancel);
|
||||
app.dispatch(crate::action::Action::Command);
|
||||
for character in "remove old/entry".chars() {
|
||||
app.handle_command_input(
|
||||
crossterm::event::KeyCode::Char(character),
|
||||
crossterm::event::KeyModifiers::NONE,
|
||||
);
|
||||
}
|
||||
app.handle_command_input(
|
||||
crossterm::event::KeyCode::Enter,
|
||||
crossterm::event::KeyModifiers::NONE,
|
||||
);
|
||||
let confirmation = render(120, 24, &app);
|
||||
assert!(confirmation.contains("Confirm remove"));
|
||||
assert!(confirmation.contains("confirm> y / n / Esc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hierarchy_selection_and_storage_indicators_have_stable_rendering() {
|
||||
let mut app = App::new();
|
||||
|
||||
Reference in New Issue
Block a user