@@ -16,6 +16,7 @@ use helix_core::{
16use helix_view::{
16use helix_view::{
17 document::{IndentStyle, Mode},
17 document::{IndentStyle, Mode},
18 editor::Action,
18 editor::Action,
19 input::KeyEvent,
20 input::KeyEvent,
20 keyboard::KeyCode,
21 keyboard::KeyCode,
21 view::{View, PADDING},
22 view::{View, PADDING},
@@ -38,6 +39,7 @@ use crate::{
39use crate::job::{self, Job, Jobs};
40use crate::job::{self, Job, Jobs};
40use futures_util::{FutureExt, TryFutureExt};
41use futures_util::{FutureExt, TryFutureExt};
42+use std::collections::HashMap;
41use std::{fmt, future::Future};
43use std::{fmt, future::Future};
@@ -45,7 +47,7 @@ use std::{
45 path::{Path, PathBuf},
47 path::{Path, PathBuf},
48−use once_cell::sync::Lazy;
50+use once_cell::sync::{Lazy, OnceCell};
49use serde::de::{self, Deserialize, Deserializer};
51use serde::de::{self, Deserialize, Deserializer};
51pub struct Context<'a> {
53pub struct Context<'a> {
@@ -74,6 +76,16 @@ impl<'a> Context<'a> {
74 self.on_next_key_callback = Some(Box::new(on_next_key_callback));
76 self.on_next_key_callback = Some(Box::new(on_next_key_callback));
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) {
78 pub fn callback<T, F>(
90 pub fn callback<T, F>(
79 &mut self,
91 &mut self,
@@ -153,17 +165,12 @@ impl Command {
153 move_char_right,
165 move_char_right,
154 move_line_up,
166 move_line_up,
155 move_line_down,
167 move_line_down,
158− move_first_nonwhitespace,
159 move_next_word_start,
168 move_next_word_start,
160 move_prev_word_start,
169 move_prev_word_start,
161 move_next_word_end,
170 move_next_word_end,
162 move_next_long_word_start,
171 move_next_long_word_start,
163 move_prev_long_word_start,
172 move_prev_long_word_start,
164 move_next_long_word_end,
173 move_next_long_word_end,
167 extend_next_word_start,
174 extend_next_word_start,
168 extend_prev_word_start,
175 extend_prev_word_start,
169 extend_next_word_end,
176 extend_next_word_end,
@@ -175,7 +182,6 @@ impl Command {
175 find_prev_char,
182 find_prev_char,
176 extend_till_prev_char,
183 extend_till_prev_char,
177 extend_prev_char,
184 extend_prev_char,
178− extend_first_nonwhitespace,
181 page_down,
187 page_down,
@@ -185,8 +191,6 @@ impl Command {
185 extend_char_right,
191 extend_char_right,
186 extend_line_up,
192 extend_line_up,
187 extend_line_down,
193 extend_line_down,
190 select_all,
194 select_all,
191 select_regex,
195 select_regex,
192 split_selection,
196 split_selection,
@@ -196,6 +200,7 @@ impl Command {
196 extend_search_next,
200 extend_search_next,
197 search_selection,
201 search_selection,
198 extend_line,
202 extend_line,
203+ extend_to_line_bounds,
199 delete_selection,
204 delete_selection,
200 change_selection,
205 change_selection,
201 collapse_selection,
206 collapse_selection,
@@ -217,11 +222,17 @@ impl Command {
217 goto_definition,
222 goto_definition,
218 goto_type_definition,
223 goto_type_definition,
219 goto_implementation,
224 goto_implementation,
220 goto_reference,
227 goto_reference,
221 goto_first_diag,
228 goto_first_diag,
222 goto_last_diag,
229 goto_last_diag,
223 goto_next_diag,
230 goto_next_diag,
224 goto_prev_diag,
231 goto_prev_diag,
234+ goto_line_end_newline,
235+ goto_first_nonwhitespace,
225 signature_help,
236 signature_help,
226 insert_tab,
237 insert_tab,
227 insert_newline,
238 insert_newline,
@@ -376,7 +387,7 @@ fn move_line_down(cx: &mut Context) {
379−fn move_line_end(cx: &mut Context) {
390+fn goto_line_end(cx: &mut Context) {
380 let (view, doc) = current!(cx.editor);
391 let (view, doc) = current!(cx.editor);
381 doc.set_selection(
392 doc.set_selection(
@@ -388,12 +399,33 @@ fn move_line_end(cx: &mut Context) {
388 let pos = graphemes::nth_prev_grapheme_boundary(text.slice(..), pos, 1);
399 let pos = graphemes::nth_prev_grapheme_boundary(text.slice(..), pos, 1);
389 let pos = range.head.max(pos).max(text.line_to_char(line));
400 let pos = range.head.max(pos).max(text.line_to_char(line));
404+ Mode::Normal | Mode::Insert => pos,
405+ Mode::Select => range.anchor,
413+fn goto_line_end_newline(cx: &mut Context) {
414+ let (view, doc) = current!(cx.editor);
418+ doc.selection(view.id).clone().transform(|range| {
419+ let text = doc.text();
420+ let line = text.char_to_line(range.head);
422+ let pos = line_end_char_index(&text.slice(..), line);
391 Range::new(pos, pos)
423 Range::new(pos, pos)
396−fn move_line_start(cx: &mut Context) {
428+fn goto_line_start(cx: &mut Context) {
397 let (view, doc) = current!(cx.editor);
429 let (view, doc) = current!(cx.editor);
398 doc.set_selection(
430 doc.set_selection(
@@ -403,12 +435,18 @@ fn move_line_start(cx: &mut Context) {
404 // adjust to start of the line
436 // adjust to start of the line
405 let pos = text.line_to_char(line);
437 let pos = text.line_to_char(line);
406− Range::new(pos, pos)
438+ Range::new(
440+ Mode::Normal | Mode::Insert => pos,
441+ Mode::Select => range.anchor,
411−fn move_first_nonwhitespace(cx: &mut Context) {
449+fn goto_first_nonwhitespace(cx: &mut Context) {
412 let (view, doc) = current!(cx.editor);
450 let (view, doc) = current!(cx.editor);
413 doc.set_selection(
451 doc.set_selection(
@@ -418,7 +456,13 @@ fn move_first_nonwhitespace(cx: &mut Context) {
419 if let Some(pos) = find_first_non_whitespace_char(text.line(line_idx)) {
457 if let Some(pos) = find_first_non_whitespace_char(text.line(line_idx)) {
420 let pos = pos + text.line_to_char(line_idx);
458 let pos = pos + text.line_to_char(line_idx);
421− Range::new(pos, pos)
459+ Range::new(
461+ Mode::Normal | Mode::Insert => pos,
462+ Mode::Select => range.anchor,
@@ -426,6 +470,37 @@ fn move_first_nonwhitespace(cx: &mut Context) {
473+fn goto_window(cx: &mut Context, align: Align) {
474+ let (view, doc) = current!(cx.editor);
476+ let scrolloff = PADDING.min(view.area.height as usize / 2); // TODO: user pref
478+ let last_line = view.last_line(doc);
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),
485+ .min(last_line.saturating_sub(scrolloff));
487+ let pos = doc.text().line_to_char(line);
489+ doc.set_selection(view.id, Selection::point(pos));
492+fn goto_window_top(cx: &mut Context) {
493+ goto_window(cx, Align::Top)
496+fn goto_window_middle(cx: &mut Context) {
497+ goto_window(cx, Align::Center)
500+fn goto_window_bottom(cx: &mut Context) {
501+ goto_window(cx, Align::Bottom)
429// TODO: move vs extend could take an extra type Extend/Move that would
504// TODO: move vs extend could take an extra type Extend/Move that would
430// Range::new(if Move { pos } if Extend { range.anchor }, pos)
505// Range::new(if Move { pos } if Extend { range.anchor }, pos)
431// since these all really do the same thing
506// since these all really do the same thing
@@ -497,13 +572,13 @@ fn move_next_long_word_end(cx: &mut Context) {
500−fn move_file_start(cx: &mut Context) {
575+fn goto_file_start(cx: &mut Context) {
501 push_jump(cx.editor);
576 push_jump(cx.editor);
502 let (view, doc) = current!(cx.editor);
577 let (view, doc) = current!(cx.editor);
503 doc.set_selection(view.id, Selection::point(0));
578 doc.set_selection(view.id, Selection::point(0));
506−fn move_file_end(cx: &mut Context) {
581+fn goto_file_end(cx: &mut Context) {
507 push_jump(cx.editor);
582 push_jump(cx.editor);
508 let (view, doc) = current!(cx.editor);
583 let (view, doc) = current!(cx.editor);
509 let text = doc.text();
584 let text = doc.text();
@@ -683,24 +758,6 @@ fn extend_prev_char(cx: &mut Context) {
686−fn extend_first_nonwhitespace(cx: &mut Context) {
687− let (view, doc) = current!(cx.editor);
690− doc.selection(view.id).clone().transform(|range| {
691− let text = doc.text();
692− let line_idx = text.char_to_line(range.head);
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)
704fn replace(cx: &mut Context) {
761fn replace(cx: &mut Context) {
705 let mut buf = [0u8; 4]; // To hold utf8 encoded char.
762 let mut buf = [0u8; 4]; // To hold utf8 encoded char.
@@ -880,38 +937,6 @@ fn extend_line_down(cx: &mut Context) {
883−fn extend_line_end(cx: &mut Context) {
884− let (view, doc) = current!(cx.editor);
887− doc.selection(view.id).clone().transform(|range| {
888− let text = doc.text().slice(..);
889− let line = text.char_to_line(range.head);
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));
895− Range::new(range.anchor, pos)
900−fn extend_line_start(cx: &mut Context) {
901− let (view, doc) = current!(cx.editor);
904− doc.selection(view.id).clone().transform(|range| {
905− let text = doc.text();
906− let line = text.char_to_line(range.head);
908− // adjust to start of the line
909− let pos = text.line_to_char(line);
910− Range::new(range.anchor, pos)
915fn select_all(cx: &mut Context) {
940fn select_all(cx: &mut Context) {
916 let (view, doc) = current!(cx.editor);
941 let (view, doc) = current!(cx.editor);
@@ -1055,6 +1080,27 @@ fn extend_line(cx: &mut Context) {
1055 doc.set_selection(view.id, Selection::single(start, end));
1080 doc.set_selection(view.id, Selection::single(start, end));
1083+fn extend_to_line_bounds(cx: &mut Context) {
1084+ let (view, doc) = current!(cx.editor);
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()));
1092+ .line_to_char(text.char_to_line(range.to()) + 1)
1093+ .saturating_sub(1);
1095+ if range.anchor < range.head {
1096+ Range::new(start, end)
1098+ Range::new(end, start)
1058fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) {
1104fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) {
1059 let text = doc.text().slice(..);
1105 let text = doc.text().slice(..);
1060 let selection = doc.selection(view_id).clone().min_width_1(text);
1106 let selection = doc.selection(view_id).clone().min_width_1(text);
@@ -1580,6 +1626,24 @@ mod cmd {
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()));
1636+ let encoding = doc.encoding().name().to_string();
1637+ cx.editor.set_status(encoding)
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();
1583 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
1647 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
1584 TypableCommand {
1648 TypableCommand {
1585 name: "quit",
1649 name: "quit",
@@ -1763,6 +1827,20 @@ mod cmd {
1763 fun: show_current_directory,
1827 fun: show_current_directory,
1764 completer: None,
1828 completer: None,
1833+ doc: "Set encoding based on `https://encoding.spec.whatwg.org`",
1840+ doc: "Discard changes and reload from the source file.",
1768 pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
1846 pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
@@ -1955,7 +2033,7 @@ fn symbol_picker(cx: &mut Context) {
1956// I inserts at the first nonwhitespace character of each line with a selection
2034// I inserts at the first nonwhitespace character of each line with a selection
1957fn prepend_to_line(cx: &mut Context) {
2035fn prepend_to_line(cx: &mut Context) {
1958− move_first_nonwhitespace(cx);
2036+ goto_first_nonwhitespace(cx);
1959 let doc = doc_mut!(cx.editor);
2037 let doc = doc_mut!(cx.editor);
1960 enter_insert_mode(doc);
2038 enter_insert_mode(doc);
@@ -2124,7 +2202,7 @@ fn push_jump(editor: &mut Editor) {
2124 view.jumps.push(jump);
2202 view.jumps.push(jump);
2127−fn switch_to_last_accessed_file(cx: &mut Context) {
2205+fn goto_last_accessed_file(cx: &mut Context) {
2128 let alternate_file = view!(cx.editor).last_accessed_doc;
2206 let alternate_file = view!(cx.editor).last_accessed_doc;
2129 if let Some(alt) = alternate_file {
2207 if let Some(alt) = alternate_file {
2130 cx.editor.switch(alt, Action::Replace);
2208 cx.editor.switch(alt, Action::Replace);
@@ -2133,65 +2211,6 @@ fn switch_to_last_accessed_file(cx: &mut Context) {
2136−fn goto_mode(cx: &mut Context) {
2137− if let Some(count) = cx.count {
2138− push_jump(cx.editor);
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));
2147− cx.on_next_key(move |cx, event| {
2149− code: KeyCode::Char(ch),
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),
2170− (_, 't') | (_, 'm') | (_, 'b') => {
2171− let (view, doc) = current!(cx.editor);
2173− let scrolloff = PADDING.min(view.area.height as usize / 2); // TODO: user pref
2175− let last_line = view.last_line(doc);
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!(),
2183− .min(last_line.saturating_sub(scrolloff));
2185− let pos = doc.text().line_to_char(line);
2187− doc.set_selection(view.id, Selection::point(pos));
2195fn select_mode(cx: &mut Context) {
2214fn select_mode(cx: &mut Context) {
2196 let (view, doc) = current!(cx.editor);
2215 let (view, doc) = current!(cx.editor);
@@ -2211,13 +2230,27 @@ fn select_mode(cx: &mut Context) {
2214− doc.mode = Mode::Select;
2233+ doc_mut!(cx.editor).mode = Mode::Select;
2217fn exit_select_mode(cx: &mut Context) {
2236fn exit_select_mode(cx: &mut Context) {
2218 doc_mut!(cx.editor).mode = Mode::Normal;
2237 doc_mut!(cx.editor).mode = Mode::Normal;
2240+fn goto_prehook(cx: &mut Context) -> bool {
2241+ if let Some(count) = cx.count {
2242+ push_jump(cx.editor);
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));
2221fn goto_impl(
2254fn goto_impl(
2222 editor: &mut Editor,
2255 editor: &mut Editor,
2223 compositor: &mut Compositor,
2256 compositor: &mut Compositor,
@@ -3457,33 +3490,6 @@ fn select_register(cx: &mut Context) {
3460−fn space_mode(cx: &mut Context) {
3461− cx.on_next_key(move |cx, event| {
3463− code: KeyCode::Char(ch),
3467− // TODO: temporarily show SPC in the mode list
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),
3487fn view_mode(cx: &mut Context) {
3493fn view_mode(cx: &mut Context) {
3488 cx.on_next_key(move |cx, event| {
3494 cx.on_next_key(move |cx, event| {
3489 if let KeyEvent {
3495 if let KeyEvent {
@@ -3559,6 +3565,9 @@ fn right_bracket_mode(cx: &mut Context) {
3568+use helix_core::surround;
3569+use helix_core::textobject;
3562fn match_mode(cx: &mut Context) {
3571fn match_mode(cx: &mut Context) {
3563 let count = cx.count;
3572 let count = cx.count;
3564 cx.on_next_key(move |cx, event| {
3573 cx.on_next_key(move |cx, event| {
@@ -3574,13 +3583,41 @@ fn match_mode(cx: &mut Context) {
3574 's' => surround_add(cx),
3583 's' => surround_add(cx),
3575 'r' => surround_replace(cx),
3584 'r' => surround_replace(cx),
3576 'd' => surround_delete(cx),
3585 'd' => surround_delete(cx),
3586+ 'a' => select_textobject(cx, textobject::TextObject::Around),
3587+ 'i' => select_textobject(cx, textobject::TextObject::Inside),
3577 _ => (),
3588 _ => (),
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| {
3598+ code: KeyCode::Char(ch),
3602+ let (view, doc) = current!(cx.editor);
3606+ doc.selection(view.id).clone().transform(|range| {
3607+ let text = doc.text().slice(..);
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)
3585fn surround_add(cx: &mut Context) {
3622fn surround_add(cx: &mut Context) {
3586 cx.on_next_key(move |cx, event| {
3623 cx.on_next_key(move |cx, event| {
@@ -3671,3 +3708,132 @@ fn surround_delete(cx: &mut Context) {
3712+/// Do nothing, just for modeinfo.
3713+fn noop(_cx: &mut Context) -> bool {
3717+/// Generate modeinfo.
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),*)
3725+ (@name #[doc = $name:literal] $(#[$rest:meta])*) => {
3729+ #[doc = $name:literal] $(#[$doc:meta])* $mode:ident, $stat:ident,
3730+ $(#[doc = $desc:literal] $($key:tt)|+ => $func:expr),+,
3735+ $mode, $stat, noop,
3743+ #[doc = $name:literal] $(#[$doc:meta])* $mode:ident, $stat:ident, $prehook:expr,
3744+ $(#[doc = $desc:literal] $($key:tt)|+ => $func:expr),+,
3749+ #[doc = "<table><tr><th>key</th><th>desc</th></tr><tbody>"]
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),+)]
3759+ #[doc = "</td><td>"]
3761+ #[doc = "</td></tr>"]
3763+ #[doc = "</tbody></table>"]
3764+ pub fn $mode(cx: &mut Context) {
3768+ static $stat: OnceCell<Info> = OnceCell::new();
3769+ cx.editor.autoinfo = Some($stat.get_or_init(|| Info::key(
3771+ vec![$((&[$($key.parse().unwrap()),+], $desc)),+],
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>)),+),*
3778+ cx.on_next_key_mode(map);
3785+ space_mode, SPACE_MODE,
3787+ "f" => file_picker,
3789+ "b" => buffer_picker,
3791+ "s" => symbol_picker,
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,
3811+ /// When specified with a count, it will go to that line without entering the mode.
3812+ goto_mode, GOTO_MODE, goto_prehook,
3814+ "g" => goto_file_start,
3816+ "e" => goto_file_end,
3818+ "h" => goto_line_start,
3820+ "l" => goto_line_end,
3821+ /// line first non blank
3822+ "s" => goto_first_nonwhitespace,
3824+ "d" => goto_definition,
3825+ /// type references
3826+ "y" => goto_type_definition,
3828+ "r" => goto_reference,
3830+ "i" => goto_implementation,
3832+ "t" => goto_window_top,
3834+ "m" => goto_window_middle,
3836+ "b" => goto_window_bottom,
3837+ /// last accessed file
3838+ "a" => goto_last_accessed_file,