Unnamed repository; edit this file 'description' to name the repository.
Add :help command
Omnikar 2022-12-13
parent fa436fa · commit 321fce3
-rw-r--r--book/src/generated/typable-cmd.md1
-rw-r--r--helix-term/src/commands/typed.rs156
-rw-r--r--helix-term/src/ui/mod.rs44
-rw-r--r--runtime/help/static-commands/append_mode.txt5
-rw-r--r--runtime/help/static-commands/append_to_line.txt5
-rw-r--r--runtime/help/static-commands/copy_selection_on_next_line.txt18
-rw-r--r--runtime/help/static-commands/copy_selection_on_prev_line.txt18
-rw-r--r--runtime/help/static-commands/extend_char_left.txt3
-rw-r--r--runtime/help/static-commands/extend_char_right.txt3
-rw-r--r--runtime/help/static-commands/extend_line_down.txt3
-rw-r--r--runtime/help/static-commands/extend_line_up.txt3
-rw-r--r--runtime/help/static-commands/extend_next_char.txt3
-rw-r--r--runtime/help/static-commands/extend_next_long_word_end.txt3
-rw-r--r--runtime/help/static-commands/extend_next_long_word_start.txt3
-rw-r--r--runtime/help/static-commands/extend_next_word_end.txt3
-rw-r--r--runtime/help/static-commands/extend_next_word_start.txt3
-rw-r--r--runtime/help/static-commands/extend_prev_char.txt3
-rw-r--r--runtime/help/static-commands/extend_prev_long_word_start.txt3
-rw-r--r--runtime/help/static-commands/extend_prev_word_start.txt3
-rw-r--r--runtime/help/static-commands/extend_till_char.txt3
-rw-r--r--runtime/help/static-commands/extend_till_prev_char.txt3
-rw-r--r--runtime/help/static-commands/find_next_char.txt22
-rw-r--r--runtime/help/static-commands/find_prev_char.txt22
-rw-r--r--runtime/help/static-commands/find_till_char.txt22
-rw-r--r--runtime/help/static-commands/half_page_down.txt3
-rw-r--r--runtime/help/static-commands/half_page_up.txt3
-rw-r--r--runtime/help/static-commands/insert_mode.txt5
-rw-r--r--runtime/help/static-commands/move_char_left.txt4
-rw-r--r--runtime/help/static-commands/move_char_right.txt4
-rw-r--r--runtime/help/static-commands/move_line_down.txt15
-rw-r--r--runtime/help/static-commands/move_line_up.txt15
-rw-r--r--runtime/help/static-commands/move_next_long_word_end.txt17
-rw-r--r--runtime/help/static-commands/move_next_long_word_start.txt17
-rw-r--r--runtime/help/static-commands/move_next_word_end.txt15
-rw-r--r--runtime/help/static-commands/move_next_word_start.txt15
-rw-r--r--runtime/help/static-commands/move_prev_long_word_start.txt20
-rw-r--r--runtime/help/static-commands/move_prev_word_start.txt18
-rw-r--r--runtime/help/static-commands/no_op.txt3
-rw-r--r--runtime/help/static-commands/page_down.txt3
-rw-r--r--runtime/help/static-commands/page_up.txt3
-rw-r--r--runtime/help/static-commands/prepend_to_line.txt5
-rw-r--r--runtime/help/static-commands/replace.txt21
-rw-r--r--runtime/help/static-commands/select_all.txt3
-rw-r--r--runtime/help/static-commands/switch_case.txt13
-rw-r--r--runtime/help/static-commands/switch_to_lowercase.txt9
-rw-r--r--runtime/help/static-commands/switch_to_uppercase.txt9
-rw-r--r--runtime/help/static-commands/till_prev_char.txt22
-rw-r--r--runtime/help/topics/Primary Modes.txt21
-rw-r--r--runtime/help/topics/Words vs. WORDS.txt19
-rw-r--r--runtime/help/typable-commands/buffer-close!.txt6
-rw-r--r--runtime/help/typable-commands/buffer-close.txt6
-rw-r--r--runtime/help/typable-commands/new.txt6
-rw-r--r--runtime/help/typable-commands/open.txt8
-rw-r--r--runtime/help/typable-commands/quit!.txt7
-rw-r--r--runtime/help/typable-commands/quit-all!.txt6
-rw-r--r--runtime/help/typable-commands/quit-all.txt6
-rw-r--r--runtime/help/typable-commands/quit.txt7
-rw-r--r--runtime/help/typable-commands/write-all.txt7
-rw-r--r--runtime/help/typable-commands/write-quit!.txt9
-rw-r--r--runtime/help/typable-commands/write-quit-all!.txt10
-rw-r--r--runtime/help/typable-commands/write-quit-all.txt10
-rw-r--r--runtime/help/typable-commands/write-quit.txt8
-rw-r--r--runtime/help/typable-commands/write.txt9
63 files changed, 742 insertions, 0 deletions
diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md
index 66e6ac03..26ce6c05 100644
--- a/book/src/generated/typable-cmd.md
+++ b/book/src/generated/typable-cmd.md
@@ -73,3 +73,4 @@
| `:pipe` | Pipe each selection to the shell command. |
| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
| `:run-shell-command`, `:sh` | Run a shell command |
+| `:help`, `:h` | Open documentation for a command or keybind. |
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>> =
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index 107e48dd..a68f423a 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -441,6 +441,50 @@ pub mod completers {
})
}
+ pub fn help(_editor: &Editor, input: &str) -> Vec<Completion> {
+ let static_cmds_path = helix_loader::runtime_dir().join("help/static-commands");
+ let typable_cmds_path = helix_loader::runtime_dir().join("help/typable-commands");
+ let mut items: Vec<String> = std::fs::read_dir(static_cmds_path)
+ .map(|entries| {
+ entries
+ .filter_map(|entry| {
+ let entry = entry.ok()?;
+ let path = entry.path();
+ (path.extension()? == "txt")
+ .then(|| path.file_stem().unwrap().to_string_lossy().into_owned())
+ })
+ .chain(
+ std::fs::read_dir(typable_cmds_path)
+ .map(|entries| {
+ entries.filter_map(|entry| {
+ let entry = entry.ok()?;
+ let path = entry.path();
+ (path.extension()? == "txt").then(|| {
+ format!(":{}", path.file_stem().unwrap().to_string_lossy())
+ })
+ })
+ })
+ .into_iter()
+ .flatten(),
+ )
+ .collect()
+ })
+ .unwrap_or_default();
+ items.push("topics".to_owned());
+
+ let matcher = Matcher::default();
+
+ let mut matches: Vec<_> = items
+ .into_iter()
+ .map(Cow::from)
+ .filter_map(|name| matcher.fuzzy_match(&name, input).map(|score| (name, score)))
+ .collect();
+
+ matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
+
+ matches.into_iter().map(|(name, _)| ((0..), name)).collect()
+ }
+
#[derive(Copy, Clone, PartialEq, Eq)]
enum FileMatch {
/// Entry should be ignored
diff --git a/runtime/help/static-commands/append_mode.txt b/runtime/help/static-commands/append_mode.txt
new file mode 100644
index 00000000..a50dff17
--- /dev/null
+++ b/runtime/help/static-commands/append_mode.txt
@@ -0,0 +1,5 @@
+`append_mode`
+
+Enters Insert mode at the end of the selection.
+
+For information about Insert mode, see "Primary Modes".
diff --git a/runtime/help/static-commands/append_to_line.txt b/runtime/help/static-commands/append_to_line.txt
new file mode 100644
index 00000000..0550be6f
--- /dev/null
+++ b/runtime/help/static-commands/append_to_line.txt
@@ -0,0 +1,5 @@
+`append_to_line`
+
+Enters Insert mode at the end of the line.
+
+For information about Insert mode, see "Primary Modes".
diff --git a/runtime/help/static-commands/copy_selection_on_next_line.txt b/runtime/help/static-commands/copy_selection_on_next_line.txt
new file mode 100644
index 00000000..0187444d
--- /dev/null
+++ b/runtime/help/static-commands/copy_selection_on_next_line.txt
@@ -0,0 +1,18 @@
+`copy_selection_on_next_line`
+
+Copies the current primary selection to the next line long enough to accomodate it.
+
+--- Examples ---
+
+The selection is copied from line 1 to line 2.
+┌───────────────────────────┐ ┌───────────────────────────┐
+│ This is text (on line 1]. │ --> │ This is text (on line 1]. │
+│ This is text on line 2. │ │ This is text (on line 2]. │
+└───────────────────────────┘ └───────────────────────────┘
+
+The selection duplication skips line 2 because it is too short.
+┌──────────────────────────────────┐ ┌──────────────────────────────────┐
+│ This is a longer li(ne of t]ext. │ │ This is a longer li(ne of t]ext. │
+│ This is a shorter line. │ --> │ This is a shorter line. │
+│ This is another longer line. │ │ This is another lon(ger lin]e. │
+└──────────────────────────────────┘ └──────────────────────────────────┘
diff --git a/runtime/help/static-commands/copy_selection_on_prev_line.txt b/runtime/help/static-commands/copy_selection_on_prev_line.txt
new file mode 100644
index 00000000..756c2b36
--- /dev/null
+++ b/runtime/help/static-commands/copy_selection_on_prev_line.txt
@@ -0,0 +1,18 @@
+`copy_selection_on_prev_line`
+
+Copies the current primary selection to the first previous line long enough to accomodate it.
+
+--- Examples ---
+
+The selection is copied from line 2 to line 1.
+┌───────────────────────────┐ ┌───────────────────────────┐
+│ This is text on line 1. │ --> │ This is text (on line 1]. │
+│ This is text (on line 2]. │ │ This is text (on line 2]. │
+└───────────────────────────┘ └───────────────────────────┘
+
+The selection duplication skips line 2 because it is too short.
+┌──────────────────────────────────┐ ┌──────────────────────────────────┐
+│ This is a longer line of text. │ │ This is a longer li(ne of t]ext. │
+│ This is a shorter line. │ --> │ This is a shorter line. │
+│ This is another lon(ger lin]e. │ │ This is another lon(ger lin]e. │
+└──────────────────────────────────┘ └──────────────────────────────────┘
diff --git a/runtime/help/static-commands/extend_char_left.txt b/runtime/help/static-commands/extend_char_left.txt
new file mode 100644
index 00000000..2e87ef60
--- /dev/null
+++ b/runtime/help/static-commands/extend_char_left.txt
@@ -0,0 +1,3 @@
+`extend_char_left`
+
+Extending version of `move_char_left`.
diff --git a/runtime/help/static-commands/extend_char_right.txt b/runtime/help/static-commands/extend_char_right.txt
new file mode 100644
index 00000000..7568169f
--- /dev/null
+++ b/runtime/help/static-commands/extend_char_right.txt
@@ -0,0 +1,3 @@
+`extend_char_right`
+
+Extending version of `move_char_right`.
diff --git a/runtime/help/static-commands/extend_line_down.txt b/runtime/help/static-commands/extend_line_down.txt
new file mode 100644
index 00000000..f37497c5
--- /dev/null
+++ b/runtime/help/static-commands/extend_line_down.txt
@@ -0,0 +1,3 @@
+`extend_line_down`
+
+Extending version of `move_line_down`.
diff --git a/runtime/help/static-commands/extend_line_up.txt b/runtime/help/static-commands/extend_line_up.txt
new file mode 100644
index 00000000..2adcbac6
--- /dev/null
+++ b/runtime/help/static-commands/extend_line_up.txt
@@ -0,0 +1,3 @@
+`extend_line_up`
+
+Extending version of `move_line_up`.
diff --git a/runtime/help/static-commands/extend_next_char.txt b/runtime/help/static-commands/extend_next_char.txt
new file mode 100644
index 00000000..3a850520
--- /dev/null
+++ b/runtime/help/static-commands/extend_next_char.txt
@@ -0,0 +1,3 @@
+`extend_next_char`
+
+Extending version of `find_next_char`.
diff --git a/runtime/help/static-commands/extend_next_long_word_end.txt b/runtime/help/static-commands/extend_next_long_word_end.txt
new file mode 100644
index 00000000..edc3acec
--- /dev/null
+++ b/runtime/help/static-commands/extend_next_long_word_end.txt
@@ -0,0 +1,3 @@
+`extend_next_long_word_end`
+
+Extending version of `move_next_long_word_end`.
diff --git a/runtime/help/static-commands/extend_next_long_word_start.txt b/runtime/help/static-commands/extend_next_long_word_start.txt
new file mode 100644
index 00000000..04f8e248
--- /dev/null
+++ b/runtime/help/static-commands/extend_next_long_word_start.txt
@@ -0,0 +1,3 @@
+`extend_next_long_word_start`
+
+Extending version of `move_next_long_word_start`.
diff --git a/runtime/help/static-commands/extend_next_word_end.txt b/runtime/help/static-commands/extend_next_word_end.txt
new file mode 100644
index 00000000..aee4840e
--- /dev/null
+++ b/runtime/help/static-commands/extend_next_word_end.txt
@@ -0,0 +1,3 @@
+`extend_next_word_end`
+
+Extending version of `move_next_word_end`.
diff --git a/runtime/help/static-commands/extend_next_word_start.txt b/runtime/help/static-commands/extend_next_word_start.txt
new file mode 100644
index 00000000..4c6e3875
--- /dev/null
+++ b/runtime/help/static-commands/extend_next_word_start.txt
@@ -0,0 +1,3 @@
+`extend_next_word_start`
+
+Extending version of `move_next_word_start`.
diff --git a/runtime/help/static-commands/extend_prev_char.txt b/runtime/help/static-commands/extend_prev_char.txt
new file mode 100644
index 00000000..631f8335
--- /dev/null
+++ b/runtime/help/static-commands/extend_prev_char.txt
@@ -0,0 +1,3 @@
+`extend_prev_char`
+
+Extending version of `find_prev_char`.
diff --git a/runtime/help/static-commands/extend_prev_long_word_start.txt b/runtime/help/static-commands/extend_prev_long_word_start.txt
new file mode 100644
index 00000000..26aa259c
--- /dev/null
+++ b/runtime/help/static-commands/extend_prev_long_word_start.txt
@@ -0,0 +1,3 @@
+`extend_prev_long_word_start`
+
+Extending version of `move_prev_long_word_start`.
diff --git a/runtime/help/static-commands/extend_prev_word_start.txt b/runtime/help/static-commands/extend_prev_word_start.txt
new file mode 100644
index 00000000..699343c8
--- /dev/null
+++ b/runtime/help/static-commands/extend_prev_word_start.txt
@@ -0,0 +1,3 @@
+`extend_prev_word_start`
+
+Extending version of `move_prev_word_start`.
diff --git a/runtime/help/static-commands/extend_till_char.txt b/runtime/help/static-commands/extend_till_char.txt
new file mode 100644
index 00000000..e614fe28
--- /dev/null
+++ b/runtime/help/static-commands/extend_till_char.txt
@@ -0,0 +1,3 @@
+`extend_till_char`
+
+Extending version of `find_till_char`.
diff --git a/runtime/help/static-commands/extend_till_prev_char.txt b/runtime/help/static-commands/extend_till_prev_char.txt
new file mode 100644
index 00000000..7f7047bb
--- /dev/null
+++ b/runtime/help/static-commands/extend_till_prev_char.txt
@@ -0,0 +1,3 @@
+`extend_till_prev_char`
+
+Extending version of `till_prev_char`.
diff --git a/runtime/help/static-commands/find_next_char.txt b/runtime/help/static-commands/find_next_char.txt
new file mode 100644
index 00000000..086754ea
--- /dev/null
+++ b/runtime/help/static-commands/find_next_char.txt
@@ -0,0 +1,22 @@
+`find_next_char`
+
+Waits for another keypress, then moves and
+selects forward, stopping at the first
+instance of the pressed key. Can take
+a count, which will cause it to stop
+at the nth instance of the keypress,
+rather than the first.
+
+--- Examples ---
+
+The cursor moves forward, stopping at 'c'
+and selecting everything along the way.
+┌───────────────────────┐ c ┌───────────────────────┐
+│ This i[s] a sentence. │ --> │ This i(s a sentenc]e. │
+└───────────────────────┘ └───────────────────────┘
+
+The cursor is not stopped by line breaks.
+┌───────────────────────────┐ ┌────────────────────────────┐
+│ This is the fi[r]st line. │ Q │ This is the fi(rst line. │
+│ This second line has a Q. │ --> │ This second line has a Q]. │
+└───────────────────────────┘ └────────────────────────────┘
diff --git a/runtime/help/static-commands/find_prev_char.txt b/runtime/help/static-commands/find_prev_char.txt
new file mode 100644
index 00000000..4d277a40
--- /dev/null
+++ b/runtime/help/static-commands/find_prev_char.txt
@@ -0,0 +1,22 @@
+`find_prev_char`
+
+Waits for another keypress, then moves and
+selects backward, stopping at the first
+instance of the pressed key. Can take
+a count, which will cause it to stop
+at the nth instance of the keypress,
+rather than the first.
+
+--- Examples ---
+
+The cursor moves backward, stopping at 'h'
+and selecting everything along the way.
+┌───────────────────────┐ h ┌───────────────────────┐
+│ This is a sent[e]nce. │ --> │ T[his is a sente)nce. │
+└───────────────────────┘ └───────────────────────┘
+
+The cursor is not stopped by line breaks.
+┌──────────────────────────────────┐ ┌───────────────────────────────────┐
+│ There is a Q in this first line. │ Q │ There is a [Q in this first line. │
+│ This is the se[c]ond line. │ --> │ This is the sec)ond line. │
+└──────────────────────────────────┘ └───────────────────────────────────┘
diff --git a/runtime/help/static-commands/find_till_char.txt b/runtime/help/static-commands/find_till_char.txt
new file mode 100644
index 00000000..dc00bb84
--- /dev/null
+++ b/runtime/help/static-commands/find_till_char.txt
@@ -0,0 +1,22 @@
+`find_till_char`
+
+Waits for another keypress, then moves and
+selects forward, stopping before the first
+instance of the pressed key. Can take
+a count, which will cause it to stop
+before the nth instance of the keypress,
+rather than the first.
+
+--- Examples ---
+
+The cursor moves forward, stopping before 'c'
+and selecting everything along the way.
+┌───────────────────────┐ c ┌───────────────────────┐
+│ This i[s] a sentence. │ --> │ This i(s a senten]ce. │
+└───────────────────────┘ └───────────────────────┘
+
+The cursor is not stopped by line breaks.
+┌───────────────────────────┐ ┌────────────────────────────┐
+│ This is the fi[r]st line. │ Q │ This is the fi(rst line. │
+│ This second line has a Q. │ --> │ This second line has a ]Q. │
+└───────────────────────────┘ └────────────────────────────┘
diff --git a/runtime/help/static-commands/half_page_down.txt b/runtime/help/static-commands/half_page_down.txt
new file mode 100644
index 00000000..2ddeac6b
--- /dev/null
+++ b/runtime/help/static-commands/half_page_down.txt
@@ -0,0 +1,3 @@
+`half_page_down`
+
+Scrolls down by half of a screen.
diff --git a/runtime/help/static-commands/half_page_up.txt b/runtime/help/static-commands/half_page_up.txt
new file mode 100644
index 00000000..1fdaec92
--- /dev/null
+++ b/runtime/help/static-commands/half_page_up.txt
@@ -0,0 +1,3 @@
+`half_page_up`
+
+Scrolls up by half of a screen.
diff --git a/runtime/help/static-commands/insert_mode.txt b/runtime/help/static-commands/insert_mode.txt
new file mode 100644
index 00000000..dda2f14a
--- /dev/null
+++ b/runtime/help/static-commands/insert_mode.txt
@@ -0,0 +1,5 @@
+`insert_mode`
+
+Enters Insert mode at the start of the selection.
+
+For information about Insert mode, see "Primary Modes".
diff --git a/runtime/help/static-commands/move_char_left.txt b/runtime/help/static-commands/move_char_left.txt
new file mode 100644
index 00000000..97b1363a
--- /dev/null
+++ b/runtime/help/static-commands/move_char_left.txt
@@ -0,0 +1,4 @@
+`move_char_left`
+
+Moves all cursors 1 character left, removing
+any selections and wrapping across line breaks.
diff --git a/runtime/help/static-commands/move_char_right.txt b/runtime/help/static-commands/move_char_right.txt
new file mode 100644
index 00000000..dc085d72
--- /dev/null
+++ b/runtime/help/static-commands/move_char_right.txt
@@ -0,0 +1,4 @@
+`move_char_right`
+
+Moves all cursors 1 character right, removing
+any selections and wrapping across line breaks.
diff --git a/runtime/help/static-commands/move_line_down.txt b/runtime/help/static-commands/move_line_down.txt
new file mode 100644
index 00000000..7cbe45c0
--- /dev/null
+++ b/runtime/help/static-commands/move_line_down.txt
@@ -0,0 +1,15 @@
+`move_line_down`
+
+Moves all cursors 1 line down, removing any selections.
+Repeating this will remember the vertical position of the cursors,
+even when moving across shorter lines.
+
+--- Examples ---
+
+The cursor remembers its vertical position,
+even after moving across the shorter line.
+┌────────────────────────────┐ ┌────────────────────────────┐ ┌──────────────────────────────┐
+│ This is a longer l[i]ne. │ │ This is a longer line. │ │ This is a longer line. │
+│ Shorter line. │ --> │ Shorter line.[] │ --> │ Shorter line. │
+│ This is another long line. │ │ This is another long line. │ │ This is another lo[n]g line. │
+└────────────────────────────┘ └────────────────────────────┘ └──────────────────────────────┘
diff --git a/runtime/help/static-commands/move_line_up.txt b/runtime/help/static-commands/move_line_up.txt
new file mode 100644
index 00000000..5fdef6c1
--- /dev/null
+++ b/runtime/help/static-commands/move_line_up.txt
@@ -0,0 +1,15 @@
+`move_line_up`
+
+Moves all cursors 1 line up, removing any selections.
+Repeating this will remember the vertical position of the cursors,
+even when moving across shorter lines.
+
+--- Examples ---
+
+The cursor remembers its vertical position,
+even after moving across the shorter line.
+┌──────────────────────────────┐ ┌────────────────────────────┐ ┌────────────────────────────┐
+│ This is a longer line. │ │ This is a longer line. │ │ This is a longer l[i]ne. │
+│ Shorter line. │ --> │ Shorter line.[] │ --> │ Shorter line. │
+│ This is another lo[n]g line. │ │ This is another long line. │ │ This is another long line. │
+└──────────────────────────────┘ └────────────────────────────┘ └────────────────────────────┘
diff --git a/runtime/help/static-commands/move_next_long_word_end.txt b/runtime/help/static-commands/move_next_long_word_end.txt
new file mode 100644
index 00000000..fd5a5933
--- /dev/null
+++ b/runtime/help/static-commands/move_next_long_word_end.txt
@@ -0,0 +1,17 @@
+`move_next_long_word_end`
+
+Moves and selects forward, stopping at
+the last character of the current WORD.
+
+For the difference between words and WORDS, see "Words vs. WORDS".
+
+--- Examples ---
+
+The cursor moves forward, stopping at the end of 'These-are'
+and selecting everything along the way.
+┌────────────────────┐ ┌────────────────────┐
+│ [T]hese-are WORDS. │ --> │ (These-are] WORDS. │
+└────────────────────┘ └────────────────────┘
+┌────────────────────┐ ┌────────────────────┐
+│ Th[e]se-are WORDS. │ --> │ Th(ese-are] WORDS. │
+└────────────────────┘ └────────────────────┘
diff --git a/runtime/help/static-commands/move_next_long_word_start.txt b/runtime/help/static-commands/move_next_long_word_start.txt
new file mode 100644
index 00000000..86f4a6cf
--- /dev/null
+++ b/runtime/help/static-commands/move_next_long_word_start.txt
@@ -0,0 +1,17 @@
+`move_next_long_word_start`
+
+Moves and selects forward, stopping before
+the first character of the next WORD.
+
+For the difference between words and WORDS, see "Words vs. WORDS".
+
+--- Examples ---
+
+The cursor moves forward, stopping before the start of 'WORDS'
+and selecting everything along the way.
+┌────────────────────┐ ┌────────────────────┐
+│ [T]hese-are WORDS. │ --> │ (These-are ]WORDS. │
+└────────────────────┘ └────────────────────┘
+┌────────────────────┐ ┌────────────────────┐
+│ Th[e]se-are WORDS. │ --> │ Th(ese-are ]WORDS. │
+└────────────────────┘ └────────────────────┘
diff --git a/runtime/help/static-commands/move_next_word_end.txt b/runtime/help/static-commands/move_next_word_end.txt
new file mode 100644
index 00000000..0b859a8a
--- /dev/null
+++ b/runtime/help/static-commands/move_next_word_end.txt
@@ -0,0 +1,15 @@
+`move_next_word_end`
+
+Moves and selects forward, stopping at
+the last character of the current word.
+
+--- Examples ---
+
+The cursor moves forward, stopping at the end of 'These'
+and selecting everything along the way.
+┌────────────────────┐ ┌────────────────────┐
+│ [T]hese are words. │ --> │ (These] are words. │
+└────────────────────┘ └────────────────────┘
+┌────────────────────┐ ┌────────────────────┐
+│ Th[e]se are words. │ --> │ Th(ese] are words. │
+└────────────────────┘ └────────────────────┘
diff --git a/runtime/help/static-commands/move_next_word_start.txt b/runtime/help/static-commands/move_next_word_start.txt
new file mode 100644
index 00000000..ca8b1117
--- /dev/null
+++ b/runtime/help/static-commands/move_next_word_start.txt
@@ -0,0 +1,15 @@
+`move_next_word_start`
+
+Moves and selects forward, stopping before
+the first character of the next word.
+
+--- Examples ---
+
+The cursor moves forward, stopping before the start of 'are'
+and selecting everything along the way.
+┌────────────────────┐ ┌────────────────────┐
+│ [T]hese are words. │ --> │ (These ]are words. │
+└────────────────────┘ └────────────────────┘
+┌────────────────────┐ ┌────────────────────┐
+│ Th[e]se are words. │ --> │ Th(ese ]are words. │
+└────────────────────┘ └────────────────────┘
diff --git a/runtime/help/static-commands/move_prev_long_word_start.txt b/runtime/help/static-commands/move_prev_long_word_start.txt
new file mode 100644
index 00000000..1b223c96
--- /dev/null
+++ b/runtime/help/static-commands/move_prev_long_word_start.txt
@@ -0,0 +1,20 @@
+`move_prev_long_word_start`
+
+Moves and selects backward, stopping at
+the first character of the previous WORD.
+
+For the difference between words and WORDS, see "Words vs. WORDS".
+
+--- Examples ---
+
+The cursor moves backwards, stopping at the start of 'These-are'
+and selecting everything along the way.
+┌────────────────────┐ ┌────────────────────┐
+│ These-are[ ]WORDS. │ --> │ [These-are )WORDS. │
+└────────────────────┘ └────────────────────┘
+┌────────────────────┐ ┌────────────────────┐
+│ These-are [W]ORDS. │ --> │ [These-are )WORDS. │
+└────────────────────┘ └────────────────────┘
+┌────────────────────┐ ┌────────────────────┐
+│ These-a[r]e WORDS. │ --> │ [These-ar)e WORDS. │
+└────────────────────┘ └────────────────────┘
diff --git a/runtime/help/static-commands/move_prev_word_start.txt b/runtime/help/static-commands/move_prev_word_start.txt
new file mode 100644
index 00000000..0e71450a
--- /dev/null
+++ b/runtime/help/static-commands/move_prev_word_start.txt
@@ -0,0 +1,18 @@
+`move_prev_word_start`
+
+Moves and selects backward, stopping at
+the first character of the previous word.
+
+--- Examples ---
+
+The cursor moves backwards, stopping at the start of 'These'
+and selecting everything along the way.
+┌────────────────────┐ ┌────────────────────┐
+│ These[ ]are words. │ --> │ [These )are words. │
+└────────────────────┘ └────────────────────┘
+┌────────────────────┐ ┌────────────────────┐
+│ These [a]re words. │ --> │ [These )are words. │
+└────────────────────┘ └────────────────────┘
+┌────────────────────┐ ┌────────────────────┐
+│ Th[e]se are words. │ --> │ [The)se are words. │
+└────────────────────┘ └────────────────────┘
diff --git a/runtime/help/static-commands/no_op.txt b/runtime/help/static-commands/no_op.txt
new file mode 100644
index 00000000..b8e9d20c
--- /dev/null
+++ b/runtime/help/static-commands/no_op.txt
@@ -0,0 +1,3 @@
+`no_op`
+
+Does nothing. Use this to disable default keybinds.
diff --git a/runtime/help/static-commands/page_down.txt b/runtime/help/static-commands/page_down.txt
new file mode 100644
index 00000000..0679b1d4
--- /dev/null
+++ b/runtime/help/static-commands/page_down.txt
@@ -0,0 +1,3 @@
+`page_down`
+
+Scrolls down by one screen.
diff --git a/runtime/help/static-commands/page_up.txt b/runtime/help/static-commands/page_up.txt
new file mode 100644
index 00000000..dab743af
--- /dev/null
+++ b/runtime/help/static-commands/page_up.txt
@@ -0,0 +1,3 @@
+`page_up`
+
+Scrolls up by one screen.
diff --git a/runtime/help/static-commands/prepend_to_line.txt b/runtime/help/static-commands/prepend_to_line.txt
new file mode 100644
index 00000000..628d8362
--- /dev/null
+++ b/runtime/help/static-commands/prepend_to_line.txt
@@ -0,0 +1,5 @@
+`prepend_to_line`
+
+Enters Insert mode before the first non-whitespace character in the line.
+
+For information about Insert mode, see "Primary Modes".
diff --git a/runtime/help/static-commands/replace.txt b/runtime/help/static-commands/replace.txt
new file mode 100644
index 00000000..ebe1a41f
--- /dev/null
+++ b/runtime/help/static-commands/replace.txt
@@ -0,0 +1,21 @@
+`replace`
+
+Waits for another keypress, then replaces all
+selected characters with the pressed key.
+
+--- Examples ---
+
+'a' is replaced with 'e'.
+┌──────────────────────────┐ e ┌──────────────────────────┐
+│ Do this, th[a]n do that. │ --> │ Do this, th[e]n do that. │
+└──────────────────────────┘ └──────────────────────────┘
+
+All instances of ',' are replaced with '.'.
+┌──────────────────────────────┐ . ┌──────────────────────────────┐
+│ This sentence continues(,,,] │ --> │ This sentence continues(...] │
+└──────────────────────────────┘ └──────────────────────────────┘
+
+All instances of 'a' are replaced with 'e'.
+┌──────────────────────────────────┐ e ┌──────────────────────────────────┐
+│ 1, th[a]n 2, th[a]n 3, th[a]n 4. │ --> │ 1, th[e]n 2, th[e]n 3, th[e]n 4. │
+└──────────────────────────────────┘ └──────────────────────────────────┘
diff --git a/runtime/help/static-commands/select_all.txt b/runtime/help/static-commands/select_all.txt
new file mode 100644
index 00000000..c3543a18
--- /dev/null
+++ b/runtime/help/static-commands/select_all.txt
@@ -0,0 +1,3 @@
+`select_all`
+
+Selects the entire buffer.
diff --git a/runtime/help/static-commands/switch_case.txt b/runtime/help/static-commands/switch_case.txt
new file mode 100644
index 00000000..84ca73c6
--- /dev/null
+++ b/runtime/help/static-commands/switch_case.txt
@@ -0,0 +1,13 @@
+`switch_case`
+
+Toggles the case of all selected letters.
+
+--- Examples ---
+
+┌───────────────────────┐ ┌───────────────────────┐
+│ [t]his is a sentence. │ --> │ [T]his is a sentence. │
+└───────────────────────┘ └───────────────────────┘
+
+┌───────────────────────┐ ┌───────────────────────┐
+│ (tHIS] is a sentence. │ --> │ (This] is a sentence. │
+└───────────────────────┘ └───────────────────────┘
diff --git a/runtime/help/static-commands/switch_to_lowercase.txt b/runtime/help/static-commands/switch_to_lowercase.txt
new file mode 100644
index 00000000..05569281
--- /dev/null
+++ b/runtime/help/static-commands/switch_to_lowercase.txt
@@ -0,0 +1,9 @@
+`switch_to_lowercase`
+
+Changes all selected letters to lowercase.
+
+--- Examples ---
+
+┌──────────────────────────────────┐ ┌──────────────────────────────────┐
+│ This (wOrD] should be lowercase. │ --> │ This (word] should be lowercase. │
+└──────────────────────────────────┘ └──────────────────────────────────┘
diff --git a/runtime/help/static-commands/switch_to_uppercase.txt b/runtime/help/static-commands/switch_to_uppercase.txt
new file mode 100644
index 00000000..e04673b7
--- /dev/null
+++ b/runtime/help/static-commands/switch_to_uppercase.txt
@@ -0,0 +1,9 @@
+`switch_to_uppercase`
+
+Changes all selected letters to uppercase.
+
+--- Examples ---
+
+┌──────────────────────────────────┐ ┌──────────────────────────────────┐
+│ This (wOrD] should be uppercase. │ --> │ This (WORD] should be uppercase. │
+└──────────────────────────────────┘ └──────────────────────────────────┘
diff --git a/runtime/help/static-commands/till_prev_char.txt b/runtime/help/static-commands/till_prev_char.txt
new file mode 100644
index 00000000..0ae3ad37
--- /dev/null
+++ b/runtime/help/static-commands/till_prev_char.txt
@@ -0,0 +1,22 @@
+`till_prev_char`
+
+Waits for another keypress, then moves and
+selects backward, stopping before the first
+instance of the pressed key. Can take
+a count, which will cause it to stop
+before the nth instance of the keypress,
+rather than the first.
+
+--- Examples ---
+
+The cursor moves backward, stopping before 'h'
+and selecting everything along the way.
+┌───────────────────────┐ h ┌───────────────────────┐
+│ This is a sent[e]nce. │ --> │ Th[is is a sente)nce. │
+└───────────────────────┘ └───────────────────────┘
+
+The cursor is not stopped by line breaks.
+┌──────────────────────────────────┐ ┌───────────────────────────────────┐
+│ There is a Q in this first line. │ Q │ There is a Q[ in this first line. │
+│ This is the se[c]ond line. │ --> │ This is the sec)ond line. │
+└──────────────────────────────────┘ └───────────────────────────────────┘
diff --git a/runtime/help/topics/Primary Modes.txt b/runtime/help/topics/Primary Modes.txt
new file mode 100644
index 00000000..d9026aa7
--- /dev/null
+++ b/runtime/help/topics/Primary Modes.txt
@@ -0,0 +1,21 @@
+Primary Modes
+
+Helix is a modal editor, and has two primary "modes" of operation.
+
+Normal mode, which is the mode you start in by default, and which
+you will likely spend a substantial amount of time in, is the main
+mode used for text editing operations.
+
+Insert mode, which can be entered in numerous ways — such as by
+pressing i — is used for typing text.
+
+A third mode, called "Select" or "Extend" mode, is essentially a
+subset of Normal mode, where moving cursors leaves selection
+anchors in place — useful for making modifications to existing
+selections. Select mode can be toggled by pressing v.
+
+You can see which of these three modes you are currently in by
+looking at the left side of the statusline. It will display:
+ NOR — Normal mode
+ INS — Insert mode
+ SEL — Select mode
diff --git a/runtime/help/topics/Words vs. WORDS.txt b/runtime/help/topics/Words vs. WORDS.txt
new file mode 100644
index 00000000..ab1aa9b6
--- /dev/null
+++ b/runtime/help/topics/Words vs. WORDS.txt
@@ -0,0 +1,19 @@
+Words vs. WORDS
+
+Words and WORDS are two very similar types of text objects. A word
+is a string of "word" characters, which include letters, numbers,
+and underscores. A WORD, on the other hand, is any string of
+non-whitespace characters.
+
+In the example below, the words are underlined by 'w', and the
+WORDS are underlined by 'W'.
+
+ This "stuff" is not-so difficult!
+ ---------------------------------
+ wwww wwwww ww www ww wwwwwwwww
+ WWWW WWWWWWW WW WWWWWW WWWWWWWWWW
+
+As is visible in the example, the words do not include any of the
+non-alphanumeric punctuation, while the WORDS do include the
+quotes, hyphen, and exclamation point. Also notice that 'not-so'
+contains two words, but only one WORD.
diff --git a/runtime/help/typable-commands/buffer-close!.txt b/runtime/help/typable-commands/buffer-close!.txt
new file mode 100644
index 00000000..42be7a52
--- /dev/null
+++ b/runtime/help/typable-commands/buffer-close!.txt
@@ -0,0 +1,6 @@
+`:buffer-close!`
+
+Aliases: `:bc!`, `:bclose!`
+
+Closes the current buffer, discarding any
+unsaved changes to the buffer.
diff --git a/runtime/help/typable-commands/buffer-close.txt b/runtime/help/typable-commands/buffer-close.txt
new file mode 100644
index 00000000..6dcb3152
--- /dev/null
+++ b/runtime/help/typable-commands/buffer-close.txt
@@ -0,0 +1,6 @@
+`:buffer-close`
+
+Aliases: `:bc`, `:bclose`
+
+Closes the current buffer, failing if the buffer
+has unsaved changes.
diff --git a/runtime/help/typable-commands/new.txt b/runtime/help/typable-commands/new.txt
new file mode 100644
index 00000000..e077c11a
--- /dev/null
+++ b/runtime/help/typable-commands/new.txt
@@ -0,0 +1,6 @@
+`:new`
+
+Aliases: `:n`
+
+Creates and switches to a new scratch buffer. This
+buffer will not be associated with any file path.
diff --git a/runtime/help/typable-commands/open.txt b/runtime/help/typable-commands/open.txt
new file mode 100644
index 00000000..d92d0f5d
--- /dev/null
+++ b/runtime/help/typable-commands/open.txt
@@ -0,0 +1,8 @@
+`:open <path>`
+
+Aliases: `:o`
+
+If <path> exists, opens the file at <path>.
+If <path> does not exist, creates a new buffer
+and associates it with <path>. Note that this
+does not automatically save the created buffer.
diff --git a/runtime/help/typable-commands/quit!.txt b/runtime/help/typable-commands/quit!.txt
new file mode 100644
index 00000000..ad40f76d
--- /dev/null
+++ b/runtime/help/typable-commands/quit!.txt
@@ -0,0 +1,7 @@
+`:quit!`
+
+Aliases: `:q!`
+
+Closes the current view, exiting the editor if it is
+the last view. If this would exit the editor, unsaved
+changes are discarded.
diff --git a/runtime/help/typable-commands/quit-all!.txt b/runtime/help/typable-commands/quit-all!.txt
new file mode 100644
index 00000000..185533e0
--- /dev/null
+++ b/runtime/help/typable-commands/quit-all!.txt
@@ -0,0 +1,6 @@
+`:quit-all!`
+
+Aliases: `:qa!`
+
+Closes all views, exiting the editor. This will
+discard any unsaved changes.
diff --git a/runtime/help/typable-commands/quit-all.txt b/runtime/help/typable-commands/quit-all.txt
new file mode 100644
index 00000000..69c570d7
--- /dev/null
+++ b/runtime/help/typable-commands/quit-all.txt
@@ -0,0 +1,6 @@
+`:quit-all`
+
+Aliases: `:qa`
+
+Closes all views, exiting the editor. This will
+fail if there are any unsaved changes.
diff --git a/runtime/help/typable-commands/quit.txt b/runtime/help/typable-commands/quit.txt
new file mode 100644
index 00000000..14bf23af
--- /dev/null
+++ b/runtime/help/typable-commands/quit.txt
@@ -0,0 +1,7 @@
+`:quit`
+
+Aliases: `:q`
+
+Closes the current view, exiting the editor if it is
+the last view. This will fail if it would exit the
+editor while there are unsaved changes.
diff --git a/runtime/help/typable-commands/write-all.txt b/runtime/help/typable-commands/write-all.txt
new file mode 100644
index 00000000..3518402a
--- /dev/null
+++ b/runtime/help/typable-commands/write-all.txt
@@ -0,0 +1,7 @@
+`:write-all`
+
+Aliases: `:wa`
+
+Writes (saves) all unsaved buffers to disk.
+Scratch buffers (without associated paths)
+will not be saved.
diff --git a/runtime/help/typable-commands/write-quit!.txt b/runtime/help/typable-commands/write-quit!.txt
new file mode 100644
index 00000000..916bd07f
--- /dev/null
+++ b/runtime/help/typable-commands/write-quit!.txt
@@ -0,0 +1,9 @@
+`:write-quit! [path]`
+
+Aliases: `:wq!`, `:x!`
+
+Writes (saves) the buffer to disk, then
+closes the view, discarding unsaved changes
+if this would exit the editor.
+Identical to `:write [path]` followed by `:quit!`.
+See those commands for more info.
diff --git a/runtime/help/typable-commands/write-quit-all!.txt b/runtime/help/typable-commands/write-quit-all!.txt
new file mode 100644
index 00000000..cfb3394d
--- /dev/null
+++ b/runtime/help/typable-commands/write-quit-all!.txt
@@ -0,0 +1,10 @@
+`:write-quit-all!`
+
+Aliases: `:wqa!`, `:xa!`
+
+Writes (saves) all unsaved buffers to disk, then
+closes all views, exiting the editor.
+Scratch buffers (without associated paths) will
+not be saved, and will be discarded when the
+editor exits.
+Identical to `:write-all` followed by `:quit-all!`.
diff --git a/runtime/help/typable-commands/write-quit-all.txt b/runtime/help/typable-commands/write-quit-all.txt
new file mode 100644
index 00000000..b551bcb3
--- /dev/null
+++ b/runtime/help/typable-commands/write-quit-all.txt
@@ -0,0 +1,10 @@
+`:write-quit-all`
+
+Aliases: `:wqa`, `:xa`
+
+Writes (saves) all unsaved buffers to disk, then
+closes all views, exiting the editor.
+Scratch buffers (without associated paths) will
+not be saved, and exiting the editor will fail
+if there are any such buffers open.
+Identical to `:write-all` followed by `:quit-all`.
diff --git a/runtime/help/typable-commands/write-quit.txt b/runtime/help/typable-commands/write-quit.txt
new file mode 100644
index 00000000..17629d5a
--- /dev/null
+++ b/runtime/help/typable-commands/write-quit.txt
@@ -0,0 +1,8 @@
+`:write-quit [path]`
+
+Aliases: `:wq`, `:x`
+
+Writes (saves) the buffer to disk, then
+closes the view.
+Identical to `:write [path]` followed by `:quit`.
+See those commands for more info.
diff --git a/runtime/help/typable-commands/write.txt b/runtime/help/typable-commands/write.txt
new file mode 100644
index 00000000..8035714a
--- /dev/null
+++ b/runtime/help/typable-commands/write.txt
@@ -0,0 +1,9 @@
+`:write [path]`
+
+Aliases: `:w`
+
+Writes (saves) the buffer to disk.
+If [path] is supplied, that path is written to.
+If the buffer is a scratch buffer, meaning it
+is not already bound to a file path, [path]
+becomes required for this command to succeed.