use crate::{
StatusInfoText,
buffer::{BufferFile, BufferWriteError},
ecs::{
components::buffer::{BufferText, EditorBuffer, ViewEntity},
events::{
command::{CommandFailed, CommandRejected, CommandRejectionReason, CommandRequested},
lifecycle::{LifecycleRequested, ShutdownRequest},
status::{StatusMessage, StatusMessageRequested},
},
},
fs_utils::FilesystemConfig,
presentation::{ResolvedTheme, TransparencyMode},
text_stream::TextByteStream,
vim::{QuitPolicy, VimCommand, VimError, WriteQuitPolicy},
};
use bevy::{
ecs::system::SystemParam,
prelude::{MessageReader, MessageWriter, Query, Res, ResMut, With},
window::{PrimaryWindow, Window},
};
use super::error::BufferCommandError;
#[allow(clippy::needless_pass_by_value)]
pub(super) fn apply_command_requests(
mut requests: MessageReader<CommandRequested>,
mut state: CommandRequestState,
) {
let filesystem_config = state.filesystem_config.as_deref();
for request in requests.read() {
let Ok((buffer_text, mut buffer_file)) = state.buffers.get_mut(request.target.get()) else {
let _sent = state.rejected.write(CommandRejected {
target: request.target,
source: request.source,
command: request.command,
reason: CommandRejectionReason::MissingTarget {
target: request.target,
},
});
continue;
};
match execute_buffer_command(
request.command,
&buffer_text.stream,
&mut buffer_file,
filesystem_config,
) {
Ok(outcome) => {
apply_command_outcome(
request.source,
&outcome,
&mut state.theme,
&mut state.windows,
&mut state.status_requests,
&mut state.lifecycle_requests,
);
}
Err(error) => {
let _sent = state.failed.write(CommandFailed {
target: request.target,
source: request.source,
command: request.command,
diagnostic: error.diagnostic(),
});
let _sent = state.status_requests.write(StatusMessageRequested {
target: request.source,
message: StatusMessage::Error(error.status_error()),
});
}
}
}
}
#[derive(SystemParam)]
pub(super) struct CommandRequestState<'w, 's> {
failed: MessageWriter<'w, CommandFailed>,
rejected: MessageWriter<'w, CommandRejected>,
status_requests: MessageWriter<'w, StatusMessageRequested>,
lifecycle_requests: MessageWriter<'w, LifecycleRequested>,
filesystem_config: Option<Res<'w, FilesystemConfig>>,
theme: Option<ResMut<'w, ResolvedTheme>>,
windows: Query<'w, 's, &'static mut Window, With<PrimaryWindow>>,
buffers: Query<'w, 's, (&'static BufferText, &'static mut BufferFile), With<EditorBuffer>>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum CommandOutcome {
Continue(StatusDirective),
Shutdown {
status: StatusDirective,
request: ShutdownRequest,
},
Presentation {
transparency_mode: TransparencyMode,
status: StatusDirective,
},
}
impl CommandOutcome {
fn continue_with(status_text: Option<String>) -> Self {
Self::Continue(StatusDirective::from_status_text(status_text))
}
fn shutdown(status_text: Option<String>) -> Self {
Self::Shutdown {
status: StatusDirective::from_status_text(status_text),
request: ShutdownRequest::successful_user_command(),
}
}
const fn status(&self) -> &StatusDirective {
match self {
Self::Continue(status)
| Self::Shutdown { status, .. }
| Self::Presentation { status, .. } => status,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum StatusDirective {
Clear,
Info(String),
}
impl StatusDirective {
fn from_status_text(status_text: Option<String>) -> Self {
status_text.map_or(Self::Clear, Self::Info)
}
}
fn execute_buffer_command(
command: VimCommand,
stream: &TextByteStream,
buffer: &mut BufferFile,
config: Option<&FilesystemConfig>,
) -> Result<CommandOutcome, BufferCommandError> {
match command {
VimCommand::Write => {
write_buffer(stream, buffer, config).map(CommandOutcome::continue_with)
}
VimCommand::Quit(QuitPolicy::PreserveChanges) if buffer.is_modified(stream) => {
Err(BufferCommandError::Vim(VimError::NoWriteSinceLastChange))
}
VimCommand::Quit(QuitPolicy::PreserveChanges | QuitPolicy::Force) => {
Ok(CommandOutcome::shutdown(None))
}
VimCommand::WriteQuit(WriteQuitPolicy::AlwaysWrite) => {
write_buffer(stream, buffer, config).map(CommandOutcome::shutdown)
}
VimCommand::WriteQuit(WriteQuitPolicy::WriteIfModified) => {
if buffer.is_modified(stream) {
write_buffer(stream, buffer, config).map(CommandOutcome::shutdown)
} else {
Ok(CommandOutcome::shutdown(None))
}
}
VimCommand::SetTransparency(transparency_mode) => Ok(CommandOutcome::Presentation {
transparency_mode,
status: StatusDirective::Info(transparency_status_text(transparency_mode)),
}),
}
}
fn transparency_status_text(transparency_mode: TransparencyMode) -> String {
match transparency_mode {
TransparencyMode::Opaque => String::from("transparency=opaque"),
TransparencyMode::Transparent => String::from("transparency=transparent"),
}
}
fn write_buffer(
stream: &TextByteStream,
buffer: &mut BufferFile,
config: Option<&FilesystemConfig>,
) -> Result<Option<String>, BufferCommandError> {
if buffer.path().is_none() {
return Err(BufferCommandError::Vim(VimError::NoFileName));
}
let Some(config) = config else {
return Err(BufferCommandError::FilesystemUnavailable);
};
buffer
.write(stream, config)
.map(|report| Some(report.status_text()))
.map_err(|error| match error {
BufferWriteError::NoFileName => BufferCommandError::Vim(VimError::NoFileName),
BufferWriteError::File(source) => BufferCommandError::Write(source),
})
}
fn apply_command_outcome(
target: ViewEntity,
outcome: &CommandOutcome,
theme: &mut Option<ResMut<ResolvedTheme>>,
windows: &mut Query<&mut Window, With<PrimaryWindow>>,
status_requests: &mut MessageWriter<StatusMessageRequested>,
lifecycle_requests: &mut MessageWriter<LifecycleRequested>,
) {
if let CommandOutcome::Presentation {
transparency_mode, ..
} = outcome
{
if let Some(theme) = theme.as_deref_mut() {
theme.transparency_mode = *transparency_mode;
}
if let Ok(mut window) = windows.single_mut() {
window.transparent = transparency_mode.is_window_transparent();
}
}
match outcome.status() {
StatusDirective::Clear => {
let _sent = status_requests.write(StatusMessageRequested {
target,
message: StatusMessage::Clear,
});
}
StatusDirective::Info(status_text) => {
let _sent = status_requests.write(StatusMessageRequested {
target,
message: StatusMessage::Info(StatusInfoText::trusted_generated(
status_text.to_owned(),
)),
});
}
}
if let CommandOutcome::Shutdown { request, .. } = outcome {
let _sent = lifecycle_requests.write(LifecycleRequested::Shutdown(*request));
}
}