@@ -16,6 +16,7 @@ use helix_core::{
1616use helix_view::{
1717 document::{IndentStyle, Mode},
1818 editor::Action,
19+ info::Info,
1920 input::KeyEvent,
2021 keyboard::KeyCode,
2122 view::{View, PADDING},
@@ -38,6 +39,7 @@ use crate::{
3839
3940use crate::job::{self, Job, Jobs};
4041use futures_util::{FutureExt, TryFutureExt};
42+use std::collections::HashMap;
4143use std::{fmt, future::Future};
4244
4345use std::{
@@ -45,7 +47,7 @@ use std::{
4547 path::{Path, PathBuf},
4648};
4749
48−use once_cell::sync::Lazy;
50+use once_cell::sync::{Lazy, OnceCell};
4951use serde::de::{self, Deserialize, Deserializer};
5052
5153pub struct Context<'a> {
@@ -74,6 +76,16 @@ impl<'a> Context<'a> {
7476 self.on_next_key_callback = Some(Box::new(on_next_key_callback));
7577 }
7678
79+ #[inline]
80+ pub fn on_next_key_mode(&mut self, map: HashMap<KeyEvent, fn(&mut Context)>) {
81+ self.on_next_key(move |cx, event| {
82+ cx.editor.autoinfo = None;
83+ if let Some(func) = map.get(&event) {
84+ func(cx);
85+ }
86+ });
87+ }
88+
7789 #[inline]
7890 pub fn callback<T, F>(
7991 &mut self,
@@ -153,17 +165,12 @@ impl Command {
153165 move_char_right,
154166 move_line_up,
155167 move_line_down,
156− move_line_end,
157− move_line_start,
158− move_first_nonwhitespace,
159168 move_next_word_start,
160169 move_prev_word_start,
161170 move_next_word_end,
162171 move_next_long_word_start,
163172 move_prev_long_word_start,
164173 move_next_long_word_end,
165− move_file_start,
166− move_file_end,
167174 extend_next_word_start,
168175 extend_prev_word_start,
169176 extend_next_word_end,
@@ -175,7 +182,6 @@ impl Command {
175182 find_prev_char,
176183 extend_till_prev_char,
177184 extend_prev_char,
178− extend_first_nonwhitespace,
179185 replace,
180186 page_up,
181187 page_down,
@@ -185,8 +191,6 @@ impl Command {
185191 extend_char_right,
186192 extend_line_up,
187193 extend_line_down,
188− extend_line_end,
189− extend_line_start,
190194 select_all,
191195 select_regex,
192196 split_selection,
@@ -196,6 +200,7 @@ impl Command {
196200 extend_search_next,
197201 search_selection,
198202 extend_line,
203+ extend_to_line_bounds,
199204 delete_selection,
200205 change_selection,
201206 collapse_selection,
@@ -217,11 +222,17 @@ impl Command {
217222 goto_definition,
218223 goto_type_definition,
219224 goto_implementation,
225+ goto_file_start,
226+ goto_file_end,
220227 goto_reference,
221228 goto_first_diag,
222229 goto_last_diag,
223230 goto_next_diag,
224231 goto_prev_diag,
232+ goto_line_start,
233+ goto_line_end,
234+ goto_line_end_newline,
235+ goto_first_nonwhitespace,
225236 signature_help,
226237 insert_tab,
227238 insert_newline,
@@ -376,7 +387,7 @@ fn move_line_down(cx: &mut Context) {
376387 );
377388}
378389
379−fn move_line_end(cx: &mut Context) {
390+fn goto_line_end(cx: &mut Context) {
380391 let (view, doc) = current!(cx.editor);
381392 doc.set_selection(
382393 view.id,
@@ -388,12 +399,33 @@ fn move_line_end(cx: &mut Context) {
388399 let pos = graphemes::nth_prev_grapheme_boundary(text.slice(..), pos, 1);
389400 let pos = range.head.max(pos).max(text.line_to_char(line));
390401
402+ Range::new(
403+ match doc.mode {
404+ Mode::Normal | Mode::Insert => pos,
405+ Mode::Select => range.anchor,
406+ },
407+ pos,
408+ )
409+ }),
410+ );
411+}
412+
413+fn goto_line_end_newline(cx: &mut Context) {
414+ let (view, doc) = current!(cx.editor);
415+
416+ doc.set_selection(
417+ view.id,
418+ doc.selection(view.id).clone().transform(|range| {
419+ let text = doc.text();
420+ let line = text.char_to_line(range.head);
421+
422+ let pos = line_end_char_index(&text.slice(..), line);
391423 Range::new(pos, pos)
392424 }),
393425 );
394426}
395427
396−fn move_line_start(cx: &mut Context) {
428+fn goto_line_start(cx: &mut Context) {
397429 let (view, doc) = current!(cx.editor);
398430 doc.set_selection(
399431 view.id,
@@ -403,12 +435,18 @@ fn move_line_start(cx: &mut Context) {
403435
404436 // adjust to start of the line
405437 let pos = text.line_to_char(line);
406− Range::new(pos, pos)
438+ Range::new(
439+ match doc.mode {
440+ Mode::Normal | Mode::Insert => pos,
441+ Mode::Select => range.anchor,
442+ },
443+ pos,
444+ )
407445 }),
408446 );
409447}
410448
411−fn move_first_nonwhitespace(cx: &mut Context) {
449+fn goto_first_nonwhitespace(cx: &mut Context) {
412450 let (view, doc) = current!(cx.editor);
413451 doc.set_selection(
414452 view.id,
@@ -418,7 +456,13 @@ fn move_first_nonwhitespace(cx: &mut Context) {
418456
419457 if let Some(pos) = find_first_non_whitespace_char(text.line(line_idx)) {
420458 let pos = pos + text.line_to_char(line_idx);
421− Range::new(pos, pos)
459+ Range::new(
460+ match doc.mode {
461+ Mode::Normal | Mode::Insert => pos,
462+ Mode::Select => range.anchor,
463+ },
464+ pos,
465+ )
422466 } else {
423467 range
424468 }
@@ -426,6 +470,37 @@ fn move_first_nonwhitespace(cx: &mut Context) {
426470 );
427471}
428472
473+fn goto_window(cx: &mut Context, align: Align) {
474+ let (view, doc) = current!(cx.editor);
475+
476+ let scrolloff = PADDING.min(view.area.height as usize / 2); // TODO: user pref
477+
478+ let last_line = view.last_line(doc);
479+
480+ let line = match align {
481+ Align::Top => (view.first_line + scrolloff),
482+ Align::Center => (view.first_line + (view.area.height as usize / 2)),
483+ Align::Bottom => last_line.saturating_sub(scrolloff),
484+ }
485+ .min(last_line.saturating_sub(scrolloff));
486+
487+ let pos = doc.text().line_to_char(line);
488+
489+ doc.set_selection(view.id, Selection::point(pos));
490+}
491+
492+fn goto_window_top(cx: &mut Context) {
493+ goto_window(cx, Align::Top)
494+}
495+
496+fn goto_window_middle(cx: &mut Context) {
497+ goto_window(cx, Align::Center)
498+}
499+
500+fn goto_window_bottom(cx: &mut Context) {
501+ goto_window(cx, Align::Bottom)
502+}
503+
429504// TODO: move vs extend could take an extra type Extend/Move that would
430505// Range::new(if Move { pos } if Extend { range.anchor }, pos)
431506// since these all really do the same thing
@@ -497,13 +572,13 @@ fn move_next_long_word_end(cx: &mut Context) {
497572 );
498573}
499574
500−fn move_file_start(cx: &mut Context) {
575+fn goto_file_start(cx: &mut Context) {
501576 push_jump(cx.editor);
502577 let (view, doc) = current!(cx.editor);
503578 doc.set_selection(view.id, Selection::point(0));
504579}
505580
506−fn move_file_end(cx: &mut Context) {
581+fn goto_file_end(cx: &mut Context) {
507582 push_jump(cx.editor);
508583 let (view, doc) = current!(cx.editor);
509584 let text = doc.text();
@@ -683,24 +758,6 @@ fn extend_prev_char(cx: &mut Context) {
683758 )
684759}
685760
686−fn extend_first_nonwhitespace(cx: &mut Context) {
687− let (view, doc) = current!(cx.editor);
688− doc.set_selection(
689− view.id,
690− doc.selection(view.id).clone().transform(|range| {
691− let text = doc.text();
692− let line_idx = text.char_to_line(range.head);
693−
694− if let Some(pos) = find_first_non_whitespace_char(text.line(line_idx)) {
695− let pos = pos + text.line_to_char(line_idx);
696− Range::new(range.anchor, pos)
697− } else {
698− range
699− }
700− }),
701− );
702−}
703−
704761fn replace(cx: &mut Context) {
705762 let mut buf = [0u8; 4]; // To hold utf8 encoded char.
706763
@@ -880,38 +937,6 @@ fn extend_line_down(cx: &mut Context) {
880937 );
881938}
882939
883−fn extend_line_end(cx: &mut Context) {
884− let (view, doc) = current!(cx.editor);
885− doc.set_selection(
886− view.id,
887− doc.selection(view.id).clone().transform(|range| {
888− let text = doc.text().slice(..);
889− let line = text.char_to_line(range.head);
890−
891− let pos = line_end_char_index(&text, line);
892− let pos = graphemes::nth_prev_grapheme_boundary(text, pos, 1);
893− let pos = range.head.max(pos).max(text.line_to_char(line));
894−
895− Range::new(range.anchor, pos)
896− }),
897− );
898−}
899−
900−fn extend_line_start(cx: &mut Context) {
901− let (view, doc) = current!(cx.editor);
902− doc.set_selection(
903− view.id,
904− doc.selection(view.id).clone().transform(|range| {
905− let text = doc.text();
906− let line = text.char_to_line(range.head);
907−
908− // adjust to start of the line
909− let pos = text.line_to_char(line);
910− Range::new(range.anchor, pos)
911− }),
912− );
913−}
914−
915940fn select_all(cx: &mut Context) {
916941 let (view, doc) = current!(cx.editor);
917942
@@ -1055,6 +1080,27 @@ fn extend_line(cx: &mut Context) {
10551080 doc.set_selection(view.id, Selection::single(start, end));
10561081}
10571082
1083+fn extend_to_line_bounds(cx: &mut Context) {
1084+ let (view, doc) = current!(cx.editor);
1085+
1086+ doc.set_selection(
1087+ view.id,
1088+ doc.selection(view.id).clone().transform(|range| {
1089+ let text = doc.text();
1090+ let start = text.line_to_char(text.char_to_line(range.from()));
1091+ let end = text
1092+ .line_to_char(text.char_to_line(range.to()) + 1)
1093+ .saturating_sub(1);
1094+
1095+ if range.anchor < range.head {
1096+ Range::new(start, end)
1097+ } else {
1098+ Range::new(end, start)
1099+ }
1100+ }),
1101+ );
1102+}
1103+
10581104fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) {
10591105 let text = doc.text().slice(..);
10601106 let selection = doc.selection(view_id).clone().min_width_1(text);
@@ -1580,6 +1626,24 @@ mod cmd {
15801626 }
15811627 }
15821628
1629+ /// Sets the [`Document`]'s encoding..
1630+ fn set_encoding(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) {
1631+ let (_, doc) = current!(cx.editor);
1632+ if let Some(label) = args.first() {
1633+ doc.set_encoding(label)
1634+ .unwrap_or_else(|e| cx.editor.set_error(e.to_string()));
1635+ } else {
1636+ let encoding = doc.encoding().name().to_string();
1637+ cx.editor.set_status(encoding)
1638+ }
1639+ }
1640+
1641+ /// Reload the [`Document`] from its source file.
1642+ fn reload(cx: &mut compositor::Context, _args: &[&str], _: PromptEvent) {
1643+ let (view, doc) = current!(cx.editor);
1644+ doc.reload(view.id).unwrap();
1645+ }
1646+
15831647 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
15841648 TypableCommand {
15851649 name: "quit",
@@ -1763,6 +1827,20 @@ mod cmd {
17631827 fun: show_current_directory,
17641828 completer: None,
17651829 },
1830+ TypableCommand {
1831+ name: "encoding",
1832+ alias: None,
1833+ doc: "Set encoding based on `https://encoding.spec.whatwg.org`",
1834+ fun: set_encoding,
1835+ completer: None,
1836+ },
1837+ TypableCommand {
1838+ name: "reload",
1839+ alias: None,
1840+ doc: "Discard changes and reload from the source file.",
1841+ fun: reload,
1842+ completer: None,
1843+ }
17661844 ];
17671845
17681846 pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
@@ -1955,7 +2033,7 @@ fn symbol_picker(cx: &mut Context) {
19552033
19562034// I inserts at the first nonwhitespace character of each line with a selection
19572035fn prepend_to_line(cx: &mut Context) {
1958− move_first_nonwhitespace(cx);
2036+ goto_first_nonwhitespace(cx);
19592037 let doc = doc_mut!(cx.editor);
19602038 enter_insert_mode(doc);
19612039}
@@ -2124,7 +2202,7 @@ fn push_jump(editor: &mut Editor) {
21242202 view.jumps.push(jump);
21252203}
21262204
2127−fn switch_to_last_accessed_file(cx: &mut Context) {
2205+fn goto_last_accessed_file(cx: &mut Context) {
21282206 let alternate_file = view!(cx.editor).last_accessed_doc;
21292207 if let Some(alt) = alternate_file {
21302208 cx.editor.switch(alt, Action::Replace);
@@ -2133,65 +2211,6 @@ fn switch_to_last_accessed_file(cx: &mut Context) {
21332211 }
21342212}
21352213
2136−fn goto_mode(cx: &mut Context) {
2137− if let Some(count) = cx.count {
2138− push_jump(cx.editor);
2139−
2140− let (view, doc) = current!(cx.editor);
2141− let line_idx = std::cmp::min(count.get() - 1, doc.text().len_lines().saturating_sub(2));
2142− let pos = doc.text().line_to_char(line_idx);
2143− doc.set_selection(view.id, Selection::point(pos));
2144− return;
2145− }
2146−
2147− cx.on_next_key(move |cx, event| {
2148− if let KeyEvent {
2149− code: KeyCode::Char(ch),
2150− ..
2151− } = event
2152− {
2153− // TODO: temporarily show GOTO in the mode list
2154− let doc = doc_mut!(cx.editor);
2155− match (doc.mode, ch) {
2156− (_, 'g') => move_file_start(cx),
2157− (_, 'e') => move_file_end(cx),
2158− (_, 'a') => switch_to_last_accessed_file(cx),
2159− (Mode::Normal, 'h') => move_line_start(cx),
2160− (Mode::Normal, 'l') => move_line_end(cx),
2161− (Mode::Select, 'h') => extend_line_start(cx),
2162− (Mode::Select, 'l') => extend_line_end(cx),
2163− (_, 'd') => goto_definition(cx),
2164− (_, 'y') => goto_type_definition(cx),
2165− (_, 'r') => goto_reference(cx),
2166− (_, 'i') => goto_implementation(cx),
2167− (Mode::Normal, 's') => move_first_nonwhitespace(cx),
2168− (Mode::Select, 's') => extend_first_nonwhitespace(cx),
2169−
2170− (_, 't') | (_, 'm') | (_, 'b') => {
2171− let (view, doc) = current!(cx.editor);
2172−
2173− let scrolloff = PADDING.min(view.area.height as usize / 2); // TODO: user pref
2174−
2175− let last_line = view.last_line(doc);
2176−
2177− let line = match ch {
2178− 't' => (view.first_line + scrolloff),
2179− 'm' => (view.first_line + (view.area.height as usize / 2)),
2180− 'b' => last_line.saturating_sub(scrolloff),
2181− _ => unreachable!(),
2182− }
2183− .min(last_line.saturating_sub(scrolloff));
2184−
2185− let pos = doc.text().line_to_char(line);
2186−
2187− doc.set_selection(view.id, Selection::point(pos));
2188− }
2189− _ => (),
2190− }
2191− }
2192− })
2193−}
2194−
21952214fn select_mode(cx: &mut Context) {
21962215 let (view, doc) = current!(cx.editor);
21972216
@@ -2211,13 +2230,27 @@ fn select_mode(cx: &mut Context) {
22112230 }),
22122231 );
22132232
2214− doc.mode = Mode::Select;
2233+ doc_mut!(cx.editor).mode = Mode::Select;
22152234}
22162235
22172236fn exit_select_mode(cx: &mut Context) {
22182237 doc_mut!(cx.editor).mode = Mode::Normal;
22192238}
22202239
2240+fn goto_prehook(cx: &mut Context) -> bool {
2241+ if let Some(count) = cx.count {
2242+ push_jump(cx.editor);
2243+
2244+ let (view, doc) = current!(cx.editor);
2245+ let line_idx = std::cmp::min(count.get() - 1, doc.text().len_lines().saturating_sub(1));
2246+ let pos = doc.text().line_to_char(line_idx);
2247+ doc.set_selection(view.id, Selection::point(pos));
2248+ true
2249+ } else {
2250+ false
2251+ }
2252+}
2253+
22212254fn goto_impl(
22222255 editor: &mut Editor,
22232256 compositor: &mut Compositor,
@@ -3457,33 +3490,6 @@ fn select_register(cx: &mut Context) {
34573490 })
34583491}
34593492
3460−fn space_mode(cx: &mut Context) {
3461− cx.on_next_key(move |cx, event| {
3462− if let KeyEvent {
3463− code: KeyCode::Char(ch),
3464− ..
3465− } = event
3466− {
3467− // TODO: temporarily show SPC in the mode list
3468− match ch {
3469− 'f' => file_picker(cx),
3470− 'b' => buffer_picker(cx),
3471− 's' => symbol_picker(cx),
3472− 'w' => window_mode(cx),
3473− 'y' => yank_joined_to_clipboard(cx),
3474− 'Y' => yank_main_selection_to_clipboard(cx),
3475− 'p' => paste_clipboard_after(cx),
3476− 'P' => paste_clipboard_before(cx),
3477− 'R' => replace_selections_with_clipboard(cx),
3478− // ' ' => toggle_alternate_buffer(cx),
3479− // TODO: temporary since space mode took its old key
3480− ' ' => keep_primary_selection(cx),
3481− _ => (),
3482− }
3483− }
3484− })
3485−}
3486−
34873493fn view_mode(cx: &mut Context) {
34883494 cx.on_next_key(move |cx, event| {
34893495 if let KeyEvent {
@@ -3559,6 +3565,9 @@ fn right_bracket_mode(cx: &mut Context) {
35593565 })
35603566}
35613567
3568+use helix_core::surround;
3569+use helix_core::textobject;
3570+
35623571fn match_mode(cx: &mut Context) {
35633572 let count = cx.count;
35643573 cx.on_next_key(move |cx, event| {
@@ -3574,13 +3583,41 @@ fn match_mode(cx: &mut Context) {
35743583 's' => surround_add(cx),
35753584 'r' => surround_replace(cx),
35763585 'd' => surround_delete(cx),
3586+ 'a' => select_textobject(cx, textobject::TextObject::Around),
3587+ 'i' => select_textobject(cx, textobject::TextObject::Inside),
35773588 _ => (),
35783589 }
35793590 }
35803591 })
35813592}
35823593
3583−use helix_core::surround;
3594+fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
3595+ let count = cx.count();
3596+ cx.on_next_key(move |cx, event| {
3597+ if let KeyEvent {
3598+ code: KeyCode::Char(ch),
3599+ ..
3600+ } = event
3601+ {
3602+ let (view, doc) = current!(cx.editor);
3603+
3604+ doc.set_selection(
3605+ view.id,
3606+ doc.selection(view.id).clone().transform(|range| {
3607+ let text = doc.text().slice(..);
3608+ match ch {
3609+ 'w' => textobject::textobject_word(text, range, objtype, count),
3610+ // TODO: cancel new ranges if inconsistent surround matches across lines
3611+ ch if !ch.is_ascii_alphanumeric() => {
3612+ textobject::textobject_surround(text, range, objtype, ch, count)
3613+ }
3614+ _ => range,
3615+ }
3616+ }),
3617+ );
3618+ }
3619+ })
3620+}
35843621
35853622fn surround_add(cx: &mut Context) {
35863623 cx.on_next_key(move |cx, event| {
@@ -3671,3 +3708,132 @@ fn surround_delete(cx: &mut Context) {
36713708 }
36723709 })
36733710}
3711+
3712+/// Do nothing, just for modeinfo.
3713+fn noop(_cx: &mut Context) -> bool {
3714+ false
3715+}
3716+
3717+/// Generate modeinfo.
3718+///
3719+/// If prehook returns true then it will stop the rest.
3720+macro_rules! mode_info {
3721+ // TODO: reuse $mode for $stat
3722+ (@join $first:expr $(,$rest:expr)*) => {
3723+ concat!($first, $(", ", $rest),*)
3724+ };
3725+ (@name #[doc = $name:literal] $(#[$rest:meta])*) => {
3726+ $name
3727+ };
3728+ {
3729+ #[doc = $name:literal] $(#[$doc:meta])* $mode:ident, $stat:ident,
3730+ $(#[doc = $desc:literal] $($key:tt)|+ => $func:expr),+,
3731+ } => {
3732+ mode_info! {
3733+ #[doc = $name]
3734+ $(#[$doc])*
3735+ $mode, $stat, noop,
3736+ $(
3737+ #[doc = $desc]
3738+ $($key)|+ => $func
3739+ ),+,
3740+ }
3741+ };
3742+ {
3743+ #[doc = $name:literal] $(#[$doc:meta])* $mode:ident, $stat:ident, $prehook:expr,
3744+ $(#[doc = $desc:literal] $($key:tt)|+ => $func:expr),+,
3745+ } => {
3746+ #[doc = $name]
3747+ $(#[$doc])*
3748+ #[doc = ""]
3749+ #[doc = "<table><tr><th>key</th><th>desc</th></tr><tbody>"]
3750+ $(
3751+ #[doc = "<tr><td>"]
3752+ // TODO switch to this once we use rust 1.54
3753+ // right now it will produce multiple rows
3754+ // #[doc = mode_info!(@join $($key),+)]
3755+ $(
3756+ #[doc = $key]
3757+ )+
3758+ // <-
3759+ #[doc = "</td><td>"]
3760+ #[doc = $desc]
3761+ #[doc = "</td></tr>"]
3762+ )+
3763+ #[doc = "</tbody></table>"]
3764+ pub fn $mode(cx: &mut Context) {
3765+ if $prehook(cx) {
3766+ return;
3767+ }
3768+ static $stat: OnceCell<Info> = OnceCell::new();
3769+ cx.editor.autoinfo = Some($stat.get_or_init(|| Info::key(
3770+ $name.trim(),
3771+ vec![$((&[$($key.parse().unwrap()),+], $desc)),+],
3772+ )));
3773+ use helix_core::hashmap;
3774+ // TODO: try and convert this to match later
3775+ let map = hashmap! {
3776+ $($($key.parse::<KeyEvent>().unwrap() => $func as for<'r, 's> fn(&'r mut Context<'s>)),+),*
3777+ };
3778+ cx.on_next_key_mode(map);
3779+ }
3780+ };
3781+}
3782+
3783+mode_info! {
3784+ /// space mode
3785+ space_mode, SPACE_MODE,
3786+ /// file picker
3787+ "f" => file_picker,
3788+ /// buffer picker
3789+ "b" => buffer_picker,
3790+ /// symbol picker
3791+ "s" => symbol_picker,
3792+ /// window mode
3793+ "w" => window_mode,
3794+ /// yank joined to clipboard
3795+ "y" => yank_joined_to_clipboard,
3796+ /// yank main selection to clipboard
3797+ "Y" => yank_main_selection_to_clipboard,
3798+ /// paste system clipboard after selections
3799+ "p" => paste_clipboard_after,
3800+ /// paste system clipboard before selections
3801+ "P" => paste_clipboard_before,
3802+ /// replace selections with clipboard
3803+ "R" => replace_selections_with_clipboard,
3804+ /// keep primary selection
3805+ "space" => keep_primary_selection,
3806+}
3807+
3808+mode_info! {
3809+ /// goto mode
3810+ ///
3811+ /// When specified with a count, it will go to that line without entering the mode.
3812+ goto_mode, GOTO_MODE, goto_prehook,
3813+ /// file start
3814+ "g" => goto_file_start,
3815+ /// file end
3816+ "e" => goto_file_end,
3817+ /// line start
3818+ "h" => goto_line_start,
3819+ /// line end
3820+ "l" => goto_line_end,
3821+ /// line first non blank
3822+ "s" => goto_first_nonwhitespace,
3823+ /// definition
3824+ "d" => goto_definition,
3825+ /// type references
3826+ "y" => goto_type_definition,
3827+ /// references
3828+ "r" => goto_reference,
3829+ /// implementation
3830+ "i" => goto_implementation,
3831+ /// window top
3832+ "t" => goto_window_top,
3833+ /// window middle
3834+ "m" => goto_window_middle,
3835+ /// window bottom
3836+ "b" => goto_window_bottom,
3837+ /// last accessed file
3838+ "a" => goto_last_accessed_file,
3839+}