Merge nucleic/sleek-thistle-egret-fyej into dev
This commit is contained in:
@@ -0,0 +1,713 @@
|
||||
use brush_core::{
|
||||
interfaces::{self, InputFunction, Key, KeyAction, KeyBindings as _, KeySequence, KeyStroke},
|
||||
trace_categories,
|
||||
};
|
||||
use radix_trie::Trie;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum KeyError {
|
||||
/// Unsupported key sequence
|
||||
#[error("unsupported key sequence: {0}")]
|
||||
UnsupportedKeySequence(KeySequence),
|
||||
|
||||
/// Unsupported key action
|
||||
#[error("unsupported key action: {0}")]
|
||||
UnsupportedKeyAction(KeyAction),
|
||||
}
|
||||
|
||||
pub(crate) struct MutableEditMode {
|
||||
inner: Arc<Mutex<UpdatableBindings>>,
|
||||
}
|
||||
|
||||
impl MutableEditMode {
|
||||
pub fn new(bindings: reedline::Keybindings) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(UpdatableBindings::new(bindings))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bindings(&self) -> Arc<Mutex<UpdatableBindings>> {
|
||||
self.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl reedline::EditMode for MutableEditMode {
|
||||
fn parse_event(&mut self, event: reedline::ReedlineRawEvent) -> reedline::ReedlineEvent {
|
||||
let mut inner = tokio::task::block_in_place(|| {
|
||||
tokio::runtime::Handle::current().block_on(self.inner.lock())
|
||||
});
|
||||
|
||||
inner.parse_event(event)
|
||||
}
|
||||
|
||||
fn edit_mode(&self) -> reedline::PromptEditMode {
|
||||
let inner = tokio::task::block_in_place(|| {
|
||||
tokio::runtime::Handle::current().block_on(self.inner.lock())
|
||||
});
|
||||
|
||||
inner.edit_mode()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct UpdatableBindings {
|
||||
bindings: reedline::Keybindings,
|
||||
edit_mode: Box<dyn reedline::EditMode>,
|
||||
/// Trie for raw byte sequences. Supports both exact lookups and prefix matching
|
||||
/// during macro resolution.
|
||||
raw_mappings: Trie<Vec<u8>, interfaces::KeyAction>,
|
||||
/// Tracks defined macros.
|
||||
macros: HashMap<interfaces::KeySequence, interfaces::KeySequence>,
|
||||
}
|
||||
|
||||
impl UpdatableBindings {
|
||||
pub fn new(bindings: reedline::Keybindings) -> Self {
|
||||
// Clone the bindings so we can keep a copy for later updates.
|
||||
let edit_mode = Self::rebuild_edit_mode(&bindings);
|
||||
|
||||
Self {
|
||||
bindings,
|
||||
edit_mode,
|
||||
raw_mappings: Trie::new(),
|
||||
macros: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, f: impl Fn(&mut reedline::Keybindings)) {
|
||||
f(&mut self.bindings);
|
||||
self.try_update_bindings_for_all_macros();
|
||||
self.edit_mode = Self::rebuild_edit_mode(&self.bindings);
|
||||
}
|
||||
|
||||
fn rebuild_edit_mode(bindings: &reedline::Keybindings) -> Box<dyn reedline::EditMode> {
|
||||
Box::new(reedline::Emacs::new(bindings.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl reedline::EditMode for UpdatableBindings {
|
||||
fn parse_event(&mut self, event: reedline::ReedlineRawEvent) -> reedline::ReedlineEvent {
|
||||
self.edit_mode.parse_event(event)
|
||||
}
|
||||
|
||||
fn edit_mode(&self) -> reedline::PromptEditMode {
|
||||
self.edit_mode.edit_mode()
|
||||
}
|
||||
}
|
||||
|
||||
impl interfaces::KeyBindings for UpdatableBindings {
|
||||
fn get_current(&self) -> HashMap<interfaces::KeySequence, interfaces::KeyAction> {
|
||||
let mut results = HashMap::new();
|
||||
|
||||
for (key_combo, event) in self.bindings.get_keybindings() {
|
||||
let action = translate_reedline_event_to_action(event);
|
||||
if let Some(action) = action {
|
||||
if let Some(key) = translate_reedline_keycode(key_combo.key_code) {
|
||||
let mut stroke = KeyStroke::from(key);
|
||||
|
||||
if key_combo.modifier.contains(reedline::KeyModifiers::CONTROL) {
|
||||
stroke.control = true;
|
||||
}
|
||||
if key_combo.modifier.contains(reedline::KeyModifiers::ALT) {
|
||||
stroke.alt = true;
|
||||
}
|
||||
if key_combo.modifier.contains(reedline::KeyModifiers::SHIFT) {
|
||||
stroke.shift = true;
|
||||
}
|
||||
if key_combo.modifier.contains(reedline::KeyModifiers::HYPER) {
|
||||
// TODO
|
||||
}
|
||||
if key_combo.modifier.contains(reedline::KeyModifiers::META) {
|
||||
// TODO
|
||||
}
|
||||
if key_combo.modifier.contains(reedline::KeyModifiers::SUPER) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
let seq = KeySequence::from(stroke);
|
||||
results.insert(seq, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
fn get_untranslated(&self, bytes: &[u8]) -> Option<&KeyAction> {
|
||||
self.raw_mappings.get(bytes)
|
||||
}
|
||||
|
||||
fn bind(&mut self, seq: KeySequence, action: KeyAction) -> Result<(), std::io::Error> {
|
||||
self.do_bind(seq, action, true)
|
||||
}
|
||||
|
||||
fn try_unbind(&mut self, seq: KeySequence) -> bool {
|
||||
self.try_unbind_impl(&seq, true)
|
||||
}
|
||||
|
||||
fn define_macro(
|
||||
&mut self,
|
||||
seq: KeySequence,
|
||||
target: KeySequence,
|
||||
) -> Result<(), std::io::Error> {
|
||||
self.macros.insert(seq, target);
|
||||
self.update(|_| {});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_macros(&self) -> HashMap<KeySequence, KeySequence> {
|
||||
self.macros.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdatableBindings {
|
||||
/// Internal implementation that optionally removes from the macros map.
|
||||
/// When updating bindings for macros, we don't want to remove the macro definition itself.
|
||||
fn try_unbind_impl(&mut self, seq: &KeySequence, remove_from_macros: bool) -> bool {
|
||||
// Optionally remove from macros.
|
||||
let removed_macro = if remove_from_macros {
|
||||
self.macros.remove(seq).is_some()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
match seq {
|
||||
interfaces::KeySequence::Strokes(_) => {
|
||||
if let Some((modifiers, key_code)) = translate_key_sequence_to_reedline(seq) {
|
||||
let found = self.bindings.find_binding(modifiers, key_code).is_some();
|
||||
|
||||
if found {
|
||||
self.update(|bindings| {
|
||||
let _ = bindings.remove_binding(modifiers, key_code);
|
||||
});
|
||||
}
|
||||
|
||||
found || removed_macro
|
||||
} else {
|
||||
removed_macro
|
||||
}
|
||||
}
|
||||
interfaces::KeySequence::Bytes(bytes) => {
|
||||
let flat_bytes: Vec<u8> = bytes.iter().flatten().copied().collect();
|
||||
let removed_raw = self.raw_mappings.remove(&flat_bytes).is_some();
|
||||
|
||||
removed_raw || removed_macro
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_bind(
|
||||
&mut self,
|
||||
seq: KeySequence,
|
||||
action: KeyAction,
|
||||
rebuild_for_reedline: bool,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let Some(event) = translate_action_to_reedline_event(&action) else {
|
||||
return Err(std::io::Error::other(KeyError::UnsupportedKeyAction(
|
||||
action,
|
||||
)));
|
||||
};
|
||||
|
||||
match seq {
|
||||
interfaces::KeySequence::Strokes(_) => {
|
||||
if let Some((modifiers, key_code)) = translate_key_sequence_to_reedline(&seq) {
|
||||
if rebuild_for_reedline {
|
||||
self.update(|bindings| {
|
||||
bindings.add_binding(modifiers, key_code, event.clone());
|
||||
});
|
||||
} else {
|
||||
self.bindings
|
||||
.add_binding(modifiers, key_code, event.clone());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(std::io::Error::other(KeyError::UnsupportedKeySequence(seq)))
|
||||
}
|
||||
}
|
||||
interfaces::KeySequence::Bytes(ref bytes) => {
|
||||
let flat_bytes: Vec<u8> = bytes.iter().flatten().copied().collect();
|
||||
self.raw_mappings.insert(flat_bytes, action);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_update_bindings_for_all_macros(&mut self) {
|
||||
let macros = self.macros.clone();
|
||||
for (seq, target) in macros {
|
||||
let _ = self.update_bindings_for_macro(seq, target);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_bindings_for_macro(
|
||||
&mut self,
|
||||
seq: KeySequence,
|
||||
target: KeySequence,
|
||||
) -> Result<(), std::io::Error> {
|
||||
match target {
|
||||
// TODO(input): We acknowledge that this implementation eagerly resolves the macro
|
||||
// and what it will do. Subsequent changes to other key binding might invalidate
|
||||
// this. We also are *extremely* limited in what we support here.
|
||||
interfaces::KeySequence::Strokes(key_strokes) => {
|
||||
if key_strokes.is_empty() {
|
||||
// Empty macro target - unbind any existing binding for this sequence.
|
||||
self.try_unbind(seq);
|
||||
} else {
|
||||
return Err(std::io::Error::other(
|
||||
"binding key sequence to readline macro with strokes",
|
||||
));
|
||||
}
|
||||
}
|
||||
interfaces::KeySequence::Bytes(items) => {
|
||||
// Flatten all byte sequences into one contiguous buffer for prefix matching.
|
||||
let flat_bytes: Vec<u8> = items.iter().flatten().copied().collect();
|
||||
let actions = self.resolve_macro_body(&flat_bytes);
|
||||
|
||||
if actions.is_empty() {
|
||||
// No actions resolved - unbind any existing key binding for this sequence,
|
||||
// but keep the macro definition so it shows up in `bind -s/-S`.
|
||||
self.try_unbind_impl(&seq, false);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Create a single action: either the action itself (if just one), or a Sequence.
|
||||
let action = if let [single] = actions.as_slice() {
|
||||
single.clone()
|
||||
} else {
|
||||
KeyAction::Sequence(actions)
|
||||
};
|
||||
|
||||
self.do_bind(seq, action, false)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve a macro body (byte sequence) into a sequence of actions using prefix matching.
|
||||
/// Returns a flattened vector of actions (any nested Sequences are expanded).
|
||||
fn resolve_macro_body(&self, bytes: &[u8]) -> Vec<KeyAction> {
|
||||
let mut actions = Vec::new();
|
||||
let mut remaining = bytes;
|
||||
|
||||
while !remaining.is_empty() {
|
||||
// Find the longest prefix match in the trie.
|
||||
if let Some((matched_key, action)) = self.find_longest_prefix_match(remaining) {
|
||||
// Flatten any nested Sequence actions.
|
||||
Self::flatten_action_into(&mut actions, action.clone());
|
||||
remaining = &remaining[matched_key.len()..];
|
||||
} else {
|
||||
// No match found - skip one byte and continue.
|
||||
// This handles unbound byte sequences gracefully.
|
||||
tracing::debug!(
|
||||
target: trace_categories::INPUT,
|
||||
"skipping unbound byte in macro resolution: 0x{:02x}",
|
||||
remaining[0]
|
||||
);
|
||||
remaining = &remaining[1..];
|
||||
}
|
||||
}
|
||||
|
||||
actions
|
||||
}
|
||||
|
||||
/// Find the longest prefix match in the trie for the given bytes.
|
||||
/// Uses the trie's native `get_ancestor` method which efficiently finds
|
||||
/// the longest matching prefix.
|
||||
fn find_longest_prefix_match(&self, bytes: &[u8]) -> Option<(Vec<u8>, &KeyAction)> {
|
||||
use radix_trie::TrieCommon;
|
||||
|
||||
self.raw_mappings.get_ancestor(bytes).and_then(|subtrie| {
|
||||
let key = subtrie.key()?.clone();
|
||||
let value = subtrie.value()?;
|
||||
Some((key, value))
|
||||
})
|
||||
}
|
||||
|
||||
/// Flatten an action into the actions vector, expanding any Sequence variants.
|
||||
fn flatten_action_into(actions: &mut Vec<KeyAction>, action: KeyAction) {
|
||||
match action {
|
||||
KeyAction::Sequence(inner_actions) => {
|
||||
for inner in inner_actions {
|
||||
Self::flatten_action_into(actions, inner);
|
||||
}
|
||||
}
|
||||
other => actions.push(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_key_sequence_to_reedline(
|
||||
seq: &KeySequence,
|
||||
) -> Option<(reedline::KeyModifiers, reedline::KeyCode)> {
|
||||
let KeySequence::Strokes(strokes) = seq else {
|
||||
// TODO(input): handle other kinds of key sequences
|
||||
return None;
|
||||
};
|
||||
|
||||
let [stroke] = &strokes.as_slice() else {
|
||||
// TODO(input): handle multiple strokes
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut modifiers = reedline::KeyModifiers::empty();
|
||||
modifiers.set(reedline::KeyModifiers::ALT, stroke.alt);
|
||||
modifiers.set(reedline::KeyModifiers::CONTROL, stroke.control);
|
||||
modifiers.set(reedline::KeyModifiers::SHIFT, stroke.shift);
|
||||
|
||||
let key_code = match stroke.key {
|
||||
Key::Character(c) => reedline::KeyCode::Char(c),
|
||||
Key::Backspace => reedline::KeyCode::Backspace,
|
||||
Key::Enter => reedline::KeyCode::Enter,
|
||||
Key::Left => reedline::KeyCode::Left,
|
||||
Key::Right => reedline::KeyCode::Right,
|
||||
Key::Up => reedline::KeyCode::Up,
|
||||
Key::Down => reedline::KeyCode::Down,
|
||||
Key::Home => reedline::KeyCode::Home,
|
||||
Key::End => reedline::KeyCode::End,
|
||||
Key::PageUp => reedline::KeyCode::PageUp,
|
||||
Key::PageDown => reedline::KeyCode::PageDown,
|
||||
Key::Tab => reedline::KeyCode::Tab,
|
||||
Key::BackTab => reedline::KeyCode::BackTab,
|
||||
Key::Delete => reedline::KeyCode::Delete,
|
||||
Key::Insert => reedline::KeyCode::Insert,
|
||||
Key::F(n) => reedline::KeyCode::F(n),
|
||||
Key::Escape => reedline::KeyCode::Esc,
|
||||
};
|
||||
|
||||
Some((modifiers, key_code))
|
||||
}
|
||||
|
||||
fn translate_action_to_reedline_event(action: &KeyAction) -> Option<reedline::ReedlineEvent> {
|
||||
match action {
|
||||
KeyAction::ShellCommand(cmd) => Some(reedline::ReedlineEvent::ExecuteHostCommand(
|
||||
format_reedline_host_command(cmd.as_str()),
|
||||
)),
|
||||
KeyAction::DoInputFunction(func) => translate_input_function_to_reedline_event(func),
|
||||
KeyAction::Sequence(actions) => {
|
||||
// Convert each action in the sequence to a reedline event.
|
||||
let events: Vec<_> = actions
|
||||
.iter()
|
||||
.filter_map(translate_action_to_reedline_event)
|
||||
.collect();
|
||||
|
||||
if events.is_empty() {
|
||||
None
|
||||
} else if events.len() == 1 {
|
||||
events.into_iter().next()
|
||||
} else {
|
||||
Some(reedline::ReedlineEvent::Multiple(events))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_reedline_host_command(cmd: &str) -> String {
|
||||
// NOTE: When this command gets returned from reedline's `read_line` function,
|
||||
// we need a way to know that it didn't come from user input (e.g., so we don't
|
||||
// add it to history, etc.). Since reedline doesn't provide any facilities for
|
||||
// doing this, we apply a workaround of appending a special marker comment at
|
||||
// the end of the command.
|
||||
std::format!("{cmd} # bind-command")
|
||||
}
|
||||
|
||||
fn parse_reedline_host_command(cmd: &str) -> Option<&str> {
|
||||
// See the implementation of `format_reedline_host_command`. We look for the marker.
|
||||
cmd.strip_suffix(" # bind-command")
|
||||
}
|
||||
|
||||
fn translate_input_function_to_reedline_event(
|
||||
func: &InputFunction,
|
||||
) -> Option<reedline::ReedlineEvent> {
|
||||
use reedline::{EditCommand, ReedlineEvent};
|
||||
|
||||
match func {
|
||||
InputFunction::BackwardDeleteChar => {
|
||||
Some(ReedlineEvent::Edit(vec![EditCommand::Backspace]))
|
||||
}
|
||||
InputFunction::BackwardKillWord => {
|
||||
Some(ReedlineEvent::Edit(vec![EditCommand::CutWordLeft]))
|
||||
}
|
||||
InputFunction::KillLine => Some(ReedlineEvent::Edit(vec![EditCommand::KillLine])),
|
||||
InputFunction::KillWholeLine => Some(ReedlineEvent::Edit(vec![EditCommand::CutFromStart])),
|
||||
InputFunction::KillWord => Some(ReedlineEvent::Edit(vec![EditCommand::CutWordRight])),
|
||||
InputFunction::DeleteChar => Some(ReedlineEvent::Edit(vec![EditCommand::Delete])),
|
||||
InputFunction::DowncaseWord => Some(ReedlineEvent::Edit(vec![EditCommand::LowercaseWord])),
|
||||
InputFunction::BackwardChar => Some(ReedlineEvent::Edit(vec![EditCommand::MoveLeft {
|
||||
select: false,
|
||||
}])),
|
||||
InputFunction::ForwardChar => Some(ReedlineEvent::Edit(vec![EditCommand::MoveRight {
|
||||
select: false,
|
||||
}])),
|
||||
InputFunction::EndOfLine => Some(ReedlineEvent::Edit(vec![EditCommand::MoveToLineEnd {
|
||||
select: false,
|
||||
}])),
|
||||
InputFunction::BeginningOfLine => {
|
||||
Some(ReedlineEvent::Edit(vec![EditCommand::MoveToLineStart {
|
||||
select: false,
|
||||
}]))
|
||||
}
|
||||
InputFunction::BackwardWord => Some(ReedlineEvent::Edit(vec![EditCommand::MoveWordLeft {
|
||||
select: false,
|
||||
}])),
|
||||
InputFunction::ForwardWord => Some(ReedlineEvent::Edit(vec![EditCommand::MoveWordRight {
|
||||
select: false,
|
||||
}])),
|
||||
InputFunction::Yank => Some(ReedlineEvent::Edit(vec![EditCommand::PasteCutBufferAfter])),
|
||||
InputFunction::ViRedo => Some(ReedlineEvent::Edit(vec![EditCommand::Redo])),
|
||||
InputFunction::TransposeChars => {
|
||||
Some(ReedlineEvent::Edit(vec![EditCommand::SwapGraphemes]))
|
||||
}
|
||||
InputFunction::UpcaseWord => Some(ReedlineEvent::Edit(vec![EditCommand::UppercaseWord])),
|
||||
InputFunction::Undo => Some(ReedlineEvent::Edit(vec![EditCommand::Undo])),
|
||||
InputFunction::ClearScreen => Some(ReedlineEvent::ClearScreen),
|
||||
InputFunction::AcceptLine => Some(ReedlineEvent::Enter),
|
||||
InputFunction::HistorySearchBackward => Some(ReedlineEvent::SearchHistory),
|
||||
InputFunction::RedrawCurrentLine => Some(ReedlineEvent::Repaint),
|
||||
InputFunction::Complete => Some(ReedlineEvent::Edit(vec![EditCommand::Complete])),
|
||||
InputFunction::BrushAcceptHint => Some(ReedlineEvent::HistoryHintComplete),
|
||||
InputFunction::BrushAcceptHintWord => Some(ReedlineEvent::HistoryHintWordComplete),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_reedline_host_command(cmd: &str) -> bool {
|
||||
// See the implementation of `format_reedline_host_command`. We look for the marker.
|
||||
cmd.ends_with("# bind-command")
|
||||
}
|
||||
|
||||
const fn translate_reedline_keycode(keycode: reedline::KeyCode) -> Option<Key> {
|
||||
match keycode {
|
||||
reedline::KeyCode::Backspace => Some(Key::Backspace),
|
||||
reedline::KeyCode::Enter => Some(Key::Enter),
|
||||
reedline::KeyCode::Left => Some(Key::Left),
|
||||
reedline::KeyCode::Right => Some(Key::Right),
|
||||
reedline::KeyCode::Up => Some(Key::Up),
|
||||
reedline::KeyCode::Down => Some(Key::Down),
|
||||
reedline::KeyCode::Home => Some(Key::Home),
|
||||
reedline::KeyCode::End => Some(Key::End),
|
||||
reedline::KeyCode::PageUp => Some(Key::PageUp),
|
||||
reedline::KeyCode::PageDown => Some(Key::PageDown),
|
||||
reedline::KeyCode::Tab => Some(Key::Tab),
|
||||
reedline::KeyCode::BackTab => Some(Key::BackTab),
|
||||
reedline::KeyCode::Delete => Some(Key::Delete),
|
||||
reedline::KeyCode::Insert => Some(Key::Insert),
|
||||
reedline::KeyCode::F(n) => Some(Key::F(n)),
|
||||
reedline::KeyCode::Char(c) => Some(Key::Character(c)),
|
||||
reedline::KeyCode::Null => None,
|
||||
reedline::KeyCode::Esc => Some(Key::Escape),
|
||||
reedline::KeyCode::CapsLock => None,
|
||||
reedline::KeyCode::ScrollLock => None,
|
||||
reedline::KeyCode::NumLock => None,
|
||||
reedline::KeyCode::PrintScreen => None,
|
||||
reedline::KeyCode::Pause => None,
|
||||
reedline::KeyCode::Menu => None,
|
||||
reedline::KeyCode::KeypadBegin => None,
|
||||
reedline::KeyCode::Media(_media_key_code) => None,
|
||||
reedline::KeyCode::Modifier(_modifier_key_code) => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn translate_reedline_event_to_action(event: &reedline::ReedlineEvent) -> Option<KeyAction> {
|
||||
match event {
|
||||
reedline::ReedlineEvent::Edit(cmds) => {
|
||||
match cmds.as_slice() {
|
||||
[reedline::EditCommand::Backspace] => Some(KeyAction::DoInputFunction(
|
||||
InputFunction::BackwardDeleteChar,
|
||||
)),
|
||||
[reedline::EditCommand::BackspaceWord] => {
|
||||
// Not quite accurate, because it doesn't save the deleted text.
|
||||
Some(KeyAction::DoInputFunction(InputFunction::BackwardKillWord))
|
||||
}
|
||||
[reedline::EditCommand::CapitalizeChar] => None,
|
||||
[reedline::EditCommand::ClearToLineEnd] => {
|
||||
// Not quite accurate, because it doesn't save the deleted text.
|
||||
Some(KeyAction::DoInputFunction(InputFunction::KillLine))
|
||||
}
|
||||
[reedline::EditCommand::Complete] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::Complete))
|
||||
}
|
||||
[reedline::EditCommand::CutFromStart] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::KillWholeLine))
|
||||
}
|
||||
[reedline::EditCommand::KillLine] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::KillLine))
|
||||
}
|
||||
[reedline::EditCommand::CutWordLeft] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::BackwardKillWord))
|
||||
}
|
||||
[reedline::EditCommand::CutWordRight] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::KillWord))
|
||||
}
|
||||
[reedline::EditCommand::Delete] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::DeleteChar))
|
||||
}
|
||||
[reedline::EditCommand::DeleteWord] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::KillWord))
|
||||
}
|
||||
[reedline::EditCommand::InsertNewline] => None,
|
||||
[reedline::EditCommand::LowercaseWord] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::DowncaseWord))
|
||||
}
|
||||
[reedline::EditCommand::MoveLeft { select: false }] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::BackwardChar))
|
||||
}
|
||||
[reedline::EditCommand::MoveLeft { select: true }] => None,
|
||||
[reedline::EditCommand::MoveRight { select: false }] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::ForwardChar))
|
||||
}
|
||||
[reedline::EditCommand::MoveRight { select: true }] => None,
|
||||
[reedline::EditCommand::MoveToEnd { select: false }] => {
|
||||
// TODO(input): Not quite accurate, because it doesn't just go to end of line.
|
||||
Some(KeyAction::DoInputFunction(InputFunction::EndOfLine))
|
||||
}
|
||||
[reedline::EditCommand::MoveToEnd { select: true }] => None,
|
||||
[reedline::EditCommand::MoveToLineEnd { select: false }] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::EndOfLine))
|
||||
}
|
||||
[reedline::EditCommand::MoveToLineEnd { select: true }] => None,
|
||||
[reedline::EditCommand::MoveToLineStart { select: false }] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::BeginningOfLine))
|
||||
}
|
||||
[reedline::EditCommand::MoveToLineStart { select: true }] => None,
|
||||
[reedline::EditCommand::MoveToStart { select: false }] => {
|
||||
// TODO(input): Not quite accurate, because it doesn't just go to beginning of
|
||||
// line.
|
||||
Some(KeyAction::DoInputFunction(InputFunction::BeginningOfLine))
|
||||
}
|
||||
[reedline::EditCommand::MoveToStart { select: true }] => None,
|
||||
[reedline::EditCommand::MoveWordLeft { select: false }] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::BackwardWord))
|
||||
}
|
||||
[reedline::EditCommand::MoveWordLeft { select: true }] => None,
|
||||
[reedline::EditCommand::MoveWordRight { select: false }] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::ForwardWord))
|
||||
}
|
||||
[reedline::EditCommand::MoveWordRight { select: true }] => None,
|
||||
[reedline::EditCommand::PasteCutBufferAfter] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::Yank))
|
||||
}
|
||||
[reedline::EditCommand::PasteCutBufferBefore] => None,
|
||||
[reedline::EditCommand::Redo] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::ViRedo))
|
||||
}
|
||||
[reedline::EditCommand::SelectAll] => None,
|
||||
[reedline::EditCommand::SwapGraphemes] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::TransposeChars))
|
||||
}
|
||||
[reedline::EditCommand::UppercaseWord] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::UpcaseWord))
|
||||
}
|
||||
[reedline::EditCommand::Undo] => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::Undo))
|
||||
}
|
||||
_ => {
|
||||
// TODO(input): Handle more?
|
||||
tracing::debug!(target: trace_categories::INPUT, "unhandled edit commands: {cmds:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
reedline::ReedlineEvent::ClearScreen => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::ClearScreen))
|
||||
}
|
||||
reedline::ReedlineEvent::CtrlC => None,
|
||||
reedline::ReedlineEvent::CtrlD => None,
|
||||
reedline::ReedlineEvent::Enter => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::AcceptLine))
|
||||
}
|
||||
reedline::ReedlineEvent::Esc => None,
|
||||
reedline::ReedlineEvent::MenuPrevious => None,
|
||||
reedline::ReedlineEvent::OpenEditor => None,
|
||||
reedline::ReedlineEvent::Left => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::BackwardChar))
|
||||
}
|
||||
reedline::ReedlineEvent::Right => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::ForwardChar))
|
||||
}
|
||||
reedline::ReedlineEvent::Up => Some(KeyAction::DoInputFunction(
|
||||
InputFunction::PreviousScreenLine,
|
||||
)),
|
||||
reedline::ReedlineEvent::Down => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::NextScreenLine))
|
||||
}
|
||||
reedline::ReedlineEvent::SearchHistory => Some(KeyAction::DoInputFunction(
|
||||
InputFunction::HistorySearchBackward,
|
||||
)),
|
||||
reedline::ReedlineEvent::Repaint => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::RedrawCurrentLine))
|
||||
}
|
||||
reedline::ReedlineEvent::HistoryHintComplete => {
|
||||
Some(KeyAction::DoInputFunction(InputFunction::BrushAcceptHint))
|
||||
}
|
||||
reedline::ReedlineEvent::HistoryHintWordComplete => Some(KeyAction::DoInputFunction(
|
||||
InputFunction::BrushAcceptHintWord,
|
||||
)),
|
||||
reedline::ReedlineEvent::Multiple(evts) => {
|
||||
if let &[
|
||||
reedline::ReedlineEvent::Edit(ref edit_cmds),
|
||||
reedline::ReedlineEvent::Enter,
|
||||
] = evts.as_slice()
|
||||
{
|
||||
if let &[
|
||||
reedline::EditCommand::MoveToStart { select: false },
|
||||
reedline::EditCommand::InsertChar('#'),
|
||||
] = edit_cmds.as_slice()
|
||||
{
|
||||
return Some(KeyAction::DoInputFunction(InputFunction::InsertComment));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(input): Try to extract something from these?
|
||||
tracing::debug!(target: trace_categories::INPUT, "unhandled composite event: {evts:?}");
|
||||
None
|
||||
}
|
||||
reedline::ReedlineEvent::UntilFound(uf_events) => {
|
||||
let mut i = 0;
|
||||
|
||||
if uf_events.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
while i < uf_events.len() {
|
||||
match &uf_events[i] {
|
||||
reedline::ReedlineEvent::HistoryHintComplete
|
||||
| reedline::ReedlineEvent::HistoryHintWordComplete
|
||||
| reedline::ReedlineEvent::Menu(_)
|
||||
| reedline::ReedlineEvent::MenuDown
|
||||
| reedline::ReedlineEvent::MenuUp
|
||||
| reedline::ReedlineEvent::MenuLeft
|
||||
| reedline::ReedlineEvent::MenuRight
|
||||
| reedline::ReedlineEvent::MenuNext
|
||||
| reedline::ReedlineEvent::MenuPrevious
|
||||
| reedline::ReedlineEvent::MenuPageNext
|
||||
| reedline::ReedlineEvent::MenuPagePrevious => {
|
||||
i += 1;
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i == uf_events.len() - 1 {
|
||||
translate_reedline_event_to_action(&uf_events[i])
|
||||
} else {
|
||||
// TODO(input): Try to extract something from these?
|
||||
tracing::debug!(target: trace_categories::INPUT, "unhandled until-found event: {uf_events:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
reedline::ReedlineEvent::ExecuteHostCommand(cmd) => parse_reedline_host_command(cmd)
|
||||
.map(|cmd_str| KeyAction::ShellCommand(cmd_str.to_string())),
|
||||
evt => {
|
||||
// TODO(input): Handle more?
|
||||
tracing::debug!(target: trace_categories::INPUT, "unhandled event: {evt:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user