use super::state::{EditorState, EditorStateRef};
use reedline::{EditCommand, EditMode, PromptEditMode, ReedlineEvent};
pub trait KeyCondition: Send + Sync {
fn check(&self, state: &EditorState) -> bool;
}
#[derive(Debug, Clone, Copy)]
pub struct CursorAtBegin;
impl KeyCondition for CursorAtBegin {
fn check(&self, state: &EditorState) -> bool {
state.cursor_at_begin()
}
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct BufferEmpty;
impl KeyCondition for BufferEmpty {
fn check(&self, state: &EditorState) -> bool {
state.is_empty()
}
}
#[derive(Debug, Clone, Copy)]
pub struct BufferKnownEmpty;
impl KeyCondition for BufferKnownEmpty {
fn check(&self, state: &EditorState) -> bool {
state.is_empty() && !state.uncertain
}
}
pub type EventMatcher = Box<dyn Fn(&ReedlineEvent) -> bool + Send + Sync>;
pub struct ConditionalRule {
pub match_event: EventMatcher,
pub condition: Box<dyn KeyCondition>,
pub fallback_event: ReedlineEvent,
}
pub struct ConditionalEditMode<E: EditMode> {
inner: E,
state: EditorStateRef,
rules: Vec<ConditionalRule>,
completion_min_chars: Option<usize>,
tree_sitter_word_nav: bool,
}
impl<E: EditMode> ConditionalEditMode<E> {
pub fn new(inner: E, state: EditorStateRef) -> Self {
Self {
inner,
state,
rules: Vec::new(),
completion_min_chars: None,
tree_sitter_word_nav: false,
}
}
pub fn with_completion_min_chars(mut self, min_chars: Option<usize>) -> Self {
self.completion_min_chars = min_chars;
self
}
pub fn with_tree_sitter_word_nav(mut self, enabled: bool) -> Self {
self.tree_sitter_word_nav = enabled;
self
}
pub fn with_rule(mut self, rule: ConditionalRule) -> Self {
self.rules.push(rule);
self
}
fn apply_rules(&self, event: ReedlineEvent) -> ReedlineEvent {
let state = self.state.lock().unwrap();
for rule in &self.rules {
if (rule.match_event)(&event) && !rule.condition.check(&state) {
return rule.fallback_event.clone();
}
}
event
}
pub(super) fn handle_tree_sitter_word_nav(
&self,
event: &ReedlineEvent,
) -> Option<ReedlineEvent> {
use crate::editor::word_nav::{token_left_position, token_right_position};
if !self.tree_sitter_word_nav {
return None;
}
let state = self.state.lock().unwrap();
if state.uncertain {
return None;
}
match event {
ReedlineEvent::Edit(commands) if commands.len() == 1 => match &commands[0] {
EditCommand::MoveWordLeft { select } => {
let target = token_left_position(&state.buffer, state.cursor_pos);
Some(Self::create_move_event(state.cursor_pos, target, *select))
}
EditCommand::MoveWordRight { select } => {
let target = token_right_position(&state.buffer, state.cursor_pos);
Some(Self::create_move_event(state.cursor_pos, target, *select))
}
_ => None,
},
ReedlineEvent::UntilFound(events) => {
for (i, e) in events.iter().enumerate() {
if let ReedlineEvent::Edit(commands) = e
&& commands.len() == 1
{
if let EditCommand::MoveWordRight { select } = &commands[0] {
let target = token_right_position(&state.buffer, state.cursor_pos);
let move_event =
Self::create_move_event(state.cursor_pos, target, *select);
let mut new_events = events.clone();
new_events[i] = move_event;
return Some(ReedlineEvent::UntilFound(new_events));
}
if let EditCommand::MoveWordLeft { select } = &commands[0] {
let target = token_left_position(&state.buffer, state.cursor_pos);
let move_event =
Self::create_move_event(state.cursor_pos, target, *select);
let mut new_events = events.clone();
new_events[i] = move_event;
return Some(ReedlineEvent::UntilFound(new_events));
}
}
}
None
}
_ => None,
}
}
pub(super) fn create_move_event(current: usize, target: usize, select: bool) -> ReedlineEvent {
if current == target {
return ReedlineEvent::None;
}
if target < current {
let diff = current - target;
let commands: Vec<EditCommand> =
std::iter::repeat_n(EditCommand::MoveLeft { select }, diff).collect();
ReedlineEvent::Edit(commands)
} else {
let diff = target - current;
let commands: Vec<EditCommand> =
std::iter::repeat_n(EditCommand::MoveRight { select }, diff).collect();
ReedlineEvent::Edit(commands)
}
}
}
impl<E: EditMode> EditMode for ConditionalEditMode<E> {
fn parse_event(&mut self, event: reedline::ReedlineRawEvent) -> ReedlineEvent {
let inner_event = self.inner.parse_event(event);
let rules_event = self.apply_rules(inner_event);
let final_event = self
.handle_tree_sitter_word_nav(&rules_event)
.unwrap_or(rules_event);
{
let mut state = self.state.lock().unwrap();
state.update_from_event(&final_event);
}
if let Some(min_chars) = self.completion_min_chars {
let state = self.state.lock().unwrap();
if state.buffer_len >= min_chars
&& !state.uncertain
&& is_character_insert(&final_event)
{
return ReedlineEvent::Multiple(vec![
final_event,
ReedlineEvent::Menu("completion_menu".to_string()),
]);
}
}
final_event
}
fn edit_mode(&self) -> PromptEditMode {
self.inner.edit_mode()
}
}
fn is_character_insert(event: &ReedlineEvent) -> bool {
match event {
ReedlineEvent::Edit(commands) => commands.iter().any(|cmd| {
matches!(
cmd,
EditCommand::InsertChar(_) | EditCommand::InsertString(_)
)
}),
ReedlineEvent::Multiple(events) => events.iter().any(is_character_insert),
_ => false,
}
}