Improve command mode handling.

imgbot
Blaž Hrastnik 3 years ago
parent 243456a583
commit 9604a0c294

@ -809,53 +809,22 @@ pub fn append_mode(cx: &mut Context) {
doc.set_selection(view.id, selection); doc.set_selection(view.id, selection);
} }
const COMMAND_LIST: &[&str] = &["write", "open", "quit", "quit!"]; mod cmd {
use super::*;
pub fn command_mode(cx: &mut Context) { use std::collections::HashMap;
let prompt = Prompt::new(
":".to_owned(),
|input: &str| {
// we use .this over split_ascii_whitespace() because we care about empty segments
let parts = input.split(' ').collect::<Vec<&str>>();
// simple heuristic: if there's no space, complete command.
// if there's a space, file completion kicks in. We should specialize by command later.
if parts.len() <= 1 {
use std::{borrow::Cow, ops::Range};
let end = 0..;
COMMAND_LIST
.iter()
.filter(|command| command.contains(input))
.map(|command| (end.clone(), Cow::Borrowed(*command)))
.collect()
} else {
let part = parts.last().unwrap();
ui::completers::filename(part)
.into_iter()
.map(|(range, file)| {
// offset ranges to input
let offset = input.len() - part.len();
let range = (range.start + offset)..;
(range, file)
})
.collect()
// TODO
// additionally, completion items could have a info section that would get
// displayed in a popup above the prompt when items are tabbed over
}
}, // completion
move |editor: &mut Editor, input: &str, event: PromptEvent| {
use helix_view::editor::Action; use helix_view::editor::Action;
if event != PromptEvent::Validate { #[derive(Clone)]
return; pub struct Command {
pub name: &'static str,
pub alias: Option<&'static str>,
pub doc: &'static str,
// params, flags, helper, completer
pub fun: fn(&mut Editor, &[&str], PromptEvent),
} }
let parts = input.split_ascii_whitespace().collect::<Vec<&str>>(); fn quit(editor: &mut Editor, args: &[&str], event: PromptEvent) {
match *parts.as_slice() {
["q"] | ["quit"] => {
// last view and we have unsaved changes // last view and we have unsaved changes
if editor.tree.views().count() == 1 { if editor.tree.views().count() == 1 {
let modified: Vec<_> = editor let modified: Vec<_> = editor
@ -880,13 +849,17 @@ pub fn command_mode(cx: &mut Context) {
} }
editor.close(editor.view().id, /* close_buffer */ false); editor.close(editor.view().id, /* close_buffer */ false);
} }
["q!"] | ["quit!"] => {
fn force_quit(editor: &mut Editor, args: &[&str], event: PromptEvent) {
editor.close(editor.view().id, /* close_buffer */ false); editor.close(editor.view().id, /* close_buffer */ false);
} }
["o", path] | ["open", path] => {
fn open(editor: &mut Editor, args: &[&str], event: PromptEvent) {
let path = args[0];
editor.open(path.into(), Action::Replace); editor.open(path.into(), Action::Replace);
} }
["w"] | ["write"] => {
fn write(editor: &mut Editor, args: &[&str], event: PromptEvent) {
let id = editor.view().doc; let id = editor.view().doc;
let doc = &mut editor.documents[id]; let doc = &mut editor.documents[id];
if doc.path().is_none() { if doc.path().is_none() {
@ -895,11 +868,105 @@ pub fn command_mode(cx: &mut Context) {
} }
tokio::spawn(doc.save()); tokio::spawn(doc.save());
} }
["new"] => { fn new_file(editor: &mut Editor, args: &[&str], event: PromptEvent) {
editor.new_file(Action::Replace); editor.new_file(Action::Replace);
} }
_ => (),
pub const COMMAND_LIST: &[Command] = &[
Command {
name: "quit",
alias: Some("q"),
doc: "Close the current view.",
fun: quit,
},
Command {
name: "quit!",
alias: Some("q!"),
doc: "Close the current view.",
fun: force_quit,
},
Command {
name: "open",
alias: Some("o"),
doc: "Open a file from disk into the current view.",
fun: open,
},
Command {
name: "write",
alias: Some("w"),
doc: "Write changes to disk.",
fun: write,
},
Command {
name: "new",
alias: Some("n"),
doc: "Create a new scratch buffer.",
fun: new_file,
},
];
pub static COMMANDS: Lazy<HashMap<&'static str, Command>> = Lazy::new(|| {
let mut map = HashMap::new();
for cmd in COMMAND_LIST {
map.insert(cmd.name, cmd.clone());
if let Some(alias) = cmd.alias {
map.insert(alias, cmd.clone());
}
}
map
});
}
pub fn command_mode(cx: &mut Context) {
let prompt = Prompt::new(
":".to_owned(),
|input: &str| {
// we use .this over split_ascii_whitespace() because we care about empty segments
let parts = input.split(' ').collect::<Vec<&str>>();
// simple heuristic: if there's no space, complete command.
// if there's a space, file completion kicks in. We should specialize by command later.
if parts.len() <= 1 {
use std::{borrow::Cow, ops::Range};
let end = 0..;
cmd::COMMAND_LIST
.iter()
.filter(|command| command.name.contains(input))
.map(|command| (end.clone(), Cow::Borrowed(command.name)))
.collect()
} else {
let part = parts.last().unwrap();
ui::completers::filename(part)
.into_iter()
.map(|(range, file)| {
// offset ranges to input
let offset = input.len() - part.len();
let range = (range.start + offset)..;
(range, file)
})
.collect()
// TODO
// additionally, completion items could have a info section that would get
// displayed in a popup above the prompt when items are tabbed over
} }
}, // completion
move |editor: &mut Editor, input: &str, event: PromptEvent| {
use helix_view::editor::Action;
if event != PromptEvent::Validate {
return;
}
let parts = input.split_ascii_whitespace().collect::<Vec<&str>>();
if let Some(cmd) = cmd::COMMANDS.get(parts[0]) {
(cmd.fun)(editor, &parts[1..], event);
} else {
editor.set_error(format!("no such command: '{}'", parts[0]));
};
}, },
); );
cx.push_layer(Box::new(prompt)); cx.push_layer(Box::new(prompt));

Loading…
Cancel
Save