on master help sign in
Improve command mode handling.
9604a0cBlaž Hrastnik
helix-term/src/commands.rs+117−50
@@ -809,7 +809,115 @@ pub fn append_mode(cx: &mut Context) {
809 doc.set_selection(view.id, selection);
809 doc.set_selection(view.id, selection);
810}
810}
811
811
812+mod cmd {
813+ use super::*;
814+ use std::collections::HashMap;
815+
816+ use helix_view::editor::Action;
817+
818+ #[derive(Clone)]
819+ pub struct Command {
820+ pub name: &'static str,
821+ pub alias: Option<&'static str>,
822+ pub doc: &'static str,
823+ // params, flags, helper, completer
824+ pub fun: fn(&mut Editor, &[&str], PromptEvent),
825+ }
826+
827+ fn quit(editor: &mut Editor, args: &[&str], event: PromptEvent) {
828+ // last view and we have unsaved changes
829+ if editor.tree.views().count() == 1 {
830+ let modified: Vec<_> = editor
831+ .documents()
832+ .filter(|doc| doc.is_modified())
833+ .map(|doc| {
834+ doc.relative_path()
835+ .and_then(|path| path.to_str())
836+ .unwrap_or("[scratch]")
837+ })
838+ .collect();
839+
840+ if !modified.is_empty() {
841+ let err = format!(
842+ "{} unsaved buffer(s) remaining: {:?}",
843+ modified.len(),
844+ modified
845+ );
846+ editor.set_error(err);
847+ return;
848+ }
849+ }
850+ editor.close(editor.view().id, /* close_buffer */ false);
851+ }
852+
853+ fn force_quit(editor: &mut Editor, args: &[&str], event: PromptEvent) {
854+ editor.close(editor.view().id, /* close_buffer */ false);
855+ }
856+
857+ fn open(editor: &mut Editor, args: &[&str], event: PromptEvent) {
858+ let path = args[0];
859+ editor.open(path.into(), Action::Replace);
860+ }
861+
862+ fn write(editor: &mut Editor, args: &[&str], event: PromptEvent) {
863+ let id = editor.view().doc;
864+ let doc = &mut editor.documents[id];
865+ if doc.path().is_none() {
866+ editor.set_error("cannot write a buffer without a filename".to_string());
867+ return;
868+ }
869+ tokio::spawn(doc.save());
870+ }
871+ fn new_file(editor: &mut Editor, args: &[&str], event: PromptEvent) {
872+ editor.new_file(Action::Replace);
873+ }
874+
812−const COMMAND_LIST: &[&str] = &["write", "open", "quit", "quit!"];
875+ pub const COMMAND_LIST: &[Command] = &[
876+ Command {
877+ name: "quit",
878+ alias: Some("q"),
879+ doc: "Close the current view.",
880+ fun: quit,
881+ },
882+ Command {
883+ name: "quit!",
884+ alias: Some("q!"),
885+ doc: "Close the current view.",
886+ fun: force_quit,
887+ },
888+ Command {
889+ name: "open",
890+ alias: Some("o"),
891+ doc: "Open a file from disk into the current view.",
892+ fun: open,
893+ },
894+ Command {
895+ name: "write",
896+ alias: Some("w"),
897+ doc: "Write changes to disk.",
898+ fun: write,
899+ },
900+ Command {
901+ name: "new",
902+ alias: Some("n"),
903+ doc: "Create a new scratch buffer.",
904+ fun: new_file,
905+ },
906+ ];
907+
908+ pub static COMMANDS: Lazy<HashMap<&'static str, Command>> = Lazy::new(|| {
909+ let mut map = HashMap::new();
910+
911+ for cmd in COMMAND_LIST {
912+ map.insert(cmd.name, cmd.clone());
913+ if let Some(alias) = cmd.alias {
914+ map.insert(alias, cmd.clone());
915+ }
916+ }
917+
918+ map
919+ });
920+}
813
921
814pub fn command_mode(cx: &mut Context) {
922pub fn command_mode(cx: &mut Context) {
815 let prompt = Prompt::new(
923 let prompt = Prompt::new(
@@ -823,10 +931,10 @@ pub fn command_mode(cx: &mut Context) {
823 if parts.len() <= 1 {
931 if parts.len() <= 1 {
824 use std::{borrow::Cow, ops::Range};
932 use std::{borrow::Cow, ops::Range};
825 let end = 0..;
933 let end = 0..;
826− COMMAND_LIST
934+ cmd::COMMAND_LIST
827 .iter()
935 .iter()
828− .filter(|command| command.contains(input))
936+ .filter(|command| command.name.contains(input))
829− .map(|command| (end.clone(), Cow::Borrowed(*command)))
937+ .map(|command| (end.clone(), Cow::Borrowed(command.name)))
830 .collect()
938 .collect()
831 } else {
939 } else {
832 let part = parts.last().unwrap();
940 let part = parts.last().unwrap();
@@ -854,52 +962,11 @@ pub fn command_mode(cx: &mut Context) {
854
962
855 let parts = input.split_ascii_whitespace().collect::<Vec<&str>>();
963 let parts = input.split_ascii_whitespace().collect::<Vec<&str>>();
856
964
857− match *parts.as_slice() {
965+ if let Some(cmd) = cmd::COMMANDS.get(parts[0]) {
858− ["q"] | ["quit"] => {
966+ (cmd.fun)(editor, &parts[1..], event);
859− // last view and we have unsaved changes
967+ } else {
860− if editor.tree.views().count() == 1 {
968+ editor.set_error(format!("no such command: '{}'", parts[0]));
861− let modified: Vec<_> = editor
862− .documents()
863− .filter(|doc| doc.is_modified())
864− .map(|doc| {
865− doc.relative_path()
866− .and_then(|path| path.to_str())
867− .unwrap_or("[scratch]")
868− })
869− .collect();
870−
871− if !modified.is_empty() {
872− let err = format!(
873− "{} unsaved buffer(s) remaining: {:?}",
874− modified.len(),
875− modified
876− );
877− editor.set_error(err);
878− return;
879− }
880− }
881− editor.close(editor.view().id, /* close_buffer */ false);
882− }
883− ["q!"] | ["quit!"] => {
884− editor.close(editor.view().id, /* close_buffer */ false);
885− }
886− ["o", path] | ["open", path] => {
887− editor.open(path.into(), Action::Replace);
888− }
889− ["w"] | ["write"] => {
890− let id = editor.view().doc;
891− let doc = &mut editor.documents[id];
892− if doc.path().is_none() {
893− editor.set_error("cannot write a buffer without a filename".to_string());
894− return;
895− }
896− tokio::spawn(doc.save());
897− }
969+ };
898− ["new"] => {
899− editor.new_file(Action::Replace);
900− }
901− _ => (),
902− }
903 },
970 },
904 );
971 );
905 cx.push_layer(Box::new(prompt));
972 cx.push_layer(Box::new(prompt));