use crate::{
buffer::{EditTransaction, UndoRecord, UndoStack},
ecs::{
components::{
buffer::{
BufferEntity, BufferRevision, BufferText, BufferUndoHistory, EditorBuffer,
EditorView,
},
cursor::{ByteIndex, CursorPosition},
vim::VimModalState,
},
events::{
cursor::{
CursorMoveDestination, CursorMoveRejected, CursorMoveRejectionReason,
CursorMoveRequestShape, CursorMoveRequested, CursorMoved,
},
edit::{
BufferEditRejected, BufferEditRequested, BufferEdited, EditHistoryApplied,
EditHistoryDirection, EditHistoryRejected, EditHistoryRequested,
PluginBufferEditProposalReceipt, PluginBufferEditProvenance,
RevisionGuardedBufferEditRequested,
},
error::{BufferEditRejectionReason, EditHistoryRejectionReason},
},
},
vim::{VimCursor, motion},
};
use bevy::prelude::{MessageReader, MessageWriter, Mut, Query, With};
struct BufferEditOutputs<'borrow, 'edited, 'rejected, 'receipt> {
edited: &'borrow mut MessageWriter<'edited, BufferEdited>,
rejected: &'borrow mut MessageWriter<'rejected, BufferEditRejected>,
proposal_receipts: &'borrow mut MessageWriter<'receipt, PluginBufferEditProposalReceipt>,
}
pub(super) fn apply_buffer_edit_requests(
mut requests: MessageReader<BufferEditRequested>,
mut guarded_requests: MessageReader<RevisionGuardedBufferEditRequested>,
mut edited: MessageWriter<BufferEdited>,
mut rejected: MessageWriter<BufferEditRejected>,
mut proposal_receipts: MessageWriter<PluginBufferEditProposalReceipt>,
mut query: Query<
(
&mut BufferText,
&mut BufferRevision,
Option<&mut BufferUndoHistory>,
),
With<EditorBuffer>,
>,
mut views: Query<(&EditorView, &mut CursorPosition, Option<&VimModalState>)>,
) {
let mut outputs = BufferEditOutputs {
edited: &mut edited,
rejected: &mut rejected,
proposal_receipts: &mut proposal_receipts,
};
for request in requests.read() {
apply_one_buffer_edit_request(
request.target,
&request.edit,
None,
&mut outputs,
&mut query,
&mut views,
);
}
for request in guarded_requests.read() {
apply_one_buffer_edit_request(
request.target,
&request.edit,
Some(&request.provenance),
&mut outputs,
&mut query,
&mut views,
);
}
}
fn apply_one_buffer_edit_request(
target: BufferEntity,
edit: &crate::buffer::BufferEdit,
provenance: Option<&PluginBufferEditProvenance>,
outputs: &mut BufferEditOutputs<'_, '_, '_, '_>,
query: &mut Query<
(
&mut BufferText,
&mut BufferRevision,
Option<&mut BufferUndoHistory>,
),
With<EditorBuffer>,
>,
views: &mut Query<(&EditorView, &mut CursorPosition, Option<&VimModalState>)>,
) {
let Ok((mut buffer_text, mut revision, history)) = query.get_mut(target.get()) else {
let reason = BufferEditRejectionReason::MissingTarget { target };
reject_buffer_edit(target, edit, &reason, provenance, outputs);
return;
};
if let Some(provenance) = provenance {
let expected = provenance.base_revision();
let actual = buffer_text.stream.revision();
if actual != expected {
let reason = BufferEditRejectionReason::StaleRevision { expected, actual };
reject_buffer_edit(target, edit, &reason, Some(provenance), outputs);
return;
}
}
let transaction = match EditTransaction::from_edit(edit.clone(), &buffer_text.stream) {
Ok(transaction) => transaction,
Err(error) => {
let reason = BufferEditRejectionReason::InvalidEdit { error };
reject_buffer_edit(target, edit, &reason, provenance, outputs);
return;
}
};
match transaction.apply(&mut buffer_text.stream) {
Ok(report) => {
revision.revision = buffer_text.stream.revision();
if let Some(mut history) = history {
history
.stack_mut()
.record_applied(transaction.into_undo_record());
}
clamp_buffer_view_cursors(target, buffer_text.stream.as_str(), views);
let (previous_revision, current_revision) = edit_revisions(report);
let _sent = outputs.edited.write(BufferEdited {
target,
previous_revision,
current_revision,
});
if let Some(provenance) = provenance
&& let Some(receipt) = PluginBufferEditProposalReceipt::owner_applied(
provenance,
target,
crate::buffer::BufferEditShape::from_edit(edit),
previous_revision,
current_revision,
)
{
let _sent = outputs.proposal_receipts.write(receipt);
}
}
Err(error) => {
let reason = BufferEditRejectionReason::InvalidEdit { error };
reject_buffer_edit(target, edit, &reason, provenance, outputs);
}
}
}
fn reject_buffer_edit(
target: BufferEntity,
edit: &crate::buffer::BufferEdit,
reason: &BufferEditRejectionReason,
provenance: Option<&PluginBufferEditProvenance>,
outputs: &mut BufferEditOutputs<'_, '_, '_, '_>,
) {
let rejected_shape = crate::buffer::BufferEditShape::from_edit(edit);
let _sent = outputs.rejected.write(BufferEditRejected {
target,
rejected: rejected_shape.clone(),
reason: reason.clone(),
});
if let Some(provenance) = provenance
&& let Some(receipt) = PluginBufferEditProposalReceipt::owner_rejected(
provenance,
target,
rejected_shape,
reason,
)
{
let _sent = outputs.proposal_receipts.write(receipt);
}
}
const fn edit_revisions(
report: crate::buffer::BufferEditReport,
) -> (BufferRevision, BufferRevision) {
(
BufferRevision {
revision: report.previous_revision,
},
BufferRevision {
revision: report.current_revision,
},
)
}
pub(super) fn apply_edit_history_requests(
mut requests: MessageReader<EditHistoryRequested>,
mut applied: MessageWriter<EditHistoryApplied>,
mut rejected: MessageWriter<EditHistoryRejected>,
mut query: Query<
(
&mut BufferText,
&mut BufferRevision,
Option<&mut BufferUndoHistory>,
),
With<EditorBuffer>,
>,
mut views: Query<(&EditorView, &mut CursorPosition, Option<&VimModalState>)>,
) {
for request in requests.read() {
let Ok((mut buffer_text, mut revision, history)) = query.get_mut(request.target.get())
else {
let _sent = rejected.write(EditHistoryRejected {
target: request.target,
direction: request.direction,
reason: EditHistoryRejectionReason::MissingTarget {
target: request.target,
},
});
continue;
};
let Some(history) = history else {
reject_empty_history(request, &mut rejected);
continue;
};
apply_history_record(
*request,
history,
&mut buffer_text,
&mut revision,
&mut applied,
&mut rejected,
&mut views,
);
}
}
fn apply_history_record(
request: EditHistoryRequested,
mut history: Mut<'_, BufferUndoHistory>,
buffer_text: &mut BufferText,
revision: &mut BufferRevision,
applied: &mut MessageWriter<EditHistoryApplied>,
rejected: &mut MessageWriter<EditHistoryRejected>,
views: &mut Query<(&EditorView, &mut CursorPosition, Option<&VimModalState>)>,
) {
let stack = history.stack_mut();
let Some(record) = pop_history_record(request.direction, stack) else {
reject_empty_history(&request, rejected);
return;
};
let edit = match request.direction {
EditHistoryDirection::Undo => record.undo_edit(),
EditHistoryDirection::Redo => record.redo_edit(),
}
.clone();
match edit.apply(&mut buffer_text.stream) {
Ok(report) => {
revision.revision = buffer_text.stream.revision();
match request.direction {
EditHistoryDirection::Undo => stack.record_undone(record),
EditHistoryDirection::Redo => stack.record_redone(record),
}
clamp_buffer_view_cursors(request.target, buffer_text.stream.as_str(), views);
let _sent = applied.write(EditHistoryApplied {
target: request.target,
direction: request.direction,
previous_revision: BufferRevision {
revision: report.previous_revision,
},
current_revision: BufferRevision {
revision: report.current_revision,
},
});
}
Err(error) => {
match request.direction {
EditHistoryDirection::Undo => stack.restore_undo(record),
EditHistoryDirection::Redo => stack.restore_redo(record),
}
let _sent = rejected.write(EditHistoryRejected {
target: request.target,
direction: request.direction,
reason: EditHistoryRejectionReason::InvalidEdit { error },
});
}
}
}
fn pop_history_record(
direction: EditHistoryDirection,
stack: &mut UndoStack,
) -> Option<UndoRecord> {
match direction {
EditHistoryDirection::Undo => stack.pop_undo(),
EditHistoryDirection::Redo => stack.pop_redo(),
}
}
fn reject_empty_history(
request: &EditHistoryRequested,
rejected: &mut MessageWriter<EditHistoryRejected>,
) {
let reason = match request.direction {
EditHistoryDirection::Undo => EditHistoryRejectionReason::EmptyUndoStack,
EditHistoryDirection::Redo => EditHistoryRejectionReason::EmptyRedoStack,
};
let _sent = rejected.write(EditHistoryRejected {
target: request.target,
direction: request.direction,
reason,
});
}
fn clamp_buffer_view_cursors(
buffer: BufferEntity,
text: &str,
views: &mut Query<(&EditorView, &mut CursorPosition, Option<&VimModalState>)>,
) {
for (view, mut cursor_position, modal_state) in views {
if view.buffer != buffer {
continue;
}
if modal_state.is_some_and(|state| state.editor.mode.is_insert()) {
cursor_position.byte_index = ByteIndex(motion::clamp_to_boundary(
text,
cursor_position.byte_index.get(),
));
cursor_position.desired_column = None;
continue;
}
let mut cursor = VimCursor::new();
cursor.set_byte_index(text, cursor_position.byte_index.get());
cursor.set_desired_column(cursor_position.desired_column);
cursor.clamp_to_text(text);
cursor_position.byte_index = ByteIndex(cursor.byte_index());
cursor_position.desired_column = cursor.desired_column();
}
}
pub(super) fn apply_cursor_move_requests(
mut requests: MessageReader<CursorMoveRequested>,
mut moved: MessageWriter<CursorMoved>,
mut rejected: MessageWriter<CursorMoveRejected>,
buffers: Query<&BufferText, With<EditorBuffer>>,
mut views: Query<(&EditorView, &mut CursorPosition), With<EditorView>>,
) {
for request in requests.read() {
let Ok((view, mut cursor_position)) = views.get_mut(request.target.get()) else {
let _sent = rejected.write(CursorMoveRejected {
target: request.target,
rejected: CursorMoveRequestShape::from(request),
reason: CursorMoveRejectionReason::MissingTarget {
target: request.target,
},
});
continue;
};
let Ok(buffer_text) = buffers.get(view.buffer.get()) else {
let _sent = rejected.write(CursorMoveRejected {
target: request.target,
rejected: CursorMoveRequestShape::from(request),
reason: CursorMoveRejectionReason::MissingBuffer {
target: request.target,
buffer: view.buffer,
},
});
continue;
};
let previous = cursor_position.byte_index;
let previous_desired_column = cursor_position.desired_column;
let text = buffer_text.stream.as_str();
match request.destination {
CursorMoveDestination::InsertCaret(caret) => {
cursor_position.byte_index =
ByteIndex(motion::clamp_to_boundary(text, caret.byte_index().get()));
cursor_position.desired_column = request.desired_column;
}
CursorMoveDestination::CursorCell(cell) => {
let mut cursor = VimCursor::new();
cursor.set_byte_index(text, cursor_position.byte_index.get());
cursor.set_desired_column(cursor_position.desired_column);
cursor.set_byte_index(text, cell.byte_index().get());
cursor.set_desired_column(request.desired_column);
cursor_position.byte_index = ByteIndex(cursor.byte_index());
cursor_position.desired_column = cursor.desired_column();
}
}
if previous != cursor_position.byte_index
|| previous_desired_column != cursor_position.desired_column
{
let _sent = moved.write(CursorMoved {
target: request.target,
previous_byte_index: previous,
current_byte_index: cursor_position.byte_index,
desired_column: cursor_position.desired_column,
});
}
}
}