Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/commands/typed.rs')
-rw-r--r--helix-term/src/commands/typed.rs156
1 files changed, 156 insertions, 0 deletions
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index 2119a48d..242eee70 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -1808,6 +1808,155 @@ fn run_shell_command(
Ok(())
}
+fn help(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
+ if event != PromptEvent::Validate {
+ return Ok(());
+ }
+
+ if args.is_empty() {
+ // TODO: Open a list of commands?
+ todo!()
+ }
+
+ if args[0] == "topics" {
+ let dir_path = helix_loader::runtime_dir().join("help/topics");
+
+ struct Topic(PathBuf);
+ impl crate::ui::menu::Item for Topic {
+ type Data = ();
+ fn label(&self, _data: &Self::Data) -> Spans {
+ self.0
+ .file_stem()
+ .and_then(|s| s.to_str())
+ .map(From::from)
+ .unwrap_or_default()
+ }
+ }
+
+ let entries: Vec<Topic> = std::fs::read_dir(&dir_path)
+ .map(|entries| {
+ entries
+ .filter_map(|entry| {
+ let entry = entry.ok()?;
+ let path = entry.path();
+ Some(path)
+ })
+ .map(Topic)
+ .collect()
+ })
+ .unwrap_or_default();
+
+ cx.jobs.callback(async move {
+ let callback = job::Callback::EditorCompositor(Box::new(
+ move |_editor: &mut Editor, compositor: &mut Compositor| {
+ let picker = FilePicker::new(
+ entries,
+ (),
+ |cx, Topic(path), _action| {
+ if let Err(e) =
+ cx.editor
+ .open(path, Action::HorizontalSplit)
+ .and_then(|id| {
+ cx.editor
+ .document_mut(id)
+ .unwrap()
+ .set_path(None)
+ .map_err(Into::into)
+ })
+ {
+ cx.editor.set_error(e.to_string());
+ }
+ },
+ |_editor, Topic(path)| Some((path.clone().into(), None)),
+ );
+ compositor.push(Box::new(picker));
+ },
+ ));
+
+ Ok(callback)
+ });
+
+ return Ok(());
+ }
+
+ let args_msg = args.join(" ");
+ let open_help =
+ move |help_dir: &str, command: &str, editor: &mut Editor| -> anyhow::Result<()> {
+ let mut path = helix_loader::runtime_dir();
+ path.push("help");
+ path.push(help_dir);
+ path.push(format!("{}.txt", command));
+
+ ensure!(path.is_file(), "No help available for '{}'", args_msg);
+ let id = editor.open(&path, Action::HorizontalSplit)?;
+ editor.document_mut(id).unwrap().set_path(None)?;
+ Ok(())
+ };
+
+ const STATIC_HELP_DIR: &str = "static-commands";
+ const TYPABLE_HELP_DIR: &str = "typable-commands";
+
+ let (help_dir, command): (&str, &str) = {
+ let arg = &args[0];
+ if let Some(command) = arg.strip_prefix(':').and_then(|arg| {
+ TYPABLE_COMMAND_LIST.iter().find_map(|command| {
+ (command.name == arg || command.aliases.iter().any(|alias| *alias == arg))
+ .then(|| command.name)
+ })
+ }) {
+ (TYPABLE_HELP_DIR, command)
+ } else if MappableCommand::STATIC_COMMAND_LIST
+ .iter()
+ .any(|command| command.name() == arg)
+ {
+ (STATIC_HELP_DIR, arg)
+ } else {
+ let arg = arg.to_owned().into_owned();
+ let keys = arg
+ .parse::<KeyEvent>()
+ .map(|key| vec![key])
+ .or_else(|_| helix_view::input::parse_macro(&arg))?;
+
+ cx.jobs.callback(async move {
+ let callback = job::Callback::EditorCompositor(Box::new(
+ move |editor: &mut Editor, compositor: &mut Compositor| {
+ use crate::keymap::KeymapResult;
+ let editor_view = compositor.find::<ui::EditorView>().unwrap();
+ let mode = editor.mode;
+ let keymaps = &mut editor_view.keymaps;
+ let (keys, last_key) = (&keys[..keys.len() - 1], keys.last().unwrap());
+ keys.iter().for_each(|key| {
+ keymaps.get(mode, *key);
+ });
+ let result = keymaps.get(mode, *last_key);
+ let res: anyhow::Result<(&str, &str)> = match &result {
+ KeymapResult::Matched(command) => match command {
+ MappableCommand::Static { name, .. } => Ok((STATIC_HELP_DIR, name)),
+ MappableCommand::Typable { name, .. } => {
+ Ok((TYPABLE_HELP_DIR, name))
+ }
+ },
+ KeymapResult::NotFound | KeymapResult::Cancelled(_) => {
+ Err(anyhow!("No command found for '{}'", arg))
+ }
+ _ => todo!(),
+ };
+ if let Err(e) =
+ res.and_then(|(help_dir, command)| open_help(help_dir, command, editor))
+ {
+ editor.set_error(e.to_string());
+ }
+ },
+ ));
+ Ok(callback)
+ });
+ return Ok(());
+ }
+ };
+
+ open_help(help_dir, command, cx.editor)
+}
+
pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "quit",
@@ -2323,6 +2472,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: run_shell_command,
completer: Some(completers::directory),
},
+ TypableCommand {
+ name: "help",
+ aliases: &["h"],
+ doc: "Open documentation for a command or keybind.",
+ fun: help,
+ completer: Some(completers::help),
+ },
];
pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =