@@ -1,16 +1,16 @@
1use helix_core::{
1use helix_core::{
2 comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes,
2 comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes,
3 history::UndoKind,
3 history::UndoKind,
4+ increment::date_time::DateTimeIncrementor,
5+ increment::{number::NumberIncrementor, Increment},
5 indent::IndentStyle,
7 indent::IndentStyle,
6 line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending},
8 line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending},
7 match_brackets,
9 match_brackets,
8 movement::{self, Direction},
10 movement::{self, Direction},
9− numbers::NumberIncrementor,
10 object, pos_at_coords,
11 object, pos_at_coords,
11 regex::{self, Regex, RegexBuilder},
12 regex::{self, Regex, RegexBuilder},
13− search, selection, surround, textobject,
13+ search, selection, shellwords, surround, textobject,
14 unicode::width::UnicodeWidthChar,
14 unicode::width::UnicodeWidthChar,
15 LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
15 LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
16 Transaction,
16 Transaction,
@@ -25,7 +25,7 @@ use helix_view::{
25 Document, DocumentId, Editor, ViewId,
25 Document, DocumentId, Editor, ViewId,
28−use anyhow::{anyhow, bail, Context as _};
28+use anyhow::{anyhow, bail, ensure, Context as _};
29use helix_lsp::{
29use helix_lsp::{
30 block_on, lsp,
30 block_on, lsp,
31 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
31 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
@@ -41,7 +41,7 @@ use crate::{
42use crate::job::{self, Job, Jobs};
42use crate::job::{self, Job, Jobs};
43use futures_util::{FutureExt, StreamExt};
43use futures_util::{FutureExt, StreamExt};
44−use std::num::NonZeroUsize;
44+use std::{collections::HashSet, num::NonZeroUsize};
45use std::{fmt, future::Future};
45use std::{fmt, future::Future};
@@ -70,7 +70,7 @@ pub struct Context<'a> {
70impl<'a> Context<'a> {
70impl<'a> Context<'a> {
71 /// Push a new component onto the compositor.
71 /// Push a new component onto the compositor.
72 pub fn push_layer(&mut self, component: Box<dyn Component>) {
72 pub fn push_layer(&mut self, component: Box<dyn Component>) {
73− self.callback = Some(Box::new(|compositor: &mut Compositor| {
73+ self.callback = Some(Box::new(|compositor: &mut Compositor, _| {
74 compositor.push(component)
74 compositor.push(component)
@@ -135,47 +135,76 @@ fn align_view(doc: &Document, view: &mut View, align: Align) {
135 view.offset.row = line.saturating_sub(relative);
135 view.offset.row = line.saturating_sub(relative);
138−/// A command is composed of a static name, and a function that takes the current state plus a count,
138+/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
139−/// and does a side-effect on the state (usually by creating and applying a transaction).
139+/// :format. It causes a side-effect on the state (usually by creating and applying a transaction).
140+/// Both of these types of commands can be mapped with keybindings in the config.toml.
140−#[derive(Copy, Clone)]
141+#[derive(Clone)]
141−pub struct Command {
142+pub enum MappableCommand {
142− name: &'static str,
149+ name: &'static str,
143− fun: fn(cx: &mut Context),
150+ fun: fn(cx: &mut Context),
144− doc: &'static str,
151+ doc: &'static str,
147−macro_rules! commands {
155+macro_rules! static_commands {
148 ( $($name:ident, $doc:literal,)* ) => {
156 ( $($name:ident, $doc:literal,)* ) => {
150 #[allow(non_upper_case_globals)]
158 #[allow(non_upper_case_globals)]
151− pub const $name: Self = Self {
159+ pub const $name: Self = Self::Static {
152 name: stringify!($name),
160 name: stringify!($name),
153 fun: $name,
161 fun: $name,
154 doc: $doc
162 doc: $doc
158− pub const COMMAND_LIST: &'static [Self] = &[
166+ pub const STATIC_COMMAND_LIST: &'static [Self] = &[
159 $( Self::$name, )*
167 $( Self::$name, )*
164−impl Command {
172+impl MappableCommand {
165 pub fn execute(&self, cx: &mut Context) {
173 pub fn execute(&self, cx: &mut Context) {
166− (self.fun)(cx);
174+ match &self {
175+ MappableCommand::Typable { name, args, doc: _ } => {
176+ let args: Vec<Cow<str>> = args.iter().map(Cow::from).collect();
177+ if let Some(command) = cmd::TYPABLE_COMMAND_MAP.get(name.as_str()) {
178+ let mut cx = compositor::Context {
183+ if let Err(e) = (command.fun)(&mut cx, &args[..], PromptEvent::Validate) {
184+ cx.editor.set_error(format!("{}", e));
188+ MappableCommand::Static { fun, .. } => (fun)(cx),
169− pub fn name(&self) -> &'static str {
192+ pub fn name(&self) -> &str {
170− self.name
193+ match &self {
194+ MappableCommand::Typable { name, .. } => name,
195+ MappableCommand::Static { name, .. } => name,
173− pub fn doc(&self) -> &'static str {
199+ pub fn doc(&self) -> &str {
174− self.doc
200+ match &self {
201+ MappableCommand::Typable { doc, .. } => doc,
202+ MappableCommand::Static { doc, .. } => doc,
177 #[rustfmt::skip]
206 #[rustfmt::skip]
178− commands!(
207+ static_commands!(
179 no_op, "Do nothing",
208 no_op, "Do nothing",
180 move_char_left, "Move left",
209 move_char_left, "Move left",
181 move_char_right, "Move right",
210 move_char_right, "Move right",
@@ -232,7 +261,9 @@ impl Command {
232 extend_line, "Select current line, if already selected, extend to next line",
261 extend_line, "Select current line, if already selected, extend to next line",
233 extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)",
262 extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)",
234 delete_selection, "Delete selection",
263 delete_selection, "Delete selection",
264+ delete_selection_noyank, "Delete selection, without yanking",
235 change_selection, "Change selection (delete and enter insert mode)",
265 change_selection, "Change selection (delete and enter insert mode)",
266+ change_selection_noyank, "Change selection (delete and enter insert mode, without yanking)",
236 collapse_selection, "Collapse selection onto a single cursor",
267 collapse_selection, "Collapse selection onto a single cursor",
237 flip_selections, "Flip selection cursor and anchor",
268 flip_selections, "Flip selection cursor and anchor",
238 insert_mode, "Insert before selection",
269 insert_mode, "Insert before selection",
@@ -258,11 +289,15 @@ impl Command {
258 goto_implementation, "Goto implementation",
289 goto_implementation, "Goto implementation",
259 goto_file_start, "Goto file start/line",
290 goto_file_start, "Goto file start/line",
260 goto_file_end, "Goto file end",
291 goto_file_end, "Goto file end",
292+ goto_file, "Goto files in selection",
293+ goto_file_hsplit, "Goto files in selection (hsplit)",
294+ goto_file_vsplit, "Goto files in selection (vsplit)",
261 goto_reference, "Goto references",
295 goto_reference, "Goto references",
262 goto_window_top, "Goto window top",
296 goto_window_top, "Goto window top",
263− goto_window_middle, "Goto window middle",
297+ goto_window_center, "Goto window center",
264 goto_window_bottom, "Goto window bottom",
298 goto_window_bottom, "Goto window bottom",
265 goto_last_accessed_file, "Goto last accessed file",
299 goto_last_accessed_file, "Goto last accessed file",
300+ goto_last_modified_file, "Goto last modified file",
266 goto_last_modification, "Goto last modification",
301 goto_last_modification, "Goto last modification",
267 goto_line, "Goto line",
302 goto_line, "Goto line",
268 goto_last_line, "Goto last line",
303 goto_last_line, "Goto last line",
@@ -327,6 +362,7 @@ impl Command {
327 expand_selection, "Expand selection to parent syntax node",
362 expand_selection, "Expand selection to parent syntax node",
328 jump_forward, "Jump forward on jumplist",
363 jump_forward, "Jump forward on jumplist",
329 jump_backward, "Jump backward on jumplist",
364 jump_backward, "Jump backward on jumplist",
365+ save_selection, "Save the current selection to the jumplist",
330 jump_view_right, "Jump to the split to the right",
366 jump_view_right, "Jump to the split to the right",
331 jump_view_left, "Jump to the split to the left",
367 jump_view_left, "Jump to the split to the left",
332 jump_view_up, "Jump to the split above",
368 jump_view_up, "Jump to the split above",
@@ -359,36 +395,56 @@ impl Command {
359 rename_symbol, "Rename symbol",
395 rename_symbol, "Rename symbol",
360 increment, "Increment",
396 increment, "Increment",
361 decrement, "Decrement",
397 decrement, "Decrement",
398+ record_macro, "Record macro",
399+ play_macro, "Play macro",
365−impl fmt::Debug for Command {
403+impl fmt::Debug for MappableCommand {
366 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
404 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
367− let Command { name, .. } = self;
405+ f.debug_tuple("MappableCommand")
368− f.debug_tuple("Command").field(name).finish()
406+ .field(&self.name())
372−impl fmt::Display for Command {
411+impl fmt::Display for MappableCommand {
373 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
412 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374− let Command { name, .. } = self;
375− f.write_str(name)
413+ f.write_str(self.name())
379−impl std::str::FromStr for Command {
417+impl std::str::FromStr for MappableCommand {
380 type Err = anyhow::Error;
418 type Err = anyhow::Error;
382 fn from_str(s: &str) -> Result<Self, Self::Err> {
420 fn from_str(s: &str) -> Result<Self, Self::Err> {
383− Command::COMMAND_LIST
421+ if let Some(suffix) = s.strip_prefix(':') {
422+ let mut typable_command = suffix.split(' ').into_iter().map(|arg| arg.trim());
423+ let name = typable_command
425+ .ok_or_else(|| anyhow!("Expected typable command name"))?;
426+ let args = typable_command
427+ .map(|s| s.to_owned())
428+ .collect::<Vec<String>>();
429+ cmd::TYPABLE_COMMAND_MAP
431+ .map(|cmd| MappableCommand::Typable {
432+ name: cmd.name.to_owned(),
433+ doc: format!(":{} {:?}", cmd.name, args),
436+ .ok_or_else(|| anyhow!("No TypableCommand named '{}'", s))
438+ MappableCommand::STATIC_COMMAND_LIST
385− .copied()
440+ .cloned()
386− .find(|cmd| cmd.name == s)
441+ .find(|cmd| cmd.name() == s)
387− .ok_or_else(|| anyhow!("No command named '{}'", s))
442+ .ok_or_else(|| anyhow!("No command named '{}'", s))
391−impl<'de> Deserialize<'de> for Command {
447+impl<'de> Deserialize<'de> for MappableCommand {
392 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
448 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
394 D: Deserializer<'de>,
450 D: Deserializer<'de>,
@@ -398,9 +454,27 @@ impl<'de> Deserialize<'de> for Command {
401−impl PartialEq for Command {
457+impl PartialEq for MappableCommand {
402 fn eq(&self, other: &Self) -> bool {
458 fn eq(&self, other: &Self) -> bool {
403− self.name() == other.name()
459+ match (self, other) {
461+ MappableCommand::Typable {
462+ name: first_name, ..
464+ MappableCommand::Typable {
465+ name: second_name, ..
467+ ) => first_name == second_name,
469+ MappableCommand::Static {
470+ name: first_name, ..
472+ MappableCommand::Static {
473+ name: second_name, ..
475+ ) => first_name == second_name,
@@ -599,8 +673,15 @@ fn kill_to_line_end(cx: &mut Context) {
600 let selection = doc.selection(view.id).clone().transform(|range| {
674 let selection = doc.selection(view.id).clone().transform(|range| {
601 let line = range.cursor_line(text);
675 let line = range.cursor_line(text);
602− let pos = line_end_char_index(&text, line);
676+ let line_end_pos = line_end_char_index(&text, line);
677+ let pos = range.cursor(text);
603− range.put_cursor(text, pos, true)
679+ let mut new_range = range.put_cursor(text, line_end_pos, true);
680+ // don't want to remove the line separator itself if the cursor doesn't reach the end of line.
681+ if pos != line_end_pos {
682+ new_range.head = line_end_pos;
605 delete_selection_insert_mode(doc, view, &selection);
686 delete_selection_insert_mode(doc, view, &selection);
@@ -729,10 +810,12 @@ fn align_fragment_to_width(fragment: &str, width: usize, align_style: usize) ->
731fn goto_window(cx: &mut Context, align: Align) {
812fn goto_window(cx: &mut Context, align: Align) {
813+ let count = cx.count() - 1;
732 let (view, doc) = current!(cx.editor);
814 let (view, doc) = current!(cx.editor);
734 let height = view.inner_area().height as usize;
816 let height = view.inner_area().height as usize;
818+ // respect user given count if any
736 // - 1 so we have at least one gap in the middle.
819 // - 1 so we have at least one gap in the middle.
737 // a height of 6 with padding of 3 on each side will keep shifting the view back and forth
820 // a height of 6 with padding of 3 on each side will keep shifting the view back and forth
738 // as we type
821 // as we type
@@ -741,10 +824,11 @@ fn goto_window(cx: &mut Context, align: Align) {
741 let last_line = view.last_line(doc);
824 let last_line = view.last_line(doc);
743 let line = match align {
826 let line = match align {
744− Align::Top => (view.offset.row + scrolloff),
827+ Align::Top => (view.offset.row + scrolloff + count),
745− Align::Center => (view.offset.row + (height / 2)),
828+ Align::Center => (view.offset.row + ((last_line - view.offset.row) / 2)),
746− Align::Bottom => last_line.saturating_sub(scrolloff),
829+ Align::Bottom => last_line.saturating_sub(scrolloff + count),
831+ .max(view.offset.row + scrolloff)
748 .min(last_line.saturating_sub(scrolloff));
832 .min(last_line.saturating_sub(scrolloff));
750 let pos = doc.text().line_to_char(line);
834 let pos = doc.text().line_to_char(line);
@@ -756,7 +840,7 @@ fn goto_window_top(cx: &mut Context) {
756 goto_window(cx, Align::Top)
840 goto_window(cx, Align::Top)
759−fn goto_window_middle(cx: &mut Context) {
843+fn goto_window_center(cx: &mut Context) {
760 goto_window(cx, Align::Center)
844 goto_window(cx, Align::Center)
@@ -834,6 +918,49 @@ fn goto_file_end(cx: &mut Context) {
834 doc.set_selection(view.id, selection);
918 doc.set_selection(view.id, selection);
921+fn goto_file(cx: &mut Context) {
922+ goto_file_impl(cx, Action::Replace);
925+fn goto_file_hsplit(cx: &mut Context) {
926+ goto_file_impl(cx, Action::HorizontalSplit);
929+fn goto_file_vsplit(cx: &mut Context) {
930+ goto_file_impl(cx, Action::VerticalSplit);
933+fn goto_file_impl(cx: &mut Context, action: Action) {
934+ let (view, doc) = current_ref!(cx.editor);
935+ let text = doc.text();
936+ let selections = doc.selection(view.id);
937+ let mut paths: Vec<_> = selections
939+ .map(|r| text.slice(r.from()..r.to()).to_string())
941+ let primary = selections.primary();
942+ if selections.len() == 1 && primary.to() - primary.from() == 1 {
943+ let current_word = movement::move_next_long_word_start(
945+ movement::move_prev_long_word_start(text.slice(..), primary, 1),
950+ text.slice(current_word.from()..current_word.to())
957+ if let Err(e) = cx.editor.open(PathBuf::from(p), action) {
958+ cx.editor.set_error(format!("Open file failed: {:?}", e));
837fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
964fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
839 F: Fn(RopeSlice, Range, usize) -> Range,
966 F: Fn(RopeSlice, Range, usize) -> Range,
@@ -1693,19 +1820,42 @@ fn extend_to_line_bounds(cx: &mut Context) {
1696−fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) {
1828+fn delete_selection_impl(cx: &mut Context, op: Operation) {
1829+ let (view, doc) = current!(cx.editor);
1697 let text = doc.text().slice(..);
1831 let text = doc.text().slice(..);
1698− let selection = doc.selection(view_id);
1832+ let selection = doc.selection(view.id);
1834+ if cx.register != Some('_') {
1700− // first yank the selection
1835+ // first yank the selection
1701− let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
1836+ let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
1837+ let reg_name = cx.register.unwrap_or('"');
1838+ let registers = &mut cx.editor.registers;
1839+ let reg = registers.get_mut(reg_name);
1702− reg.write(values);
1840+ reg.write(values);
1704 // then delete
1843 // then delete
1705 let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
1844 let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
1706 (range.from(), range.to(), None)
1845 (range.from(), range.to(), None)
1708− doc.apply(&transaction, view_id);
1847+ doc.apply(&transaction, view.id);
1850+ Operation::Delete => {
1851+ doc.append_changes_to_history(view.id);
1852+ // exit select mode, if currently in select mode
1853+ exit_select_mode(cx);
1855+ Operation::Change => {
1856+ enter_insert_mode(doc);
1711#[inline]
1861#[inline]
@@ -1720,25 +1870,21 @@ fn delete_selection_insert_mode(doc: &mut Document, view: &View, selection: &Sel
1722fn delete_selection(cx: &mut Context) {
1872fn delete_selection(cx: &mut Context) {
1723− let reg_name = cx.register.unwrap_or('"');
1724− let (view, doc) = current!(cx.editor);
1725− let registers = &mut cx.editor.registers;
1726− let reg = registers.get_mut(reg_name);
1727− delete_selection_impl(reg, doc, view.id);
1873+ delete_selection_impl(cx, Operation::Delete);
1729− doc.append_changes_to_history(view.id);
1731− // exit select mode, if currently in select mode
1876+fn delete_selection_noyank(cx: &mut Context) {
1732− exit_select_mode(cx);
1877+ cx.register = Some('_');
1878+ delete_selection_impl(cx, Operation::Delete);
1735fn change_selection(cx: &mut Context) {
1881fn change_selection(cx: &mut Context) {
1736− let reg_name = cx.register.unwrap_or('"');
1882+ delete_selection_impl(cx, Operation::Change);
1737− let (view, doc) = current!(cx.editor);
1883+}
1738− let registers = &mut cx.editor.registers;
1884+
1739− let reg = registers.get_mut(reg_name);
1885+fn change_selection_noyank(cx: &mut Context) {
1886+ cx.register = Some('_');
1740− delete_selection_impl(reg, doc, view.id);
1887+ delete_selection_impl(cx, Operation::Change);
1741− enter_insert_mode(doc);
1744fn collapse_selection(cx: &mut Context) {
1890fn collapse_selection(cx: &mut Context) {
@@ -1806,7 +1952,7 @@ fn append_mode(cx: &mut Context) {
1806 doc.set_selection(view.id, selection);
1952 doc.set_selection(view.id, selection);
1809−mod cmd {
1955+pub mod cmd {
1810 use super::*;
1956 use super::*;
1811 use std::collections::HashMap;
1957 use std::collections::HashMap;
@@ -1819,13 +1965,13 @@ mod cmd {
1819 pub aliases: &'static [&'static str],
1965 pub aliases: &'static [&'static str],
1820 pub doc: &'static str,
1966 pub doc: &'static str,
1821 // params, flags, helper, completer
1967 // params, flags, helper, completer
1822− pub fun: fn(&mut compositor::Context, &[&str], PromptEvent) -> anyhow::Result<()>,
1968+ pub fun: fn(&mut compositor::Context, &[Cow<str>], PromptEvent) -> anyhow::Result<()>,
1823 pub completer: Option<Completer>,
1969 pub completer: Option<Completer>,
1826 fn quit(
1972 fn quit(
1827 cx: &mut compositor::Context,
1973 cx: &mut compositor::Context,
1828− _args: &[&str],
1974+ _args: &[Cow<str>],
1829 _event: PromptEvent,
1975 _event: PromptEvent,
1830 ) -> anyhow::Result<()> {
1976 ) -> anyhow::Result<()> {
1831 // last view and we have unsaved changes
1977 // last view and we have unsaved changes
@@ -1840,7 +1986,7 @@ mod cmd {
1841 fn force_quit(
1987 fn force_quit(
1842 cx: &mut compositor::Context,
1988 cx: &mut compositor::Context,
1843− _args: &[&str],
1989+ _args: &[Cow<str>],
1844 _event: PromptEvent,
1990 _event: PromptEvent,
1845 ) -> anyhow::Result<()> {
1991 ) -> anyhow::Result<()> {
1846 cx.editor.close(view!(cx.editor).id);
1992 cx.editor.close(view!(cx.editor).id);
@@ -1850,17 +1996,19 @@ mod cmd {
1851 fn open(
1997 fn open(
1852 cx: &mut compositor::Context,
1998 cx: &mut compositor::Context,
1853− args: &[&str],
1999+ args: &[Cow<str>],
1854 _event: PromptEvent,
2000 _event: PromptEvent,
1855 ) -> anyhow::Result<()> {
2001 ) -> anyhow::Result<()> {
1856− let path = args.get(0).context("wrong argument count")?;
2002+ ensure!(!args.is_empty(), "wrong argument count");
1857− let _ = cx.editor.open(path.into(), Action::Replace)?;
2004+ let _ = cx.editor.open(arg.as_ref().into(), Action::Replace)?;
1861 fn buffer_close(
2009 fn buffer_close(
1862 cx: &mut compositor::Context,
2010 cx: &mut compositor::Context,
1863− _args: &[&str],
2011+ _args: &[Cow<str>],
1864 _event: PromptEvent,
2012 _event: PromptEvent,
1865 ) -> anyhow::Result<()> {
2013 ) -> anyhow::Result<()> {
1866 let view = view!(cx.editor);
2014 let view = view!(cx.editor);
@@ -1871,7 +2019,7 @@ mod cmd {
1872 fn force_buffer_close(
2020 fn force_buffer_close(
1873 cx: &mut compositor::Context,
2021 cx: &mut compositor::Context,
1874− _args: &[&str],
2022+ _args: &[Cow<str>],
1875 _event: PromptEvent,
2023 _event: PromptEvent,
1876 ) -> anyhow::Result<()> {
2024 ) -> anyhow::Result<()> {
1877 let view = view!(cx.editor);
2025 let view = view!(cx.editor);
@@ -1880,15 +2028,12 @@ mod cmd {
1883− fn write_impl<P: AsRef<Path>>(
1884− cx: &mut compositor::Context,
2031+ fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
1886− ) -> anyhow::Result<()> {
1887 let jobs = &mut cx.jobs;
2032 let jobs = &mut cx.jobs;
1888 let (_, doc) = current!(cx.editor);
2033 let (_, doc) = current!(cx.editor);
1890− if let Some(path) = path {
2035+ if let Some(ref path) = path {
1891− doc.set_path(Some(path.as_ref()))
2036+ doc.set_path(Some(path.as_ref().as_ref()))
1892 .context("invalid filepath")?;
2037 .context("invalid filepath")?;
1894 if doc.path().is_none() {
2039 if doc.path().is_none() {
@@ -1907,12 +2052,17 @@ mod cmd {
1908 let future = doc.format_and_save(fmt);
2053 let future = doc.format_and_save(fmt);
1909 cx.jobs.add(Job::new(future).wait_before_exiting());
2054 cx.jobs.add(Job::new(future).wait_before_exiting());
2056+ if path.is_some() {
2058+ let _ = cx.editor.refresh_language_server(id);
1913 fn write(
2063 fn write(
1914 cx: &mut compositor::Context,
2064 cx: &mut compositor::Context,
1915− args: &[&str],
2065+ args: &[Cow<str>],
1916 _event: PromptEvent,
2066 _event: PromptEvent,
1917 ) -> anyhow::Result<()> {
2067 ) -> anyhow::Result<()> {
1918 write_impl(cx, args.first())
2068 write_impl(cx, args.first())
@@ -1920,7 +2070,7 @@ mod cmd {
1921 fn new_file(
2071 fn new_file(
1922 cx: &mut compositor::Context,
2072 cx: &mut compositor::Context,
1923− _args: &[&str],
2073+ _args: &[Cow<str>],
1924 _event: PromptEvent,
2074 _event: PromptEvent,
1925 ) -> anyhow::Result<()> {
2075 ) -> anyhow::Result<()> {
1926 cx.editor.new_file(Action::Replace);
2076 cx.editor.new_file(Action::Replace);
@@ -1930,7 +2080,7 @@ mod cmd {
1931 fn format(
2081 fn format(
1932 cx: &mut compositor::Context,
2082 cx: &mut compositor::Context,
1933− _args: &[&str],
2083+ _args: &[Cow<str>],
1934 _event: PromptEvent,
2084 _event: PromptEvent,
1935 ) -> anyhow::Result<()> {
2085 ) -> anyhow::Result<()> {
1936 let (_, doc) = current!(cx.editor);
2086 let (_, doc) = current!(cx.editor);
@@ -1945,7 +2095,7 @@ mod cmd {
1946 fn set_indent_style(
2096 fn set_indent_style(
1947 cx: &mut compositor::Context,
2097 cx: &mut compositor::Context,
1948− args: &[&str],
2098+ args: &[Cow<str>],
1949 _event: PromptEvent,
2099 _event: PromptEvent,
1950 ) -> anyhow::Result<()> {
2100 ) -> anyhow::Result<()> {
1951 use IndentStyle::*;
2101 use IndentStyle::*;
@@ -1965,7 +2115,7 @@ mod cmd {
1965 // Attempt to parse argument as an indent style.
2115 // Attempt to parse argument as an indent style.
1966 let style = match args.get(0) {
2116 let style = match args.get(0) {
1967 Some(arg) if "tabs".starts_with(&arg.to_lowercase()) => Some(Tabs),
2117 Some(arg) if "tabs".starts_with(&arg.to_lowercase()) => Some(Tabs),
1968− Some(&"0") => Some(Tabs),
2118+ Some(Cow::Borrowed("0")) => Some(Tabs),
1969 Some(arg) => arg
2119 Some(arg) => arg
1970 .parse::<u8>()
2120 .parse::<u8>()
@@ -1984,7 +2134,7 @@ mod cmd {
1984 /// Sets or reports the current document's line ending setting.
2134 /// Sets or reports the current document's line ending setting.
1985 fn set_line_ending(
2135 fn set_line_ending(
1986 cx: &mut compositor::Context,
2136 cx: &mut compositor::Context,
1987− args: &[&str],
2137+ args: &[Cow<str>],
1988 _event: PromptEvent,
2138 _event: PromptEvent,
1989 ) -> anyhow::Result<()> {
2139 ) -> anyhow::Result<()> {
1990 use LineEnding::*;
2140 use LineEnding::*;
@@ -2028,7 +2178,7 @@ mod cmd {
2029 fn earlier(
2179 fn earlier(
2030 cx: &mut compositor::Context,
2180 cx: &mut compositor::Context,
2031− args: &[&str],
2181+ args: &[Cow<str>],
2032 _event: PromptEvent,
2182 _event: PromptEvent,
2033 ) -> anyhow::Result<()> {
2183 ) -> anyhow::Result<()> {
2034 let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
2184 let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
@@ -2044,7 +2194,7 @@ mod cmd {
2045 fn later(
2195 fn later(
2046 cx: &mut compositor::Context,
2196 cx: &mut compositor::Context,
2047− args: &[&str],
2197+ args: &[Cow<str>],
2048 _event: PromptEvent,
2198 _event: PromptEvent,
2049 ) -> anyhow::Result<()> {
2199 ) -> anyhow::Result<()> {
2050 let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
2200 let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
@@ -2059,7 +2209,7 @@ mod cmd {
2060 fn write_quit(
2210 fn write_quit(
2061 cx: &mut compositor::Context,
2211 cx: &mut compositor::Context,
2062− args: &[&str],
2212+ args: &[Cow<str>],
2063 event: PromptEvent,
2213 event: PromptEvent,
2064 ) -> anyhow::Result<()> {
2214 ) -> anyhow::Result<()> {
2065 write_impl(cx, args.first())?;
2215 write_impl(cx, args.first())?;
@@ -2068,7 +2218,7 @@ mod cmd {
2069 fn force_write_quit(
2219 fn force_write_quit(
2070 cx: &mut compositor::Context,
2220 cx: &mut compositor::Context,
2071− args: &[&str],
2221+ args: &[Cow<str>],
2072 event: PromptEvent,
2222 event: PromptEvent,
2073 ) -> anyhow::Result<()> {
2223 ) -> anyhow::Result<()> {
2074 write_impl(cx, args.first())?;
2224 write_impl(cx, args.first())?;
@@ -2099,7 +2249,7 @@ mod cmd {
2100 fn write_all_impl(
2250 fn write_all_impl(
2101 cx: &mut compositor::Context,
2251 cx: &mut compositor::Context,
2102− _args: &[&str],
2252+ _args: &[Cow<str>],
2103 _event: PromptEvent,
2253 _event: PromptEvent,
2104 quit: bool,
2254 quit: bool,
2105 force: bool,
2255 force: bool,
@@ -2135,7 +2285,7 @@ mod cmd {
2136 fn write_all(
2286 fn write_all(
2137 cx: &mut compositor::Context,
2287 cx: &mut compositor::Context,
2138− args: &[&str],
2288+ args: &[Cow<str>],
2139 event: PromptEvent,
2289 event: PromptEvent,
2140 ) -> anyhow::Result<()> {
2290 ) -> anyhow::Result<()> {
2141 write_all_impl(cx, args, event, false, false)
2291 write_all_impl(cx, args, event, false, false)
@@ -2143,7 +2293,7 @@ mod cmd {
2144 fn write_all_quit(
2294 fn write_all_quit(
2145 cx: &mut compositor::Context,
2295 cx: &mut compositor::Context,
2146− args: &[&str],
2296+ args: &[Cow<str>],
2147 event: PromptEvent,
2297 event: PromptEvent,
2148 ) -> anyhow::Result<()> {
2298 ) -> anyhow::Result<()> {
2149 write_all_impl(cx, args, event, true, false)
2299 write_all_impl(cx, args, event, true, false)
@@ -2151,7 +2301,7 @@ mod cmd {
2152 fn force_write_all_quit(
2302 fn force_write_all_quit(
2153 cx: &mut compositor::Context,
2303 cx: &mut compositor::Context,
2154− args: &[&str],
2304+ args: &[Cow<str>],
2155 event: PromptEvent,
2305 event: PromptEvent,
2156 ) -> anyhow::Result<()> {
2306 ) -> anyhow::Result<()> {
2157 write_all_impl(cx, args, event, true, true)
2307 write_all_impl(cx, args, event, true, true)
@@ -2159,7 +2309,7 @@ mod cmd {
2160 fn quit_all_impl(
2310 fn quit_all_impl(
2161 editor: &mut Editor,
2311 editor: &mut Editor,
2162− _args: &[&str],
2312+ _args: &[Cow<str>],
2163 _event: PromptEvent,
2313 _event: PromptEvent,
2164 force: bool,
2314 force: bool,
2165 ) -> anyhow::Result<()> {
2315 ) -> anyhow::Result<()> {
@@ -2178,23 +2328,23 @@ mod cmd {
2179 fn quit_all(
2329 fn quit_all(
2180 cx: &mut compositor::Context,
2330 cx: &mut compositor::Context,
2181− args: &[&str],
2331+ args: &[Cow<str>],
2182 event: PromptEvent,
2332 event: PromptEvent,
2183 ) -> anyhow::Result<()> {
2333 ) -> anyhow::Result<()> {
2184− quit_all_impl(&mut cx.editor, args, event, false)
2334+ quit_all_impl(cx.editor, args, event, false)
2187 fn force_quit_all(
2337 fn force_quit_all(
2188 cx: &mut compositor::Context,
2338 cx: &mut compositor::Context,
2189− args: &[&str],
2339+ args: &[Cow<str>],
2190 event: PromptEvent,
2340 event: PromptEvent,
2191 ) -> anyhow::Result<()> {
2341 ) -> anyhow::Result<()> {
2192− quit_all_impl(&mut cx.editor, args, event, true)
2342+ quit_all_impl(cx.editor, args, event, true)
2195 fn cquit(
2345 fn cquit(
2196 cx: &mut compositor::Context,
2346 cx: &mut compositor::Context,
2197− args: &[&str],
2347+ args: &[Cow<str>],
2198 _event: PromptEvent,
2348 _event: PromptEvent,
2199 ) -> anyhow::Result<()> {
2349 ) -> anyhow::Result<()> {
2200 let exit_code = args
2350 let exit_code = args
@@ -2213,85 +2363,91 @@ mod cmd {
2214 fn theme(
2364 fn theme(
2215 cx: &mut compositor::Context,
2365 cx: &mut compositor::Context,
2216− args: &[&str],
2366+ args: &[Cow<str>],
2217 _event: PromptEvent,
2367 _event: PromptEvent,
2218 ) -> anyhow::Result<()> {
2368 ) -> anyhow::Result<()> {
2219− let theme = args.first().context("theme not provided")?;
2369+ let theme = args.first().context("Theme not provided")?;
2220− cx.editor.set_theme_from_name(theme)
2370+ let theme = cx
2374+ .with_context(|| format!("Failed setting theme {}", theme))?;
2375+ let true_color = cx.editor.config.true_color || crate::true_color();
2376+ if !(true_color || theme.is_16_color()) {
2377+ bail!("Unsupported theme: theme requires true color support");
2379+ cx.editor.set_theme(theme);
2223 fn yank_main_selection_to_clipboard(
2383 fn yank_main_selection_to_clipboard(
2224 cx: &mut compositor::Context,
2384 cx: &mut compositor::Context,
2225− _args: &[&str],
2385+ _args: &[Cow<str>],
2226 _event: PromptEvent,
2386 _event: PromptEvent,
2227 ) -> anyhow::Result<()> {
2387 ) -> anyhow::Result<()> {
2228− yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard)
2388+ yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard)
2231 fn yank_joined_to_clipboard(
2391 fn yank_joined_to_clipboard(
2232 cx: &mut compositor::Context,
2392 cx: &mut compositor::Context,
2233− args: &[&str],
2393+ args: &[Cow<str>],
2234 _event: PromptEvent,
2394 _event: PromptEvent,
2235 ) -> anyhow::Result<()> {
2395 ) -> anyhow::Result<()> {
2236 let (_, doc) = current!(cx.editor);
2396 let (_, doc) = current!(cx.editor);
2237− let separator = args
2240− .unwrap_or_else(|| doc.line_ending.as_str());
2397+ let default_sep = Cow::Borrowed(doc.line_ending.as_str());
2398+ let separator = args.first().unwrap_or(&default_sep);
2241− yank_joined_to_clipboard_impl(&mut cx.editor, separator, ClipboardType::Clipboard)
2399+ yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
2244 fn yank_main_selection_to_primary_clipboard(
2402 fn yank_main_selection_to_primary_clipboard(
2245 cx: &mut compositor::Context,
2403 cx: &mut compositor::Context,
2246− _args: &[&str],
2404+ _args: &[Cow<str>],
2247 _event: PromptEvent,
2405 _event: PromptEvent,
2248 ) -> anyhow::Result<()> {
2406 ) -> anyhow::Result<()> {
2249− yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Selection)
2407+ yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection)
2252 fn yank_joined_to_primary_clipboard(
2410 fn yank_joined_to_primary_clipboard(
2253 cx: &mut compositor::Context,
2411 cx: &mut compositor::Context,
2254− args: &[&str],
2412+ args: &[Cow<str>],
2255 _event: PromptEvent,
2413 _event: PromptEvent,
2256 ) -> anyhow::Result<()> {
2414 ) -> anyhow::Result<()> {
2257 let (_, doc) = current!(cx.editor);
2415 let (_, doc) = current!(cx.editor);
2258− let separator = args
2261− .unwrap_or_else(|| doc.line_ending.as_str());
2416+ let default_sep = Cow::Borrowed(doc.line_ending.as_str());
2417+ let separator = args.first().unwrap_or(&default_sep);
2262− yank_joined_to_clipboard_impl(&mut cx.editor, separator, ClipboardType::Selection)
2418+ yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
2265 fn paste_clipboard_after(
2421 fn paste_clipboard_after(
2266 cx: &mut compositor::Context,
2422 cx: &mut compositor::Context,
2267− _args: &[&str],
2423+ _args: &[Cow<str>],
2268 _event: PromptEvent,
2424 _event: PromptEvent,
2269 ) -> anyhow::Result<()> {
2425 ) -> anyhow::Result<()> {
2270− paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard)
2426+ paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard, 1)
2273 fn paste_clipboard_before(
2429 fn paste_clipboard_before(
2274 cx: &mut compositor::Context,
2430 cx: &mut compositor::Context,
2275− _args: &[&str],
2431+ _args: &[Cow<str>],
2276 _event: PromptEvent,
2432 _event: PromptEvent,
2277 ) -> anyhow::Result<()> {
2433 ) -> anyhow::Result<()> {
2278− paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard)
2434+ paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard, 1)
2281 fn paste_primary_clipboard_after(
2437 fn paste_primary_clipboard_after(
2282 cx: &mut compositor::Context,
2438 cx: &mut compositor::Context,
2283− _args: &[&str],
2439+ _args: &[Cow<str>],
2284 _event: PromptEvent,
2440 _event: PromptEvent,
2285 ) -> anyhow::Result<()> {
2441 ) -> anyhow::Result<()> {
2286− paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection)
2442+ paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection, 1)
2289 fn paste_primary_clipboard_before(
2445 fn paste_primary_clipboard_before(
2290 cx: &mut compositor::Context,
2446 cx: &mut compositor::Context,
2291− _args: &[&str],
2447+ _args: &[Cow<str>],
2292 _event: PromptEvent,
2448 _event: PromptEvent,
2293 ) -> anyhow::Result<()> {
2449 ) -> anyhow::Result<()> {
2294− paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection)
2450+ paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection, 1)
2297 fn replace_selections_with_clipboard_impl(
2453 fn replace_selections_with_clipboard_impl(
@@ -2318,7 +2474,7 @@ mod cmd {
2319 fn replace_selections_with_clipboard(
2475 fn replace_selections_with_clipboard(
2320 cx: &mut compositor::Context,
2476 cx: &mut compositor::Context,
2321− _args: &[&str],
2477+ _args: &[Cow<str>],
2322 _event: PromptEvent,
2478 _event: PromptEvent,
2323 ) -> anyhow::Result<()> {
2479 ) -> anyhow::Result<()> {
2324 replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
2480 replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
@@ -2326,7 +2482,7 @@ mod cmd {
2327 fn replace_selections_with_primary_clipboard(
2483 fn replace_selections_with_primary_clipboard(
2328 cx: &mut compositor::Context,
2484 cx: &mut compositor::Context,
2329− _args: &[&str],
2485+ _args: &[Cow<str>],
2330 _event: PromptEvent,
2486 _event: PromptEvent,
2331 ) -> anyhow::Result<()> {
2487 ) -> anyhow::Result<()> {
2332 replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
2488 replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
@@ -2334,7 +2490,7 @@ mod cmd {
2335 fn show_clipboard_provider(
2491 fn show_clipboard_provider(
2336 cx: &mut compositor::Context,
2492 cx: &mut compositor::Context,
2337− _args: &[&str],
2493+ _args: &[Cow<str>],
2338 _event: PromptEvent,
2494 _event: PromptEvent,
2339 ) -> anyhow::Result<()> {
2495 ) -> anyhow::Result<()> {
2340 cx.editor
2496 cx.editor
@@ -2344,12 +2500,13 @@ mod cmd {
2345 fn change_current_directory(
2501 fn change_current_directory(
2346 cx: &mut compositor::Context,
2502 cx: &mut compositor::Context,
2347− args: &[&str],
2503+ args: &[Cow<str>],
2348 _event: PromptEvent,
2504 _event: PromptEvent,
2349 ) -> anyhow::Result<()> {
2505 ) -> anyhow::Result<()> {
2350 let dir = helix_core::path::expand_tilde(
2506 let dir = helix_core::path::expand_tilde(
2351 args.first()
2507 args.first()
2352 .context("target directory not provided")?
2508 .context("target directory not provided")?
2353 .as_ref(),
2510 .as_ref(),
@@ -2367,7 +2524,7 @@ mod cmd {
2368 fn show_current_directory(
2525 fn show_current_directory(
2369 cx: &mut compositor::Context,
2526 cx: &mut compositor::Context,
2370− _args: &[&str],
2527+ _args: &[Cow<str>],
2371 _event: PromptEvent,
2528 _event: PromptEvent,
2372 ) -> anyhow::Result<()> {
2529 ) -> anyhow::Result<()> {
2373 let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
2530 let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
@@ -2379,7 +2536,7 @@ mod cmd {
2379 /// Sets the [`Document`]'s encoding..
2536 /// Sets the [`Document`]'s encoding..
2380 fn set_encoding(
2537 fn set_encoding(
2381 cx: &mut compositor::Context,
2538 cx: &mut compositor::Context,
2382− args: &[&str],
2539+ args: &[Cow<str>],
2383 _event: PromptEvent,
2540 _event: PromptEvent,
2384 ) -> anyhow::Result<()> {
2541 ) -> anyhow::Result<()> {
2385 let (_, doc) = current!(cx.editor);
2542 let (_, doc) = current!(cx.editor);
@@ -2395,7 +2552,7 @@ mod cmd {
2395 /// Reload the [`Document`] from its source file.
2552 /// Reload the [`Document`] from its source file.
2396 fn reload(
2553 fn reload(
2397 cx: &mut compositor::Context,
2554 cx: &mut compositor::Context,
2398− _args: &[&str],
2555+ _args: &[Cow<str>],
2399 _event: PromptEvent,
2556 _event: PromptEvent,
2400 ) -> anyhow::Result<()> {
2557 ) -> anyhow::Result<()> {
2401 let (view, doc) = current!(cx.editor);
2558 let (view, doc) = current!(cx.editor);
@@ -2404,7 +2561,7 @@ mod cmd {
2405 fn tree_sitter_scopes(
2562 fn tree_sitter_scopes(
2406 cx: &mut compositor::Context,
2563 cx: &mut compositor::Context,
2407− _args: &[&str],
2564+ _args: &[Cow<str>],
2408 _event: PromptEvent,
2565 _event: PromptEvent,
2409 ) -> anyhow::Result<()> {
2566 ) -> anyhow::Result<()> {
2410 let (view, doc) = current!(cx.editor);
2567 let (view, doc) = current!(cx.editor);
@@ -2418,15 +2575,18 @@ mod cmd {
2419 fn vsplit(
2576 fn vsplit(
2420 cx: &mut compositor::Context,
2577 cx: &mut compositor::Context,
2421− args: &[&str],
2578+ args: &[Cow<str>],
2422 _event: PromptEvent,
2579 _event: PromptEvent,
2423 ) -> anyhow::Result<()> {
2580 ) -> anyhow::Result<()> {
2424 let id = view!(cx.editor).doc;
2581 let id = view!(cx.editor).doc;
2426− if let Some(path) = args.get(0) {
2583+ if args.is_empty() {
2427− cx.editor.open(path.into(), Action::VerticalSplit)?;
2429 cx.editor.switch(id, Action::VerticalSplit);
2584 cx.editor.switch(id, Action::VerticalSplit);
2588+ .open(PathBuf::from(arg.as_ref()), Action::VerticalSplit)?;
@@ -2434,15 +2594,18 @@ mod cmd {
2435 fn hsplit(
2595 fn hsplit(
2436 cx: &mut compositor::Context,
2596 cx: &mut compositor::Context,
2437− args: &[&str],
2597+ args: &[Cow<str>],
2438 _event: PromptEvent,
2598 _event: PromptEvent,
2439 ) -> anyhow::Result<()> {
2599 ) -> anyhow::Result<()> {
2440 let id = view!(cx.editor).doc;
2600 let id = view!(cx.editor).doc;
2442− if let Some(path) = args.get(0) {
2602+ if args.is_empty() {
2443− cx.editor.open(path.into(), Action::HorizontalSplit)?;
2445 cx.editor.switch(id, Action::HorizontalSplit);
2603 cx.editor.switch(id, Action::HorizontalSplit);
2607+ .open(PathBuf::from(arg.as_ref()), Action::HorizontalSplit)?;
@@ -2450,7 +2613,7 @@ mod cmd {
2451 fn tutor(
2614 fn tutor(
2452 cx: &mut compositor::Context,
2615 cx: &mut compositor::Context,
2453− _args: &[&str],
2616+ _args: &[Cow<str>],
2454 _event: PromptEvent,
2617 _event: PromptEvent,
2455 ) -> anyhow::Result<()> {
2618 ) -> anyhow::Result<()> {
2456 let path = helix_core::runtime_dir().join("tutor.txt");
2619 let path = helix_core::runtime_dir().join("tutor.txt");
@@ -2460,6 +2623,24 @@ mod cmd {
2626+ pub(super) fn goto_line_number(
2627+ cx: &mut compositor::Context,
2629+ _event: PromptEvent,
2630+ ) -> anyhow::Result<()> {
2631+ ensure!(!args.is_empty(), "Line number required");
2633+ let line = args[0].parse::<usize>()?;
2635+ goto_line_impl(cx.editor, NonZeroUsize::new(line));
2637+ let (view, doc) = current!(cx.editor);
2639+ view.ensure_cursor_in_view(doc, line);
2463 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2644 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2464 TypableCommand {
2645 TypableCommand {
2465 name: "quit",
2646 name: "quit",
@@ -2513,7 +2694,7 @@ mod cmd {
2513 TypableCommand {
2694 TypableCommand {
2514 name: "format",
2695 name: "format",
2515 aliases: &["fmt"],
2696 aliases: &["fmt"],
2516− doc: "Format the file using a formatter.",
2697+ doc: "Format the file using the LSP formatter.",
2517 fun: format,
2698 fun: format,
2518 completer: None,
2699 completer: None,
@@ -2604,7 +2785,7 @@ mod cmd {
2604 TypableCommand {
2785 TypableCommand {
2605 name: "theme",
2786 name: "theme",
2606 aliases: &[],
2787 aliases: &[],
2607− doc: "Change the theme of current view. Requires theme name as argument (:theme <name>)",
2788+ doc: "Change the editor theme.",
2608 fun: theme,
2789 fun: theme,
2609 completer: Some(completers::theme),
2790 completer: Some(completers::theme),
@@ -2688,7 +2869,7 @@ mod cmd {
2688 TypableCommand {
2869 TypableCommand {
2689 name: "change-current-directory",
2870 name: "change-current-directory",
2690 aliases: &["cd"],
2871 aliases: &["cd"],
2691− doc: "Change the current working directory (:cd <dir>).",
2872+ doc: "Change the current working directory.",
2692 fun: change_current_directory,
2873 fun: change_current_directory,
2693 completer: Some(completers::directory),
2874 completer: Some(completers::directory),
@@ -2741,17 +2922,25 @@ mod cmd {
2741 fun: tutor,
2922 fun: tutor,
2742 completer: None,
2923 completer: None,
2928+ doc: "Go to line number.",
2929+ fun: goto_line_number,
2746− pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
2934+ pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
2747− TYPABLE_COMMAND_LIST
2936+ TYPABLE_COMMAND_LIST
2748− .iter()
2937+ .iter()
2749− .flat_map(|cmd| {
2938+ .flat_map(|cmd| {
2750− std::iter::once((cmd.name, cmd))
2939+ std::iter::once((cmd.name, cmd))
2751− .chain(cmd.aliases.iter().map(move |&alias| (alias, cmd)))
2940+ .chain(cmd.aliases.iter().map(move |&alias| (alias, cmd)))
2753− .collect()
2942+ .collect()
2757fn command_mode(cx: &mut Context) {
2946fn command_mode(cx: &mut Context) {
@@ -2777,7 +2966,7 @@ fn command_mode(cx: &mut Context) {
2777 if let Some(cmd::TypableCommand {
2966 if let Some(cmd::TypableCommand {
2778 completer: Some(completer),
2967 completer: Some(completer),
2780− }) = cmd::COMMANDS.get(parts[0])
2969+ }) = cmd::TYPABLE_COMMAND_MAP.get(parts[0])
2782 completer(part)
2971 completer(part)
2783 .into_iter()
2972 .into_iter()
@@ -2803,8 +2992,18 @@ fn command_mode(cx: &mut Context) {
2995+ // If command is numeric, interpret as line number and go there.
2996+ if parts.len() == 1 && parts[0].parse::<usize>().ok().is_some() {
2997+ if let Err(e) = cmd::goto_line_number(cx, &[Cow::from(parts[0])], event) {
2998+ cx.editor.set_error(format!("{}", e));
3003+ // Handle typable commands
2806− if let Some(cmd) = cmd::COMMANDS.get(parts[0]) {
3004+ if let Some(cmd) = cmd::TYPABLE_COMMAND_MAP.get(parts[0]) {
3005+ let args = shellwords::shellwords(input);
2807− if let Err(e) = (cmd.fun)(cx, &parts[1..], event) {
3006+ if let Err(e) = (cmd.fun)(cx, &args[1..], event) {
2808 cx.editor.set_error(format!("{}", e));
3007 cx.editor.set_error(format!("{}", e));
2810 } else {
3009 } else {
@@ -2816,7 +3015,7 @@ fn command_mode(cx: &mut Context) {
2816 prompt.doc_fn = Box::new(|input: &str| {
3015 prompt.doc_fn = Box::new(|input: &str| {
2817 let part = input.split(' ').next().unwrap_or_default();
3016 let part = input.split(' ').next().unwrap_or_default();
2819− if let Some(cmd::TypableCommand { doc, .. }) = cmd::COMMANDS.get(part) {
3018+ if let Some(cmd::TypableCommand { doc, .. }) = cmd::TYPABLE_COMMAND_MAP.get(part) {
2820 return Some(doc);
3019 return Some(doc);
@@ -3254,7 +3453,7 @@ fn apply_workspace_edit(
3255fn last_picker(cx: &mut Context) {
3454fn last_picker(cx: &mut Context) {
3256 // TODO: last picker does not seem to work well with buffer_picker
3455 // TODO: last picker does not seem to work well with buffer_picker
3257− cx.callback = Some(Box::new(|compositor: &mut Compositor| {
3456+ cx.callback = Some(Box::new(|compositor: &mut Compositor, _| {
3258 if let Some(picker) = compositor.last_picker.take() {
3457 if let Some(picker) = compositor.last_picker.take() {
3259 compositor.push(picker);
3458 compositor.push(picker);
@@ -3436,10 +3635,14 @@ fn push_jump(editor: &mut Editor) {
3438fn goto_line(cx: &mut Context) {
3637fn goto_line(cx: &mut Context) {
3439− if let Some(count) = cx.count {
3638+ goto_line_impl(cx.editor, cx.count)
3440− push_jump(cx.editor);
3639+}
3641+fn goto_line_impl(editor: &mut Editor, count: Option<NonZeroUsize>) {
3642+ if let Some(count) = count {
3442− let (view, doc) = current!(cx.editor);
3645+ let (view, doc) = current!(editor);
3443 let max_line = if doc.text().line(doc.text().len_lines() - 1).len_chars() == 0 {
3646 let max_line = if doc.text().line(doc.text().len_lines() - 1).len_chars() == 0 {
3444 // If the last line is blank, don't jump to it.
3647 // If the last line is blank, don't jump to it.
3445 doc.text().len_lines().saturating_sub(2)
3648 doc.text().len_lines().saturating_sub(2)
@@ -3498,6 +3701,20 @@ fn goto_last_modification(cx: &mut Context) {
3704+fn goto_last_modified_file(cx: &mut Context) {
3705+ let view = view!(cx.editor);
3706+ let alternate_file = view
3707+ .last_modified_docs
3710+ .find(|&id| id != view.doc);
3711+ if let Some(alt) = alternate_file {
3712+ cx.editor.switch(alt, Action::Replace);
3714+ cx.editor.set_error("no last modified buffer".to_owned())
3501fn select_mode(cx: &mut Context) {
3718fn select_mode(cx: &mut Context) {
3502 let (view, doc) = current!(cx.editor);
3719 let (view, doc) = current!(cx.editor);
3503 let text = doc.text().slice(..);
3720 let text = doc.text().slice(..);
@@ -3982,8 +4199,9 @@ pub mod insert {
3982 // The default insert hook: simply insert the character
4199 // The default insert hook: simply insert the character
3983 #[allow(clippy::unnecessary_wraps)] // need to use Option<> because of the Hook signature
4200 #[allow(clippy::unnecessary_wraps)] // need to use Option<> because of the Hook signature
3984 fn insert(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
4201 fn insert(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
4202+ let cursors = selection.clone().cursors(doc.slice(..));
3985 let t = Tendril::from_char(ch);
4203 let t = Tendril::from_char(ch);
3986− let transaction = Transaction::insert(doc, selection, t);
4204+ let transaction = Transaction::insert(doc, &cursors, t);
3987 Some(transaction)
4205 Some(transaction)
@@ -3998,11 +4216,11 @@ pub mod insert {
4000 let text = doc.text();
4218 let text = doc.text();
4001− let selection = doc.selection(view.id).clone().cursors(text.slice(..));
4219+ let selection = doc.selection(view.id);
4003 // run through insert hooks, stopping on the first one that returns Some(t)
4221 // run through insert hooks, stopping on the first one that returns Some(t)
4004 for hook in hooks {
4222 for hook in hooks {
4005− if let Some(transaction) = hook(text, &selection, c) {
4223+ if let Some(transaction) = hook(text, selection, c) {
4006 doc.apply(&transaction, view.id);
4224 doc.apply(&transaction, view.id);
@@ -4317,11 +4535,8 @@ fn yank_joined_to_clipboard_impl(
4318fn yank_joined_to_clipboard(cx: &mut Context) {
4536fn yank_joined_to_clipboard(cx: &mut Context) {
4319 let line_ending = doc!(cx.editor).line_ending;
4537 let line_ending = doc!(cx.editor).line_ending;
4320− let _ = yank_joined_to_clipboard_impl(
4539+ yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Clipboard);
4322− line_ending.as_str(),
4323− ClipboardType::Clipboard,
4325 exit_select_mode(cx);
4540 exit_select_mode(cx);
@@ -4346,20 +4561,17 @@ fn yank_main_selection_to_clipboard_impl(
4348fn yank_main_selection_to_clipboard(cx: &mut Context) {
4563fn yank_main_selection_to_clipboard(cx: &mut Context) {
4349− let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard);
4564+ let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard);
4352fn yank_joined_to_primary_clipboard(cx: &mut Context) {
4567fn yank_joined_to_primary_clipboard(cx: &mut Context) {
4353 let line_ending = doc!(cx.editor).line_ending;
4568 let line_ending = doc!(cx.editor).line_ending;
4354− let _ = yank_joined_to_clipboard_impl(
4570+ yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Selection);
4356− line_ending.as_str(),
4357− ClipboardType::Selection,
4361fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
4573fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
4362− let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Selection);
4574+ let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection);
4363 exit_select_mode(cx);
4575 exit_select_mode(cx);
@@ -4374,11 +4586,12 @@ fn paste_impl(
4374 doc: &mut Document,
4586 doc: &mut Document,
4375 view: &View,
4587 view: &View,
4376 action: Paste,
4588 action: Paste,
4377) -> Option<Transaction> {
4590) -> Option<Transaction> {
4378 let repeat = std::iter::repeat(
4591 let repeat = std::iter::repeat(
4381− .map(|value| Tendril::from_slice(value))
4594+ .map(|value| Tendril::from(value.repeat(count)))
4382 .unwrap(),
4595 .unwrap(),
@@ -4393,7 +4606,7 @@ fn paste_impl(
4393 let mut values = values
4606 let mut values = values
4395 .map(|value| REGEX.replace_all(value, doc.line_ending.as_str()))
4608 .map(|value| REGEX.replace_all(value, doc.line_ending.as_str()))
4396− .map(|value| Tendril::from(value.as_ref()))
4609+ .map(|value| Tendril::from(value.as_ref().repeat(count)))
4397 .chain(repeat);
4610 .chain(repeat);
4399 let text = doc.text();
4612 let text = doc.text();
@@ -4413,7 +4626,7 @@ fn paste_impl(
4413 // paste append
4626 // paste append
4414 (Paste::After, false) => range.to(),
4627 (Paste::After, false) => range.to(),
4416− (pos, pos, Some(values.next().unwrap()))
4629+ (pos, pos, values.next())
4419 Some(transaction)
4632 Some(transaction)
@@ -4423,13 +4636,14 @@ fn paste_clipboard_impl(
4423 editor: &mut Editor,
4636 editor: &mut Editor,
4424 action: Paste,
4637 action: Paste,
4425 clipboard_type: ClipboardType,
4638 clipboard_type: ClipboardType,
4426) -> anyhow::Result<()> {
4640) -> anyhow::Result<()> {
4427 let (view, doc) = current!(editor);
4641 let (view, doc) = current!(editor);
4429 match editor
4643 match editor
4430 .clipboard_provider
4644 .clipboard_provider
4431 .get_contents(clipboard_type)
4645 .get_contents(clipboard_type)
4432− .map(|contents| paste_impl(&[contents], doc, view, action))
4646+ .map(|contents| paste_impl(&[contents], doc, view, action, count))
4434 Ok(Some(transaction)) => {
4648 Ok(Some(transaction)) => {
4435 doc.apply(&transaction, view.id);
4649 doc.apply(&transaction, view.id);
@@ -4442,22 +4656,43 @@ fn paste_clipboard_impl(
4444fn paste_clipboard_after(cx: &mut Context) {
4658fn paste_clipboard_after(cx: &mut Context) {
4445− let _ = paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard);
4659+ let _ = paste_clipboard_impl(
4662+ ClipboardType::Clipboard,
4448fn paste_clipboard_before(cx: &mut Context) {
4667fn paste_clipboard_before(cx: &mut Context) {
4449− let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before, ClipboardType::Clipboard);
4668+ let _ = paste_clipboard_impl(
4671+ ClipboardType::Clipboard,
4452fn paste_primary_clipboard_after(cx: &mut Context) {
4676fn paste_primary_clipboard_after(cx: &mut Context) {
4453− let _ = paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection);
4677+ let _ = paste_clipboard_impl(
4680+ ClipboardType::Selection,
4456fn paste_primary_clipboard_before(cx: &mut Context) {
4685fn paste_primary_clipboard_before(cx: &mut Context) {
4457− let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before, ClipboardType::Selection);
4686+ let _ = paste_clipboard_impl(
4689+ ClipboardType::Selection,
4460fn replace_with_yanked(cx: &mut Context) {
4694fn replace_with_yanked(cx: &mut Context) {
4695+ let count = cx.count();
4461 let reg_name = cx.register.unwrap_or('"');
4696 let reg_name = cx.register.unwrap_or('"');
4462 let (view, doc) = current!(cx.editor);
4697 let (view, doc) = current!(cx.editor);
4463 let registers = &mut cx.editor.registers;
4698 let registers = &mut cx.editor.registers;
@@ -4467,12 +4702,12 @@ fn replace_with_yanked(cx: &mut Context) {
4467 let repeat = std::iter::repeat(
4702 let repeat = std::iter::repeat(
4470− .map(|value| Tendril::from_slice(value))
4705+ .map(|value| Tendril::from_slice(&value.repeat(count)))
4471 .unwrap(),
4706 .unwrap(),
4473 let mut values = values
4708 let mut values = values
4475− .map(|value| Tendril::from_slice(value))
4710+ .map(|value| Tendril::from_slice(&value.repeat(count)))
4476 .chain(repeat);
4711 .chain(repeat);
4477 let selection = doc.selection(view.id);
4712 let selection = doc.selection(view.id);
4478 let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4713 let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
@@ -4492,6 +4727,7 @@ fn replace_with_yanked(cx: &mut Context) {
4492fn replace_selections_with_clipboard_impl(
4727fn replace_selections_with_clipboard_impl(
4493 editor: &mut Editor,
4728 editor: &mut Editor,
4494 clipboard_type: ClipboardType,
4729 clipboard_type: ClipboardType,
4495) -> anyhow::Result<()> {
4731) -> anyhow::Result<()> {
4496 let (view, doc) = current!(editor);
4732 let (view, doc) = current!(editor);
@@ -4499,7 +4735,11 @@ fn replace_selections_with_clipboard_impl(
4499 Ok(contents) => {
4735 Ok(contents) => {
4500 let selection = doc.selection(view.id);
4736 let selection = doc.selection(view.id);
4501 let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4737 let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4502− (range.from(), range.to(), Some(contents.as_str().into()))
4741+ Some(contents.repeat(count).as_str().into()),
4505 doc.apply(&transaction, view.id);
4745 doc.apply(&transaction, view.id);
@@ -4511,21 +4751,22 @@ fn replace_selections_with_clipboard_impl(
4513fn replace_selections_with_clipboard(cx: &mut Context) {
4753fn replace_selections_with_clipboard(cx: &mut Context) {
4514− let _ = replace_selections_with_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard);
4754+ let _ = replace_selections_with_clipboard_impl(cx.editor, ClipboardType::Clipboard, cx.count());
4517fn replace_selections_with_primary_clipboard(cx: &mut Context) {
4757fn replace_selections_with_primary_clipboard(cx: &mut Context) {
4518− let _ = replace_selections_with_clipboard_impl(&mut cx.editor, ClipboardType::Selection);
4758+ let _ = replace_selections_with_clipboard_impl(cx.editor, ClipboardType::Selection, cx.count());
4521fn paste_after(cx: &mut Context) {
4761fn paste_after(cx: &mut Context) {
4762+ let count = cx.count();
4522 let reg_name = cx.register.unwrap_or('"');
4763 let reg_name = cx.register.unwrap_or('"');
4523 let (view, doc) = current!(cx.editor);
4764 let (view, doc) = current!(cx.editor);
4524 let registers = &mut cx.editor.registers;
4765 let registers = &mut cx.editor.registers;
4526 if let Some(transaction) = registers
4767 if let Some(transaction) = registers
4527 .read(reg_name)
4768 .read(reg_name)
4528− .and_then(|values| paste_impl(values, doc, view, Paste::After))
4769+ .and_then(|values| paste_impl(values, doc, view, Paste::After, count))
4530 doc.apply(&transaction, view.id);
4771 doc.apply(&transaction, view.id);
4531 doc.append_changes_to_history(view.id);
4772 doc.append_changes_to_history(view.id);
@@ -4533,13 +4774,14 @@ fn paste_after(cx: &mut Context) {
4535fn paste_before(cx: &mut Context) {
4776fn paste_before(cx: &mut Context) {
4777+ let count = cx.count();
4536 let reg_name = cx.register.unwrap_or('"');
4778 let reg_name = cx.register.unwrap_or('"');
4537 let (view, doc) = current!(cx.editor);
4779 let (view, doc) = current!(cx.editor);
4538 let registers = &mut cx.editor.registers;
4780 let registers = &mut cx.editor.registers;
4540 if let Some(transaction) = registers
4782 if let Some(transaction) = registers
4541 .read(reg_name)
4783 .read(reg_name)
4542− .and_then(|values| paste_impl(values, doc, view, Paste::Before))
4784+ .and_then(|values| paste_impl(values, doc, view, Paste::Before, count))
4544 doc.apply(&transaction, view.id);
4786 doc.apply(&transaction, view.id);
4545 doc.append_changes_to_history(view.id);
4787 doc.append_changes_to_history(view.id);
@@ -4935,8 +5177,12 @@ fn hover(cx: &mut Context) {
4935 // skip if contents empty
5177 // skip if contents empty
4937 let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
5179 let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
4938− let popup = Popup::new(contents);
5180+ let popup = Popup::new("documentation", contents);
5181+ if let Some(doc_popup) = compositor.find_id("documentation") {
5182+ *doc_popup = popup;
4939− compositor.push(Box::new(popup));
5184+ compositor.push(Box::new(popup));
@@ -5030,7 +5276,7 @@ fn expand_selection(cx: &mut Context) {
5030 doc.set_selection(view.id, selection);
5276 doc.set_selection(view.id, selection);
5033− motion(&mut cx.editor);
5279+ motion(cx.editor);
5034 cx.editor.last_motion = Some(Motion(Box::new(motion)));
5280 cx.editor.last_motion = Some(Motion(Box::new(motion)));
@@ -5086,6 +5332,12 @@ fn jump_backward(cx: &mut Context) {
5335+fn save_selection(cx: &mut Context) {
5336+ push_jump(cx.editor);
5338+ .set_status("Selection saved to jumplist".to_owned());
5089fn rotate_view(cx: &mut Context) {
5341fn rotate_view(cx: &mut Context) {
5090 cx.editor.focus_next()
5342 cx.editor.focus_next()
@@ -5262,7 +5514,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
5263 doc.set_selection(view.id, selection);
5515 doc.set_selection(view.id, selection);
5265− textobject(&mut cx.editor);
5517+ textobject(cx.editor);
5266 cx.editor.last_motion = Some(Motion(Box::new(textobject)));
5518 cx.editor.last_motion = Some(Motion(Box::new(textobject)));
@@ -5428,9 +5680,7 @@ fn shell_impl(
5428) -> anyhow::Result<(Tendril, bool)> {
5680) -> anyhow::Result<(Tendril, bool)> {
5429 use std::io::Write;
5681 use std::io::Write;
5430 use std::process::{Command, Stdio};
5682 use std::process::{Command, Stdio};
5431− if shell.is_empty() {
5683+ ensure!(!shell.is_empty(), "No shell set");
5432− bail!("No shell set");
5435 let mut process = match Command::new(&shell[0])
5685 let mut process = match Command::new(&shell[0])
5436 .args(&shell[1..])
5686 .args(&shell[1..])
@@ -5594,7 +5844,7 @@ fn rename_symbol(cx: &mut Context) {
5594 let task = language_server.rename_symbol(doc.identifier(), pos, input.to_string());
5844 let task = language_server.rename_symbol(doc.identifier(), pos, input.to_string());
5595 let edits = block_on(task).unwrap_or_default();
5845 let edits = block_on(task).unwrap_or_default();
5596 log::debug!("Edits from LSP: {:?}", edits);
5846 log::debug!("Edits from LSP: {:?}", edits);
5597− apply_workspace_edit(&mut cx.editor, offset_encoding, &edits);
5847+ apply_workspace_edit(cx.editor, offset_encoding, &edits);
5600 cx.push_layer(Box::new(prompt));
5850 cx.push_layer(Box::new(prompt));
@@ -5614,16 +5864,45 @@ fn decrement(cx: &mut Context) {
5614fn increment_impl(cx: &mut Context, amount: i64) {
5864fn increment_impl(cx: &mut Context, amount: i64) {
5615 let (view, doc) = current!(cx.editor);
5865 let (view, doc) = current!(cx.editor);
5616 let selection = doc.selection(view.id);
5866 let selection = doc.selection(view.id);
5617− let text = doc.text();
5867+ let text = doc.text().slice(..);
5869+ let changes: Vec<_> = selection
5872+ .filter_map(|range| {
5873+ let incrementor: Box<dyn Increment> =
5874+ if let Some(incrementor) = DateTimeIncrementor::from_range(text, *range) {
5875+ Box::new(incrementor)
5876+ } else if let Some(incrementor) = NumberIncrementor::from_range(text, *range) {
5877+ Box::new(incrementor)
5882+ let (range, new_text) = incrementor.increment(amount);
5884+ Some((range.from(), range.to(), Some(new_text)))
5619− let changes = selection.ranges().iter().filter_map(|range| {
5888+ // Overlapping changes in a transaction will panic, so we need to find and remove them.
5620− let incrementor = NumberIncrementor::from_range(text.slice(..), *range)?;
5889+ // For example, if there are cursors on each of the year, month, and day of `2021-11-29`,
5621− let new_text = incrementor.incremented_text(amount);
5890+ // incrementing will give overlapping changes, with each change incrementing a different part of
5622− Some((
5891+ // the date. Since these conflict with each other we remove these changes from the transaction
5623− incrementor.range.from(),
5892+ // so nothing happens.
5624− incrementor.range.to(),
5893+ let mut overlapping_indexes = HashSet::new();
5625− Some(new_text),
5894+ for (i, changes) in changes.windows(2).enumerate() {
5626− ))
5895+ if changes[0].1 > changes[1].0 {
5896+ overlapping_indexes.insert(i);
5897+ overlapping_indexes.insert(i + 1);
5900+ let changes = changes.into_iter().enumerate().filter_map(|(i, change)| {
5901+ if overlapping_indexes.contains(&i) {
5629 if changes.clone().count() > 0 {
5908 if changes.clone().count() > 0 {
@@ -5634,3 +5913,56 @@ fn increment_impl(cx: &mut Context, amount: i64) {
5634 doc.append_changes_to_history(view.id);
5913 doc.append_changes_to_history(view.id);
5917+fn record_macro(cx: &mut Context) {
5918+ if let Some((reg, mut keys)) = cx.editor.macro_recording.take() {
5919+ // Remove the keypress which ends the recording
5923+ .map(|key| format!("{}", key))
5924+ .collect::<Vec<_>>()
5926+ cx.editor.registers.get_mut(reg).write(vec![s]);
5928+ .set_status(format!("Recorded to register {}", reg));
5930+ let reg = cx.register.take().unwrap_or('@');
5931+ cx.editor.macro_recording = Some((reg, Vec::new()));
5933+ .set_status(format!("Recording to register {}", reg));
5937+fn play_macro(cx: &mut Context) {
5938+ let reg = cx.register.unwrap_or('@');
5939+ let keys = match cx
5943+ .and_then(|reg| reg.read().get(0))
5944+ .context("Register empty")
5946+ s.split_whitespace()
5947+ .map(str::parse::<KeyEvent>)
5948+ .collect::<Result<Vec<_>, _>>()
5949+ .context("Failed to parse macro")
5953+ cx.editor.set_error(format!("{}", e));
5957+ let count = cx.count();
5959+ cx.callback = Some(Box::new(
5960+ move |compositor: &mut Compositor, cx: &mut compositor::Context| {
5961+ for _ in 0..count {
5962+ for &key in keys.iter() {
5963+ compositor.handle_event(crossterm::event::Event::Key(key.into()), cx);