Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make custom key bindings an optional Cargo feature #615

Merged
merged 7 commits into from
Apr 18, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 4 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@ jobs:
run: cargo clippy --workspace -- -D warnings
- name: Format
run: cargo fmt --all -- --check
- name: Check with no default features
run: cargo check --workspace --no-default-features
env:
RUSTFLAGS: "-D warnings"
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ unicode-width = "0.1"
unicode-segmentation = "1.0"
memchr = "2.0"
# For custom bindings
radix_trie = "0.2"
radix_trie = { version = "0.2", optional = true }
regex = { version = "1.5.4", optional = true }

[target.'cfg(unix)'.dependencies]
Expand All @@ -58,13 +58,15 @@ assert_matches = "1.2"
rustyline-derive = { version = "0.6.0", path = "rustyline-derive" }

[features]
default = ["with-dirs"]
default = ["custom-bindings", "with-dirs"]
custom-bindings = ["radix_trie"]
with-dirs = ["dirs-next"]
with-fuzzy = ["skim"]
case_insensitive_history_search = ["regex"]

[package.metadata.docs.rs]
features = ["with-dirs", "with-fuzzy"]
features = ["custom-bindings", "with-dirs", "with-fuzzy"]
all-features = false
no-default-features = true
default-target = "x86_64-unknown-linux-gnu"
rustdoc-args = ["--cfg", "docsrs"]
2 changes: 2 additions & 0 deletions src/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
Cmd, EditMode, InputMode, InputState, KeyCode, KeyEvent, Modifiers, Refresher, RepeatCount,
};

#[cfg(feature = "radix_trie")]
use radix_trie::TrieKey;

/// Input event
Expand Down Expand Up @@ -103,6 +104,7 @@ impl KeyEvent {
}
}

