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 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
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"]
4 changes: 4 additions & 0 deletions src/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use radix_trie::TrieKey;

/// Input event
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
pub enum Event {
/// Wildcard.
/// Useful if you want to filter out some keys.
Expand Down Expand Up @@ -120,6 +121,7 @@ impl TrieKey for Event {
}

/// Event handler
#[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
pub enum EventHandler {
/// unconditional command
Simple(Cmd),
Expand All @@ -136,6 +138,7 @@ impl From<Cmd> for EventHandler {
}

/// Give access to user input.
#[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
pub struct EventContext<'r> {
mode: EditMode,
input_mode: InputMode,
Expand Down Expand Up @@ -197,6 +200,7 @@ impl<'r> EventContext<'r> {
/// * original key pressed (when same command is bound to different key)
/// * hint
/// * ...
#[cfg_attr(docsrs, doc(cfg(feature = "custom-bindings")))]
pub trait ConditionalEventHandler: Send + Sync {
/// Takes the current input state and
/// returns the command to be performed or `None` to perform the default
Expand Down
168 changes: 109 additions & 59 deletions src/keymap.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! 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};
#[cfg(feature = "custom-bindings")]
use crate::{Event, EventContext, EventHandler};

/// The number of times one command should be repeated.
pub type RepeatCount = usize;
Expand Down Expand Up @@ -344,7 +345,8 @@ 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>,
#[cfg_attr(not(feature = "custom-bindings"), allow(dead_code))]
custom_bindings: &'b Bindings,
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 +397,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 Bindings) -> Self {
Self {
mode: config.edit_mode(),
custom_bindings,
Expand Down Expand Up @@ -452,29 +454,6 @@ impl<'b> InputState<'b> {
}
}

/// Application customized binding
fn custom_binding(
&self,
wrt: &mut dyn Refresher,
evt: &Event,
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
}
}

/// Terminal peculiar binding
fn term_binding<R: RawReader>(
rdr: &mut R,
Expand All @@ -489,38 +468,6 @@ impl<'b> InputState<'b> {
}
}

fn custom_seq_binding<R: RawReader>(
&self,
rdr: &mut R,
wrt: &mut dyn Refresher,
evt: &mut Event,
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)
}

fn emacs_digit_argument<R: RawReader>(
&mut self,
rdr: &mut R,
Expand Down Expand Up @@ -1175,3 +1122,106 @@ impl<'b> InputState<'b> {
}
}
}

#[cfg(feature = "custom-bindings")]
impl<'b> InputState<'b> {
/// Application customized binding
fn custom_binding(
&self,
wrt: &mut dyn Refresher,
evt: &Event,
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
}
}

fn custom_seq_binding<R: RawReader>(
&self,
rdr: &mut R,
wrt: &mut dyn Refresher,
evt: &mut Event,
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)
}
}

#[cfg(not(feature = "custom-bindings"))]
impl<'b> InputState<'b> {
fn custom_binding(
&self,
_: &mut dyn Refresher,
_: &Event,
_: RepeatCount,
_: bool,
) -> Option<Cmd> {
None
}

fn custom_seq_binding<R: RawReader>(
&self,
_: &mut R,
_: &mut dyn Refresher,
_: &mut Event,
_: RepeatCount,
_: bool,
) -> Result<Option<Cmd>> {
Ok(None)
}
}

cfg_if::cfg_if! {
if #[cfg(feature = "custom-bindings")] {
pub type Bindings = radix_trie::Trie<Event, EventHandler>;
} else {
enum Event {
KeySeq([KeyEvent; 1]),
}
impl From<KeyEvent> for Event {
fn from(k: KeyEvent) -> Event {
Event::KeySeq([k])
}
}
pub struct Bindings {}
impl Bindings {
pub fn new() -> Bindings {
Bindings {}
}
}
}
}
14 changes: 10 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(feature = "custom-bindings")]
mod binding;
mod command;
pub mod completion;
Expand All @@ -42,11 +44,11 @@ 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};

#[cfg(feature = "custom-bindings")]
pub use crate::binding::{ConditionalEventHandler, Event, EventContext, EventHandler};
use crate::completion::{longest_common_prefix, Candidate, Completer};
pub use crate::config::{Behavior, ColorMode, CompletionType, Config, EditMode, HistoryDuplicates};
Expand All @@ -55,7 +57,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::{Bindings, 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 +578,7 @@ pub struct Editor<H: Helper> {
helper: Option<H>,
kill_ring: Arc<Mutex<KillRing>>,
config: Config,
custom_bindings: Trie<Event, EventHandler>,
custom_bindings: Bindings,
}

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

Expand Down Expand Up @@ -826,6 +828,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 +840,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::{Bindings, Cmd, 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 = Bindings::new();
let mut input_state = InputState::new(&config, &bindings);
let keys = vec![E::ENTER];
let mut rdr: IntoIter<KeyEvent> = keys.into_iter();
Expand Down