Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
952 changes: 622 additions & 330 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ crossbeam = { version = "0.8.2", optional = true }
crossterm = { version = "0.29.0", features = ["serde"] }
fd-lock = "4.0.2"
itertools = "0.13.0"
keybindings = "0.0.2"
nu-ansi-term = "0.50.0"
rusqlite = { version = "0.37.0", optional = true }
serde = { version = "1.0", features = ["derive"] }
Expand Down
11 changes: 9 additions & 2 deletions examples/helix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ use reedline::{DefaultPrompt, Helix, Reedline, Signal};
use std::io;

fn main() -> io::Result<()> {
println!("Helix edit mode demo:\nAbort with Ctrl-C");
println!(
"Helix edit mode demo:
Default mode is insert (`:` prompt), so you can type words.
Press Esc for normal mode.
Press `i` to return to insert mode, or `a` to insert after the current selection.
Only `h`/`l` motions are currently implemented.
Abort with Ctrl-C"
);

let prompt = DefaultPrompt::default();
let mut line_editor = Reedline::create().with_edit_mode(Box::new(Helix));
let mut line_editor = Reedline::create().with_edit_mode(Box::new(Helix::default()));

loop {
let sig = line_editor.read_line(&prompt)?;
Expand Down
64 changes: 0 additions & 64 deletions src/edit_mode/helix.rs

This file was deleted.

25 changes: 25 additions & 0 deletions src/edit_mode/helix/action.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::enums::{EditCommand, ReedlineEvent};

#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub(super) enum HelixAction {
Type(char),
MoveCharRight,
MoveCharLeft,
#[default]
NoOp,
}

impl HelixAction {
pub(super) fn into_reedline_event(self) -> Option<ReedlineEvent> {
match self {
HelixAction::Type(c) => Some(ReedlineEvent::Edit(vec![EditCommand::InsertChar(c)])),
HelixAction::MoveCharLeft => Some(ReedlineEvent::Edit(vec![EditCommand::MoveLeft {
select: false,
}])),
HelixAction::MoveCharRight => Some(ReedlineEvent::Edit(vec![EditCommand::MoveRight {
select: false,
}])),
HelixAction::NoOp => None,
}
}
}
70 changes: 70 additions & 0 deletions src/edit_mode/helix/bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use crossterm::event::{KeyCode, KeyModifiers};
use keybindings::{EdgeEvent, EdgePath, EdgeRepeat, EmptyKeyClass, InputBindings};

use super::{
key::HelixKey,
mode::{HelixMachine, HelixMode, HelixStep},
};

#[derive(Default)]
pub(super) struct HelixBindings;

impl HelixBindings {
fn add_single_keypress_mapping(
machine: &mut HelixMachine,
mode: HelixMode,
code: KeyCode,
step: HelixStep,
) {
let path: &EdgePath<HelixKey, EmptyKeyClass> = &[(
EdgeRepeat::Once,
EdgeEvent::Key(HelixKey::new(code, KeyModifiers::NONE)),
)];
machine.add_mapping(mode, path, &step);
}

fn add_bindings(
machine: &mut HelixMachine,
mode: HelixMode,
bindings: &[(KeyCode, HelixStep)],
) {
for (code, step) in bindings {
Self::add_single_keypress_mapping(machine, mode, *code, step.clone());
}
}
}

impl InputBindings<HelixKey, HelixStep> for HelixBindings {
fn setup(&self, machine: &mut HelixMachine) {
let insert_bindings = [(KeyCode::Esc, (None, Some(HelixMode::Normal)))];
let normal_bindings = [
(KeyCode::Char('i'), (None, Some(HelixMode::Insert))),
(
KeyCode::Char('h'),
(Some(super::action::HelixAction::MoveCharLeft), None),
),
(
KeyCode::Left,
(Some(super::action::HelixAction::MoveCharLeft), None),
),
(
KeyCode::Char('l'),
(Some(super::action::HelixAction::MoveCharRight), None),
),
(
KeyCode::Right,
(Some(super::action::HelixAction::MoveCharRight), None),
),
(
KeyCode::Char('a'),
(
Some(super::action::HelixAction::MoveCharRight),
Some(HelixMode::Insert),
),
),
];

Self::add_bindings(machine, HelixMode::Insert, &insert_bindings);
Self::add_bindings(machine, HelixMode::Normal, &normal_bindings);
}
}
54 changes: 54 additions & 0 deletions src/edit_mode/helix/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use crate::enums::{ReedlineEvent, ReedlineRawEvent};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use keybindings::BindingMachine;

use super::{action::HelixAction, mode::HelixMachine};

pub(super) fn parse_event(machine: &mut HelixMachine, event: ReedlineRawEvent) -> ReedlineEvent {
let Some(key_event) = KeyEvent::try_from(event).ok() else {
return ReedlineEvent::None;
};

handle_key_event(machine, key_event)
}

fn is_interrupt_event(key_event: &KeyEvent) -> bool {
matches!(
key_event,
KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
..
}
)
}

fn handle_key_event(machine: &mut HelixMachine, key_event: KeyEvent) -> ReedlineEvent {
if is_interrupt_event(&key_event) {
return ReedlineEvent::CtrlC;
}

let (action, mode_changed) = apply_key_event(machine, key_event);

action
.and_then(HelixAction::into_reedline_event)
.unwrap_or_else(|| mode_change_event(mode_changed))
}

fn apply_key_event(machine: &mut HelixMachine, key_event: KeyEvent) -> (Option<HelixAction>, bool) {
let previous_mode = machine.mode();
machine.input_key(key_event.into());

let mode_changed = machine.mode() != previous_mode;
let action = machine.pop().map(|(action, _ctx)| action);

(action, mode_changed)
}

fn mode_change_event(mode_changed: bool) -> ReedlineEvent {
if mode_changed {
ReedlineEvent::Repaint
} else {
ReedlineEvent::None
}
}
46 changes: 46 additions & 0 deletions src/edit_mode/helix/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use keybindings::InputKey;

/// A simple `InputKey` implementation around `crossterm` types.
///
/// This avoids pulling in the `crossterm` types used by `modalkit` (which can be a different
/// version than the one used by `reedline`).
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
pub(super) struct HelixKey {
code: KeyCode,
modifiers: KeyModifiers,
}

impl HelixKey {
pub(super) fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
Self { code, modifiers }
}
}

impl From<KeyEvent> for HelixKey {
fn from(event: KeyEvent) -> Self {
Self::new(event.code, event.modifiers)
}
}

impl InputKey for HelixKey {
type Error = std::convert::Infallible;

fn decompose(&mut self) -> Option<Self> {
None
}

fn from_macro_str(mstr: &str) -> Result<Vec<Self>, Self::Error> {
Ok(mstr
.chars()
.map(|c| HelixKey::new(KeyCode::Char(c), KeyModifiers::NONE))
.collect())
}

fn get_char(&self) -> Option<char> {
match self.code {
KeyCode::Char(c) => Some(c),
_ => None,
}
}
}
Loading
Loading