#[cfg(feature = "radix_trie")]
impl TrieKey for Event {
fn encode_bytes(&self) -> Vec<u8> {
match self {
Expand Down
173 changes: 133 additions & 40 deletions src/keymap.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,135 @@
//! Bindings from keys to command for Emacs and Vi modes
use log::debug;
use radix_trie::Trie;

use super::Result;
use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
use crate::tty::{self, RawReader, Term, Terminal};
use crate::{Config, EditMode, Event, EventContext, EventHandler};
use crate::{Config, EditMode, Event};
#[cfg(feature = "custom-bindings")]
use crate::{EventContext, EventHandler};
use log::debug;

#[cfg(feature = "custom-bindings")]
mod custom_bindings {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if you'd prefer for me to use cfg_if::cfg_if! here

use super::{
Cmd, Event, EventContext, EventHandler, InputState, RawReader, Refresher, RepeatCount,
Result,
};
use radix_trie::{Trie, TrieKey};
use std::borrow::Borrow;

pub struct CustomBindings(Trie<Event, EventHandler>);

impl CustomBindings {
pub fn new() -> Self {
Self(Trie::new())
}

pub fn custom_binding(
&self,
input_state: &InputState,
wrt: &mut dyn Refresher,
evt: &Event,
n: RepeatCount,
positive: bool,
) -> Option<Cmd> {
let handler = self.0.get(evt).or_else(|| self.0.get(&Event::Any));
if let Some(handler) = handler {
match handler {
EventHandler::Simple(cmd) => Some(cmd.clone()),
EventHandler::Conditional(handler) => {
let ctx = EventContext::new(input_state, wrt);
handler.handle(evt, n, positive, &ctx)
}
}
} else {
None
}
}

pub fn custom_seq_binding<R: RawReader>(
&self,
input_state: &InputState,
rdr: &mut R,
wrt: &mut dyn Refresher,
evt: &mut Event,
n: RepeatCount,
positive: bool,
) -> Result<Option<Cmd>> {
while let Some(subtrie) = self.0.get_raw_descendant(evt) {
let snd_key = rdr.next_key(true)?;
if let Event::KeySeq(ref mut key_seq) = evt {
key_seq.push(snd_key);
} else {
break;
}
let handler = subtrie.get(evt).unwrap();
if let Some(handler) = handler {
let cmd = match handler {
EventHandler::Simple(cmd) => Some(cmd.clone()),
EventHandler::Conditional(handler) => {
let ctx = EventContext::new(input_state, wrt);
handler.handle(evt, n, positive, &ctx)
}
};
if cmd.is_some() {
return Ok(cmd);
}
}
}
Ok(None)
}

pub fn insert(&mut self, key: Event, value: EventHandler) -> Option<EventHandler> {
self.0.insert(key, value)
}

pub fn remove<Q: ?Sized>(&mut self, key: &Q) -> Option<EventHandler>
where
Event: Borrow<Q>,
Q: TrieKey,
{
self.0.remove(key)
}
}
}

#[cfg(not(feature = "custom-bindings"))]
mod custom_bindings {
use super::{Cmd, Event, InputState, RawReader, Refresher, RepeatCount, Result};

pub struct CustomBindings(());

impl CustomBindings {
pub fn new() -> Self {
Self(())
}

pub fn custom_binding(
&self,
_input_state: &InputState,
_wrt: &mut dyn Refresher,
_evt: &Event,
_n: RepeatCount,
_positive: bool,
) -> Option<Cmd> {
None
}

pub fn custom_seq_binding<R: RawReader>(
&self,
_input_state: &InputState,
_rdr: &mut R,
_wrt: &mut dyn Refresher,
_evt: &mut Event,
_n: RepeatCount,
_positive: bool,
) -> Result<Option<Cmd>> {
Ok(None)
}
}
}

pub use custom_bindings::CustomBindings;

/// The number of times one command should be repeated.
pub type RepeatCount = usize;
Expand Down Expand Up @@ -344,7 +468,7 @@ pub enum InputMode {
/// Transform key(s) to commands based on current input mode
pub struct InputState<'b> {
pub(crate) mode: EditMode,
custom_bindings: &'b Trie<Event, EventHandler>,
custom_bindings: &'b CustomBindings,
pub(crate) input_mode: InputMode, // vi only ?
// numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7
num_args: i16,
Expand Down Expand Up @@ -395,7 +519,7 @@ pub trait Refresher {
}

impl<'b> InputState<'b> {
pub fn new(config: &Config, custom_bindings: &'b Trie<Event, EventHandler>) -> Self {
pub fn new(config: &Config, custom_bindings: &'b CustomBindings) -> Self {
Self {
mode: config.edit_mode(),
custom_bindings,
Expand Down Expand Up @@ -460,19 +584,8 @@ impl<'b> InputState<'b> {
n: RepeatCount,
positive: bool,
) -> Option<Cmd> {
let bindings = self.custom_bindings;
let handler = bindings.get(evt).or_else(|| bindings.get(&Event::Any));
if let Some(handler) = handler {
match handler {
EventHandler::Simple(cmd) => Some(cmd.clone()),
EventHandler::Conditional(handler) => {
let ctx = EventContext::new(self, wrt);
handler.handle(evt, n, positive, &ctx)
}
}
} else {
None
}
self.custom_bindings
.custom_binding(self, wrt, evt, n, positive)
}

/// Terminal peculiar binding
Expand All @@ -497,28 +610,8 @@ impl<'b> InputState<'b> {
n: RepeatCount,
positive: bool,
) -> Result<Option<Cmd>> {
while let Some(subtrie) = self.custom_bindings.get_raw_descendant(evt) {
let snd_key = rdr.next_key(true)?;
if let Event::KeySeq(ref mut key_seq) = evt {
key_seq.push(snd_key);
} else {
break;
}
let handler = subtrie.get(evt).unwrap();
if let Some(handler) = handler {
let cmd = match handler {
EventHandler::Simple(cmd) => Some(cmd.clone()),
EventHandler::Conditional(handler) => {
let ctx = EventContext::new(self, wrt);
handler.handle(evt, n, positive, &ctx)
}
};
if cmd.is_some() {
return Ok(cmd);
}
}
}
Ok(None)
self.custom_bindings
.custom_seq_binding(self, rdr, wrt, evt, n, positive)
}

fn emacs_digit_argument<R: RawReader>(
Expand Down
13 changes: 9 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
//! }
//! ```
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]

#[cfg_attr(not(feature = "custom-bindings"), allow(dead_code))]
mod binding;
mod command;
pub mod completion;
Expand All @@ -42,7 +44,6 @@ use std::result;
use std::sync::{Arc, Mutex};

use log::debug;
use radix_trie::Trie;
use unicode_width::UnicodeWidthStr;

use crate::tty::{RawMode, Renderer, Term, Terminal};
Expand All @@ -55,7 +56,7 @@ use crate::highlight::Highlighter;
use crate::hint::Hinter;
use crate::history::{History, SearchDirection};
pub use crate::keymap::{Anchor, At, CharSearch, Cmd, InputMode, Movement, RepeatCount, Word};
use crate::keymap::{InputState, Refresher};
use crate::keymap::{CustomBindings, InputState, Refresher};
pub use crate::keys::{KeyCode, KeyEvent, Modifiers};
use crate::kill_ring::KillRing;
pub use crate::tty::ExternalPrinter;
Expand Down Expand Up @@ -576,7 +577,7 @@ pub struct Editor<H: Helper> {
helper: Option<H>,
kill_ring: Arc<Mutex<KillRing>>,
config: Config,
custom_bindings: Trie<Event, EventHandler>,
custom_bindings: CustomBindings,
}

#[allow(clippy::new_without_default)]
Expand All @@ -603,7 +604,7 @@ impl<H: Helper> Editor<H> {
helper: None,
kill_ring: Arc::new(Mutex::new(KillRing::new(60))),
config,
custom_bindings: Trie::new(),
custom_bindings: CustomBindings::new(),
}
}

Expand Down Expand Up @@ -826,6 +827,8 @@ impl<H: Helper> Editor<H> {
}

/// Bind a sequence to a command.
#[cfg(feature = "custom-bindings")]
#[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
pub fn bind_sequence<E: Into<Event>, R: Into<EventHandler>>(
&mut self,
key_seq: E,
Expand All @@ -836,6 +839,8 @@ impl<H: Helper> Editor<H> {
}

/// Remove a binding for the given sequence.
#[cfg(feature = "custom-bindings")]
#[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
pub fn unbind_sequence<E: Into<Event>>(&mut self, key_seq: E) -> Option<EventHandler> {
self.custom_bindings
.remove(&Event::normalize(key_seq.into()))
Expand Down
6 changes: 2 additions & 4 deletions src/test/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use std::vec::IntoIter;

use radix_trie::Trie;

use crate::completion::Completer;
use crate::config::{Config, EditMode};
use crate::edit::init_state;
use crate::highlight::Highlighter;
use crate::hint::Hinter;
use crate::keymap::{Cmd, InputState};
use crate::keymap::{Cmd, CustomBindings, InputState};
use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
use crate::tty::Sink;
use crate::validate::Validator;
Expand Down Expand Up @@ -58,7 +56,7 @@ fn complete_line() {
let helper = Some(SimpleCompleter);
let mut s = init_state(&mut out, "rus", 3, helper.as_ref(), &history);
let config = Config::default();
let bindings = Trie::new();
let bindings = CustomBindings::new();
let mut input_state = InputState::new(&config, &bindings);
let keys = vec![E::ENTER];
let mut rdr: IntoIter<KeyEvent> = keys.into_iter();
Expand Down