mirror of https://github.com/helix-editor/helix
feat: Make it possible to keybind `TypableCommands` (#1169)
* Make TypableCommands mappable * Fix pr comments * Update PartialEq implementationpull/1235/head
parent
70c62530ee
commit
a06871a689
|
@ -134,47 +134,76 @@ fn align_view(doc: &Document, view: &mut View, align: Align) {
|
||||||
view.offset.row = line.saturating_sub(relative);
|
view.offset.row = line.saturating_sub(relative);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A command is composed of a static name, and a function that takes the current state plus a count,
|
/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
|
||||||
/// and does a side-effect on the state (usually by creating and applying a transaction).
|
/// :format. It causes a side-effect on the state (usually by creating and applying a transaction).
|
||||||
#[derive(Copy, Clone)]
|
/// Both of these types of commands can be mapped with keybindings in the config.toml.
|
||||||
pub struct Command {
|
#[derive(Clone)]
|
||||||
name: &'static str,
|
pub enum MappableCommand {
|
||||||
fun: fn(cx: &mut Context),
|
Typable {
|
||||||
doc: &'static str,
|
name: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
doc: String,
|
||||||
|
},
|
||||||
|
Static {
|
||||||
|
name: &'static str,
|
||||||
|
fun: fn(cx: &mut Context),
|
||||||
|
doc: &'static str,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! commands {
|
macro_rules! static_commands {
|
||||||
( $($name:ident, $doc:literal,)* ) => {
|
( $($name:ident, $doc:literal,)* ) => {
|
||||||
$(
|
$(
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
pub const $name: Self = Self {
|
pub const $name: Self = Self::Static {
|
||||||
name: stringify!($name),
|
name: stringify!($name),
|
||||||
fun: $name,
|
fun: $name,
|
||||||
doc: $doc
|
doc: $doc
|
||||||
};
|
};
|
||||||
)*
|
)*
|
||||||
|
|
||||||
pub const COMMAND_LIST: &'static [Self] = &[
|
pub const STATIC_COMMAND_LIST: &'static [Self] = &[
|
||||||
$( Self::$name, )*
|
$( Self::$name, )*
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl MappableCommand {
|
||||||
pub fn execute(&self, cx: &mut Context) {
|
pub fn execute(&self, cx: &mut Context) {
|
||||||
(self.fun)(cx);
|
match &self {
|
||||||
|
MappableCommand::Typable { name, args, doc: _ } => {
|
||||||
|
let args: Vec<&str> = args.iter().map(|arg| arg.as_str()).collect();
|
||||||
|
if let Some(command) = cmd::TYPABLE_COMMAND_MAP.get(name.as_str()) {
|
||||||
|
let mut cx = compositor::Context {
|
||||||
|
editor: cx.editor,
|
||||||
|
jobs: cx.jobs,
|
||||||
|
scroll: None,
|
||||||
|
};
|
||||||
|
if let Err(e) = (command.fun)(&mut cx, &args, PromptEvent::Validate) {
|
||||||
|
cx.editor.set_error(format!("{}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MappableCommand::Static { fun, .. } => (fun)(cx),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &str {
|
||||||
self.name
|
match &self {
|
||||||
|
MappableCommand::Typable { name, .. } => name,
|
||||||
|
MappableCommand::Static { name, .. } => name,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn doc(&self) -> &'static str {
|
pub fn doc(&self) -> &str {
|
||||||
self.doc
|
match &self {
|
||||||
|
MappableCommand::Typable { doc, .. } => doc,
|
||||||
|
MappableCommand::Static { doc, .. } => doc,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
commands!(
|
static_commands!(
|
||||||
no_op, "Do nothing",
|
no_op, "Do nothing",
|
||||||
move_char_left, "Move left",
|
move_char_left, "Move left",
|
||||||
move_char_right, "Move right",
|
move_char_right, "Move right",
|
||||||
|
@ -367,33 +396,51 @@ impl Command {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Command {
|
impl fmt::Debug for MappableCommand {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let Command { name, .. } = self;
|
f.debug_tuple("MappableCommand")
|
||||||
f.debug_tuple("Command").field(name).finish()
|
.field(&self.name())
|
||||||
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Command {
|
impl fmt::Display for MappableCommand {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let Command { name, .. } = self;
|
f.write_str(self.name())
|
||||||
f.write_str(name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for Command {
|
impl std::str::FromStr for MappableCommand {
|
||||||
type Err = anyhow::Error;
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Command::COMMAND_LIST
|
if let Some(suffix) = s.strip_prefix(':') {
|
||||||
.iter()
|
let mut typable_command = suffix.split(' ').into_iter().map(|arg| arg.trim());
|
||||||
.copied()
|
let name = typable_command
|
||||||
.find(|cmd| cmd.name == s)
|
.next()
|
||||||
.ok_or_else(|| anyhow!("No command named '{}'", s))
|
.ok_or_else(|| anyhow!("Expected typable command name"))?;
|
||||||
|
let args = typable_command
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
cmd::TYPABLE_COMMAND_MAP
|
||||||
|
.get(name)
|
||||||
|
.map(|cmd| MappableCommand::Typable {
|
||||||
|
name: cmd.name.to_owned(),
|
||||||
|
doc: format!(":{} {:?}", cmd.name, args),
|
||||||
|
args,
|
||||||
|
})
|
||||||
|
.ok_or_else(|| anyhow!("No TypableCommand named '{}'", s))
|
||||||
|
} else {
|
||||||
|
MappableCommand::STATIC_COMMAND_LIST
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.find(|cmd| cmd.name() == s)
|
||||||
|
.ok_or_else(|| anyhow!("No command named '{}'", s))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Command {
|
impl<'de> Deserialize<'de> for MappableCommand {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
|
@ -403,9 +450,27 @@ impl<'de> Deserialize<'de> for Command {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Command {
|
impl PartialEq for MappableCommand {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.name() == other.name()
|
match (self, other) {
|
||||||
|
(
|
||||||
|
MappableCommand::Typable {
|
||||||
|
name: first_name, ..
|
||||||
|
},
|
||||||
|
MappableCommand::Typable {
|
||||||
|
name: second_name, ..
|
||||||
|
},
|
||||||
|
) => first_name == second_name,
|
||||||
|
(
|
||||||
|
MappableCommand::Static {
|
||||||
|
name: first_name, ..
|
||||||
|
},
|
||||||
|
MappableCommand::Static {
|
||||||
|
name: second_name, ..
|
||||||
|
},
|
||||||
|
) => first_name == second_name,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2843,15 +2908,16 @@ mod cmd {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
|
pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
|
||||||
TYPABLE_COMMAND_LIST
|
Lazy::new(|| {
|
||||||
.iter()
|
TYPABLE_COMMAND_LIST
|
||||||
.flat_map(|cmd| {
|
.iter()
|
||||||
std::iter::once((cmd.name, cmd))
|
.flat_map(|cmd| {
|
||||||
.chain(cmd.aliases.iter().map(move |&alias| (alias, cmd)))
|
std::iter::once((cmd.name, cmd))
|
||||||
})
|
.chain(cmd.aliases.iter().map(move |&alias| (alias, cmd)))
|
||||||
.collect()
|
})
|
||||||
});
|
.collect()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn command_mode(cx: &mut Context) {
|
fn command_mode(cx: &mut Context) {
|
||||||
|
@ -2877,7 +2943,7 @@ fn command_mode(cx: &mut Context) {
|
||||||
if let Some(cmd::TypableCommand {
|
if let Some(cmd::TypableCommand {
|
||||||
completer: Some(completer),
|
completer: Some(completer),
|
||||||
..
|
..
|
||||||
}) = cmd::COMMANDS.get(parts[0])
|
}) = cmd::TYPABLE_COMMAND_MAP.get(parts[0])
|
||||||
{
|
{
|
||||||
completer(part)
|
completer(part)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -2912,7 +2978,7 @@ fn command_mode(cx: &mut Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle typable commands
|
// Handle typable commands
|
||||||
if let Some(cmd) = cmd::COMMANDS.get(parts[0]) {
|
if let Some(cmd) = cmd::TYPABLE_COMMAND_MAP.get(parts[0]) {
|
||||||
if let Err(e) = (cmd.fun)(cx, &parts[1..], event) {
|
if let Err(e) = (cmd.fun)(cx, &parts[1..], event) {
|
||||||
cx.editor.set_error(format!("{}", e));
|
cx.editor.set_error(format!("{}", e));
|
||||||
}
|
}
|
||||||
|
@ -2925,7 +2991,7 @@ fn command_mode(cx: &mut Context) {
|
||||||
prompt.doc_fn = Box::new(|input: &str| {
|
prompt.doc_fn = Box::new(|input: &str| {
|
||||||
let part = input.split(' ').next().unwrap_or_default();
|
let part = input.split(' ').next().unwrap_or_default();
|
||||||
|
|
||||||
if let Some(cmd::TypableCommand { doc, .. }) = cmd::COMMANDS.get(part) {
|
if let Some(cmd::TypableCommand { doc, .. }) = cmd::TYPABLE_COMMAND_MAP.get(part) {
|
||||||
return Some(doc);
|
return Some(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
pub use crate::commands::Command;
|
pub use crate::commands::MappableCommand;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use helix_core::hashmap;
|
use helix_core::hashmap;
|
||||||
use helix_view::{document::Mode, info::Info, input::KeyEvent};
|
use helix_view::{document::Mode, info::Info, input::KeyEvent};
|
||||||
|
@ -92,7 +92,7 @@ macro_rules! alt {
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! keymap {
|
macro_rules! keymap {
|
||||||
(@trie $cmd:ident) => {
|
(@trie $cmd:ident) => {
|
||||||
$crate::keymap::KeyTrie::Leaf($crate::commands::Command::$cmd)
|
$crate::keymap::KeyTrie::Leaf($crate::commands::MappableCommand::$cmd)
|
||||||
};
|
};
|
||||||
|
|
||||||
(@trie
|
(@trie
|
||||||
|
@ -260,8 +260,8 @@ impl DerefMut for KeyTrieNode {
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum KeyTrie {
|
pub enum KeyTrie {
|
||||||
Leaf(Command),
|
Leaf(MappableCommand),
|
||||||
Sequence(Vec<Command>),
|
Sequence(Vec<MappableCommand>),
|
||||||
Node(KeyTrieNode),
|
Node(KeyTrieNode),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,9 +304,9 @@ impl KeyTrie {
|
||||||
pub enum KeymapResultKind {
|
pub enum KeymapResultKind {
|
||||||
/// Needs more keys to execute a command. Contains valid keys for next keystroke.
|
/// Needs more keys to execute a command. Contains valid keys for next keystroke.
|
||||||
Pending(KeyTrieNode),
|
Pending(KeyTrieNode),
|
||||||
Matched(Command),
|
Matched(MappableCommand),
|
||||||
/// Matched a sequence of commands to execute.
|
/// Matched a sequence of commands to execute.
|
||||||
MatchedSequence(Vec<Command>),
|
MatchedSequence(Vec<MappableCommand>),
|
||||||
/// Key was not found in the root keymap
|
/// Key was not found in the root keymap
|
||||||
NotFound,
|
NotFound,
|
||||||
/// Key is invalid in combination with previous keys. Contains keys leading upto
|
/// Key is invalid in combination with previous keys. Contains keys leading upto
|
||||||
|
@ -386,10 +386,10 @@ impl Keymap {
|
||||||
};
|
};
|
||||||
|
|
||||||
let trie = match trie_node.search(&[*first]) {
|
let trie = match trie_node.search(&[*first]) {
|
||||||
Some(&KeyTrie::Leaf(cmd)) => {
|
Some(KeyTrie::Leaf(ref cmd)) => {
|
||||||
return KeymapResult::new(KeymapResultKind::Matched(cmd), self.sticky())
|
return KeymapResult::new(KeymapResultKind::Matched(cmd.clone()), self.sticky())
|
||||||
}
|
}
|
||||||
Some(&KeyTrie::Sequence(ref cmds)) => {
|
Some(KeyTrie::Sequence(ref cmds)) => {
|
||||||
return KeymapResult::new(
|
return KeymapResult::new(
|
||||||
KeymapResultKind::MatchedSequence(cmds.clone()),
|
KeymapResultKind::MatchedSequence(cmds.clone()),
|
||||||
self.sticky(),
|
self.sticky(),
|
||||||
|
@ -408,9 +408,9 @@ impl Keymap {
|
||||||
}
|
}
|
||||||
KeymapResult::new(KeymapResultKind::Pending(map.clone()), self.sticky())
|
KeymapResult::new(KeymapResultKind::Pending(map.clone()), self.sticky())
|
||||||
}
|
}
|
||||||
Some(&KeyTrie::Leaf(cmd)) => {
|
Some(&KeyTrie::Leaf(ref cmd)) => {
|
||||||
self.state.clear();
|
self.state.clear();
|
||||||
return KeymapResult::new(KeymapResultKind::Matched(cmd), self.sticky());
|
return KeymapResult::new(KeymapResultKind::Matched(cmd.clone()), self.sticky());
|
||||||
}
|
}
|
||||||
Some(&KeyTrie::Sequence(ref cmds)) => {
|
Some(&KeyTrie::Sequence(ref cmds)) => {
|
||||||
self.state.clear();
|
self.state.clear();
|
||||||
|
@ -833,36 +833,36 @@ mod tests {
|
||||||
let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
|
let keymap = merged_config.keys.0.get_mut(&Mode::Normal).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.get(key!('i')).kind,
|
keymap.get(key!('i')).kind,
|
||||||
KeymapResultKind::Matched(Command::normal_mode),
|
KeymapResultKind::Matched(MappableCommand::normal_mode),
|
||||||
"Leaf should replace leaf"
|
"Leaf should replace leaf"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.get(key!('无')).kind,
|
keymap.get(key!('无')).kind,
|
||||||
KeymapResultKind::Matched(Command::insert_mode),
|
KeymapResultKind::Matched(MappableCommand::insert_mode),
|
||||||
"New leaf should be present in merged keymap"
|
"New leaf should be present in merged keymap"
|
||||||
);
|
);
|
||||||
// Assumes that z is a node in the default keymap
|
// Assumes that z is a node in the default keymap
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.get(key!('z')).kind,
|
keymap.get(key!('z')).kind,
|
||||||
KeymapResultKind::Matched(Command::jump_backward),
|
KeymapResultKind::Matched(MappableCommand::jump_backward),
|
||||||
"Leaf should replace node"
|
"Leaf should replace node"
|
||||||
);
|
);
|
||||||
// Assumes that `g` is a node in default keymap
|
// Assumes that `g` is a node in default keymap
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
|
keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
|
||||||
&KeyTrie::Leaf(Command::goto_line_end),
|
&KeyTrie::Leaf(MappableCommand::goto_line_end),
|
||||||
"Leaf should be present in merged subnode"
|
"Leaf should be present in merged subnode"
|
||||||
);
|
);
|
||||||
// Assumes that `gg` is in default keymap
|
// Assumes that `gg` is in default keymap
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.root().search(&[key!('g'), key!('g')]).unwrap(),
|
keymap.root().search(&[key!('g'), key!('g')]).unwrap(),
|
||||||
&KeyTrie::Leaf(Command::delete_char_forward),
|
&KeyTrie::Leaf(MappableCommand::delete_char_forward),
|
||||||
"Leaf should replace old leaf in merged subnode"
|
"Leaf should replace old leaf in merged subnode"
|
||||||
);
|
);
|
||||||
// Assumes that `ge` is in default keymap
|
// Assumes that `ge` is in default keymap
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
keymap.root().search(&[key!('g'), key!('e')]).unwrap(),
|
keymap.root().search(&[key!('g'), key!('e')]).unwrap(),
|
||||||
&KeyTrie::Leaf(Command::goto_last_line),
|
&KeyTrie::Leaf(MappableCommand::goto_last_line),
|
||||||
"Old leaves in subnode should be present in merged node"
|
"Old leaves in subnode should be present in merged node"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -896,7 +896,7 @@ mod tests {
|
||||||
.root()
|
.root()
|
||||||
.search(&[key!(' '), key!('s'), key!('v')])
|
.search(&[key!(' '), key!('s'), key!('v')])
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
&KeyTrie::Leaf(Command::vsplit),
|
&KeyTrie::Leaf(MappableCommand::vsplit),
|
||||||
"Leaf should be present in merged subnode"
|
"Leaf should be present in merged subnode"
|
||||||
);
|
);
|
||||||
// Make sure an order was set during merge
|
// Make sure an order was set during merge
|
||||||
|
|
|
@ -31,7 +31,7 @@ use tui::buffer::Buffer as Surface;
|
||||||
pub struct EditorView {
|
pub struct EditorView {
|
||||||
keymaps: Keymaps,
|
keymaps: Keymaps,
|
||||||
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
|
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
|
||||||
last_insert: (commands::Command, Vec<KeyEvent>),
|
last_insert: (commands::MappableCommand, Vec<KeyEvent>),
|
||||||
pub(crate) completion: Option<Completion>,
|
pub(crate) completion: Option<Completion>,
|
||||||
spinners: ProgressSpinners,
|
spinners: ProgressSpinners,
|
||||||
autoinfo: Option<Info>,
|
autoinfo: Option<Info>,
|
||||||
|
@ -48,7 +48,7 @@ impl EditorView {
|
||||||
Self {
|
Self {
|
||||||
keymaps,
|
keymaps,
|
||||||
on_next_key: None,
|
on_next_key: None,
|
||||||
last_insert: (commands::Command::normal_mode, Vec::new()),
|
last_insert: (commands::MappableCommand::normal_mode, Vec::new()),
|
||||||
completion: None,
|
completion: None,
|
||||||
spinners: ProgressSpinners::default(),
|
spinners: ProgressSpinners::default(),
|
||||||
autoinfo: None,
|
autoinfo: None,
|
||||||
|
@ -875,7 +875,7 @@ impl EditorView {
|
||||||
return EventResult::Ignored;
|
return EventResult::Ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
commands::Command::yank_main_selection_to_primary_clipboard.execute(cxt);
|
commands::MappableCommand::yank_main_selection_to_primary_clipboard.execute(cxt);
|
||||||
|
|
||||||
EventResult::Consumed(None)
|
EventResult::Consumed(None)
|
||||||
}
|
}
|
||||||
|
@ -893,7 +893,8 @@ impl EditorView {
|
||||||
}
|
}
|
||||||
|
|
||||||
if modifiers == crossterm::event::KeyModifiers::ALT {
|
if modifiers == crossterm::event::KeyModifiers::ALT {
|
||||||
commands::Command::replace_selections_with_primary_clipboard.execute(cxt);
|
commands::MappableCommand::replace_selections_with_primary_clipboard
|
||||||
|
.execute(cxt);
|
||||||
|
|
||||||
return EventResult::Consumed(None);
|
return EventResult::Consumed(None);
|
||||||
}
|
}
|
||||||
|
@ -907,7 +908,7 @@ impl EditorView {
|
||||||
let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap();
|
let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap();
|
||||||
doc.set_selection(view_id, Selection::point(pos));
|
doc.set_selection(view_id, Selection::point(pos));
|
||||||
editor.tree.focus = view_id;
|
editor.tree.focus = view_id;
|
||||||
commands::Command::paste_primary_clipboard_before.execute(cxt);
|
commands::MappableCommand::paste_primary_clipboard_before.execute(cxt);
|
||||||
return EventResult::Consumed(None);
|
return EventResult::Consumed(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue