-
Notifications
You must be signed in to change notification settings - Fork 178
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
Conditional Bind Sequences #293
Changes from 7 commits
f78700f
e9377fe
b9e654f
1a58df5
0d41a9b
7f3cbb4
bbe6314
e880088
2a52ede
63581d3
0053242
31cf971
a515d35
5439a15
86c2da5
45df451
7e36c69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
//! Bindings from keys to command for Emacs and Vi modes | ||
use std::collections::HashMap; | ||
use std::fmt; | ||
use std::sync::{Arc, RwLock}; | ||
|
||
use log::debug; | ||
|
@@ -13,6 +14,58 @@ use crate::tty::{RawReader, Term, Terminal}; | |
/// The number of times one command should be repeated. | ||
pub type RepeatCount = usize; | ||
|
||
/// Custom dynamic action | ||
#[derive(Clone)] | ||
pub struct Action { | ||
pub name: String, | ||
/// Takes the currently edited `line` with the cursor `pos`ition and | ||
/// returns the command to be performed or `None` to perform the default | ||
/// one. | ||
pub action: fn(line: &str, pos: usize, ctx: &ActionContext) -> Option<Cmd>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like the An
would be much more flexible. Also I think it would be better to not make this public to allow non-breaking changes in the future. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of the time, you just need to check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For example I've been playing around with having a key bindings to insert bits of text with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have just merged There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mainly because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
impl std::fmt::Debug for Action { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.debug_struct("Action").field("name", &self.name).finish() | ||
} | ||
} | ||
|
||
impl PartialEq for Action { | ||
fn eq(&self, other: &Self) -> bool { | ||
self.name == other.name | ||
} | ||
} | ||
|
||
pub struct ActionContext<'r> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You've made the struct public but it's isn't exposed anywhere so no documentation gets generated for it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad. |
||
mode: EditMode, | ||
input_mode: InputMode, | ||
wrt: &'r dyn Refresher, | ||
} | ||
|
||
impl<'r> ActionContext<'r> { | ||
fn new(is: &InputState, wrt: &'r dyn Refresher) -> Self { | ||
ActionContext { | ||
mode: is.mode, | ||
input_mode: is.input_mode, | ||
wrt, | ||
} | ||
} | ||
|
||
pub fn mode(&self) -> EditMode { | ||
self.mode | ||
} | ||
|
||
/// vi only | ||
pub fn input_mode(&self) -> InputMode { | ||
self.input_mode | ||
} | ||
|
||
/// Returns `true` if there is a hint displayed. | ||
pub fn has_hint(&self) -> bool { | ||
self.wrt.has_hint() | ||
} | ||
} | ||
|
||
/// Commands | ||
// #[non_exhaustive] | ||
#[derive(Debug, Clone, PartialEq)] | ||
|
@@ -41,6 +94,8 @@ pub enum Cmd { | |
EndOfHistory, | ||
/// forward-search-history | ||
ForwardSearchHistory, | ||
/// Custom dynamic action | ||
Custom(Action), | ||
/// history-search-backward | ||
HistorySearchBackward, | ||
/// history-search-forward | ||
|
@@ -267,8 +322,8 @@ impl Movement { | |
} | ||
} | ||
|
||
#[derive(PartialEq)] | ||
enum InputMode { | ||
#[derive(Clone, Copy, PartialEq)] | ||
pub enum InputMode { | ||
/// Vi Command/Alternate | ||
Command, | ||
/// Insert/Input mode | ||
|
@@ -314,6 +369,10 @@ pub trait Refresher { | |
fn is_cursor_at_end(&self) -> bool; | ||
/// Returns `true` if there is a hint displayed. | ||
fn has_hint(&self) -> bool; | ||
/// currently edited line | ||
fn line(&self) -> &str; | ||
/// Current cursor position (byte position) | ||
fn pos(&self) -> usize; | ||
} | ||
|
||
impl InputState { | ||
|
@@ -389,6 +448,21 @@ impl InputState { | |
} | ||
} | ||
|
||
fn custom_binding(&self, wrt: &mut dyn Refresher, key: KeyPress) -> Option<Cmd> { | ||
let bindings = self.custom_bindings.read().unwrap(); | ||
let cmd = bindings.get(&key); | ||
if let Some(cmd) = cmd { | ||
debug!(target: "rustyline", "Custom command: {:?}", cmd); | ||
if let Cmd::Custom(action) = cmd { | ||
let ctx = ActionContext::new(self, wrt); | ||
return (action.action)(wrt.line(), wrt.pos(), &ctx); | ||
} else { | ||
return Some(cmd.clone()); | ||
} | ||
} | ||
None | ||
} | ||
|
||
fn emacs<R: RawReader>( | ||
&mut self, | ||
rdr: &mut R, | ||
|
@@ -402,16 +476,13 @@ impl InputState { | |
key = self.emacs_digit_argument(rdr, wrt, digit)?; | ||
} | ||
let (n, positive) = self.emacs_num_args(); // consume them in all cases | ||
{ | ||
let bindings = self.custom_bindings.read().unwrap(); | ||
if let Some(cmd) = bindings.get(&key) { | ||
debug!(target: "rustyline", "Custom command: {:?}", cmd); | ||
return Ok(if cmd.is_repeatable() { | ||
cmd.redo(Some(n), wrt) | ||
} else { | ||
cmd.clone() | ||
}); | ||
} | ||
|
||
if let Some(cmd) = self.custom_binding(wrt, key) { | ||
return Ok(if cmd.is_repeatable() { | ||
cmd.redo(Some(n), wrt) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This replaces number returned from the custom action. I.e. I can't return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. This should not be done for |
||
} else { | ||
cmd | ||
}); | ||
} | ||
let cmd = match key { | ||
KeyPress::Char(c) => { | ||
|
@@ -547,20 +618,16 @@ impl InputState { | |
} | ||
let no_num_args = self.num_args == 0; | ||
let n = self.vi_num_args(); // consume them in all cases | ||
{ | ||
let bindings = self.custom_bindings.read().unwrap(); | ||
if let Some(cmd) = bindings.get(&key) { | ||
debug!(target: "rustyline", "Custom command: {:?}", cmd); | ||
return Ok(if cmd.is_repeatable() { | ||
if no_num_args { | ||
cmd.redo(None, wrt) | ||
} else { | ||
cmd.redo(Some(n), wrt) | ||
} | ||
if let Some(cmd) = self.custom_binding(wrt, key) { | ||
return Ok(if cmd.is_repeatable() { | ||
if no_num_args { | ||
cmd.redo(None, wrt) | ||
} else { | ||
cmd.clone() | ||
}); | ||
} | ||
cmd.redo(Some(n), wrt) | ||
} | ||
} else { | ||
cmd | ||
}); | ||
} | ||
let cmd = match key { | ||
KeyPress::Char('$') | | ||
|
@@ -715,16 +782,12 @@ impl InputState { | |
|
||
fn vi_insert<R: RawReader>(&mut self, rdr: &mut R, wrt: &mut dyn Refresher) -> Result<Cmd> { | ||
let key = rdr.next_key(false)?; | ||
{ | ||
let bindings = self.custom_bindings.read().unwrap(); | ||
if let Some(cmd) = bindings.get(&key) { | ||
debug!(target: "rustyline", "Custom command: {:?}", cmd); | ||
return Ok(if cmd.is_repeatable() { | ||
cmd.redo(None, wrt) | ||
} else { | ||
cmd.clone() | ||
}); | ||
} | ||
if let Some(cmd) = self.custom_binding(wrt, key) { | ||
return Ok(if cmd.is_repeatable() { | ||
cmd.redo(None, wrt) | ||
} else { | ||
cmd | ||
}); | ||
} | ||
let cmd = match key { | ||
KeyPress::Char(c) => { | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -53,7 +53,9 @@ use crate::edit::State; | |||||
use crate::highlight::Highlighter; | ||||||
use crate::hint::Hinter; | ||||||
use crate::history::{Direction, History}; | ||||||
pub use crate::keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word}; | ||||||
pub use crate::keymap::{ | ||||||
Action, Anchor, At, CharSearch, Cmd, InputMode, Movement, RepeatCount, Word, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed. |
||||||
}; | ||||||
use crate::keymap::{InputState, Refresher}; | ||||||
pub use crate::keys::KeyPress; | ||||||
use crate::kill_ring::{KillRing, Mode}; | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you say what this name is used for? It's not clear to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is used only for tracing / debugging.