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

feat: Make it possible to keybind TypableCommands #1169

Merged
merged 3 commits into from
Dec 4, 2021
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
180 changes: 123 additions & 57 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,47 +134,76 @@ fn align_view(doc: &Document, view: &mut View, align: Align) {
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,
/// and does a side-effect on the state (usually by creating and applying a transaction).
#[derive(Copy, Clone)]
pub struct Command {
name: &'static str,
fun: fn(cx: &mut Context),
doc: &'static str,
}

macro_rules! commands {
/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
/// :format. It causes a side-effect on the state (usually by creating and applying a transaction).
/// Both of these types of commands can be mapped with keybindings in the config.toml.
#[derive(Clone)]
pub enum MappableCommand {
Typable {
name: String,
args: Vec<String>,
doc: String,
},
Static {
name: &'static str,
fun: fn(cx: &mut Context),
doc: &'static str,
},
}

macro_rules! static_commands {
( $($name:ident, $doc:literal,)* ) => {
$(
#[allow(non_upper_case_globals)]
pub const $name: Self = Self {
pub const $name: Self = Self::Static {
name: stringify!($name),
fun: $name,
doc: $doc
};
)*

pub const COMMAND_LIST: &'static [Self] = &[
pub const STATIC_COMMAND_LIST: &'static [Self] = &[
$( Self::$name, )*
];
}
}

impl Command {
impl MappableCommand {
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 {
self.name
pub fn name(&self) -> &str {
match &self {
MappableCommand::Typable { name, .. } => name,
MappableCommand::Static { name, .. } => name,
}
}

pub fn doc(&self) -> &'static str {
self.doc
pub fn doc(&self) -> &str {
match &self {
MappableCommand::Typable { doc, .. } => doc,
MappableCommand::Static { doc, .. } => doc,
}
}

#[rustfmt::skip]
commands!(
static_commands!(
no_op, "Do nothing",
move_char_left, "Move left",
move_char_right, "Move right",
Expand Down Expand Up @@ -363,33 +392,51 @@ impl Command {
);
}

impl fmt::Debug for Command {
impl fmt::Debug for MappableCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Command { name, .. } = self;
f.debug_tuple("Command").field(name).finish()
f.debug_tuple("MappableCommand")
.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 {
let Command { name, .. } = self;
f.write_str(name)
f.write_str(self.name())
}
}

impl std::str::FromStr for Command {
impl std::str::FromStr for MappableCommand {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Command::COMMAND_LIST
.iter()
.copied()
.find(|cmd| cmd.name == s)
.ok_or_else(|| anyhow!("No command named '{}'", s))
if let Some(suffix) = s.strip_prefix(':') {
let mut typable_command = suffix.split(' ').into_iter().map(|arg| arg.trim());
let name = typable_command
.next()
.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>
where
D: Deserializer<'de>,
Expand All @@ -399,9 +446,27 @@ impl<'de> Deserialize<'de> for Command {
}
}

impl PartialEq for Command {
impl PartialEq for MappableCommand {
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,
}
}
}

Expand Down Expand Up @@ -2206,15 +2271,15 @@ mod cmd {
args: &[&str],
event: PromptEvent,
) -> anyhow::Result<()> {
quit_all_impl(&mut cx.editor, args, event, false)
quit_all_impl(cx.editor, args, event, false)
}

fn force_quit_all(
cx: &mut compositor::Context,
args: &[&str],
event: PromptEvent,
) -> anyhow::Result<()> {
quit_all_impl(&mut cx.editor, args, event, true)
quit_all_impl(cx.editor, args, event, true)
}

fn cquit(
Expand Down Expand Up @@ -2250,7 +2315,7 @@ mod cmd {
_args: &[&str],
_event: PromptEvent,
) -> anyhow::Result<()> {
yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard)
yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard)
}

fn yank_joined_to_clipboard(
Expand All @@ -2263,15 +2328,15 @@ mod cmd {
.first()
.copied()
.unwrap_or_else(|| doc.line_ending.as_str());
yank_joined_to_clipboard_impl(&mut cx.editor, separator, ClipboardType::Clipboard)
yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
}

fn yank_main_selection_to_primary_clipboard(
cx: &mut compositor::Context,
_args: &[&str],
_event: PromptEvent,
) -> anyhow::Result<()> {
yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Selection)
yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection)
}

fn yank_joined_to_primary_clipboard(
Expand All @@ -2284,39 +2349,39 @@ mod cmd {
.first()
.copied()
.unwrap_or_else(|| doc.line_ending.as_str());
yank_joined_to_clipboard_impl(&mut cx.editor, separator, ClipboardType::Selection)
yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
}

fn paste_clipboard_after(
cx: &mut compositor::Context,
_args: &[&str],
_event: PromptEvent,
) -> anyhow::Result<()> {
paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard)
paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard)
}

fn paste_clipboard_before(
cx: &mut compositor::Context,
_args: &[&str],
_event: PromptEvent,
) -> anyhow::Result<()> {
paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard)
paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard)
}

fn paste_primary_clipboard_after(
cx: &mut compositor::Context,
_args: &[&str],
_event: PromptEvent,
) -> anyhow::Result<()> {
paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection)
paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection)
}

fn paste_primary_clipboard_before(
cx: &mut compositor::Context,
_args: &[&str],
_event: PromptEvent,
) -> anyhow::Result<()> {
paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection)
paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection)
}

fn replace_selections_with_clipboard_impl(
Expand Down Expand Up @@ -2496,7 +2561,7 @@ mod cmd {

let line = args[0].parse::<usize>()?;

goto_line_impl(&mut cx.editor, NonZeroUsize::new(line));
goto_line_impl(cx.editor, NonZeroUsize::new(line));

let (view, doc) = current!(cx.editor);

Expand Down Expand Up @@ -2795,15 +2860,16 @@ mod cmd {
}
];

pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
TYPABLE_COMMAND_LIST
.iter()
.flat_map(|cmd| {
std::iter::once((cmd.name, cmd))
.chain(cmd.aliases.iter().map(move |&alias| (alias, cmd)))
})
.collect()
});
pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
Lazy::new(|| {
TYPABLE_COMMAND_LIST
.iter()
.flat_map(|cmd| {
std::iter::once((cmd.name, cmd))
.chain(cmd.aliases.iter().map(move |&alias| (alias, cmd)))
})
.collect()
});
}

fn command_mode(cx: &mut Context) {
Expand All @@ -2829,7 +2895,7 @@ fn command_mode(cx: &mut Context) {
if let Some(cmd::TypableCommand {
completer: Some(completer),
..
}) = cmd::COMMANDS.get(parts[0])
}) = cmd::TYPABLE_COMMAND_MAP.get(parts[0])
{
completer(part)
.into_iter()
Expand Down Expand Up @@ -2864,7 +2930,7 @@ fn command_mode(cx: &mut Context) {
}

// 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) {
cx.editor.set_error(format!("{}", e));
}
Expand All @@ -2877,7 +2943,7 @@ fn command_mode(cx: &mut Context) {
prompt.doc_fn = Box::new(|input: &str| {
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);
}

Expand Down
Loading