Implement TUI colon command mode
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
pub mod action;
|
||||
pub mod app;
|
||||
pub mod command;
|
||||
pub mod editor;
|
||||
pub mod runtime;
|
||||
pub mod sidebar;
|
||||
@@ -100,6 +101,17 @@ pub fn run(terminal: &mut DefaultTerminal) -> io::Result<()> {
|
||||
{
|
||||
apply_authentication_event(&mut app, coordinator, &executor, event);
|
||||
}
|
||||
if let Some(effect) = app.handle_command_input(key.code, key.modifiers) {
|
||||
key_resolver.reset();
|
||||
apply_app_effect(
|
||||
&mut app,
|
||||
effect,
|
||||
&mut authentication,
|
||||
&executor,
|
||||
&mut clipboard_cancellations,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if !key.modifiers.intersects(
|
||||
crossterm::event::KeyModifiers::CONTROL
|
||||
| crossterm::event::KeyModifiers::ALT
|
||||
@@ -116,86 +128,16 @@ pub fn run(terminal: &mut DefaultTerminal) -> io::Result<()> {
|
||||
}
|
||||
} else {
|
||||
match key_resolver.feed(app.mode(), key.code, key.modifiers) {
|
||||
KeyResolution::Action(action) => match app.dispatch(action) {
|
||||
AppEffect::RefreshTree => {
|
||||
if let Some(config) = app.config().cloned() {
|
||||
let token = app.begin_latest_request();
|
||||
executor.submit(token, move || {
|
||||
load_tree(&config).map(AsyncPayload::Refreshed)
|
||||
});
|
||||
}
|
||||
}
|
||||
AppEffect::AuthenticateEntry(entry) => {
|
||||
if let Some(coordinator) = authentication.as_mut() {
|
||||
coordinator.request(entry);
|
||||
} else {
|
||||
app.authentication_failed(
|
||||
"operating-system secure storage is unavailable".to_owned(),
|
||||
);
|
||||
}
|
||||
}
|
||||
AppEffect::CopyFocused(value) => {
|
||||
if let Some(config) = app.config().cloned() {
|
||||
let (cancel, cancellation) = mpsc::channel();
|
||||
clipboard_cancellations.register(cancel);
|
||||
let token = app.begin_request();
|
||||
executor.submit(token, move || {
|
||||
let mut clipboard =
|
||||
ironstorage::presentation::NativeClipboardManager::system(
|
||||
config.clipboard_timeout(),
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
clipboard
|
||||
.copy_with(&value, |duration| {
|
||||
match cancellation.recv_timeout(duration) {
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => {
|
||||
ironstorage::presentation::ClipboardWait::Elapsed
|
||||
}
|
||||
Ok(()) | Err(mpsc::RecvTimeoutError::Disconnected) => {
|
||||
ironstorage::presentation::ClipboardWait::Cancelled
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(AsyncPayload::ClipboardFinished)
|
||||
.map_err(|error| error.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
AppEffect::GenerateField(target) => {
|
||||
let token = app.begin_request();
|
||||
executor.submit(token, move || {
|
||||
ironstorage::generate::GeneratorConfig::pass_defaults()
|
||||
.generate_secret(None, false)
|
||||
.map(|password| AsyncPayload::GeneratedField {
|
||||
target,
|
||||
password,
|
||||
})
|
||||
.map_err(|error| error.to_string())
|
||||
});
|
||||
}
|
||||
AppEffect::SaveDocument {
|
||||
config,
|
||||
entry,
|
||||
editor,
|
||||
} => {
|
||||
let token = app.begin_request();
|
||||
executor.submit(token, move || {
|
||||
Ok(save_document(&config, entry, editor))
|
||||
});
|
||||
}
|
||||
AppEffect::ManualLock => {
|
||||
if let Some(coordinator) = authentication.as_mut()
|
||||
&& let Err(error) = coordinator.lock()
|
||||
{
|
||||
app.forced_relock("manual lock requested");
|
||||
app.report_status(format!(
|
||||
"locked after secure-store cleanup failed: {error}"
|
||||
));
|
||||
}
|
||||
}
|
||||
AppEffect::OpenWorkflow(_) => {}
|
||||
AppEffect::None => {}
|
||||
},
|
||||
KeyResolution::Action(action) => {
|
||||
let effect = app.dispatch(action);
|
||||
apply_app_effect(
|
||||
&mut app,
|
||||
effect,
|
||||
&mut authentication,
|
||||
&executor,
|
||||
&mut clipboard_cancellations,
|
||||
);
|
||||
}
|
||||
KeyResolution::Pending => {
|
||||
app.report_status("Key sequence pending; Esc cancels".to_owned());
|
||||
}
|
||||
@@ -218,6 +160,86 @@ pub fn run(terminal: &mut DefaultTerminal) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_app_effect(
|
||||
app: &mut App,
|
||||
effect: AppEffect,
|
||||
authentication: &mut Option<AuthenticationCoordinator>,
|
||||
executor: &AsyncExecutor,
|
||||
clipboard_cancellations: &mut ClipboardCancellations,
|
||||
) {
|
||||
match effect {
|
||||
AppEffect::RefreshTree => {
|
||||
if let Some(config) = app.config().cloned() {
|
||||
let token = app.begin_latest_request();
|
||||
executor.submit(token, move || {
|
||||
load_tree(&config).map(AsyncPayload::Refreshed)
|
||||
});
|
||||
}
|
||||
}
|
||||
AppEffect::AuthenticateEntry(entry) => {
|
||||
if let Some(coordinator) = authentication.as_mut() {
|
||||
coordinator.request(entry);
|
||||
} else {
|
||||
app.authentication_failed(
|
||||
"operating-system secure storage is unavailable".to_owned(),
|
||||
);
|
||||
}
|
||||
}
|
||||
AppEffect::CopyFocused(value) => {
|
||||
if let Some(config) = app.config().cloned() {
|
||||
let (cancel, cancellation) = mpsc::channel();
|
||||
clipboard_cancellations.register(cancel);
|
||||
let token = app.begin_request();
|
||||
executor.submit(token, move || {
|
||||
let mut clipboard = ironstorage::presentation::NativeClipboardManager::system(
|
||||
config.clipboard_timeout(),
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
clipboard
|
||||
.copy_with(&value, |duration| {
|
||||
match cancellation.recv_timeout(duration) {
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => {
|
||||
ironstorage::presentation::ClipboardWait::Elapsed
|
||||
}
|
||||
Ok(()) | Err(mpsc::RecvTimeoutError::Disconnected) => {
|
||||
ironstorage::presentation::ClipboardWait::Cancelled
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(AsyncPayload::ClipboardFinished)
|
||||
.map_err(|error| error.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
AppEffect::GenerateField(target) => {
|
||||
let token = app.begin_request();
|
||||
executor.submit(token, move || {
|
||||
ironstorage::generate::GeneratorConfig::pass_defaults()
|
||||
.generate_secret(None, false)
|
||||
.map(|password| AsyncPayload::GeneratedField { target, password })
|
||||
.map_err(|error| error.to_string())
|
||||
});
|
||||
}
|
||||
AppEffect::SaveDocument {
|
||||
config,
|
||||
entry,
|
||||
editor,
|
||||
} => {
|
||||
let token = app.begin_request();
|
||||
executor.submit(token, move || Ok(save_document(&config, entry, editor)));
|
||||
}
|
||||
AppEffect::ManualLock => {
|
||||
if let Some(coordinator) = authentication.as_mut()
|
||||
&& let Err(error) = coordinator.lock()
|
||||
{
|
||||
app.forced_relock("manual lock requested");
|
||||
app.report_status(format!("locked after secure-store cleanup failed: {error}"));
|
||||
}
|
||||
}
|
||||
AppEffect::RunCommand(_) | AppEffect::OpenWorkflow(_) | AppEffect::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_dispatchable_key_kind(kind: KeyEventKind) -> bool {
|
||||
matches!(kind, KeyEventKind::Press | KeyEventKind::Repeat)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user