1 use helix_core::{
2     comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes, indent,
3     indent::IndentStyle,
4     line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending},
5     match_brackets,
6     movement::{self, Direction},
7     object, pos_at_coords,
8     regex::{self, Regex, RegexBuilder},
9     register::Register,
10     search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
11     RopeSlice, Selection, SmallVec, Tendril, Transaction,
12 };
13 
14 use helix_view::{
15     clipboard::ClipboardType,
16     document::Mode,
17     editor::{Action, Motion},
18     input::KeyEvent,
19     keyboard::KeyCode,
20     view::View,
21     Document, DocumentId, Editor, ViewId,
22 };
23 
24 use anyhow::{anyhow, bail, Context as _};
25 use helix_lsp::{
26     lsp,
27     util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
28     OffsetEncoding,
29 };
30 use insert::*;
31 use movement::Movement;
32 
33 use crate::{
34     compositor::{self, Component, Compositor},
35     ui::{self, FilePicker, Picker, Popup, Prompt, PromptEvent},
36 };
37 
38 use crate::job::{self, Job, Jobs};
39 use futures_util::{FutureExt, StreamExt};
40 use std::num::NonZeroUsize;
41 use std::{fmt, future::Future};
42 
43 use std::{
44     borrow::Cow,
45     path::{Path, PathBuf},
46 };
47 
48 use once_cell::sync::Lazy;
49 use serde::de::{self, Deserialize, Deserializer};
50 
51 use grep_regex::RegexMatcherBuilder;
52 use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
53 use ignore::{DirEntry, WalkBuilder, WalkState};
54 use tokio_stream::wrappers::UnboundedReceiverStream;
55 
56 pub struct Context<'a> {
57     pub register: Option<char>,
58     pub count: Option<NonZeroUsize>,
59     pub editor: &'a mut Editor,
60 
61     pub callback: Option<crate::compositor::Callback>,
62     pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
63     pub jobs: &'a mut Jobs,
64 }
65 
66 impl<'a> Context<'a> {
67     /// Push a new component onto the compositor.
push_layer(&mut self, component: Box<dyn Component>)68     pub fn push_layer(&mut self, component: Box<dyn Component>) {
69         self.callback = Some(Box::new(|compositor: &mut Compositor| {
70             compositor.push(component)
71         }));
72     }
73 
74     #[inline]
on_next_key( &mut self, on_next_key_callback: impl FnOnce(&mut Context, KeyEvent) + 'static, )75     pub fn on_next_key(
76         &mut self,
77         on_next_key_callback: impl FnOnce(&mut Context, KeyEvent) + 'static,
78     ) {
79         self.on_next_key_callback = Some(Box::new(on_next_key_callback));
80     }
81 
82     #[inline]
callback<T, F>( &mut self, call: impl Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send, callback: F, ) where T: for<'de> serde::Deserialize<'de> + Send + 'static, F: FnOnce(&mut Editor, &mut Compositor, T) + Send + 'static,83     pub fn callback<T, F>(
84         &mut self,
85         call: impl Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send,
86         callback: F,
87     ) where
88         T: for<'de> serde::Deserialize<'de> + Send + 'static,
89         F: FnOnce(&mut Editor, &mut Compositor, T) + Send + 'static,
90     {
91         let callback = Box::pin(async move {
92             let json = call.await?;
93             let response = serde_json::from_value(json)?;
94             let call: job::Callback =
95                 Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
96                     callback(editor, compositor, response)
97                 });
98             Ok(call)
99         });
100         self.jobs.callback(callback);
101     }
102 
103     /// Returns 1 if no explicit count was provided
104     #[inline]
count(&self) -> usize105     pub fn count(&self) -> usize {
106         self.count.map_or(1, |v| v.get())
107     }
108 }
109 
110 enum Align {
111     Top,
112     Center,
113     Bottom,
114 }
115 
align_view(doc: &Document, view: &mut View, align: Align)116 fn align_view(doc: &Document, view: &mut View, align: Align) {
117     let pos = doc
118         .selection(view.id)
119         .primary()
120         .cursor(doc.text().slice(..));
121     let line = doc.text().char_to_line(pos);
122 
123     let height = view.inner_area().height as usize;
124 
125     let relative = match align {
126         Align::Center => height / 2,
127         Align::Top => 0,
128         Align::Bottom => height,
129     };
130 
131     view.offset.row = line.saturating_sub(relative);
132 }
133 
134 /// A command is composed of a static name, and a function that takes the current state plus a count,
135 /// and does a side-effect on the state (usually by creating and applying a transaction).
136 #[derive(Copy, Clone)]
137 pub struct Command {
138     name: &'static str,
139     fun: fn(cx: &mut Context),
140     doc: &'static str,
141 }
142 
143 macro_rules! commands {
144     ( $($name:ident, $doc:literal,)* ) => {
145         $(
146             #[allow(non_upper_case_globals)]
147             pub const $name: Self = Self {
148                 name: stringify!($name),
149                 fun: $name,
150                 doc: $doc
151             };
152         )*
153 
154         pub const COMMAND_LIST: &'static [Self] = &[
155             $( Self::$name, )*
156         ];
157     }
158 }
159 
160 impl Command {
execute(&self, cx: &mut Context)161     pub fn execute(&self, cx: &mut Context) {
162         (self.fun)(cx);
163     }
164 
name(&self) -> &'static str165     pub fn name(&self) -> &'static str {
166         self.name
167     }
168 
doc(&self) -> &'static str169     pub fn doc(&self) -> &'static str {
170         self.doc
171     }
172 
173     #[rustfmt::skip]
174     commands!(
175         no_op, "Do nothing",
176         move_char_left, "Move left",
177         move_char_right, "Move right",
178         move_line_up, "Move up",
179         move_line_down, "Move down",
180         extend_char_left, "Extend left",
181         extend_char_right, "Extend right",
182         extend_line_up, "Extend up",
183         extend_line_down, "Extend down",
184         copy_selection_on_next_line, "Copy selection on next line",
185         copy_selection_on_prev_line, "Copy selection on previous line",
186         move_next_word_start, "Move to beginning of next word",
187         move_prev_word_start, "Move to beginning of previous word",
188         move_next_word_end, "Move to end of next word",
189         move_next_long_word_start, "Move to beginning of next long word",
190         move_prev_long_word_start, "Move to beginning of previous long word",
191         move_next_long_word_end, "Move to end of next long word",
192         extend_next_word_start, "Extend to beginning of next word",
193         extend_prev_word_start, "Extend to beginning of previous word",
194         extend_next_long_word_start, "Extend to beginning of next long word",
195         extend_prev_long_word_start, "Extend to beginning of previous long word",
196         extend_next_long_word_end, "Extend to end of next long word",
197         extend_next_word_end, "Extend to end of next word",
198         find_till_char, "Move till next occurance of char",
199         find_next_char, "Move to next occurance of char",
200         extend_till_char, "Extend till next occurance of char",
201         extend_next_char, "Extend to next occurance of char",
202         till_prev_char, "Move till previous occurance of char",
203         find_prev_char, "Move to previous occurance of char",
204         extend_till_prev_char, "Extend till previous occurance of char",
205         extend_prev_char, "Extend to previous occurance of char",
206         repeat_last_motion, "repeat last motion(extend_next_char, extend_till_char, find_next_char, find_till_char...)",
207         replace, "Replace with new char",
208         switch_case, "Switch (toggle) case",
209         switch_to_uppercase, "Switch to uppercase",
210         switch_to_lowercase, "Switch to lowercase",
211         page_up, "Move page up",
212         page_down, "Move page down",
213         half_page_up, "Move half page up",
214         half_page_down, "Move half page down",
215         select_all, "Select whole document",
216         select_regex, "Select all regex matches inside selections",
217         split_selection, "Split selection into subselections on regex matches",
218         split_selection_on_newline, "Split selection on newlines",
219         search, "Search for regex pattern",
220         search_next, "Select next search match",
221         extend_search_next, "Add next search match to selection",
222         search_selection, "Use current selection as search pattern",
223         global_search, "Global Search in workspace folder",
224         extend_line, "Select current line, if already selected, extend to next line",
225         extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)",
226         delete_selection, "Delete selection",
227         change_selection, "Change selection (delete and enter insert mode)",
228         collapse_selection, "Collapse selection onto a single cursor",
229         flip_selections, "Flip selection cursor and anchor",
230         insert_mode, "Insert before selection",
231         append_mode, "Insert after selection (append)",
232         command_mode, "Enter command mode",
233         file_picker, "Open file picker",
234         code_action, "Perform code action",
235         buffer_picker, "Open buffer picker",
236         symbol_picker, "Open symbol picker",
237         last_picker, "Open last picker",
238         prepend_to_line, "Insert at start of line",
239         append_to_line, "Insert at end of line",
240         open_below, "Open new line below selection",
241         open_above, "Open new line above selection",
242         normal_mode, "Enter normal mode",
243         select_mode, "Enter selection extend mode",
244         exit_select_mode, "Exit selection mode",
245         goto_definition, "Goto definition",
246         add_newline_above, "Add newline above",
247         add_newline_below, "Add newline below",
248         goto_type_definition, "Goto type definition",
249         goto_implementation, "Goto implementation",
250         goto_file_start, "Goto file start/line",
251         goto_file_end, "Goto file end",
252         goto_reference, "Goto references",
253         goto_window_top, "Goto window top",
254         goto_window_middle, "Goto window middle",
255         goto_window_bottom, "Goto window bottom",
256         goto_last_accessed_file, "Goto last accessed file",
257         goto_line, "Goto line",
258         goto_last_line, "Goto last line",
259         goto_first_diag, "Goto first diagnostic",
260         goto_last_diag, "Goto last diagnostic",
261         goto_next_diag, "Goto next diagnostic",
262         goto_prev_diag, "Goto previous diagnostic",
263         goto_line_start, "Goto line start",
264         goto_line_end, "Goto line end",
265         // TODO: different description ?
266         goto_line_end_newline, "Goto line end",
267         goto_first_nonwhitespace, "Goto first non-blank in line",
268         extend_to_line_start, "Extend to line start",
269         extend_to_line_end, "Extend to line end",
270         extend_to_line_end_newline, "Extend to line end",
271         signature_help, "Show signature help",
272         insert_tab, "Insert tab char",
273         insert_newline, "Insert newline char",
274         delete_char_backward, "Delete previous char",
275         delete_char_forward, "Delete next char",
276         delete_word_backward, "Delete previous word",
277         undo, "Undo change",
278         redo, "Redo change",
279         yank, "Yank selection",
280         yank_joined_to_clipboard, "Join and yank selections to clipboard",
281         yank_main_selection_to_clipboard, "Yank main selection to clipboard",
282         yank_joined_to_primary_clipboard, "Join and yank selections to primary clipboard",
283         yank_main_selection_to_primary_clipboard, "Yank main selection to primary clipboard",
284         replace_with_yanked, "Replace with yanked text",
285         replace_selections_with_clipboard, "Replace selections by clipboard content",
286         replace_selections_with_primary_clipboard, "Replace selections by primary clipboard content",
287         paste_after, "Paste after selection",
288         paste_before, "Paste before selection",
289         paste_clipboard_after, "Paste clipboard after selections",
290         paste_clipboard_before, "Paste clipboard before selections",
291         paste_primary_clipboard_after, "Paste primary clipboard after selections",
292         paste_primary_clipboard_before, "Paste primary clipboard before selections",
293         indent, "Indent selection",
294         unindent, "Unindent selection",
295         format_selections, "Format selection",
296         join_selections, "Join lines inside selection",
297         keep_selections, "Keep selections matching regex",
298         keep_primary_selection, "Keep primary selection",
299         remove_primary_selection, "Remove primary selection",
300         completion, "Invoke completion popup",
301         hover, "Show docs for item under cursor",
302         toggle_comments, "Comment/uncomment selections",
303         rotate_selections_forward, "Rotate selections forward",
304         rotate_selections_backward, "Rotate selections backward",
305         rotate_selection_contents_forward, "Rotate selection contents forward",
306         rotate_selection_contents_backward, "Rotate selections contents backward",
307         expand_selection, "Expand selection to parent syntax node",
308         jump_forward, "Jump forward on jumplist",
309         jump_backward, "Jump backward on jumplist",
310         jump_view_right, "Jump to the split to the right",
311         jump_view_left, "Jump to the split to the left",
312         jump_view_up, "Jump to the split above",
313         jump_view_down, "Jump to the split below",
314         rotate_view, "Goto next window",
315         hsplit, "Horizontal bottom split",
316         vsplit, "Vertical right split",
317         wclose, "Close window",
318         select_register, "Select register",
319         align_view_middle, "Align view middle",
320         align_view_top, "Align view top",
321         align_view_center, "Align view center",
322         align_view_bottom, "Align view bottom",
323         scroll_up, "Scroll view up",
324         scroll_down, "Scroll view down",
325         match_brackets, "Goto matching bracket",
326         surround_add, "Surround add",
327         surround_replace, "Surround replace",
328         surround_delete, "Surround delete",
329         select_textobject_around, "Select around object",
330         select_textobject_inner, "Select inside object",
331         shell_pipe, "Pipe selections through shell command",
332         shell_pipe_to, "Pipe selections into shell command, ignoring command output",
333         shell_insert_output, "Insert output of shell command before each selection",
334         shell_append_output, "Append output of shell command after each selection",
335         shell_keep_pipe, "Filter selections with shell predicate",
336         suspend, "Suspend",
337     );
338 }
339 
340 impl fmt::Debug for Command {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result341     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342         let Command { name, .. } = self;
343         f.debug_tuple("Command").field(name).finish()
344     }
345 }
346 
347 impl fmt::Display for Command {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result348     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
349         let Command { name, .. } = self;
350         f.write_str(name)
351     }
352 }
353 
354 impl std::str::FromStr for Command {
355     type Err = anyhow::Error;
356 
from_str(s: &str) -> Result<Self, Self::Err>357     fn from_str(s: &str) -> Result<Self, Self::Err> {
358         Command::COMMAND_LIST
359             .iter()
360             .copied()
361             .find(|cmd| cmd.name == s)
362             .ok_or_else(|| anyhow!("No command named '{}'", s))
363     }
364 }
365 
366 impl<'de> Deserialize<'de> for Command {
deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>,367     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
368     where
369         D: Deserializer<'de>,
370     {
371         let s = String::deserialize(deserializer)?;
372         s.parse().map_err(de::Error::custom)
373     }
374 }
375 
376 impl PartialEq for Command {
eq(&self, other: &Self) -> bool377     fn eq(&self, other: &Self) -> bool {
378         self.name() == other.name()
379     }
380 }
381 
no_op(_cx: &mut Context)382 fn no_op(_cx: &mut Context) {}
383 
move_impl<F>(cx: &mut Context, move_fn: F, dir: Direction, behaviour: Movement) where F: Fn(RopeSlice, Range, Direction, usize, Movement) -> Range,384 fn move_impl<F>(cx: &mut Context, move_fn: F, dir: Direction, behaviour: Movement)
385 where
386     F: Fn(RopeSlice, Range, Direction, usize, Movement) -> Range,
387 {
388     let count = cx.count();
389     let (view, doc) = current!(cx.editor);
390     let text = doc.text().slice(..);
391 
392     let selection = doc
393         .selection(view.id)
394         .clone()
395         .transform(|range| move_fn(text, range, dir, count, behaviour));
396     doc.set_selection(view.id, selection);
397 }
398 
399 use helix_core::movement::{move_horizontally, move_vertically};
400 
move_char_left(cx: &mut Context)401 fn move_char_left(cx: &mut Context) {
402     move_impl(cx, move_horizontally, Direction::Backward, Movement::Move)
403 }
404 
move_char_right(cx: &mut Context)405 fn move_char_right(cx: &mut Context) {
406     move_impl(cx, move_horizontally, Direction::Forward, Movement::Move)
407 }
408 
move_line_up(cx: &mut Context)409 fn move_line_up(cx: &mut Context) {
410     move_impl(cx, move_vertically, Direction::Backward, Movement::Move)
411 }
412 
move_line_down(cx: &mut Context)413 fn move_line_down(cx: &mut Context) {
414     move_impl(cx, move_vertically, Direction::Forward, Movement::Move)
415 }
416 
extend_char_left(cx: &mut Context)417 fn extend_char_left(cx: &mut Context) {
418     move_impl(cx, move_horizontally, Direction::Backward, Movement::Extend)
419 }
420 
extend_char_right(cx: &mut Context)421 fn extend_char_right(cx: &mut Context) {
422     move_impl(cx, move_horizontally, Direction::Forward, Movement::Extend)
423 }
424 
extend_line_up(cx: &mut Context)425 fn extend_line_up(cx: &mut Context) {
426     move_impl(cx, move_vertically, Direction::Backward, Movement::Extend)
427 }
428 
extend_line_down(cx: &mut Context)429 fn extend_line_down(cx: &mut Context) {
430     move_impl(cx, move_vertically, Direction::Forward, Movement::Extend)
431 }
432 
goto_line_end_impl(view: &mut View, doc: &mut Document, movement: Movement)433 fn goto_line_end_impl(view: &mut View, doc: &mut Document, movement: Movement) {
434     let text = doc.text().slice(..);
435 
436     let selection = doc.selection(view.id).clone().transform(|range| {
437         let line = range.cursor_line(text);
438         let line_start = text.line_to_char(line);
439 
440         let pos = graphemes::prev_grapheme_boundary(text, line_end_char_index(&text, line))
441             .max(line_start);
442 
443         range.put_cursor(text, pos, movement == Movement::Extend)
444     });
445     doc.set_selection(view.id, selection);
446 }
447 
goto_line_end(cx: &mut Context)448 fn goto_line_end(cx: &mut Context) {
449     let (view, doc) = current!(cx.editor);
450     goto_line_end_impl(
451         view,
452         doc,
453         if doc.mode == Mode::Select {
454             Movement::Extend
455         } else {
456             Movement::Move
457         },
458     )
459 }
460 
extend_to_line_end(cx: &mut Context)461 fn extend_to_line_end(cx: &mut Context) {
462     let (view, doc) = current!(cx.editor);
463     goto_line_end_impl(view, doc, Movement::Extend)
464 }
465 
goto_line_end_newline_impl(view: &mut View, doc: &mut Document, movement: Movement)466 fn goto_line_end_newline_impl(view: &mut View, doc: &mut Document, movement: Movement) {
467     let text = doc.text().slice(..);
468 
469     let selection = doc.selection(view.id).clone().transform(|range| {
470         let line = range.cursor_line(text);
471         let pos = line_end_char_index(&text, line);
472 
473         range.put_cursor(text, pos, movement == Movement::Extend)
474     });
475     doc.set_selection(view.id, selection);
476 }
477 
goto_line_end_newline(cx: &mut Context)478 fn goto_line_end_newline(cx: &mut Context) {
479     let (view, doc) = current!(cx.editor);
480     goto_line_end_newline_impl(
481         view,
482         doc,
483         if doc.mode == Mode::Select {
484             Movement::Extend
485         } else {
486             Movement::Move
487         },
488     )
489 }
490 
extend_to_line_end_newline(cx: &mut Context)491 fn extend_to_line_end_newline(cx: &mut Context) {
492     let (view, doc) = current!(cx.editor);
493     goto_line_end_newline_impl(view, doc, Movement::Extend)
494 }
495 
goto_line_start_impl(view: &mut View, doc: &mut Document, movement: Movement)496 fn goto_line_start_impl(view: &mut View, doc: &mut Document, movement: Movement) {
497     let text = doc.text().slice(..);
498 
499     let selection = doc.selection(view.id).clone().transform(|range| {
500         let line = range.cursor_line(text);
501 
502         // adjust to start of the line
503         let pos = text.line_to_char(line);
504         range.put_cursor(text, pos, movement == Movement::Extend)
505     });
506     doc.set_selection(view.id, selection);
507 }
508 
goto_line_start(cx: &mut Context)509 fn goto_line_start(cx: &mut Context) {
510     let (view, doc) = current!(cx.editor);
511     goto_line_start_impl(
512         view,
513         doc,
514         if doc.mode == Mode::Select {
515             Movement::Extend
516         } else {
517             Movement::Move
518         },
519     )
520 }
521 
extend_to_line_start(cx: &mut Context)522 fn extend_to_line_start(cx: &mut Context) {
523     let (view, doc) = current!(cx.editor);
524     goto_line_start_impl(view, doc, Movement::Extend)
525 }
526 
goto_first_nonwhitespace(cx: &mut Context)527 fn goto_first_nonwhitespace(cx: &mut Context) {
528     let (view, doc) = current!(cx.editor);
529     let text = doc.text().slice(..);
530 
531     let selection = doc.selection(view.id).clone().transform(|range| {
532         let line = range.cursor_line(text);
533 
534         if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
535             let pos = pos + text.line_to_char(line);
536             range.put_cursor(text, pos, doc.mode == Mode::Select)
537         } else {
538             range
539         }
540     });
541     doc.set_selection(view.id, selection);
542 }
543 
goto_window(cx: &mut Context, align: Align)544 fn goto_window(cx: &mut Context, align: Align) {
545     let (view, doc) = current!(cx.editor);
546 
547     let height = view.inner_area().height as usize;
548 
549     // - 1 so we have at least one gap in the middle.
550     // a height of 6 with padding of 3 on each side will keep shifting the view back and forth
551     // as we type
552     let scrolloff = cx.editor.config.scrolloff.min(height.saturating_sub(1) / 2);
553 
554     let last_line = view.last_line(doc);
555 
556     let line = match align {
557         Align::Top => (view.offset.row + scrolloff),
558         Align::Center => (view.offset.row + (height / 2)),
559         Align::Bottom => last_line.saturating_sub(scrolloff),
560     }
561     .min(last_line.saturating_sub(scrolloff));
562 
563     let pos = doc.text().line_to_char(line);
564 
565     doc.set_selection(view.id, Selection::point(pos));
566 }
567 
goto_window_top(cx: &mut Context)568 fn goto_window_top(cx: &mut Context) {
569     goto_window(cx, Align::Top)
570 }
571 
goto_window_middle(cx: &mut Context)572 fn goto_window_middle(cx: &mut Context) {
573     goto_window(cx, Align::Center)
574 }
575 
goto_window_bottom(cx: &mut Context)576 fn goto_window_bottom(cx: &mut Context) {
577     goto_window(cx, Align::Bottom)
578 }
579 
move_word_impl<F>(cx: &mut Context, move_fn: F) where F: Fn(RopeSlice, Range, usize) -> Range,580 fn move_word_impl<F>(cx: &mut Context, move_fn: F)
581 where
582     F: Fn(RopeSlice, Range, usize) -> Range,
583 {
584     let count = cx.count();
585     let (view, doc) = current!(cx.editor);
586     let text = doc.text().slice(..);
587 
588     let selection = doc
589         .selection(view.id)
590         .clone()
591         .transform(|range| move_fn(text, range, count));
592     doc.set_selection(view.id, selection);
593 }
594 
move_next_word_start(cx: &mut Context)595 fn move_next_word_start(cx: &mut Context) {
596     move_word_impl(cx, movement::move_next_word_start)
597 }
598 
move_prev_word_start(cx: &mut Context)599 fn move_prev_word_start(cx: &mut Context) {
600     move_word_impl(cx, movement::move_prev_word_start)
601 }
602 
move_next_word_end(cx: &mut Context)603 fn move_next_word_end(cx: &mut Context) {
604     move_word_impl(cx, movement::move_next_word_end)
605 }
606 
move_next_long_word_start(cx: &mut Context)607 fn move_next_long_word_start(cx: &mut Context) {
608     move_word_impl(cx, movement::move_next_long_word_start)
609 }
610 
move_prev_long_word_start(cx: &mut Context)611 fn move_prev_long_word_start(cx: &mut Context) {
612     move_word_impl(cx, movement::move_prev_long_word_start)
613 }
614 
move_next_long_word_end(cx: &mut Context)615 fn move_next_long_word_end(cx: &mut Context) {
616     move_word_impl(cx, movement::move_next_long_word_end)
617 }
618 
goto_file_start(cx: &mut Context)619 fn goto_file_start(cx: &mut Context) {
620     if cx.count.is_some() {
621         goto_line(cx);
622     } else {
623         push_jump(cx.editor);
624         let (view, doc) = current!(cx.editor);
625         doc.set_selection(view.id, Selection::point(0));
626     }
627 }
628 
goto_file_end(cx: &mut Context)629 fn goto_file_end(cx: &mut Context) {
630     push_jump(cx.editor);
631     let (view, doc) = current!(cx.editor);
632     doc.set_selection(view.id, Selection::point(doc.text().len_chars()));
633 }
634 
extend_word_impl<F>(cx: &mut Context, extend_fn: F) where F: Fn(RopeSlice, Range, usize) -> Range,635 fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
636 where
637     F: Fn(RopeSlice, Range, usize) -> Range,
638 {
639     let count = cx.count();
640     let (view, doc) = current!(cx.editor);
641     let text = doc.text().slice(..);
642 
643     let selection = doc.selection(view.id).clone().transform(|range| {
644         let word = extend_fn(text, range, count);
645         let pos = word.cursor(text);
646         range.put_cursor(text, pos, true)
647     });
648     doc.set_selection(view.id, selection);
649 }
650 
extend_next_word_start(cx: &mut Context)651 fn extend_next_word_start(cx: &mut Context) {
652     extend_word_impl(cx, movement::move_next_word_start)
653 }
654 
extend_prev_word_start(cx: &mut Context)655 fn extend_prev_word_start(cx: &mut Context) {
656     extend_word_impl(cx, movement::move_prev_word_start)
657 }
658 
extend_next_word_end(cx: &mut Context)659 fn extend_next_word_end(cx: &mut Context) {
660     extend_word_impl(cx, movement::move_next_word_end)
661 }
662 
extend_next_long_word_start(cx: &mut Context)663 fn extend_next_long_word_start(cx: &mut Context) {
664     extend_word_impl(cx, movement::move_next_long_word_start)
665 }
666 
extend_prev_long_word_start(cx: &mut Context)667 fn extend_prev_long_word_start(cx: &mut Context) {
668     extend_word_impl(cx, movement::move_prev_long_word_start)
669 }
670 
extend_next_long_word_end(cx: &mut Context)671 fn extend_next_long_word_end(cx: &mut Context) {
672     extend_word_impl(cx, movement::move_next_long_word_end)
673 }
674 
will_find_char<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool) where F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,675 fn will_find_char<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool)
676 where
677     F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,
678 {
679     // TODO: count is reset to 1 before next key so we move it into the closure here.
680     // Would be nice to carry over.
681     let count = cx.count();
682 
683     // need to wait for next key
684     // TODO: should this be done by grapheme rather than char?  For example,
685     // we can't properly handle the line-ending CRLF case here in terms of char.
686     cx.on_next_key(move |cx, event| {
687         let ch = match event {
688             KeyEvent {
689                 code: KeyCode::Enter,
690                 ..
691             } =>
692             // TODO: this isn't quite correct when CRLF is involved.
693             // This hack will work in most cases, since documents don't
694             // usually mix line endings.  But we should fix it eventually
695             // anyway.
696             {
697                 current!(cx.editor)
698                     .1
699                     .line_ending
700                     .as_str()
701                     .chars()
702                     .next()
703                     .unwrap()
704             }
705 
706             KeyEvent {
707                 code: KeyCode::Char(ch),
708                 ..
709             } => ch,
710             _ => return,
711         };
712 
713         find_char_impl(cx.editor, &search_fn, inclusive, extend, ch, count);
714         cx.editor.last_motion = Some(Motion(Box::new(move |editor: &mut Editor| {
715             find_char_impl(editor, &search_fn, inclusive, true, ch, 1);
716         })));
717     })
718 }
719 
720 //
721 
722 #[inline]
find_char_impl<F>( editor: &mut Editor, search_fn: &F, inclusive: bool, extend: bool, ch: char, count: usize, ) where F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,723 fn find_char_impl<F>(
724     editor: &mut Editor,
725     search_fn: &F,
726     inclusive: bool,
727     extend: bool,
728     ch: char,
729     count: usize,
730 ) where
731     F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,
732 {
733     let (view, doc) = current!(editor);
734     let text = doc.text().slice(..);
735 
736     let selection = doc.selection(view.id).clone().transform(|range| {
737         // TODO: use `Range::cursor()` here instead.  However, that works in terms of
738         // graphemes, whereas this function doesn't yet.  So we're doing the same logic
739         // here, but just in terms of chars instead.
740         let search_start_pos = if range.anchor < range.head {
741             range.head - 1
742         } else {
743             range.head
744         };
745 
746         search_fn(text, ch, search_start_pos, count, inclusive).map_or(range, |pos| {
747             if extend {
748                 range.put_cursor(text, pos, true)
749             } else {
750                 Range::point(range.cursor(text)).put_cursor(text, pos, true)
751             }
752         })
753     });
754     doc.set_selection(view.id, selection);
755 }
756 
find_next_char_impl( text: RopeSlice, ch: char, pos: usize, n: usize, inclusive: bool, ) -> Option<usize>757 fn find_next_char_impl(
758     text: RopeSlice,
759     ch: char,
760     pos: usize,
761     n: usize,
762     inclusive: bool,
763 ) -> Option<usize> {
764     let pos = (pos + 1).min(text.len_chars());
765     if inclusive {
766         search::find_nth_next(text, ch, pos, n)
767     } else {
768         let n = match text.get_char(pos) {
769             Some(next_ch) if next_ch == ch => n + 1,
770             _ => n,
771         };
772         search::find_nth_next(text, ch, pos, n).map(|n| n.saturating_sub(1))
773     }
774 }
775 
find_prev_char_impl( text: RopeSlice, ch: char, pos: usize, n: usize, inclusive: bool, ) -> Option<usize>776 fn find_prev_char_impl(
777     text: RopeSlice,
778     ch: char,
779     pos: usize,
780     n: usize,
781     inclusive: bool,
782 ) -> Option<usize> {
783     if inclusive {
784         search::find_nth_prev(text, ch, pos, n)
785     } else {
786         let n = match text.get_char(pos.saturating_sub(1)) {
787             Some(next_ch) if next_ch == ch => n + 1,
788             _ => n,
789         };
790         search::find_nth_prev(text, ch, pos, n).map(|n| (n + 1).min(text.len_chars()))
791     }
792 }
793 
find_till_char(cx: &mut Context)794 fn find_till_char(cx: &mut Context) {
795     will_find_char(cx, find_next_char_impl, false, false)
796 }
797 
find_next_char(cx: &mut Context)798 fn find_next_char(cx: &mut Context) {
799     will_find_char(cx, find_next_char_impl, true, false)
800 }
801 
extend_till_char(cx: &mut Context)802 fn extend_till_char(cx: &mut Context) {
803     will_find_char(cx, find_next_char_impl, false, true)
804 }
805 
extend_next_char(cx: &mut Context)806 fn extend_next_char(cx: &mut Context) {
807     will_find_char(cx, find_next_char_impl, true, true)
808 }
809 
till_prev_char(cx: &mut Context)810 fn till_prev_char(cx: &mut Context) {
811     will_find_char(cx, find_prev_char_impl, false, false)
812 }
813 
find_prev_char(cx: &mut Context)814 fn find_prev_char(cx: &mut Context) {
815     will_find_char(cx, find_prev_char_impl, true, false)
816 }
817 
extend_till_prev_char(cx: &mut Context)818 fn extend_till_prev_char(cx: &mut Context) {
819     will_find_char(cx, find_prev_char_impl, false, true)
820 }
821 
extend_prev_char(cx: &mut Context)822 fn extend_prev_char(cx: &mut Context) {
823     will_find_char(cx, find_prev_char_impl, true, true)
824 }
825 
repeat_last_motion(cx: &mut Context)826 fn repeat_last_motion(cx: &mut Context) {
827     let last_motion = cx.editor.last_motion.take();
828     if let Some(m) = &last_motion {
829         m.run(cx.editor);
830         cx.editor.last_motion = last_motion;
831     }
832 }
833 
replace(cx: &mut Context)834 fn replace(cx: &mut Context) {
835     let mut buf = [0u8; 4]; // To hold utf8 encoded char.
836 
837     // need to wait for next key
838     cx.on_next_key(move |cx, event| {
839         let (view, doc) = current!(cx.editor);
840         let ch = match event {
841             KeyEvent {
842                 code: KeyCode::Char(ch),
843                 ..
844             } => Some(&ch.encode_utf8(&mut buf[..])[..]),
845             KeyEvent {
846                 code: KeyCode::Enter,
847                 ..
848             } => Some(doc.line_ending.as_str()),
849             _ => None,
850         };
851 
852         let selection = doc.selection(view.id);
853 
854         if let Some(ch) = ch {
855             let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
856                 if !range.is_empty() {
857                     let text: String =
858                         RopeGraphemes::new(doc.text().slice(range.from()..range.to()))
859                             .map(|g| {
860                                 let cow: Cow<str> = g.into();
861                                 if str_is_line_ending(&cow) {
862                                     cow
863                                 } else {
864                                     ch.into()
865                                 }
866                             })
867                             .collect();
868 
869                     (range.from(), range.to(), Some(text.into()))
870                 } else {
871                     // No change.
872                     (range.from(), range.to(), None)
873                 }
874             });
875 
876             doc.apply(&transaction, view.id);
877             doc.append_changes_to_history(view.id);
878         }
879     })
880 }
881 
switch_case_impl<F>(cx: &mut Context, change_fn: F) where F: Fn(Cow<str>) -> Tendril,882 fn switch_case_impl<F>(cx: &mut Context, change_fn: F)
883 where
884     F: Fn(Cow<str>) -> Tendril,
885 {
886     let (view, doc) = current!(cx.editor);
887     let selection = doc.selection(view.id);
888     let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
889         let text: Tendril = change_fn(range.fragment(doc.text().slice(..)));
890 
891         (range.from(), range.to(), Some(text))
892     });
893 
894     doc.apply(&transaction, view.id);
895     doc.append_changes_to_history(view.id);
896 }
897 
switch_case(cx: &mut Context)898 fn switch_case(cx: &mut Context) {
899     switch_case_impl(cx, |string| {
900         string
901             .chars()
902             .flat_map(|ch| {
903                 if ch.is_lowercase() {
904                     ch.to_uppercase().collect()
905                 } else if ch.is_uppercase() {
906                     ch.to_lowercase().collect()
907                 } else {
908                     vec![ch]
909                 }
910             })
911             .collect()
912     });
913 }
914 
switch_to_uppercase(cx: &mut Context)915 fn switch_to_uppercase(cx: &mut Context) {
916     switch_case_impl(cx, |string| string.to_uppercase().into());
917 }
918 
switch_to_lowercase(cx: &mut Context)919 fn switch_to_lowercase(cx: &mut Context) {
920     switch_case_impl(cx, |string| string.to_lowercase().into());
921 }
922 
scroll(cx: &mut Context, offset: usize, direction: Direction)923 pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
924     use Direction::*;
925     let (view, doc) = current!(cx.editor);
926 
927     let range = doc.selection(view.id).primary();
928     let text = doc.text().slice(..);
929 
930     let cursor = coords_at_pos(text, range.cursor(text));
931     let doc_last_line = doc.text().len_lines().saturating_sub(1);
932 
933     let last_line = view.last_line(doc);
934 
935     if direction == Backward && view.offset.row == 0
936         || direction == Forward && last_line == doc_last_line
937     {
938         return;
939     }
940 
941     let height = view.inner_area().height;
942 
943     let scrolloff = cx.editor.config.scrolloff.min(height as usize / 2);
944 
945     view.offset.row = match direction {
946         Forward => view.offset.row + offset,
947         Backward => view.offset.row.saturating_sub(offset),
948     }
949     .min(doc_last_line);
950 
951     // recalculate last line
952     let last_line = view.last_line(doc);
953 
954     // clamp into viewport
955     let line = cursor
956         .row
957         .max(view.offset.row + scrolloff)
958         .min(last_line.saturating_sub(scrolloff));
959 
960     let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
961 
962     let anchor = if doc.mode == Mode::Select {
963         range.anchor
964     } else {
965         head
966     };
967 
968     // TODO: only manipulate main selection
969     doc.set_selection(view.id, Selection::single(anchor, head));
970 }
971 
page_up(cx: &mut Context)972 fn page_up(cx: &mut Context) {
973     let view = view!(cx.editor);
974     let offset = view.inner_area().height as usize;
975     scroll(cx, offset, Direction::Backward);
976 }
977 
page_down(cx: &mut Context)978 fn page_down(cx: &mut Context) {
979     let view = view!(cx.editor);
980     let offset = view.inner_area().height as usize;
981     scroll(cx, offset, Direction::Forward);
982 }
983 
half_page_up(cx: &mut Context)984 fn half_page_up(cx: &mut Context) {
985     let view = view!(cx.editor);
986     let offset = view.inner_area().height as usize / 2;
987     scroll(cx, offset, Direction::Backward);
988 }
989 
half_page_down(cx: &mut Context)990 fn half_page_down(cx: &mut Context) {
991     let view = view!(cx.editor);
992     let offset = view.inner_area().height as usize / 2;
993     scroll(cx, offset, Direction::Forward);
994 }
995 
copy_selection_on_line(cx: &mut Context, direction: Direction)996 fn copy_selection_on_line(cx: &mut Context, direction: Direction) {
997     let count = cx.count();
998     let (view, doc) = current!(cx.editor);
999     let text = doc.text().slice(..);
1000     let selection = doc.selection(view.id);
1001     let mut ranges = SmallVec::with_capacity(selection.ranges().len() * (count + 1));
1002     ranges.extend_from_slice(selection.ranges());
1003     let mut primary_index = 0;
1004     for range in selection.iter() {
1005         let is_primary = *range == selection.primary();
1006         let head_pos = coords_at_pos(text, range.head);
1007         let anchor_pos = coords_at_pos(text, range.anchor);
1008         let height = std::cmp::max(head_pos.row, anchor_pos.row)
1009             - std::cmp::min(head_pos.row, anchor_pos.row)
1010             + 1;
1011 
1012         if is_primary {
1013             primary_index = ranges.len();
1014         }
1015         ranges.push(*range);
1016 
1017         let mut sels = 0;
1018         let mut i = 0;
1019         while sels < count {
1020             let offset = (i + 1) * height;
1021 
1022             let anchor_row = match direction {
1023                 Direction::Forward => anchor_pos.row + offset,
1024                 Direction::Backward => anchor_pos.row.saturating_sub(offset),
1025             };
1026 
1027             let head_row = match direction {
1028                 Direction::Forward => head_pos.row + offset,
1029                 Direction::Backward => head_pos.row.saturating_sub(offset),
1030             };
1031 
1032             if anchor_row >= text.len_lines() || head_row >= text.len_lines() {
1033                 break;
1034             }
1035 
1036             let anchor = pos_at_coords(text, Position::new(anchor_row, anchor_pos.col), true);
1037             let head = pos_at_coords(text, Position::new(head_row, head_pos.col), true);
1038 
1039             // skip lines that are too short
1040             if coords_at_pos(text, anchor).col == anchor_pos.col
1041                 && coords_at_pos(text, head).col == head_pos.col
1042             {
1043                 if is_primary {
1044                     primary_index = ranges.len();
1045                 }
1046                 ranges.push(Range::new(anchor, head));
1047                 sels += 1;
1048             }
1049 
1050             i += 1;
1051         }
1052     }
1053 
1054     let selection = Selection::new(ranges, primary_index);
1055     doc.set_selection(view.id, selection);
1056 }
1057 
copy_selection_on_prev_line(cx: &mut Context)1058 fn copy_selection_on_prev_line(cx: &mut Context) {
1059     copy_selection_on_line(cx, Direction::Backward)
1060 }
1061 
copy_selection_on_next_line(cx: &mut Context)1062 fn copy_selection_on_next_line(cx: &mut Context) {
1063     copy_selection_on_line(cx, Direction::Forward)
1064 }
1065 
select_all(cx: &mut Context)1066 fn select_all(cx: &mut Context) {
1067     let (view, doc) = current!(cx.editor);
1068 
1069     let end = doc.text().len_chars();
1070     doc.set_selection(view.id, Selection::single(0, end))
1071 }
1072 
select_regex(cx: &mut Context)1073 fn select_regex(cx: &mut Context) {
1074     let reg = cx.register.unwrap_or('/');
1075     let prompt = ui::regex_prompt(
1076         cx,
1077         "select:".into(),
1078         Some(reg),
1079         move |view, doc, regex, event| {
1080             if event != PromptEvent::Update {
1081                 return;
1082             }
1083             let text = doc.text().slice(..);
1084             if let Some(selection) =
1085                 selection::select_on_matches(text, doc.selection(view.id), &regex)
1086             {
1087                 doc.set_selection(view.id, selection);
1088             }
1089         },
1090     );
1091 
1092     cx.push_layer(Box::new(prompt));
1093 }
1094 
split_selection(cx: &mut Context)1095 fn split_selection(cx: &mut Context) {
1096     let reg = cx.register.unwrap_or('/');
1097     let prompt = ui::regex_prompt(
1098         cx,
1099         "split:".into(),
1100         Some(reg),
1101         move |view, doc, regex, event| {
1102             if event != PromptEvent::Update {
1103                 return;
1104             }
1105             let text = doc.text().slice(..);
1106             let selection = selection::split_on_matches(text, doc.selection(view.id), &regex);
1107             doc.set_selection(view.id, selection);
1108         },
1109     );
1110 
1111     cx.push_layer(Box::new(prompt));
1112 }
1113 
split_selection_on_newline(cx: &mut Context)1114 fn split_selection_on_newline(cx: &mut Context) {
1115     let (view, doc) = current!(cx.editor);
1116     let text = doc.text().slice(..);
1117     // only compile the regex once
1118     #[allow(clippy::trivial_regex)]
1119     static REGEX: Lazy<Regex> =
1120         Lazy::new(|| Regex::new(r"\r\n|[\n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}]").unwrap());
1121     let selection = selection::split_on_matches(text, doc.selection(view.id), &REGEX);
1122     doc.set_selection(view.id, selection);
1123 }
1124 
search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, extend: bool)1125 fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, extend: bool) {
1126     let text = doc.text().slice(..);
1127     let selection = doc.selection(view.id);
1128 
1129     // Get the right side of the primary block cursor.
1130     let start = text.char_to_byte(graphemes::next_grapheme_boundary(
1131         text,
1132         selection.primary().cursor(text),
1133     ));
1134 
1135     // use find_at to find the next match after the cursor, loop around the end
1136     // Careful, `Regex` uses `bytes` as offsets, not character indices!
1137     let mat = regex
1138         .find_at(contents, start)
1139         .or_else(|| regex.find(contents));
1140     // TODO: message on wraparound
1141     if let Some(mat) = mat {
1142         let start = text.byte_to_char(mat.start());
1143         let end = text.byte_to_char(mat.end());
1144 
1145         if end == 0 {
1146             // skip empty matches that don't make sense
1147             return;
1148         }
1149 
1150         let selection = if extend {
1151             selection.clone().push(Range::new(start, end))
1152         } else {
1153             Selection::single(start, end)
1154         };
1155 
1156         doc.set_selection(view.id, selection);
1157         align_view(doc, view, Align::Center);
1158     };
1159 }
1160 
1161 // TODO: use one function for search vs extend
search(cx: &mut Context)1162 fn search(cx: &mut Context) {
1163     let reg = cx.register.unwrap_or('/');
1164     let (_, doc) = current!(cx.editor);
1165 
1166     // TODO: could probably share with select_on_matches?
1167 
1168     // HAXX: sadly we can't avoid allocating a single string for the whole buffer since we can't
1169     // feed chunks into the regex yet
1170     let contents = doc.text().slice(..).to_string();
1171 
1172     let prompt = ui::regex_prompt(
1173         cx,
1174         "search:".into(),
1175         Some(reg),
1176         move |view, doc, regex, event| {
1177             if event != PromptEvent::Update {
1178                 return;
1179             }
1180             search_impl(doc, view, &contents, &regex, false);
1181         },
1182     );
1183 
1184     cx.push_layer(Box::new(prompt));
1185 }
1186 
search_next_impl(cx: &mut Context, extend: bool)1187 fn search_next_impl(cx: &mut Context, extend: bool) {
1188     let (view, doc) = current!(cx.editor);
1189     let registers = &cx.editor.registers;
1190     if let Some(query) = registers.read('/') {
1191         let query = query.last().unwrap();
1192         let contents = doc.text().slice(..).to_string();
1193         let case_insensitive = if cx.editor.config.smart_case {
1194             !query.chars().any(char::is_uppercase)
1195         } else {
1196             false
1197         };
1198         if let Ok(regex) = RegexBuilder::new(query)
1199             .case_insensitive(case_insensitive)
1200             .build()
1201         {
1202             search_impl(doc, view, &contents, &regex, extend);
1203         } else {
1204             // get around warning `mutable_borrow_reservation_conflict`
1205             // which will be a hard error in the future
1206             // see: https://github.com/rust-lang/rust/issues/59159
1207             let query = query.clone();
1208             cx.editor.set_error(format!("Invalid regex: {}", query));
1209         }
1210     }
1211 }
1212 
search_next(cx: &mut Context)1213 fn search_next(cx: &mut Context) {
1214     search_next_impl(cx, false);
1215 }
1216 
extend_search_next(cx: &mut Context)1217 fn extend_search_next(cx: &mut Context) {
1218     search_next_impl(cx, true);
1219 }
1220 
search_selection(cx: &mut Context)1221 fn search_selection(cx: &mut Context) {
1222     let (view, doc) = current!(cx.editor);
1223     let contents = doc.text().slice(..);
1224     let query = doc.selection(view.id).primary().fragment(contents);
1225     let regex = regex::escape(&query);
1226     cx.editor.registers.get_mut('/').push(regex);
1227     let msg = format!("register '{}' set to '{}'", '\\', query);
1228     cx.editor.set_status(msg);
1229 }
1230 
global_search(cx: &mut Context)1231 fn global_search(cx: &mut Context) {
1232     let (all_matches_sx, all_matches_rx) =
1233         tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>();
1234     let smart_case = cx.editor.config.smart_case;
1235     let prompt = ui::regex_prompt(
1236         cx,
1237         "global search:".into(),
1238         None,
1239         move |_view, _doc, regex, event| {
1240             if event != PromptEvent::Validate {
1241                 return;
1242             }
1243 
1244             if let Ok(matcher) = RegexMatcherBuilder::new()
1245                 .case_smart(smart_case)
1246                 .build(regex.as_str())
1247             {
1248                 let searcher = SearcherBuilder::new()
1249                     .binary_detection(BinaryDetection::quit(b'\x00'))
1250                     .build();
1251 
1252                 let search_root = std::env::current_dir()
1253                     .expect("Global search error: Failed to get current dir");
1254                 WalkBuilder::new(search_root).build_parallel().run(|| {
1255                     let mut searcher_cl = searcher.clone();
1256                     let matcher_cl = matcher.clone();
1257                     let all_matches_sx_cl = all_matches_sx.clone();
1258                     Box::new(move |dent: Result<DirEntry, ignore::Error>| -> WalkState {
1259                         let dent = match dent {
1260                             Ok(dent) => dent,
1261                             Err(_) => return WalkState::Continue,
1262                         };
1263 
1264                         match dent.file_type() {
1265                             Some(fi) => {
1266                                 if !fi.is_file() {
1267                                     return WalkState::Continue;
1268                                 }
1269                             }
1270                             None => return WalkState::Continue,
1271                         }
1272 
1273                         let result_sink = sinks::UTF8(|line_num, _| {
1274                             match all_matches_sx_cl
1275                                 .send((line_num as usize - 1, dent.path().to_path_buf()))
1276                             {
1277                                 Ok(_) => Ok(true),
1278                                 Err(_) => Ok(false),
1279                             }
1280                         });
1281                         let result = searcher_cl.search_path(&matcher_cl, dent.path(), result_sink);
1282 
1283                         if let Err(err) = result {
1284                             log::error!("Global search error: {}, {}", dent.path().display(), err);
1285                         }
1286                         WalkState::Continue
1287                     })
1288                 });
1289             } else {
1290                 // Otherwise do nothing
1291                 // log::warn!("Global Search Invalid Pattern")
1292             }
1293         },
1294     );
1295 
1296     cx.push_layer(Box::new(prompt));
1297 
1298     let current_path = doc_mut!(cx.editor).path().cloned();
1299 
1300     let show_picker = async move {
1301         let all_matches: Vec<(usize, PathBuf)> =
1302             UnboundedReceiverStream::new(all_matches_rx).collect().await;
1303         let call: job::Callback =
1304             Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
1305                 if all_matches.is_empty() {
1306                     editor.set_status("No matches found".to_string());
1307                     return;
1308                 }
1309 
1310                 let picker = FilePicker::new(
1311                     all_matches,
1312                     move |(_line_num, path)| {
1313                         let relative_path = helix_core::path::get_relative_path(path)
1314                             .to_str()
1315                             .unwrap()
1316                             .to_owned();
1317                         if current_path.as_ref().map(|p| p == path).unwrap_or(false) {
1318                             format!("{} (*)", relative_path).into()
1319                         } else {
1320                             relative_path.into()
1321                         }
1322                     },
1323                     move |editor: &mut Editor, (line_num, path), action| {
1324                         match editor.open(path.into(), action) {
1325                             Ok(_) => {}
1326                             Err(e) => {
1327                                 editor.set_error(format!(
1328                                     "Failed to open file '{}': {}",
1329                                     path.display(),
1330                                     e
1331                                 ));
1332                                 return;
1333                             }
1334                         }
1335 
1336                         let line_num = *line_num;
1337                         let (view, doc) = current!(editor);
1338                         let text = doc.text();
1339                         let start = text.line_to_char(line_num);
1340                         let end = text.line_to_char((line_num + 1).min(text.len_lines()));
1341 
1342                         doc.set_selection(view.id, Selection::single(start, end));
1343                         align_view(doc, view, Align::Center);
1344                     },
1345                     |_editor, (line_num, path)| Some((path.clone(), Some((*line_num, *line_num)))),
1346                 );
1347                 compositor.push(Box::new(picker));
1348             });
1349         Ok(call)
1350     };
1351     cx.jobs.callback(show_picker);
1352 }
1353 
extend_line(cx: &mut Context)1354 fn extend_line(cx: &mut Context) {
1355     let count = cx.count();
1356     let (view, doc) = current!(cx.editor);
1357 
1358     let text = doc.text();
1359     let range = doc.selection(view.id).primary();
1360 
1361     let (start_line, end_line) = range.line_range(text.slice(..));
1362     let start = text.line_to_char(start_line);
1363     let mut end = text.line_to_char((end_line + count).min(text.len_lines()));
1364 
1365     if range.from() == start && range.to() == end {
1366         end = text.line_to_char((end_line + count + 1).min(text.len_lines()));
1367     }
1368 
1369     doc.set_selection(view.id, Selection::single(start, end));
1370 }
1371 
extend_to_line_bounds(cx: &mut Context)1372 fn extend_to_line_bounds(cx: &mut Context) {
1373     let (view, doc) = current!(cx.editor);
1374 
1375     doc.set_selection(
1376         view.id,
1377         doc.selection(view.id).clone().transform(|range| {
1378             let text = doc.text();
1379 
1380             let (start_line, end_line) = range.line_range(text.slice(..));
1381             let start = text.line_to_char(start_line);
1382             let end = text.line_to_char((end_line + 1).min(text.len_lines()));
1383 
1384             if range.anchor <= range.head {
1385                 Range::new(start, end)
1386             } else {
1387                 Range::new(end, start)
1388             }
1389         }),
1390     );
1391 }
1392 
delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId)1393 fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) {
1394     let text = doc.text().slice(..);
1395     let selection = doc.selection(view_id);
1396 
1397     // first yank the selection
1398     let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
1399     reg.write(values);
1400 
1401     // then delete
1402     let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
1403         (range.from(), range.to(), None)
1404     });
1405     doc.apply(&transaction, view_id);
1406 }
1407 
delete_selection(cx: &mut Context)1408 fn delete_selection(cx: &mut Context) {
1409     let reg_name = cx.register.unwrap_or('"');
1410     let (view, doc) = current!(cx.editor);
1411     let registers = &mut cx.editor.registers;
1412     let reg = registers.get_mut(reg_name);
1413     delete_selection_impl(reg, doc, view.id);
1414 
1415     doc.append_changes_to_history(view.id);
1416 
1417     // exit select mode, if currently in select mode
1418     exit_select_mode(cx);
1419 }
1420 
change_selection(cx: &mut Context)1421 fn change_selection(cx: &mut Context) {
1422     let reg_name = cx.register.unwrap_or('"');
1423     let (view, doc) = current!(cx.editor);
1424     let registers = &mut cx.editor.registers;
1425     let reg = registers.get_mut(reg_name);
1426     delete_selection_impl(reg, doc, view.id);
1427     enter_insert_mode(doc);
1428 }
1429 
collapse_selection(cx: &mut Context)1430 fn collapse_selection(cx: &mut Context) {
1431     let (view, doc) = current!(cx.editor);
1432     let text = doc.text().slice(..);
1433 
1434     let selection = doc.selection(view.id).clone().transform(|range| {
1435         let pos = range.cursor(text);
1436         Range::new(pos, pos)
1437     });
1438     doc.set_selection(view.id, selection);
1439 }
1440 
flip_selections(cx: &mut Context)1441 fn flip_selections(cx: &mut Context) {
1442     let (view, doc) = current!(cx.editor);
1443 
1444     let selection = doc
1445         .selection(view.id)
1446         .clone()
1447         .transform(|range| Range::new(range.head, range.anchor));
1448     doc.set_selection(view.id, selection);
1449 }
1450 
enter_insert_mode(doc: &mut Document)1451 fn enter_insert_mode(doc: &mut Document) {
1452     doc.mode = Mode::Insert;
1453 }
1454 
1455 // inserts at the start of each selection
insert_mode(cx: &mut Context)1456 fn insert_mode(cx: &mut Context) {
1457     let (view, doc) = current!(cx.editor);
1458     enter_insert_mode(doc);
1459 
1460     let selection = doc
1461         .selection(view.id)
1462         .clone()
1463         .transform(|range| Range::new(range.to(), range.from()));
1464     doc.set_selection(view.id, selection);
1465 }
1466 
1467 // inserts at the end of each selection
append_mode(cx: &mut Context)1468 fn append_mode(cx: &mut Context) {
1469     let (view, doc) = current!(cx.editor);
1470     enter_insert_mode(doc);
1471     doc.restore_cursor = true;
1472     let text = doc.text().slice(..);
1473 
1474     // Make sure there's room at the end of the document if the last
1475     // selection butts up against it.
1476     let end = text.len_chars();
1477     let last_range = doc.selection(view.id).iter().last().unwrap();
1478     if !last_range.is_empty() && last_range.head == end {
1479         let transaction = Transaction::change(
1480             doc.text(),
1481             std::array::IntoIter::new([(end, end, Some(doc.line_ending.as_str().into()))]),
1482         );
1483         doc.apply(&transaction, view.id);
1484     }
1485 
1486     let selection = doc.selection(view.id).clone().transform(|range| {
1487         Range::new(
1488             range.from(),
1489             graphemes::next_grapheme_boundary(doc.text().slice(..), range.to()),
1490         )
1491     });
1492     doc.set_selection(view.id, selection);
1493 }
1494 
1495 mod cmd {
1496     use super::*;
1497     use std::collections::HashMap;
1498 
1499     use helix_view::editor::Action;
1500     use ui::completers::{self, Completer};
1501 
1502     #[derive(Clone)]
1503     pub struct TypableCommand {
1504         pub name: &'static str,
1505         pub aliases: &'static [&'static str],
1506         pub doc: &'static str,
1507         // params, flags, helper, completer
1508         pub fun: fn(&mut compositor::Context, &[&str], PromptEvent) -> anyhow::Result<()>,
1509         pub completer: Option<Completer>,
1510     }
1511 
quit( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1512     fn quit(
1513         cx: &mut compositor::Context,
1514         _args: &[&str],
1515         _event: PromptEvent,
1516     ) -> anyhow::Result<()> {
1517         // last view and we have unsaved changes
1518         if cx.editor.tree.views().count() == 1 {
1519             buffers_remaining_impl(cx.editor)?
1520         }
1521 
1522         cx.editor
1523             .close(view!(cx.editor).id, /* close_buffer */ false);
1524 
1525         Ok(())
1526     }
1527 
force_quit( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1528     fn force_quit(
1529         cx: &mut compositor::Context,
1530         _args: &[&str],
1531         _event: PromptEvent,
1532     ) -> anyhow::Result<()> {
1533         cx.editor
1534             .close(view!(cx.editor).id, /* close_buffer */ false);
1535 
1536         Ok(())
1537     }
1538 
open( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1539     fn open(
1540         cx: &mut compositor::Context,
1541         args: &[&str],
1542         _event: PromptEvent,
1543     ) -> anyhow::Result<()> {
1544         use helix_core::path::expand_tilde;
1545         let path = args.get(0).context("wrong argument count")?;
1546         let _ = cx
1547             .editor
1548             .open(expand_tilde(Path::new(path)), Action::Replace)?;
1549         Ok(())
1550     }
1551 
write_impl<P: AsRef<Path>>( cx: &mut compositor::Context, path: Option<P>, ) -> anyhow::Result<()>1552     fn write_impl<P: AsRef<Path>>(
1553         cx: &mut compositor::Context,
1554         path: Option<P>,
1555     ) -> anyhow::Result<()> {
1556         let jobs = &mut cx.jobs;
1557         let (_, doc) = current!(cx.editor);
1558 
1559         if let Some(path) = path {
1560             doc.set_path(Some(path.as_ref()))
1561                 .context("invalid filepath")?;
1562         }
1563         if doc.path().is_none() {
1564             bail!("cannot write a buffer without a filename");
1565         }
1566         let fmt = doc.auto_format().map(|fmt| {
1567             let shared = fmt.shared();
1568             let callback = make_format_callback(
1569                 doc.id(),
1570                 doc.version(),
1571                 Modified::SetUnmodified,
1572                 shared.clone(),
1573             );
1574             jobs.callback(callback);
1575             shared
1576         });
1577         let future = doc.format_and_save(fmt);
1578         cx.jobs.add(Job::new(future).wait_before_exiting());
1579         Ok(())
1580     }
1581 
write( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1582     fn write(
1583         cx: &mut compositor::Context,
1584         args: &[&str],
1585         _event: PromptEvent,
1586     ) -> anyhow::Result<()> {
1587         write_impl(cx, args.first())
1588     }
1589 
new_file( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1590     fn new_file(
1591         cx: &mut compositor::Context,
1592         _args: &[&str],
1593         _event: PromptEvent,
1594     ) -> anyhow::Result<()> {
1595         cx.editor.new_file(Action::Replace);
1596 
1597         Ok(())
1598     }
1599 
format( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1600     fn format(
1601         cx: &mut compositor::Context,
1602         _args: &[&str],
1603         _event: PromptEvent,
1604     ) -> anyhow::Result<()> {
1605         let (_, doc) = current!(cx.editor);
1606 
1607         if let Some(format) = doc.format() {
1608             let callback =
1609                 make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
1610             cx.jobs.callback(callback);
1611         }
1612 
1613         Ok(())
1614     }
set_indent_style( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1615     fn set_indent_style(
1616         cx: &mut compositor::Context,
1617         args: &[&str],
1618         _event: PromptEvent,
1619     ) -> anyhow::Result<()> {
1620         use IndentStyle::*;
1621 
1622         // If no argument, report current indent style.
1623         if args.is_empty() {
1624             let style = current!(cx.editor).1.indent_style;
1625             cx.editor.set_status(match style {
1626                 Tabs => "tabs".into(),
1627                 Spaces(1) => "1 space".into(),
1628                 Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
1629                 _ => "error".into(), // Shouldn't happen.
1630             });
1631             return Ok(());
1632         }
1633 
1634         // Attempt to parse argument as an indent style.
1635         let style = match args.get(0) {
1636             Some(arg) if "tabs".starts_with(&arg.to_lowercase()) => Some(Tabs),
1637             Some(&"0") => Some(Tabs),
1638             Some(arg) => arg
1639                 .parse::<u8>()
1640                 .ok()
1641                 .filter(|n| (1..=8).contains(n))
1642                 .map(Spaces),
1643             _ => None,
1644         };
1645 
1646         let style = style.context("invalid indent style")?;
1647         let doc = doc_mut!(cx.editor);
1648         doc.indent_style = style;
1649 
1650         Ok(())
1651     }
1652 
1653     /// Sets or reports the current document's line ending setting.
set_line_ending( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1654     fn set_line_ending(
1655         cx: &mut compositor::Context,
1656         args: &[&str],
1657         _event: PromptEvent,
1658     ) -> anyhow::Result<()> {
1659         use LineEnding::*;
1660 
1661         // If no argument, report current line ending setting.
1662         if args.is_empty() {
1663             let line_ending = current!(cx.editor).1.line_ending;
1664             cx.editor.set_status(match line_ending {
1665                 Crlf => "crlf".into(),
1666                 LF => "line feed".into(),
1667                 FF => "form feed".into(),
1668                 CR => "carriage return".into(),
1669                 Nel => "next line".into(),
1670 
1671                 // These should never be a document's default line ending.
1672                 VT | LS | PS => "error".into(),
1673             });
1674 
1675             return Ok(());
1676         }
1677 
1678         let arg = args
1679             .get(0)
1680             .context("argument missing")?
1681             .to_ascii_lowercase();
1682 
1683         // Attempt to parse argument as a line ending.
1684         let line_ending = match arg {
1685             // We check for CR first because it shares a common prefix with CRLF.
1686             arg if arg.starts_with("cr") => CR,
1687             arg if arg.starts_with("crlf") => Crlf,
1688             arg if arg.starts_with("lf") => LF,
1689             arg if arg.starts_with("ff") => FF,
1690             arg if arg.starts_with("nel") => Nel,
1691             _ => bail!("invalid line ending"),
1692         };
1693 
1694         doc_mut!(cx.editor).line_ending = line_ending;
1695         Ok(())
1696     }
1697 
earlier( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1698     fn earlier(
1699         cx: &mut compositor::Context,
1700         args: &[&str],
1701         _event: PromptEvent,
1702     ) -> anyhow::Result<()> {
1703         let uk = args
1704             .join(" ")
1705             .parse::<helix_core::history::UndoKind>()
1706             .map_err(|s| anyhow!(s))?;
1707 
1708         let (view, doc) = current!(cx.editor);
1709         doc.earlier(view.id, uk);
1710 
1711         Ok(())
1712     }
1713 
later( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1714     fn later(
1715         cx: &mut compositor::Context,
1716         args: &[&str],
1717         _event: PromptEvent,
1718     ) -> anyhow::Result<()> {
1719         let uk = args
1720             .join(" ")
1721             .parse::<helix_core::history::UndoKind>()
1722             .map_err(|s| anyhow!(s))?;
1723         let (view, doc) = current!(cx.editor);
1724         doc.later(view.id, uk);
1725 
1726         Ok(())
1727     }
1728 
write_quit( cx: &mut compositor::Context, args: &[&str], event: PromptEvent, ) -> anyhow::Result<()>1729     fn write_quit(
1730         cx: &mut compositor::Context,
1731         args: &[&str],
1732         event: PromptEvent,
1733     ) -> anyhow::Result<()> {
1734         write_impl(cx, args.first())?;
1735         quit(cx, &[], event)
1736     }
1737 
force_write_quit( cx: &mut compositor::Context, args: &[&str], event: PromptEvent, ) -> anyhow::Result<()>1738     fn force_write_quit(
1739         cx: &mut compositor::Context,
1740         args: &[&str],
1741         event: PromptEvent,
1742     ) -> anyhow::Result<()> {
1743         write_impl(cx, args.first())?;
1744         force_quit(cx, &[], event)
1745     }
1746 
1747     /// Results an error if there are modified buffers remaining and sets editor error,
1748     /// otherwise returns `Ok(())`
buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()>1749     pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> {
1750         let modified: Vec<_> = editor
1751             .documents()
1752             .filter(|doc| doc.is_modified())
1753             .map(|doc| {
1754                 doc.relative_path()
1755                     .map(|path| path.to_string_lossy().to_string())
1756                     .unwrap_or_else(|| "[scratch]".into())
1757             })
1758             .collect();
1759         if !modified.is_empty() {
1760             bail!(
1761                 "{} unsaved buffer(s) remaining: {:?}",
1762                 modified.len(),
1763                 modified
1764             );
1765         }
1766         Ok(())
1767     }
1768 
write_all_impl( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, quit: bool, force: bool, ) -> anyhow::Result<()>1769     fn write_all_impl(
1770         cx: &mut compositor::Context,
1771         _args: &[&str],
1772         _event: PromptEvent,
1773         quit: bool,
1774         force: bool,
1775     ) -> anyhow::Result<()> {
1776         let mut errors = String::new();
1777 
1778         // save all documents
1779         for (_, doc) in &mut cx.editor.documents {
1780             if doc.path().is_none() {
1781                 errors.push_str("cannot write a buffer without a filename\n");
1782                 continue;
1783             }
1784 
1785             // TODO: handle error.
1786             let handle = doc.save();
1787             cx.jobs.add(Job::new(handle).wait_before_exiting());
1788         }
1789 
1790         if quit {
1791             if !force {
1792                 buffers_remaining_impl(cx.editor)?;
1793             }
1794 
1795             // close all views
1796             let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
1797             for view_id in views {
1798                 cx.editor.close(view_id, false);
1799             }
1800         }
1801 
1802         bail!(errors)
1803     }
1804 
write_all( cx: &mut compositor::Context, args: &[&str], event: PromptEvent, ) -> anyhow::Result<()>1805     fn write_all(
1806         cx: &mut compositor::Context,
1807         args: &[&str],
1808         event: PromptEvent,
1809     ) -> anyhow::Result<()> {
1810         write_all_impl(cx, args, event, false, false)
1811     }
1812 
write_all_quit( cx: &mut compositor::Context, args: &[&str], event: PromptEvent, ) -> anyhow::Result<()>1813     fn write_all_quit(
1814         cx: &mut compositor::Context,
1815         args: &[&str],
1816         event: PromptEvent,
1817     ) -> anyhow::Result<()> {
1818         write_all_impl(cx, args, event, true, false)
1819     }
1820 
force_write_all_quit( cx: &mut compositor::Context, args: &[&str], event: PromptEvent, ) -> anyhow::Result<()>1821     fn force_write_all_quit(
1822         cx: &mut compositor::Context,
1823         args: &[&str],
1824         event: PromptEvent,
1825     ) -> anyhow::Result<()> {
1826         write_all_impl(cx, args, event, true, true)
1827     }
1828 
quit_all_impl( editor: &mut Editor, _args: &[&str], _event: PromptEvent, force: bool, ) -> anyhow::Result<()>1829     fn quit_all_impl(
1830         editor: &mut Editor,
1831         _args: &[&str],
1832         _event: PromptEvent,
1833         force: bool,
1834     ) -> anyhow::Result<()> {
1835         if !force {
1836             buffers_remaining_impl(editor)?;
1837         }
1838 
1839         // close all views
1840         let views: Vec<_> = editor.tree.views().map(|(view, _)| view.id).collect();
1841         for view_id in views {
1842             editor.close(view_id, false);
1843         }
1844 
1845         Ok(())
1846     }
1847 
quit_all( cx: &mut compositor::Context, args: &[&str], event: PromptEvent, ) -> anyhow::Result<()>1848     fn quit_all(
1849         cx: &mut compositor::Context,
1850         args: &[&str],
1851         event: PromptEvent,
1852     ) -> anyhow::Result<()> {
1853         quit_all_impl(&mut cx.editor, args, event, false)
1854     }
1855 
force_quit_all( cx: &mut compositor::Context, args: &[&str], event: PromptEvent, ) -> anyhow::Result<()>1856     fn force_quit_all(
1857         cx: &mut compositor::Context,
1858         args: &[&str],
1859         event: PromptEvent,
1860     ) -> anyhow::Result<()> {
1861         quit_all_impl(&mut cx.editor, args, event, true)
1862     }
1863 
theme( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1864     fn theme(
1865         cx: &mut compositor::Context,
1866         args: &[&str],
1867         _event: PromptEvent,
1868     ) -> anyhow::Result<()> {
1869         let theme = args.first().context("theme not provided")?;
1870         cx.editor.set_theme_from_name(theme)
1871     }
1872 
yank_main_selection_to_clipboard( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1873     fn yank_main_selection_to_clipboard(
1874         cx: &mut compositor::Context,
1875         _args: &[&str],
1876         _event: PromptEvent,
1877     ) -> anyhow::Result<()> {
1878         yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard)
1879     }
1880 
yank_joined_to_clipboard( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1881     fn yank_joined_to_clipboard(
1882         cx: &mut compositor::Context,
1883         args: &[&str],
1884         _event: PromptEvent,
1885     ) -> anyhow::Result<()> {
1886         let (_, doc) = current!(cx.editor);
1887         let separator = args
1888             .first()
1889             .copied()
1890             .unwrap_or_else(|| doc.line_ending.as_str());
1891         yank_joined_to_clipboard_impl(&mut cx.editor, separator, ClipboardType::Clipboard)
1892     }
1893 
yank_main_selection_to_primary_clipboard( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1894     fn yank_main_selection_to_primary_clipboard(
1895         cx: &mut compositor::Context,
1896         _args: &[&str],
1897         _event: PromptEvent,
1898     ) -> anyhow::Result<()> {
1899         yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Selection)
1900     }
1901 
yank_joined_to_primary_clipboard( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1902     fn yank_joined_to_primary_clipboard(
1903         cx: &mut compositor::Context,
1904         args: &[&str],
1905         _event: PromptEvent,
1906     ) -> anyhow::Result<()> {
1907         let (_, doc) = current!(cx.editor);
1908         let separator = args
1909             .first()
1910             .copied()
1911             .unwrap_or_else(|| doc.line_ending.as_str());
1912         yank_joined_to_clipboard_impl(&mut cx.editor, separator, ClipboardType::Selection)
1913     }
1914 
paste_clipboard_after( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1915     fn paste_clipboard_after(
1916         cx: &mut compositor::Context,
1917         _args: &[&str],
1918         _event: PromptEvent,
1919     ) -> anyhow::Result<()> {
1920         paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard)
1921     }
1922 
paste_clipboard_before( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1923     fn paste_clipboard_before(
1924         cx: &mut compositor::Context,
1925         _args: &[&str],
1926         _event: PromptEvent,
1927     ) -> anyhow::Result<()> {
1928         paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard)
1929     }
1930 
paste_primary_clipboard_after( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1931     fn paste_primary_clipboard_after(
1932         cx: &mut compositor::Context,
1933         _args: &[&str],
1934         _event: PromptEvent,
1935     ) -> anyhow::Result<()> {
1936         paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection)
1937     }
1938 
paste_primary_clipboard_before( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1939     fn paste_primary_clipboard_before(
1940         cx: &mut compositor::Context,
1941         _args: &[&str],
1942         _event: PromptEvent,
1943     ) -> anyhow::Result<()> {
1944         paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection)
1945     }
1946 
replace_selections_with_clipboard_impl( cx: &mut compositor::Context, clipboard_type: ClipboardType, ) -> anyhow::Result<()>1947     fn replace_selections_with_clipboard_impl(
1948         cx: &mut compositor::Context,
1949         clipboard_type: ClipboardType,
1950     ) -> anyhow::Result<()> {
1951         let (view, doc) = current!(cx.editor);
1952 
1953         match cx.editor.clipboard_provider.get_contents(clipboard_type) {
1954             Ok(contents) => {
1955                 let selection = doc.selection(view.id);
1956                 let transaction =
1957                     Transaction::change_by_selection(doc.text(), selection, |range| {
1958                         (range.from(), range.to(), Some(contents.as_str().into()))
1959                     });
1960 
1961                 doc.apply(&transaction, view.id);
1962                 doc.append_changes_to_history(view.id);
1963                 Ok(())
1964             }
1965             Err(e) => Err(e.context("Couldn't get system clipboard contents")),
1966         }
1967     }
1968 
replace_selections_with_clipboard( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1969     fn replace_selections_with_clipboard(
1970         cx: &mut compositor::Context,
1971         _args: &[&str],
1972         _event: PromptEvent,
1973     ) -> anyhow::Result<()> {
1974         replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
1975     }
1976 
replace_selections_with_primary_clipboard( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1977     fn replace_selections_with_primary_clipboard(
1978         cx: &mut compositor::Context,
1979         _args: &[&str],
1980         _event: PromptEvent,
1981     ) -> anyhow::Result<()> {
1982         replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
1983     }
1984 
show_clipboard_provider( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1985     fn show_clipboard_provider(
1986         cx: &mut compositor::Context,
1987         _args: &[&str],
1988         _event: PromptEvent,
1989     ) -> anyhow::Result<()> {
1990         cx.editor
1991             .set_status(cx.editor.clipboard_provider.name().to_string());
1992         Ok(())
1993     }
1994 
change_current_directory( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>1995     fn change_current_directory(
1996         cx: &mut compositor::Context,
1997         args: &[&str],
1998         _event: PromptEvent,
1999     ) -> anyhow::Result<()> {
2000         let dir = helix_core::path::expand_tilde(
2001             args.first()
2002                 .context("target directory not provided")?
2003                 .as_ref(),
2004         );
2005 
2006         if let Err(e) = std::env::set_current_dir(dir) {
2007             bail!("Couldn't change the current working directory: {}", e);
2008         }
2009 
2010         let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
2011         cx.editor.set_status(format!(
2012             "Current working directory is now {}",
2013             cwd.display()
2014         ));
2015         Ok(())
2016     }
2017 
show_current_directory( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>2018     fn show_current_directory(
2019         cx: &mut compositor::Context,
2020         _args: &[&str],
2021         _event: PromptEvent,
2022     ) -> anyhow::Result<()> {
2023         let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
2024         cx.editor
2025             .set_status(format!("Current working directory is {}", cwd.display()));
2026         Ok(())
2027     }
2028 
2029     /// Sets the [`Document`]'s encoding..
set_encoding( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>2030     fn set_encoding(
2031         cx: &mut compositor::Context,
2032         args: &[&str],
2033         _event: PromptEvent,
2034     ) -> anyhow::Result<()> {
2035         let (_, doc) = current!(cx.editor);
2036         if let Some(label) = args.first() {
2037             doc.set_encoding(label)
2038         } else {
2039             let encoding = doc.encoding().name().to_string();
2040             cx.editor.set_status(encoding);
2041             Ok(())
2042         }
2043     }
2044 
2045     /// Reload the [`Document`] from its source file.
reload( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>2046     fn reload(
2047         cx: &mut compositor::Context,
2048         _args: &[&str],
2049         _event: PromptEvent,
2050     ) -> anyhow::Result<()> {
2051         let (view, doc) = current!(cx.editor);
2052         doc.reload(view.id)
2053     }
2054 
tree_sitter_scopes( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>2055     fn tree_sitter_scopes(
2056         cx: &mut compositor::Context,
2057         _args: &[&str],
2058         _event: PromptEvent,
2059     ) -> anyhow::Result<()> {
2060         let (view, doc) = current!(cx.editor);
2061         let text = doc.text().slice(..);
2062 
2063         let pos = doc.selection(view.id).primary().cursor(text);
2064         let scopes = indent::get_scopes(doc.syntax(), text, pos);
2065         cx.editor.set_status(format!("scopes: {:?}", &scopes));
2066         Ok(())
2067     }
2068 
vsplit( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>2069     fn vsplit(
2070         cx: &mut compositor::Context,
2071         args: &[&str],
2072         _event: PromptEvent,
2073     ) -> anyhow::Result<()> {
2074         let (_, doc) = current!(cx.editor);
2075         let id = doc.id();
2076 
2077         if let Some(path) = args.get(0) {
2078             cx.editor.open(path.into(), Action::VerticalSplit)?;
2079         } else {
2080             cx.editor.switch(id, Action::VerticalSplit);
2081         }
2082 
2083         Ok(())
2084     }
2085 
hsplit( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>2086     fn hsplit(
2087         cx: &mut compositor::Context,
2088         args: &[&str],
2089         _event: PromptEvent,
2090     ) -> anyhow::Result<()> {
2091         let (_, doc) = current!(cx.editor);
2092         let id = doc.id();
2093 
2094         if let Some(path) = args.get(0) {
2095             cx.editor.open(path.into(), Action::HorizontalSplit)?;
2096         } else {
2097             cx.editor.switch(id, Action::HorizontalSplit);
2098         }
2099 
2100         Ok(())
2101     }
2102 
tutor( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()>2103     fn tutor(
2104         cx: &mut compositor::Context,
2105         _args: &[&str],
2106         _event: PromptEvent,
2107     ) -> anyhow::Result<()> {
2108         let path = helix_core::runtime_dir().join("tutor.txt");
2109         cx.editor.open(path, Action::Replace)?;
2110         // Unset path to prevent accidentally saving to the original tutor file.
2111         doc_mut!(cx.editor).set_path(None)?;
2112         Ok(())
2113     }
2114 
2115     pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2116         TypableCommand {
2117             name: "quit",
2118             aliases: &["q"],
2119             doc: "Close the current view.",
2120             fun: quit,
2121             completer: None,
2122         },
2123         TypableCommand {
2124             name: "quit!",
2125             aliases: &["q!"],
2126             doc: "Close the current view forcefully (ignoring unsaved changes).",
2127             fun: force_quit,
2128             completer: None,
2129         },
2130         TypableCommand {
2131             name: "open",
2132             aliases: &["o"],
2133             doc: "Open a file from disk into the current view.",
2134             fun: open,
2135             completer: Some(completers::filename),
2136         },
2137         TypableCommand {
2138             name: "write",
2139             aliases: &["w"],
2140             doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
2141             fun: write,
2142             completer: Some(completers::filename),
2143         },
2144         TypableCommand {
2145             name: "new",
2146             aliases: &["n"],
2147             doc: "Create a new scratch buffer.",
2148             fun: new_file,
2149             completer: Some(completers::filename),
2150         },
2151         TypableCommand {
2152             name: "format",
2153             aliases: &["fmt"],
2154             doc: "Format the file using a formatter.",
2155             fun: format,
2156             completer: None,
2157         },
2158         TypableCommand {
2159             name: "indent-style",
2160             aliases: &[],
2161             doc: "Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)",
2162             fun: set_indent_style,
2163             completer: None,
2164         },
2165         TypableCommand {
2166             name: "line-ending",
2167             aliases: &[],
2168             doc: "Set the document's default line ending. Options: crlf, lf, cr, ff, nel.",
2169             fun: set_line_ending,
2170             completer: None,
2171         },
2172         TypableCommand {
2173             name: "earlier",
2174             aliases: &["ear"],
2175             doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
2176             fun: earlier,
2177             completer: None,
2178         },
2179         TypableCommand {
2180             name: "later",
2181             aliases: &["lat"],
2182             doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
2183             fun: later,
2184             completer: None,
2185         },
2186         TypableCommand {
2187             name: "write-quit",
2188             aliases: &["wq", "x"],
2189             doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
2190             fun: write_quit,
2191             completer: Some(completers::filename),
2192         },
2193         TypableCommand {
2194             name: "write-quit!",
2195             aliases: &["wq!", "x!"],
2196             doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
2197             fun: force_write_quit,
2198             completer: Some(completers::filename),
2199         },
2200         TypableCommand {
2201             name: "write-all",
2202             aliases: &["wa"],
2203             doc: "Write changes from all views to disk.",
2204             fun: write_all,
2205             completer: None,
2206         },
2207         TypableCommand {
2208             name: "write-quit-all",
2209             aliases: &["wqa", "xa"],
2210             doc: "Write changes from all views to disk and close all views.",
2211             fun: write_all_quit,
2212             completer: None,
2213         },
2214         TypableCommand {
2215             name: "write-quit-all!",
2216             aliases: &["wqa!", "xa!"],
2217             doc: "Write changes from all views to disk and close all views forcefully (ignoring unsaved changes).",
2218             fun: force_write_all_quit,
2219             completer: None,
2220         },
2221         TypableCommand {
2222             name: "quit-all",
2223             aliases: &["qa"],
2224             doc: "Close all views.",
2225             fun: quit_all,
2226             completer: None,
2227         },
2228         TypableCommand {
2229             name: "quit-all!",
2230             aliases: &["qa!"],
2231             doc: "Close all views forcefully (ignoring unsaved changes).",
2232             fun: force_quit_all,
2233             completer: None,
2234         },
2235         TypableCommand {
2236             name: "theme",
2237             aliases: &[],
2238             doc: "Change the theme of current view. Requires theme name as argument (:theme <name>)",
2239             fun: theme,
2240             completer: Some(completers::theme),
2241         },
2242         TypableCommand {
2243             name: "clipboard-yank",
2244             aliases: &[],
2245             doc: "Yank main selection into system clipboard.",
2246             fun: yank_main_selection_to_clipboard,
2247             completer: None,
2248         },
2249         TypableCommand {
2250             name: "clipboard-yank-join",
2251             aliases: &[],
2252             doc: "Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2253             fun: yank_joined_to_clipboard,
2254             completer: None,
2255         },
2256         TypableCommand {
2257             name: "primary-clipboard-yank",
2258             aliases: &[],
2259             doc: "Yank main selection into system primary clipboard.",
2260             fun: yank_main_selection_to_primary_clipboard,
2261             completer: None,
2262         },
2263         TypableCommand {
2264             name: "primary-clipboard-yank-join",
2265             aliases: &[],
2266             doc: "Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2267             fun: yank_joined_to_primary_clipboard,
2268             completer: None,
2269         },
2270         TypableCommand {
2271             name: "clipboard-paste-after",
2272             aliases: &[],
2273             doc: "Paste system clipboard after selections.",
2274             fun: paste_clipboard_after,
2275             completer: None,
2276         },
2277         TypableCommand {
2278             name: "clipboard-paste-before",
2279             aliases: &[],
2280             doc: "Paste system clipboard before selections.",
2281             fun: paste_clipboard_before,
2282             completer: None,
2283         },
2284         TypableCommand {
2285             name: "clipboard-paste-replace",
2286             aliases: &[],
2287             doc: "Replace selections with content of system clipboard.",
2288             fun: replace_selections_with_clipboard,
2289             completer: None,
2290         },
2291         TypableCommand {
2292             name: "primary-clipboard-paste-after",
2293             aliases: &[],
2294             doc: "Paste primary clipboard after selections.",
2295             fun: paste_primary_clipboard_after,
2296             completer: None,
2297         },
2298         TypableCommand {
2299             name: "primary-clipboard-paste-before",
2300             aliases: &[],
2301             doc: "Paste primary clipboard before selections.",
2302             fun: paste_primary_clipboard_before,
2303             completer: None,
2304         },
2305         TypableCommand {
2306             name: "primary-clipboard-paste-replace",
2307             aliases: &[],
2308             doc: "Replace selections with content of system primary clipboard.",
2309             fun: replace_selections_with_primary_clipboard,
2310             completer: None,
2311         },
2312         TypableCommand {
2313             name: "show-clipboard-provider",
2314             aliases: &[],
2315             doc: "Show clipboard provider name in status bar.",
2316             fun: show_clipboard_provider,
2317             completer: None,
2318         },
2319         TypableCommand {
2320             name: "change-current-directory",
2321             aliases: &["cd"],
2322             doc: "Change the current working directory (:cd <dir>).",
2323             fun: change_current_directory,
2324             completer: Some(completers::directory),
2325         },
2326         TypableCommand {
2327             name: "show-directory",
2328             aliases: &["pwd"],
2329             doc: "Show the current working directory.",
2330             fun: show_current_directory,
2331             completer: None,
2332         },
2333         TypableCommand {
2334             name: "encoding",
2335             aliases: &[],
2336             doc: "Set encoding based on `https://encoding.spec.whatwg.org`",
2337             fun: set_encoding,
2338             completer: None,
2339         },
2340         TypableCommand {
2341             name: "reload",
2342             aliases: &[],
2343             doc: "Discard changes and reload from the source file.",
2344             fun: reload,
2345             completer: None,
2346         },
2347         TypableCommand {
2348             name: "tree-sitter-scopes",
2349             aliases: &[],
2350             doc: "Display tree sitter scopes, primarily for theming and development.",
2351             fun: tree_sitter_scopes,
2352             completer: None,
2353         },
2354         TypableCommand {
2355             name: "vsplit",
2356             aliases: &["vs"],
2357             doc: "Open the file in a vertical split.",
2358             fun: vsplit,
2359             completer: Some(completers::filename),
2360         },
2361         TypableCommand {
2362             name: "hsplit",
2363             aliases: &["hs", "sp"],
2364             doc: "Open the file in a horizontal split.",
2365             fun: hsplit,
2366             completer: Some(completers::filename),
2367         },
2368         TypableCommand {
2369             name: "tutor",
2370             aliases: &[],
2371             doc: "Open the tutorial.",
2372             fun: tutor,
2373             completer: None,
2374         },
2375     ];
2376 
2377     pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
2378         TYPABLE_COMMAND_LIST
2379             .iter()
2380             .flat_map(|cmd| {
2381                 std::iter::once((cmd.name, cmd))
2382                     .chain(cmd.aliases.iter().map(move |&alias| (alias, cmd)))
2383             })
2384             .collect()
2385     });
2386 }
2387 
command_mode(cx: &mut Context)2388 fn command_mode(cx: &mut Context) {
2389     let mut prompt = Prompt::new(
2390         ":".into(),
2391         Some(':'),
2392         |input: &str| {
2393             // we use .this over split_whitespace() because we care about empty segments
2394             let parts = input.split(' ').collect::<Vec<&str>>();
2395 
2396             // simple heuristic: if there's no just one part, complete command name.
2397             // if there's a space, per command completion kicks in.
2398             if parts.len() <= 1 {
2399                 let end = 0..;
2400                 cmd::TYPABLE_COMMAND_LIST
2401                     .iter()
2402                     .filter(|command| command.name.contains(input))
2403                     .map(|command| (end.clone(), Cow::Borrowed(command.name)))
2404                     .collect()
2405             } else {
2406                 let part = parts.last().unwrap();
2407 
2408                 if let Some(cmd::TypableCommand {
2409                     completer: Some(completer),
2410                     ..
2411                 }) = cmd::COMMANDS.get(parts[0])
2412                 {
2413                     completer(part)
2414                         .into_iter()
2415                         .map(|(range, file)| {
2416                             // offset ranges to input
2417                             let offset = input.len() - part.len();
2418                             let range = (range.start + offset)..;
2419                             (range, file)
2420                         })
2421                         .collect()
2422                 } else {
2423                     Vec::new()
2424                 }
2425             }
2426         }, // completion
2427         move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
2428             if event != PromptEvent::Validate {
2429                 return;
2430             }
2431 
2432             let parts = input.split_whitespace().collect::<Vec<&str>>();
2433             if parts.is_empty() {
2434                 return;
2435             }
2436 
2437             if let Some(cmd) = cmd::COMMANDS.get(parts[0]) {
2438                 if let Err(e) = (cmd.fun)(cx, &parts[1..], event) {
2439                     cx.editor.set_error(format!("{}", e));
2440                 }
2441             } else {
2442                 cx.editor
2443                     .set_error(format!("no such command: '{}'", parts[0]));
2444             };
2445         },
2446     );
2447     prompt.doc_fn = Box::new(|input: &str| {
2448         let part = input.split(' ').next().unwrap_or_default();
2449 
2450         if let Some(cmd::TypableCommand { doc, .. }) = cmd::COMMANDS.get(part) {
2451             return Some(doc);
2452         }
2453 
2454         None
2455     });
2456 
2457     cx.push_layer(Box::new(prompt));
2458 }
2459 
file_picker(cx: &mut Context)2460 fn file_picker(cx: &mut Context) {
2461     let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
2462     let picker = ui::file_picker(root);
2463     cx.push_layer(Box::new(picker));
2464 }
2465 
buffer_picker(cx: &mut Context)2466 fn buffer_picker(cx: &mut Context) {
2467     let current = view!(cx.editor).doc;
2468 
2469     let picker = FilePicker::new(
2470         cx.editor
2471             .documents
2472             .iter()
2473             .map(|(id, doc)| (id, doc.path().cloned()))
2474             .collect(),
2475         move |(id, path): &(DocumentId, Option<PathBuf>)| {
2476             let path = path.as_deref().map(helix_core::path::get_relative_path);
2477             match path.as_ref().and_then(|path| path.to_str()) {
2478                 Some(path) => {
2479                     if *id == current {
2480                         format!("{} (*)", &path).into()
2481                     } else {
2482                         path.to_owned().into()
2483                     }
2484                 }
2485                 None => "[scratch buffer]".into(),
2486             }
2487         },
2488         |editor: &mut Editor, (id, _path): &(DocumentId, Option<PathBuf>), _action| {
2489             editor.switch(*id, Action::Replace);
2490         },
2491         |editor, (id, path)| {
2492             let doc = &editor.documents.get(*id)?;
2493             let &view_id = doc.selections().keys().next()?;
2494             let line = doc
2495                 .selection(view_id)
2496                 .primary()
2497                 .cursor_line(doc.text().slice(..));
2498             Some((path.clone()?, Some((line, line))))
2499         },
2500     );
2501     cx.push_layer(Box::new(picker));
2502 }
2503 
symbol_picker(cx: &mut Context)2504 fn symbol_picker(cx: &mut Context) {
2505     fn nested_to_flat(
2506         list: &mut Vec<lsp::SymbolInformation>,
2507         file: &lsp::TextDocumentIdentifier,
2508         symbol: lsp::DocumentSymbol,
2509     ) {
2510         #[allow(deprecated)]
2511         list.push(lsp::SymbolInformation {
2512             name: symbol.name,
2513             kind: symbol.kind,
2514             tags: symbol.tags,
2515             deprecated: symbol.deprecated,
2516             location: lsp::Location::new(file.uri.clone(), symbol.selection_range),
2517             container_name: None,
2518         });
2519         for child in symbol.children.into_iter().flatten() {
2520             nested_to_flat(list, file, child);
2521         }
2522     }
2523     let (_, doc) = current!(cx.editor);
2524 
2525     let language_server = match doc.language_server() {
2526         Some(language_server) => language_server,
2527         None => return,
2528     };
2529     let offset_encoding = language_server.offset_encoding();
2530 
2531     let future = language_server.document_symbols(doc.identifier());
2532 
2533     cx.callback(
2534         future,
2535         move |editor: &mut Editor,
2536               compositor: &mut Compositor,
2537               response: Option<lsp::DocumentSymbolResponse>| {
2538             if let Some(symbols) = response {
2539                 // lsp has two ways to represent symbols (flat/nested)
2540                 // convert the nested variant to flat, so that we have a homogeneous list
2541                 let symbols = match symbols {
2542                     lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
2543                     lsp::DocumentSymbolResponse::Nested(symbols) => {
2544                         let (_view, doc) = current!(editor);
2545                         let mut flat_symbols = Vec::new();
2546                         for symbol in symbols {
2547                             nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
2548                         }
2549                         flat_symbols
2550                     }
2551                 };
2552 
2553                 let picker = FilePicker::new(
2554                     symbols,
2555                     |symbol| (&symbol.name).into(),
2556                     move |editor: &mut Editor, symbol, _action| {
2557                         push_jump(editor);
2558                         let (view, doc) = current!(editor);
2559 
2560                         if let Some(range) =
2561                             lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding)
2562                         {
2563                             // we flip the range so that the cursor sits on the start of the symbol
2564                             // (for example start of the function).
2565                             doc.set_selection(view.id, Selection::single(range.head, range.anchor));
2566                             align_view(doc, view, Align::Center);
2567                         }
2568                     },
2569                     move |_editor, symbol| {
2570                         let path = symbol.location.uri.to_file_path().unwrap();
2571                         let line = Some((
2572                             symbol.location.range.start.line as usize,
2573                             symbol.location.range.end.line as usize,
2574                         ));
2575                         Some((path, line))
2576                     },
2577                 );
2578                 compositor.push(Box::new(picker))
2579             }
2580         },
2581     )
2582 }
2583 
code_action(cx: &mut Context)2584 pub fn code_action(cx: &mut Context) {
2585     let (view, doc) = current!(cx.editor);
2586 
2587     let language_server = match doc.language_server() {
2588         Some(language_server) => language_server,
2589         None => return,
2590     };
2591 
2592     let range = range_to_lsp_range(
2593         doc.text(),
2594         doc.selection(view.id).primary(),
2595         language_server.offset_encoding(),
2596     );
2597 
2598     let future = language_server.code_actions(doc.identifier(), range);
2599     let offset_encoding = language_server.offset_encoding();
2600 
2601     cx.callback(
2602         future,
2603         move |_editor: &mut Editor,
2604               compositor: &mut Compositor,
2605               response: Option<lsp::CodeActionResponse>| {
2606             if let Some(actions) = response {
2607                 let picker = Picker::new(
2608                     true,
2609                     actions,
2610                     |action| match action {
2611                         lsp::CodeActionOrCommand::CodeAction(action) => {
2612                             action.title.as_str().into()
2613                         }
2614                         lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
2615                     },
2616                     move |editor, code_action, _action| match code_action {
2617                         lsp::CodeActionOrCommand::Command(command) => {
2618                             log::debug!("code action command: {:?}", command);
2619                             editor.set_error(String::from("Handling code action command is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
2620                         }
2621                         lsp::CodeActionOrCommand::CodeAction(code_action) => {
2622                             log::debug!("code action: {:?}", code_action);
2623                             if let Some(ref workspace_edit) = code_action.edit {
2624                                 apply_workspace_edit(editor, offset_encoding, workspace_edit)
2625                             }
2626                         }
2627                     },
2628                 );
2629                 compositor.push(Box::new(picker))
2630             }
2631         },
2632     )
2633 }
2634 
apply_workspace_edit( editor: &mut Editor, offset_encoding: OffsetEncoding, workspace_edit: &lsp::WorkspaceEdit, )2635 fn apply_workspace_edit(
2636     editor: &mut Editor,
2637     offset_encoding: OffsetEncoding,
2638     workspace_edit: &lsp::WorkspaceEdit,
2639 ) {
2640     if let Some(ref changes) = workspace_edit.changes {
2641         log::debug!("workspace changes: {:?}", changes);
2642         editor.set_error(String::from("Handling workspace_edit.changes is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
2643         return;
2644         // Not sure if it works properly, it'll be safer to just panic here to avoid breaking some parts of code on which code actions will be used
2645         // TODO: find some example that uses workspace changes, and test it
2646         // for (url, edits) in changes.iter() {
2647         //     let file_path = url.origin().ascii_serialization();
2648         //     let file_path = std::path::PathBuf::from(file_path);
2649         //     let file = std::fs::File::open(file_path).unwrap();
2650         //     let mut text = Rope::from_reader(file).unwrap();
2651         //     let transaction = edits_to_changes(&text, edits);
2652         //     transaction.apply(&mut text);
2653         // }
2654     }
2655 
2656     if let Some(ref document_changes) = workspace_edit.document_changes {
2657         match document_changes {
2658             lsp::DocumentChanges::Edits(document_edits) => {
2659                 for document_edit in document_edits {
2660                     let path = document_edit
2661                         .text_document
2662                         .uri
2663                         .to_file_path()
2664                         .expect("unable to convert URI to filepath");
2665                     let current_view_id = view!(editor).id;
2666                     let doc = editor
2667                         .document_by_path_mut(path)
2668                         .expect("Document for document_changes not found");
2669 
2670                     // Need to determine a view for apply/append_changes_to_history
2671                     let selections = doc.selections();
2672                     let view_id = if selections.contains_key(&current_view_id) {
2673                         // use current if possible
2674                         current_view_id
2675                     } else {
2676                         // Hack: we take the first available view_id
2677                         selections
2678                             .keys()
2679                             .next()
2680                             .copied()
2681                             .expect("No view_id available")
2682                     };
2683 
2684                     let edits = document_edit
2685                         .edits
2686                         .iter()
2687                         .map(|edit| match edit {
2688                             lsp::OneOf::Left(text_edit) => text_edit,
2689                             lsp::OneOf::Right(annotated_text_edit) => {
2690                                 &annotated_text_edit.text_edit
2691                             }
2692                         })
2693                         .cloned()
2694                         .collect();
2695 
2696                     let transaction = helix_lsp::util::generate_transaction_from_edits(
2697                         doc.text(),
2698                         edits,
2699                         offset_encoding,
2700                     );
2701                     doc.apply(&transaction, view_id);
2702                     doc.append_changes_to_history(view_id);
2703                 }
2704             }
2705             lsp::DocumentChanges::Operations(operations) => {
2706                 log::debug!("document changes - operations: {:?}", operations);
2707                 editor.set_error(String::from("Handling document operations is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
2708             }
2709         }
2710     }
2711 }
2712 
last_picker(cx: &mut Context)2713 fn last_picker(cx: &mut Context) {
2714     // TODO: last picker does not seem to work well with buffer_picker
2715     cx.callback = Some(Box::new(|compositor: &mut Compositor| {
2716         if let Some(picker) = compositor.last_picker.take() {
2717             compositor.push(picker);
2718         }
2719         // XXX: figure out how to show error when no last picker lifetime
2720         // cx.editor.set_error("no last picker".to_owned())
2721     }));
2722 }
2723 
2724 // I inserts at the first nonwhitespace character of each line with a selection
prepend_to_line(cx: &mut Context)2725 fn prepend_to_line(cx: &mut Context) {
2726     goto_first_nonwhitespace(cx);
2727     let doc = doc_mut!(cx.editor);
2728     enter_insert_mode(doc);
2729 }
2730 
2731 // A inserts at the end of each line with a selection
append_to_line(cx: &mut Context)2732 fn append_to_line(cx: &mut Context) {
2733     let (view, doc) = current!(cx.editor);
2734     enter_insert_mode(doc);
2735 
2736     let selection = doc.selection(view.id).clone().transform(|range| {
2737         let text = doc.text().slice(..);
2738         let line = range.cursor_line(text);
2739         let pos = line_end_char_index(&text, line);
2740         Range::new(pos, pos)
2741     });
2742     doc.set_selection(view.id, selection);
2743 }
2744 
2745 /// Sometimes when applying formatting changes we want to mark the buffer as unmodified, for
2746 /// example because we just applied the same changes while saving.
2747 enum Modified {
2748     SetUnmodified,
2749     LeaveModified,
2750 }
2751 
2752 // Creates an LspCallback that waits for formatting changes to be computed. When they're done,
2753 // it applies them, but only if the doc hasn't changed.
2754 //
2755 // TODO: provide some way to cancel this, probably as part of a more general job cancellation
2756 // scheme
make_format_callback( doc_id: DocumentId, doc_version: i32, modified: Modified, format: impl Future<Output = helix_lsp::util::LspFormatting> + Send + 'static, ) -> anyhow::Result<job::Callback>2757 async fn make_format_callback(
2758     doc_id: DocumentId,
2759     doc_version: i32,
2760     modified: Modified,
2761     format: impl Future<Output = helix_lsp::util::LspFormatting> + Send + 'static,
2762 ) -> anyhow::Result<job::Callback> {
2763     let format = format.await;
2764     let call: job::Callback = Box::new(move |editor: &mut Editor, _compositor: &mut Compositor| {
2765         let view_id = view!(editor).id;
2766         if let Some(doc) = editor.document_mut(doc_id) {
2767             if doc.version() == doc_version {
2768                 doc.apply(&Transaction::from(format), view_id);
2769                 doc.append_changes_to_history(view_id);
2770                 if let Modified::SetUnmodified = modified {
2771                     doc.reset_modified();
2772                 }
2773             } else {
2774                 log::info!("discarded formatting changes because the document changed");
2775             }
2776         }
2777     });
2778     Ok(call)
2779 }
2780 
2781 enum Open {
2782     Below,
2783     Above,
2784 }
2785 
open(cx: &mut Context, open: Open)2786 fn open(cx: &mut Context, open: Open) {
2787     let count = cx.count();
2788     let (view, doc) = current!(cx.editor);
2789     enter_insert_mode(doc);
2790 
2791     let text = doc.text().slice(..);
2792     let contents = doc.text();
2793     let selection = doc.selection(view.id);
2794 
2795     let mut ranges = SmallVec::with_capacity(selection.len());
2796     let mut offs = 0;
2797 
2798     let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
2799         let line = range.cursor_line(text);
2800 
2801         let line = match open {
2802             // adjust position to the end of the line (next line - 1)
2803             Open::Below => line + 1,
2804             // adjust position to the end of the previous line (current line - 1)
2805             Open::Above => line,
2806         };
2807 
2808         // Index to insert newlines after, as well as the char width
2809         // to use to compensate for those inserted newlines.
2810         let (line_end_index, line_end_offset_width) = if line == 0 {
2811             (0, 0)
2812         } else {
2813             (
2814                 line_end_char_index(&doc.text().slice(..), line.saturating_sub(1)),
2815                 doc.line_ending.len_chars(),
2816             )
2817         };
2818 
2819         // TODO: share logic with insert_newline for indentation
2820         let indent_level = indent::suggested_indent_for_pos(
2821             doc.language_config(),
2822             doc.syntax(),
2823             text,
2824             line_end_index,
2825             true,
2826         );
2827         let indent = doc.indent_unit().repeat(indent_level);
2828         let indent_len = indent.len();
2829         let mut text = String::with_capacity(1 + indent_len);
2830         text.push_str(doc.line_ending.as_str());
2831         text.push_str(&indent);
2832         let text = text.repeat(count);
2833 
2834         // calculate new selection ranges
2835         let pos = offs + line_end_index + line_end_offset_width;
2836         for i in 0..count {
2837             // pos                    -> beginning of reference line,
2838             // + (i * (1+indent_len)) -> beginning of i'th line from pos
2839             // + indent_len ->        -> indent for i'th line
2840             ranges.push(Range::point(pos + (i * (1 + indent_len)) + indent_len));
2841         }
2842 
2843         offs += text.chars().count();
2844 
2845         (line_end_index, line_end_index, Some(text.into()))
2846     });
2847 
2848     transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
2849 
2850     doc.apply(&transaction, view.id);
2851 }
2852 
2853 // o inserts a new line after each line with a selection
open_below(cx: &mut Context)2854 fn open_below(cx: &mut Context) {
2855     open(cx, Open::Below)
2856 }
2857 
2858 // O inserts a new line before each line with a selection
open_above(cx: &mut Context)2859 fn open_above(cx: &mut Context) {
2860     open(cx, Open::Above)
2861 }
2862 
normal_mode(cx: &mut Context)2863 fn normal_mode(cx: &mut Context) {
2864     let (view, doc) = current!(cx.editor);
2865 
2866     if doc.mode == Mode::Normal {
2867         return;
2868     }
2869 
2870     doc.mode = Mode::Normal;
2871 
2872     doc.append_changes_to_history(view.id);
2873 
2874     // if leaving append mode, move cursor back by 1
2875     if doc.restore_cursor {
2876         let text = doc.text().slice(..);
2877         let selection = doc.selection(view.id).clone().transform(|range| {
2878             Range::new(
2879                 range.from(),
2880                 graphemes::prev_grapheme_boundary(text, range.to()),
2881             )
2882         });
2883         doc.set_selection(view.id, selection);
2884 
2885         doc.restore_cursor = false;
2886     }
2887 }
2888 
2889 // Store a jump on the jumplist.
push_jump(editor: &mut Editor)2890 fn push_jump(editor: &mut Editor) {
2891     let (view, doc) = current!(editor);
2892     let jump = (doc.id(), doc.selection(view.id).clone());
2893     view.jumps.push(jump);
2894 }
2895 
goto_line(cx: &mut Context)2896 fn goto_line(cx: &mut Context) {
2897     if let Some(count) = cx.count {
2898         push_jump(cx.editor);
2899 
2900         let (view, doc) = current!(cx.editor);
2901         let max_line = if doc.text().line(doc.text().len_lines() - 1).len_chars() == 0 {
2902             // If the last line is blank, don't jump to it.
2903             doc.text().len_lines().saturating_sub(2)
2904         } else {
2905             doc.text().len_lines() - 1
2906         };
2907         let line_idx = std::cmp::min(count.get() - 1, max_line);
2908         let pos = doc.text().line_to_char(line_idx);
2909         doc.set_selection(view.id, Selection::point(pos));
2910     }
2911 }
2912 
goto_last_line(cx: &mut Context)2913 fn goto_last_line(cx: &mut Context) {
2914     push_jump(cx.editor);
2915 
2916     let (view, doc) = current!(cx.editor);
2917     let line_idx = if doc.text().line(doc.text().len_lines() - 1).len_chars() == 0 {
2918         // If the last line is blank, don't jump to it.
2919         doc.text().len_lines().saturating_sub(2)
2920     } else {
2921         doc.text().len_lines() - 1
2922     };
2923     let pos = doc.text().line_to_char(line_idx);
2924     doc.set_selection(view.id, Selection::point(pos));
2925 }
2926 
goto_last_accessed_file(cx: &mut Context)2927 fn goto_last_accessed_file(cx: &mut Context) {
2928     let alternate_file = view!(cx.editor).last_accessed_doc;
2929     if let Some(alt) = alternate_file {
2930         cx.editor.switch(alt, Action::Replace);
2931     } else {
2932         cx.editor.set_error("no last accessed buffer".to_owned())
2933     }
2934 }
2935 
select_mode(cx: &mut Context)2936 fn select_mode(cx: &mut Context) {
2937     let (view, doc) = current!(cx.editor);
2938     let text = doc.text().slice(..);
2939 
2940     // Make sure end-of-document selections are also 1-width.
2941     // (With the exception of being in an empty document, of course.)
2942     let selection = doc.selection(view.id).clone().transform(|range| {
2943         if range.is_empty() && range.head == text.len_chars() {
2944             Range::new(
2945                 graphemes::prev_grapheme_boundary(text, range.anchor),
2946                 range.head,
2947             )
2948         } else {
2949             range
2950         }
2951     });
2952     doc.set_selection(view.id, selection);
2953 
2954     doc_mut!(cx.editor).mode = Mode::Select;
2955 }
2956 
exit_select_mode(cx: &mut Context)2957 fn exit_select_mode(cx: &mut Context) {
2958     let doc = doc_mut!(cx.editor);
2959     if doc.mode == Mode::Select {
2960         doc.mode = Mode::Normal;
2961     }
2962 }
2963 
goto_impl( editor: &mut Editor, compositor: &mut Compositor, locations: Vec<lsp::Location>, offset_encoding: OffsetEncoding, )2964 fn goto_impl(
2965     editor: &mut Editor,
2966     compositor: &mut Compositor,
2967     locations: Vec<lsp::Location>,
2968     offset_encoding: OffsetEncoding,
2969 ) {
2970     push_jump(editor);
2971 
2972     fn jump_to(
2973         editor: &mut Editor,
2974         location: &lsp::Location,
2975         offset_encoding: OffsetEncoding,
2976         action: Action,
2977     ) {
2978         let path = location
2979             .uri
2980             .to_file_path()
2981             .expect("unable to convert URI to filepath");
2982         let _id = editor.open(path, action).expect("editor.open failed");
2983         let (view, doc) = current!(editor);
2984         let definition_pos = location.range.start;
2985         // TODO: convert inside server
2986         let new_pos =
2987             if let Some(new_pos) = lsp_pos_to_pos(doc.text(), definition_pos, offset_encoding) {
2988                 new_pos
2989             } else {
2990                 return;
2991             };
2992         doc.set_selection(view.id, Selection::point(new_pos));
2993         align_view(doc, view, Align::Center);
2994     }
2995 
2996     let cwdir = std::env::current_dir().expect("couldn't determine current directory");
2997 
2998     match locations.as_slice() {
2999         [location] => {
3000             jump_to(editor, location, offset_encoding, Action::Replace);
3001         }
3002         [] => {
3003             editor.set_error("No definition found.".to_string());
3004         }
3005         _locations => {
3006             let picker = FilePicker::new(
3007                 locations,
3008                 move |location| {
3009                     let file: Cow<'_, str> = (location.uri.scheme() == "file")
3010                         .then(|| {
3011                             location
3012                                 .uri
3013                                 .to_file_path()
3014                                 .map(|path| {
3015                                     // strip root prefix
3016                                     path.strip_prefix(&cwdir)
3017                                         .map(|path| path.to_path_buf())
3018                                         .unwrap_or(path)
3019                                 })
3020                                 .ok()
3021                                 .and_then(|path| path.to_str().map(|path| path.to_owned().into()))
3022                         })
3023                         .flatten()
3024                         .unwrap_or_else(|| location.uri.as_str().into());
3025                     let line = location.range.start.line;
3026                     format!("{}:{}", file, line).into()
3027                 },
3028                 move |editor: &mut Editor, location, action| {
3029                     jump_to(editor, location, offset_encoding, action)
3030                 },
3031                 |_editor, location| {
3032                     let path = location.uri.to_file_path().unwrap();
3033                     let line = Some((
3034                         location.range.start.line as usize,
3035                         location.range.end.line as usize,
3036                     ));
3037                     Some((path, line))
3038                 },
3039             );
3040             compositor.push(Box::new(picker));
3041         }
3042     }
3043 }
3044 
goto_definition(cx: &mut Context)3045 fn goto_definition(cx: &mut Context) {
3046     let (view, doc) = current!(cx.editor);
3047     let language_server = match doc.language_server() {
3048         Some(language_server) => language_server,
3049         None => return,
3050     };
3051 
3052     let offset_encoding = language_server.offset_encoding();
3053 
3054     let pos = pos_to_lsp_pos(
3055         doc.text(),
3056         doc.selection(view.id)
3057             .primary()
3058             .cursor(doc.text().slice(..)),
3059         offset_encoding,
3060     );
3061 
3062     let future = language_server.goto_definition(doc.identifier(), pos, None);
3063 
3064     cx.callback(
3065         future,
3066         move |editor: &mut Editor,
3067               compositor: &mut Compositor,
3068               response: Option<lsp::GotoDefinitionResponse>| {
3069             let items = match response {
3070                 Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location],
3071                 Some(lsp::GotoDefinitionResponse::Array(locations)) => locations,
3072                 Some(lsp::GotoDefinitionResponse::Link(locations)) => locations
3073                     .into_iter()
3074                     .map(|location_link| lsp::Location {
3075                         uri: location_link.target_uri,
3076                         range: location_link.target_range,
3077                     })
3078                     .collect(),
3079                 None => Vec::new(),
3080             };
3081 
3082             goto_impl(editor, compositor, items, offset_encoding);
3083         },
3084     );
3085 }
3086 
goto_type_definition(cx: &mut Context)3087 fn goto_type_definition(cx: &mut Context) {
3088     let (view, doc) = current!(cx.editor);
3089     let language_server = match doc.language_server() {
3090         Some(language_server) => language_server,
3091         None => return,
3092     };
3093 
3094     let offset_encoding = language_server.offset_encoding();
3095 
3096     let pos = pos_to_lsp_pos(
3097         doc.text(),
3098         doc.selection(view.id)
3099             .primary()
3100             .cursor(doc.text().slice(..)),
3101         offset_encoding,
3102     );
3103 
3104     let future = language_server.goto_type_definition(doc.identifier(), pos, None);
3105 
3106     cx.callback(
3107         future,
3108         move |editor: &mut Editor,
3109               compositor: &mut Compositor,
3110               response: Option<lsp::GotoDefinitionResponse>| {
3111             let items = match response {
3112                 Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location],
3113                 Some(lsp::GotoDefinitionResponse::Array(locations)) => locations,
3114                 Some(lsp::GotoDefinitionResponse::Link(locations)) => locations
3115                     .into_iter()
3116                     .map(|location_link| lsp::Location {
3117                         uri: location_link.target_uri,
3118                         range: location_link.target_range,
3119                     })
3120                     .collect(),
3121                 None => Vec::new(),
3122             };
3123 
3124             goto_impl(editor, compositor, items, offset_encoding);
3125         },
3126     );
3127 }
3128 
goto_implementation(cx: &mut Context)3129 fn goto_implementation(cx: &mut Context) {
3130     let (view, doc) = current!(cx.editor);
3131     let language_server = match doc.language_server() {
3132         Some(language_server) => language_server,
3133         None => return,
3134     };
3135 
3136     let offset_encoding = language_server.offset_encoding();
3137 
3138     let pos = pos_to_lsp_pos(
3139         doc.text(),
3140         doc.selection(view.id)
3141             .primary()
3142             .cursor(doc.text().slice(..)),
3143         offset_encoding,
3144     );
3145 
3146     let future = language_server.goto_implementation(doc.identifier(), pos, None);
3147 
3148     cx.callback(
3149         future,
3150         move |editor: &mut Editor,
3151               compositor: &mut Compositor,
3152               response: Option<lsp::GotoDefinitionResponse>| {
3153             let items = match response {
3154                 Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location],
3155                 Some(lsp::GotoDefinitionResponse::Array(locations)) => locations,
3156                 Some(lsp::GotoDefinitionResponse::Link(locations)) => locations
3157                     .into_iter()
3158                     .map(|location_link| lsp::Location {
3159                         uri: location_link.target_uri,
3160                         range: location_link.target_range,
3161                     })
3162                     .collect(),
3163                 None => Vec::new(),
3164             };
3165 
3166             goto_impl(editor, compositor, items, offset_encoding);
3167         },
3168     );
3169 }
3170 
goto_reference(cx: &mut Context)3171 fn goto_reference(cx: &mut Context) {
3172     let (view, doc) = current!(cx.editor);
3173     let language_server = match doc.language_server() {
3174         Some(language_server) => language_server,
3175         None => return,
3176     };
3177 
3178     let offset_encoding = language_server.offset_encoding();
3179 
3180     let pos = pos_to_lsp_pos(
3181         doc.text(),
3182         doc.selection(view.id)
3183             .primary()
3184             .cursor(doc.text().slice(..)),
3185         offset_encoding,
3186     );
3187 
3188     let future = language_server.goto_reference(doc.identifier(), pos, None);
3189 
3190     cx.callback(
3191         future,
3192         move |editor: &mut Editor,
3193               compositor: &mut Compositor,
3194               items: Option<Vec<lsp::Location>>| {
3195             goto_impl(
3196                 editor,
3197                 compositor,
3198                 items.unwrap_or_default(),
3199                 offset_encoding,
3200             );
3201         },
3202     );
3203 }
3204 
goto_pos(editor: &mut Editor, pos: usize)3205 fn goto_pos(editor: &mut Editor, pos: usize) {
3206     push_jump(editor);
3207 
3208     let (view, doc) = current!(editor);
3209 
3210     doc.set_selection(view.id, Selection::point(pos));
3211     align_view(doc, view, Align::Center);
3212 }
3213 
goto_first_diag(cx: &mut Context)3214 fn goto_first_diag(cx: &mut Context) {
3215     let editor = &mut cx.editor;
3216     let (_, doc) = current!(editor);
3217 
3218     let diag = if let Some(diag) = doc.diagnostics().first() {
3219         diag.range.start
3220     } else {
3221         return;
3222     };
3223 
3224     goto_pos(editor, diag);
3225 }
3226 
goto_last_diag(cx: &mut Context)3227 fn goto_last_diag(cx: &mut Context) {
3228     let editor = &mut cx.editor;
3229     let (_, doc) = current!(editor);
3230 
3231     let diag = if let Some(diag) = doc.diagnostics().last() {
3232         diag.range.start
3233     } else {
3234         return;
3235     };
3236 
3237     goto_pos(editor, diag);
3238 }
3239 
goto_next_diag(cx: &mut Context)3240 fn goto_next_diag(cx: &mut Context) {
3241     let editor = &mut cx.editor;
3242     let (view, doc) = current!(editor);
3243 
3244     let cursor_pos = doc
3245         .selection(view.id)
3246         .primary()
3247         .cursor(doc.text().slice(..));
3248     let diag = if let Some(diag) = doc
3249         .diagnostics()
3250         .iter()
3251         .map(|diag| diag.range.start)
3252         .find(|&pos| pos > cursor_pos)
3253     {
3254         diag
3255     } else if let Some(diag) = doc.diagnostics().first() {
3256         diag.range.start
3257     } else {
3258         return;
3259     };
3260 
3261     goto_pos(editor, diag);
3262 }
3263 
goto_prev_diag(cx: &mut Context)3264 fn goto_prev_diag(cx: &mut Context) {
3265     let editor = &mut cx.editor;
3266     let (view, doc) = current!(editor);
3267 
3268     let cursor_pos = doc
3269         .selection(view.id)
3270         .primary()
3271         .cursor(doc.text().slice(..));
3272     let diag = if let Some(diag) = doc
3273         .diagnostics()
3274         .iter()
3275         .rev()
3276         .map(|diag| diag.range.start)
3277         .find(|&pos| pos < cursor_pos)
3278     {
3279         diag
3280     } else if let Some(diag) = doc.diagnostics().last() {
3281         diag.range.start
3282     } else {
3283         return;
3284     };
3285 
3286     goto_pos(editor, diag);
3287 }
3288 
signature_help(cx: &mut Context)3289 fn signature_help(cx: &mut Context) {
3290     let (view, doc) = current!(cx.editor);
3291 
3292     let language_server = match doc.language_server() {
3293         Some(language_server) => language_server,
3294         None => return,
3295     };
3296 
3297     let pos = pos_to_lsp_pos(
3298         doc.text(),
3299         doc.selection(view.id)
3300             .primary()
3301             .cursor(doc.text().slice(..)),
3302         language_server.offset_encoding(),
3303     );
3304 
3305     let future = language_server.text_document_signature_help(doc.identifier(), pos, None);
3306 
3307     cx.callback(
3308         future,
3309         move |_editor: &mut Editor,
3310               _compositor: &mut Compositor,
3311               response: Option<lsp::SignatureHelp>| {
3312             if let Some(signature_help) = response {
3313                 log::info!("{:?}", signature_help);
3314                 // signatures
3315                 // active_signature
3316                 // active_parameter
3317                 // render as:
3318 
3319                 // signature
3320                 // ----------
3321                 // doc
3322 
3323                 // with active param highlighted
3324             }
3325         },
3326     );
3327 }
3328 
3329 // NOTE: Transactions in this module get appended to history when we switch back to normal mode.
3330 pub mod insert {
3331     use super::*;
3332     pub type Hook = fn(&Rope, &Selection, char) -> Option<Transaction>;
3333     pub type PostHook = fn(&mut Context, char);
3334 
3335     // It trigger completion when idle timer reaches deadline
3336     // Only trigger completion if the word under cursor is longer than n characters
idle_completion(cx: &mut Context)3337     pub fn idle_completion(cx: &mut Context) {
3338         let (view, doc) = current!(cx.editor);
3339         let text = doc.text().slice(..);
3340         let cursor = doc.selection(view.id).primary().cursor(text);
3341 
3342         use helix_core::chars::char_is_word;
3343         let mut iter = text.chars_at(cursor);
3344         iter.reverse();
3345         for _ in 0..cx.editor.config.completion_trigger_len {
3346             match iter.next() {
3347                 Some(c) if char_is_word(c) => {}
3348                 _ => return,
3349             }
3350         }
3351         super::completion(cx);
3352     }
3353 
language_server_completion(cx: &mut Context, ch: char)3354     fn language_server_completion(cx: &mut Context, ch: char) {
3355         // if ch matches completion char, trigger completion
3356         let doc = doc_mut!(cx.editor);
3357         let language_server = match doc.language_server() {
3358             Some(language_server) => language_server,
3359             None => return,
3360         };
3361 
3362         let capabilities = language_server.capabilities();
3363 
3364         if let Some(lsp::CompletionOptions {
3365             trigger_characters: Some(triggers),
3366             ..
3367         }) = &capabilities.completion_provider
3368         {
3369             // TODO: what if trigger is multiple chars long
3370             if triggers.iter().any(|trigger| trigger.contains(ch)) {
3371                 cx.editor.clear_idle_timer();
3372                 super::completion(cx);
3373             }
3374         }
3375     }
3376 
signature_help(cx: &mut Context, ch: char)3377     fn signature_help(cx: &mut Context, ch: char) {
3378         // if ch matches signature_help char, trigger
3379         let doc = doc_mut!(cx.editor);
3380         let language_server = match doc.language_server() {
3381             Some(language_server) => language_server,
3382             None => return,
3383         };
3384 
3385         let capabilities = language_server.capabilities();
3386 
3387         if let lsp::ServerCapabilities {
3388             signature_help_provider:
3389                 Some(lsp::SignatureHelpOptions {
3390                     trigger_characters: Some(triggers),
3391                     // TODO: retrigger_characters
3392                     ..
3393                 }),
3394             ..
3395         } = capabilities
3396         {
3397             // TODO: what if trigger is multiple chars long
3398             let is_trigger = triggers.iter().any(|trigger| trigger.contains(ch));
3399 
3400             if is_trigger {
3401                 super::signature_help(cx);
3402             }
3403         }
3404 
3405         // SignatureHelp {
3406         // signatures: [
3407         //  SignatureInformation {
3408         //      label: "fn open(&mut self, path: PathBuf, action: Action) -> Result<DocumentId, Error>",
3409         //      documentation: None,
3410         //      parameters: Some(
3411         //          [ParameterInformation { label: Simple("path: PathBuf"), documentation: None },
3412         //          ParameterInformation { label: Simple("action: Action"), documentation: None }]
3413         //      ),
3414         //      active_parameter: Some(0)
3415         //  }
3416         // ],
3417         // active_signature: None, active_parameter: Some(0)
3418         // }
3419     }
3420 
3421     // The default insert hook: simply insert the character
3422     #[allow(clippy::unnecessary_wraps)] // need to use Option<> because of the Hook signature
insert(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction>3423     fn insert(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
3424         let t = Tendril::from_char(ch);
3425         let transaction = Transaction::insert(doc, selection, t);
3426         Some(transaction)
3427     }
3428 
3429     use helix_core::auto_pairs;
3430 
insert_char(cx: &mut Context, c: char)3431     pub fn insert_char(cx: &mut Context, c: char) {
3432         let (view, doc) = current!(cx.editor);
3433 
3434         let hooks: &[Hook] = match cx.editor.config.auto_pairs {
3435             true => &[auto_pairs::hook, insert],
3436             false => &[insert],
3437         };
3438 
3439         let text = doc.text();
3440         let selection = doc.selection(view.id).clone().cursors(text.slice(..));
3441 
3442         // run through insert hooks, stopping on the first one that returns Some(t)
3443         for hook in hooks {
3444             if let Some(transaction) = hook(text, &selection, c) {
3445                 doc.apply(&transaction, view.id);
3446                 break;
3447             }
3448         }
3449 
3450         // TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc)
3451         // this could also generically look at Transaction, but it's a bit annoying to look at
3452         // Operation instead of Change.
3453         for hook in &[language_server_completion, signature_help] {
3454             // for hook in &[signature_help] {
3455             hook(cx, c);
3456         }
3457     }
3458 
insert_tab(cx: &mut Context)3459     pub fn insert_tab(cx: &mut Context) {
3460         let (view, doc) = current!(cx.editor);
3461         // TODO: round out to nearest indentation level (for example a line with 3 spaces should
3462         // indent by one to reach 4 spaces).
3463 
3464         let indent = Tendril::from(doc.indent_unit());
3465         let transaction = Transaction::insert(
3466             doc.text(),
3467             &doc.selection(view.id).clone().cursors(doc.text().slice(..)),
3468             indent,
3469         );
3470         doc.apply(&transaction, view.id);
3471     }
3472 
insert_newline(cx: &mut Context)3473     pub fn insert_newline(cx: &mut Context) {
3474         let (view, doc) = current!(cx.editor);
3475         let text = doc.text().slice(..);
3476 
3477         let contents = doc.text();
3478         let selection = doc.selection(view.id).clone().cursors(text);
3479         let mut ranges = SmallVec::with_capacity(selection.len());
3480 
3481         // TODO: this is annoying, but we need to do it to properly calculate pos after edits
3482         let mut offs = 0;
3483 
3484         let mut transaction = Transaction::change_by_selection(contents, &selection, |range| {
3485             let pos = range.head;
3486 
3487             let prev = if pos == 0 {
3488                 ' '
3489             } else {
3490                 contents.char(pos - 1)
3491             };
3492             let curr = contents.get_char(pos).unwrap_or(' ');
3493 
3494             // TODO: offset range.head by 1? when calculating?
3495             let indent_level = indent::suggested_indent_for_pos(
3496                 doc.language_config(),
3497                 doc.syntax(),
3498                 text,
3499                 pos.saturating_sub(1),
3500                 true,
3501             );
3502             let indent = doc.indent_unit().repeat(indent_level);
3503             let mut text = String::with_capacity(1 + indent.len());
3504             text.push_str(doc.line_ending.as_str());
3505             text.push_str(&indent);
3506 
3507             let head = pos + offs + text.chars().count();
3508 
3509             // TODO: range replace or extend
3510             // range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
3511             // can be used with cx.mode to do replace or extend on most changes
3512             ranges.push(Range::new(
3513                 if range.is_empty() {
3514                     head
3515                 } else {
3516                     range.anchor + offs
3517                 },
3518                 head,
3519             ));
3520 
3521             // if between a bracket pair
3522             if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
3523                 // another newline, indent the end bracket one level less
3524                 let indent = doc.indent_unit().repeat(indent_level.saturating_sub(1));
3525                 text.push_str(doc.line_ending.as_str());
3526                 text.push_str(&indent);
3527             }
3528 
3529             offs += text.chars().count();
3530 
3531             (pos, pos, Some(text.into()))
3532         });
3533 
3534         transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
3535         //
3536 
3537         doc.apply(&transaction, view.id);
3538     }
3539 
3540     // TODO: handle indent-aware delete
delete_char_backward(cx: &mut Context)3541     pub fn delete_char_backward(cx: &mut Context) {
3542         let count = cx.count();
3543         let (view, doc) = current!(cx.editor);
3544         let text = doc.text().slice(..);
3545         let transaction =
3546             Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3547                 let pos = range.cursor(text);
3548                 (
3549                     graphemes::nth_prev_grapheme_boundary(text, pos, count),
3550                     pos,
3551                     None,
3552                 )
3553             });
3554         doc.apply(&transaction, view.id);
3555     }
3556 
delete_char_forward(cx: &mut Context)3557     pub fn delete_char_forward(cx: &mut Context) {
3558         let count = cx.count();
3559         let (view, doc) = current!(cx.editor);
3560         let text = doc.text().slice(..);
3561         let transaction =
3562             Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3563                 let pos = range.cursor(text);
3564                 (
3565                     pos,
3566                     graphemes::nth_next_grapheme_boundary(text, pos, count),
3567                     None,
3568                 )
3569             });
3570         doc.apply(&transaction, view.id);
3571     }
3572 
delete_word_backward(cx: &mut Context)3573     pub fn delete_word_backward(cx: &mut Context) {
3574         let count = cx.count();
3575         let (view, doc) = current!(cx.editor);
3576         let text = doc.text().slice(..);
3577 
3578         let selection = doc
3579             .selection(view.id)
3580             .clone()
3581             .transform(|range| movement::move_prev_word_start(text, range, count));
3582         doc.set_selection(view.id, selection);
3583         delete_selection(cx)
3584     }
3585 }
3586 
3587 // Undo / Redo
3588 
3589 // TODO: each command could simply return a Option<transaction>, then the higher level handles
3590 // storing it?
3591 
undo(cx: &mut Context)3592 fn undo(cx: &mut Context) {
3593     let (view, doc) = current!(cx.editor);
3594     let view_id = view.id;
3595     doc.undo(view_id);
3596 }
3597 
redo(cx: &mut Context)3598 fn redo(cx: &mut Context) {
3599     let (view, doc) = current!(cx.editor);
3600     let view_id = view.id;
3601     doc.redo(view_id);
3602 }
3603 
3604 // Yank / Paste
3605 
yank(cx: &mut Context)3606 fn yank(cx: &mut Context) {
3607     let (view, doc) = current!(cx.editor);
3608     let text = doc.text().slice(..);
3609 
3610     let values: Vec<String> = doc
3611         .selection(view.id)
3612         .fragments(text)
3613         .map(Cow::into_owned)
3614         .collect();
3615 
3616     let msg = format!(
3617         "yanked {} selection(s) to register {}",
3618         values.len(),
3619         cx.register.unwrap_or('"')
3620     );
3621 
3622     cx.editor
3623         .registers
3624         .write(cx.register.unwrap_or('"'), values);
3625 
3626     cx.editor.set_status(msg);
3627     exit_select_mode(cx);
3628 }
3629 
yank_joined_to_clipboard_impl( editor: &mut Editor, separator: &str, clipboard_type: ClipboardType, ) -> anyhow::Result<()>3630 fn yank_joined_to_clipboard_impl(
3631     editor: &mut Editor,
3632     separator: &str,
3633     clipboard_type: ClipboardType,
3634 ) -> anyhow::Result<()> {
3635     let (view, doc) = current!(editor);
3636     let text = doc.text().slice(..);
3637 
3638     let values: Vec<String> = doc
3639         .selection(view.id)
3640         .fragments(text)
3641         .map(Cow::into_owned)
3642         .collect();
3643 
3644     let msg = format!(
3645         "joined and yanked {} selection(s) to system clipboard",
3646         values.len(),
3647     );
3648 
3649     let joined = values.join(separator);
3650 
3651     editor
3652         .clipboard_provider
3653         .set_contents(joined, clipboard_type)
3654         .context("Couldn't set system clipboard content")?;
3655 
3656     editor.set_status(msg);
3657 
3658     Ok(())
3659 }
3660 
yank_joined_to_clipboard(cx: &mut Context)3661 fn yank_joined_to_clipboard(cx: &mut Context) {
3662     let line_ending = current!(cx.editor).1.line_ending;
3663     let _ = yank_joined_to_clipboard_impl(
3664         &mut cx.editor,
3665         line_ending.as_str(),
3666         ClipboardType::Clipboard,
3667     );
3668     exit_select_mode(cx);
3669 }
3670 
yank_main_selection_to_clipboard_impl( editor: &mut Editor, clipboard_type: ClipboardType, ) -> anyhow::Result<()>3671 fn yank_main_selection_to_clipboard_impl(
3672     editor: &mut Editor,
3673     clipboard_type: ClipboardType,
3674 ) -> anyhow::Result<()> {
3675     let (view, doc) = current!(editor);
3676     let text = doc.text().slice(..);
3677 
3678     let value = doc.selection(view.id).primary().fragment(text);
3679 
3680     if let Err(e) = editor
3681         .clipboard_provider
3682         .set_contents(value.into_owned(), clipboard_type)
3683     {
3684         bail!("Couldn't set system clipboard content: {}", e);
3685     }
3686 
3687     editor.set_status("yanked main selection to system clipboard".to_owned());
3688     Ok(())
3689 }
3690 
yank_main_selection_to_clipboard(cx: &mut Context)3691 fn yank_main_selection_to_clipboard(cx: &mut Context) {
3692     let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard);
3693 }
3694 
yank_joined_to_primary_clipboard(cx: &mut Context)3695 fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3696     let line_ending = current!(cx.editor).1.line_ending;
3697     let _ = yank_joined_to_clipboard_impl(
3698         &mut cx.editor,
3699         line_ending.as_str(),
3700         ClipboardType::Selection,
3701     );
3702 }
3703 
yank_main_selection_to_primary_clipboard(cx: &mut Context)3704 fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
3705     let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Selection);
3706     exit_select_mode(cx);
3707 }
3708 
3709 #[derive(Copy, Clone)]
3710 enum Paste {
3711     Before,
3712     After,
3713 }
3714 
paste_impl( values: &[String], doc: &mut Document, view: &View, action: Paste, ) -> Option<Transaction>3715 fn paste_impl(
3716     values: &[String],
3717     doc: &mut Document,
3718     view: &View,
3719     action: Paste,
3720 ) -> Option<Transaction> {
3721     let repeat = std::iter::repeat(
3722         values
3723             .last()
3724             .map(|value| Tendril::from_slice(value))
3725             .unwrap(),
3726     );
3727 
3728     // if any of values ends with a line ending, it's linewise paste
3729     let linewise = values
3730         .iter()
3731         .any(|value| get_line_ending_of_str(value).is_some());
3732 
3733     // Only compiled once.
3734     #[allow(clippy::trivial_regex)]
3735     static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\r\n|\r|\n").unwrap());
3736     let mut values = values
3737         .iter()
3738         .map(|value| REGEX.replace_all(value, doc.line_ending.as_str()))
3739         .map(|value| Tendril::from(value.as_ref()))
3740         .chain(repeat);
3741 
3742     let text = doc.text();
3743     let selection = doc.selection(view.id);
3744 
3745     let transaction = Transaction::change_by_selection(text, selection, |range| {
3746         let pos = match (action, linewise) {
3747             // paste linewise before
3748             (Paste::Before, true) => text.line_to_char(text.char_to_line(range.from())),
3749             // paste linewise after
3750             (Paste::After, true) => {
3751                 let line = range.line_range(text.slice(..)).1;
3752                 text.line_to_char((line + 1).min(text.len_lines()))
3753             }
3754             // paste insert
3755             (Paste::Before, false) => range.from(),
3756             // paste append
3757             (Paste::After, false) => range.to(),
3758         };
3759         (pos, pos, Some(values.next().unwrap()))
3760     });
3761 
3762     Some(transaction)
3763 }
3764 
paste_clipboard_impl( editor: &mut Editor, action: Paste, clipboard_type: ClipboardType, ) -> anyhow::Result<()>3765 fn paste_clipboard_impl(
3766     editor: &mut Editor,
3767     action: Paste,
3768     clipboard_type: ClipboardType,
3769 ) -> anyhow::Result<()> {
3770     let (view, doc) = current!(editor);
3771 
3772     match editor
3773         .clipboard_provider
3774         .get_contents(clipboard_type)
3775         .map(|contents| paste_impl(&[contents], doc, view, action))
3776     {
3777         Ok(Some(transaction)) => {
3778             doc.apply(&transaction, view.id);
3779             doc.append_changes_to_history(view.id);
3780             Ok(())
3781         }
3782         Ok(None) => Ok(()),
3783         Err(e) => Err(e.context("Couldn't get system clipboard contents")),
3784     }
3785 }
3786 
paste_clipboard_after(cx: &mut Context)3787 fn paste_clipboard_after(cx: &mut Context) {
3788     let _ = paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard);
3789 }
3790 
paste_clipboard_before(cx: &mut Context)3791 fn paste_clipboard_before(cx: &mut Context) {
3792     let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before, ClipboardType::Clipboard);
3793 }
3794 
paste_primary_clipboard_after(cx: &mut Context)3795 fn paste_primary_clipboard_after(cx: &mut Context) {
3796     let _ = paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection);
3797 }
3798 
paste_primary_clipboard_before(cx: &mut Context)3799 fn paste_primary_clipboard_before(cx: &mut Context) {
3800     let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before, ClipboardType::Selection);
3801 }
3802 
replace_with_yanked(cx: &mut Context)3803 fn replace_with_yanked(cx: &mut Context) {
3804     let reg_name = cx.register.unwrap_or('"');
3805     let (view, doc) = current!(cx.editor);
3806     let registers = &mut cx.editor.registers;
3807 
3808     if let Some(values) = registers.read(reg_name) {
3809         if !values.is_empty() {
3810             let repeat = std::iter::repeat(
3811                 values
3812                     .last()
3813                     .map(|value| Tendril::from_slice(value))
3814                     .unwrap(),
3815             );
3816             let mut values = values
3817                 .iter()
3818                 .map(|value| Tendril::from_slice(value))
3819                 .chain(repeat);
3820             let selection = doc.selection(view.id);
3821             let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3822                 if !range.is_empty() {
3823                     (range.from(), range.to(), Some(values.next().unwrap()))
3824                 } else {
3825                     (range.from(), range.to(), None)
3826                 }
3827             });
3828 
3829             doc.apply(&transaction, view.id);
3830             doc.append_changes_to_history(view.id);
3831         }
3832     }
3833 }
3834 
replace_selections_with_clipboard_impl( editor: &mut Editor, clipboard_type: ClipboardType, ) -> anyhow::Result<()>3835 fn replace_selections_with_clipboard_impl(
3836     editor: &mut Editor,
3837     clipboard_type: ClipboardType,
3838 ) -> anyhow::Result<()> {
3839     let (view, doc) = current!(editor);
3840 
3841     match editor.clipboard_provider.get_contents(clipboard_type) {
3842         Ok(contents) => {
3843             let selection = doc.selection(view.id);
3844             let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3845                 (range.from(), range.to(), Some(contents.as_str().into()))
3846             });
3847 
3848             doc.apply(&transaction, view.id);
3849             doc.append_changes_to_history(view.id);
3850             Ok(())
3851         }
3852         Err(e) => Err(e.context("Couldn't get system clipboard contents")),
3853     }
3854 }
3855 
replace_selections_with_clipboard(cx: &mut Context)3856 fn replace_selections_with_clipboard(cx: &mut Context) {
3857     let _ = replace_selections_with_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard);
3858 }
3859 
replace_selections_with_primary_clipboard(cx: &mut Context)3860 fn replace_selections_with_primary_clipboard(cx: &mut Context) {
3861     let _ = replace_selections_with_clipboard_impl(&mut cx.editor, ClipboardType::Selection);
3862 }
3863 
paste_after(cx: &mut Context)3864 fn paste_after(cx: &mut Context) {
3865     let reg_name = cx.register.unwrap_or('"');
3866     let (view, doc) = current!(cx.editor);
3867     let registers = &mut cx.editor.registers;
3868 
3869     if let Some(transaction) = registers
3870         .read(reg_name)
3871         .and_then(|values| paste_impl(values, doc, view, Paste::After))
3872     {
3873         doc.apply(&transaction, view.id);
3874         doc.append_changes_to_history(view.id);
3875     }
3876 }
3877 
paste_before(cx: &mut Context)3878 fn paste_before(cx: &mut Context) {
3879     let reg_name = cx.register.unwrap_or('"');
3880     let (view, doc) = current!(cx.editor);
3881     let registers = &mut cx.editor.registers;
3882 
3883     if let Some(transaction) = registers
3884         .read(reg_name)
3885         .and_then(|values| paste_impl(values, doc, view, Paste::Before))
3886     {
3887         doc.apply(&transaction, view.id);
3888         doc.append_changes_to_history(view.id);
3889     }
3890 }
3891 
get_lines(doc: &Document, view_id: ViewId) -> Vec<usize>3892 fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
3893     let mut lines = Vec::new();
3894 
3895     // Get all line numbers
3896     for range in doc.selection(view_id) {
3897         let (start, end) = range.line_range(doc.text().slice(..));
3898 
3899         for line in start..=end {
3900             lines.push(line)
3901         }
3902     }
3903     lines.sort_unstable(); // sorting by usize so _unstable is preferred
3904     lines.dedup();
3905     lines
3906 }
3907 
indent(cx: &mut Context)3908 fn indent(cx: &mut Context) {
3909     let count = cx.count();
3910     let (view, doc) = current!(cx.editor);
3911     let lines = get_lines(doc, view.id);
3912 
3913     // Indent by one level
3914     let indent = Tendril::from(doc.indent_unit().repeat(count));
3915 
3916     let transaction = Transaction::change(
3917         doc.text(),
3918         lines.into_iter().map(|line| {
3919             let pos = doc.text().line_to_char(line);
3920             (pos, pos, Some(indent.clone()))
3921         }),
3922     );
3923     doc.apply(&transaction, view.id);
3924     doc.append_changes_to_history(view.id);
3925 }
3926 
unindent(cx: &mut Context)3927 fn unindent(cx: &mut Context) {
3928     let count = cx.count();
3929     let (view, doc) = current!(cx.editor);
3930     let lines = get_lines(doc, view.id);
3931     let mut changes = Vec::with_capacity(lines.len());
3932     let tab_width = doc.tab_width();
3933     let indent_width = count * tab_width;
3934 
3935     for line_idx in lines {
3936         let line = doc.text().line(line_idx);
3937         let mut width = 0;
3938         let mut pos = 0;
3939 
3940         for ch in line.chars() {
3941             match ch {
3942                 ' ' => width += 1,
3943                 '\t' => width = (width / tab_width + 1) * tab_width,
3944                 _ => break,
3945             }
3946 
3947             pos += 1;
3948 
3949             if width >= indent_width {
3950                 break;
3951             }
3952         }
3953 
3954         // now delete from start to first non-blank
3955         if pos > 0 {
3956             let start = doc.text().line_to_char(line_idx);
3957             changes.push((start, start + pos, None))
3958         }
3959     }
3960 
3961     let transaction = Transaction::change(doc.text(), changes.into_iter());
3962 
3963     doc.apply(&transaction, view.id);
3964     doc.append_changes_to_history(view.id);
3965 }
3966 
format_selections(cx: &mut Context)3967 fn format_selections(cx: &mut Context) {
3968     let (view, doc) = current!(cx.editor);
3969 
3970     // via lsp if available
3971     // else via tree-sitter indentation calculations
3972 
3973     let language_server = match doc.language_server() {
3974         Some(language_server) => language_server,
3975         None => return,
3976     };
3977 
3978     let ranges: Vec<lsp::Range> = doc
3979         .selection(view.id)
3980         .iter()
3981         .map(|range| range_to_lsp_range(doc.text(), *range, language_server.offset_encoding()))
3982         .collect();
3983 
3984     // TODO: all of the TODO's and commented code inside the loop,
3985     // to make this actually work.
3986     for _range in ranges {
3987         let _language_server = match doc.language_server() {
3988             Some(language_server) => language_server,
3989             None => return,
3990         };
3991         // TODO: handle fails
3992         // TODO: concurrent map
3993 
3994         // TODO: need to block to get the formatting
3995 
3996         // let edits = block_on(language_server.text_document_range_formatting(
3997         //     doc.identifier(),
3998         //     range,
3999         //     lsp::FormattingOptions::default(),
4000         // ))
4001         // .unwrap_or_default();
4002 
4003         // let transaction = helix_lsp::util::generate_transaction_from_edits(
4004         //     doc.text(),
4005         //     edits,
4006         //     language_server.offset_encoding(),
4007         // );
4008 
4009         // doc.apply(&transaction, view.id);
4010     }
4011 
4012     doc.append_changes_to_history(view.id);
4013 }
4014 
join_selections(cx: &mut Context)4015 fn join_selections(cx: &mut Context) {
4016     use movement::skip_while;
4017     let (view, doc) = current!(cx.editor);
4018     let text = doc.text();
4019     let slice = doc.text().slice(..);
4020 
4021     let mut changes = Vec::new();
4022     let fragment = Tendril::from(" ");
4023 
4024     for selection in doc.selection(view.id) {
4025         let (start, mut end) = selection.line_range(slice);
4026         if start == end {
4027             end = (end + 1).min(text.len_lines() - 1);
4028         }
4029         let lines = start..end;
4030 
4031         changes.reserve(lines.len());
4032 
4033         for line in lines {
4034             let start = line_end_char_index(&slice, line);
4035             let mut end = text.line_to_char(line + 1);
4036             end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
4037 
4038             // need to skip from start, not end
4039             let change = (start, end, Some(fragment.clone()));
4040             changes.push(change);
4041         }
4042     }
4043 
4044     changes.sort_unstable_by_key(|(from, _to, _text)| *from);
4045     changes.dedup();
4046 
4047     // TODO: joining multiple empty lines should be replaced by a single space.
4048     // need to merge change ranges that touch
4049 
4050     let transaction = Transaction::change(doc.text(), changes.into_iter());
4051     // TODO: select inserted spaces
4052     // .with_selection(selection);
4053 
4054     doc.apply(&transaction, view.id);
4055     doc.append_changes_to_history(view.id);
4056 }
4057 
keep_selections(cx: &mut Context)4058 fn keep_selections(cx: &mut Context) {
4059     // keep selections matching regex
4060     let reg = cx.register.unwrap_or('/');
4061     let prompt = ui::regex_prompt(
4062         cx,
4063         "keep:".into(),
4064         Some(reg),
4065         move |view, doc, regex, event| {
4066             if event != PromptEvent::Update {
4067                 return;
4068             }
4069             let text = doc.text().slice(..);
4070 
4071             if let Some(selection) = selection::keep_matches(text, doc.selection(view.id), &regex) {
4072                 doc.set_selection(view.id, selection);
4073             }
4074         },
4075     );
4076 
4077     cx.push_layer(Box::new(prompt));
4078 }
4079 
keep_primary_selection(cx: &mut Context)4080 fn keep_primary_selection(cx: &mut Context) {
4081     let (view, doc) = current!(cx.editor);
4082     // TODO: handle count
4083 
4084     let range = doc.selection(view.id).primary();
4085     doc.set_selection(view.id, Selection::single(range.anchor, range.head));
4086 }
4087 
remove_primary_selection(cx: &mut Context)4088 fn remove_primary_selection(cx: &mut Context) {
4089     let (view, doc) = current!(cx.editor);
4090     // TODO: handle count
4091 
4092     let selection = doc.selection(view.id);
4093     if selection.len() == 1 {
4094         cx.editor.set_error("no selections remaining".to_owned());
4095         return;
4096     }
4097     let index = selection.primary_index();
4098     let selection = selection.clone().remove(index);
4099 
4100     doc.set_selection(view.id, selection);
4101 }
4102 
completion(cx: &mut Context)4103 pub fn completion(cx: &mut Context) {
4104     // trigger on trigger char, or if user calls it
4105     // (or on word char typing??)
4106     // after it's triggered, if response marked is_incomplete, update on every subsequent keypress
4107     //
4108     // lsp calls are done via a callback: it sends a request and doesn't block.
4109     // when we get the response similarly to notification, trigger a call to the completion popup
4110     //
4111     // language_server.completion(params, |cx: &mut Context, _meta, response| {
4112     //    // called at response time
4113     //    // compositor, lookup completion layer
4114     //    // downcast dyn Component to Completion component
4115     //    // emit response to completion (completion.complete/handle(response))
4116     // })
4117     //
4118     // typing after prompt opens: usually start offset is tracked and everything between
4119     // start_offset..cursor is replaced. For our purposes we could keep the start state (doc,
4120     // selection) and revert to them before applying. This needs to properly reset changes/history
4121     // though...
4122     //
4123     // company-mode does this by matching the prefix of the completion and removing it.
4124 
4125     // ignore isIncomplete for now
4126     // keep state while typing
4127     // the behavior should be, filter the menu based on input
4128     // if items returns empty at any point, remove the popup
4129     // if backspace past initial offset point, remove the popup
4130     //
4131     // debounce requests!
4132     //
4133     // need an idle timeout thing.
4134     // https://github.com/company-mode/company-mode/blob/master/company.el#L620-L622
4135     //
4136     //  "The idle delay in seconds until completion starts automatically.
4137     // The prefix still has to satisfy `company-minimum-prefix-length' before that
4138     // happens.  The value of nil means no idle completion."
4139 
4140     let (view, doc) = current!(cx.editor);
4141 
4142     let language_server = match doc.language_server() {
4143         Some(language_server) => language_server,
4144         None => return,
4145     };
4146 
4147     let offset_encoding = language_server.offset_encoding();
4148     let text = doc.text().slice(..);
4149     let cursor = doc.selection(view.id).primary().cursor(text);
4150 
4151     let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
4152 
4153     let future = language_server.completion(doc.identifier(), pos, None);
4154 
4155     let trigger_offset = cursor;
4156 
4157     // TODO: trigger_offset should be the cursor offset but we also need a starting offset from where we want to apply
4158     // completion filtering. For example logger.te| should filter the initial suggestion list with "te".
4159 
4160     use helix_core::chars;
4161     let mut iter = text.chars_at(cursor);
4162     iter.reverse();
4163     let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count();
4164     let start_offset = cursor.saturating_sub(offset);
4165     let prefix = text.slice(start_offset..cursor).to_string();
4166 
4167     cx.callback(
4168         future,
4169         move |editor: &mut Editor,
4170               compositor: &mut Compositor,
4171               response: Option<lsp::CompletionResponse>| {
4172             let (_, doc) = current!(editor);
4173             if doc.mode() != Mode::Insert {
4174                 // we're not in insert mode anymore
4175                 return;
4176             }
4177 
4178             let mut items = match response {
4179                 Some(lsp::CompletionResponse::Array(items)) => items,
4180                 // TODO: do something with is_incomplete
4181                 Some(lsp::CompletionResponse::List(lsp::CompletionList {
4182                     is_incomplete: _is_incomplete,
4183                     items,
4184                 })) => items,
4185                 None => Vec::new(),
4186             };
4187 
4188             if !prefix.is_empty() {
4189                 items = items
4190                     .into_iter()
4191                     .filter(|item| {
4192                         item.filter_text
4193                             .as_ref()
4194                             .unwrap_or(&item.label)
4195                             .starts_with(&prefix)
4196                     })
4197                     .collect();
4198             }
4199 
4200             if items.is_empty() {
4201                 // editor.set_error("No completion available".to_string());
4202                 return;
4203             }
4204             let size = compositor.size();
4205             let ui = compositor
4206                 .find(std::any::type_name::<ui::EditorView>())
4207                 .unwrap();
4208             if let Some(ui) = ui.as_any_mut().downcast_mut::<ui::EditorView>() {
4209                 ui.set_completion(
4210                     editor,
4211                     items,
4212                     offset_encoding,
4213                     start_offset,
4214                     trigger_offset,
4215                     size,
4216                 );
4217             };
4218         },
4219     );
4220 }
4221 
hover(cx: &mut Context)4222 fn hover(cx: &mut Context) {
4223     let (view, doc) = current!(cx.editor);
4224 
4225     let language_server = match doc.language_server() {
4226         Some(language_server) => language_server,
4227         None => return,
4228     };
4229 
4230     // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
4231 
4232     let pos = pos_to_lsp_pos(
4233         doc.text(),
4234         doc.selection(view.id)
4235             .primary()
4236             .cursor(doc.text().slice(..)),
4237         language_server.offset_encoding(),
4238     );
4239 
4240     let future = language_server.text_document_hover(doc.identifier(), pos, None);
4241 
4242     cx.callback(
4243         future,
4244         move |editor: &mut Editor, compositor: &mut Compositor, response: Option<lsp::Hover>| {
4245             if let Some(hover) = response {
4246                 // hover.contents / .range <- used for visualizing
4247                 let contents = match hover.contents {
4248                     lsp::HoverContents::Scalar(contents) => {
4249                         // markedstring(string/languagestring to be highlighted)
4250                         // TODO
4251                         log::error!("hover contents {:?}", contents);
4252                         return;
4253                     }
4254                     lsp::HoverContents::Array(contents) => {
4255                         log::error!("hover contents {:?}", contents);
4256                         return;
4257                     }
4258                     // TODO: render markdown
4259                     lsp::HoverContents::Markup(contents) => contents.value,
4260                 };
4261 
4262                 // skip if contents empty
4263 
4264                 let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
4265                 let popup = Popup::new(contents);
4266                 compositor.push(Box::new(popup));
4267             }
4268         },
4269     );
4270 }
4271 
4272 // comments
toggle_comments(cx: &mut Context)4273 fn toggle_comments(cx: &mut Context) {
4274     let (view, doc) = current!(cx.editor);
4275     let token = doc
4276         .language_config()
4277         .and_then(|lc| lc.comment_token.as_ref())
4278         .map(|tc| tc.as_ref());
4279     let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view.id), token);
4280 
4281     doc.apply(&transaction, view.id);
4282     doc.append_changes_to_history(view.id);
4283     exit_select_mode(cx);
4284 }
4285 
rotate_selections(cx: &mut Context, direction: Direction)4286 fn rotate_selections(cx: &mut Context, direction: Direction) {
4287     let count = cx.count();
4288     let (view, doc) = current!(cx.editor);
4289     let mut selection = doc.selection(view.id).clone();
4290     let index = selection.primary_index();
4291     let len = selection.len();
4292     selection.set_primary_index(match direction {
4293         Direction::Forward => (index + count) % len,
4294         Direction::Backward => (index + (len.saturating_sub(count) % len)) % len,
4295     });
4296     doc.set_selection(view.id, selection);
4297 }
rotate_selections_forward(cx: &mut Context)4298 fn rotate_selections_forward(cx: &mut Context) {
4299     rotate_selections(cx, Direction::Forward)
4300 }
rotate_selections_backward(cx: &mut Context)4301 fn rotate_selections_backward(cx: &mut Context) {
4302     rotate_selections(cx, Direction::Backward)
4303 }
4304 
rotate_selection_contents(cx: &mut Context, direction: Direction)4305 fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
4306     let count = cx.count;
4307     let (view, doc) = current!(cx.editor);
4308     let text = doc.text().slice(..);
4309 
4310     let selection = doc.selection(view.id);
4311     let mut fragments: Vec<_> = selection
4312         .fragments(text)
4313         .map(|fragment| Tendril::from_slice(&fragment))
4314         .collect();
4315 
4316     let group = count
4317         .map(|count| count.get())
4318         .unwrap_or(fragments.len()) // default to rotating everything as one group
4319         .min(fragments.len());
4320 
4321     for chunk in fragments.chunks_mut(group) {
4322         // TODO: also modify main index
4323         match direction {
4324             Direction::Forward => chunk.rotate_right(1),
4325             Direction::Backward => chunk.rotate_left(1),
4326         };
4327     }
4328 
4329     let transaction = Transaction::change(
4330         doc.text(),
4331         selection
4332             .ranges()
4333             .iter()
4334             .zip(fragments)
4335             .map(|(range, fragment)| (range.from(), range.to(), Some(fragment))),
4336     );
4337 
4338     doc.apply(&transaction, view.id);
4339     doc.append_changes_to_history(view.id);
4340 }
rotate_selection_contents_forward(cx: &mut Context)4341 fn rotate_selection_contents_forward(cx: &mut Context) {
4342     rotate_selection_contents(cx, Direction::Forward)
4343 }
rotate_selection_contents_backward(cx: &mut Context)4344 fn rotate_selection_contents_backward(cx: &mut Context) {
4345     rotate_selection_contents(cx, Direction::Backward)
4346 }
4347 
4348 // tree sitter node selection
4349 
expand_selection(cx: &mut Context)4350 fn expand_selection(cx: &mut Context) {
4351     let (view, doc) = current!(cx.editor);
4352 
4353     if let Some(syntax) = doc.syntax() {
4354         let text = doc.text().slice(..);
4355         let selection = object::expand_selection(syntax, text, doc.selection(view.id));
4356         doc.set_selection(view.id, selection);
4357     }
4358 }
4359 
match_brackets(cx: &mut Context)4360 fn match_brackets(cx: &mut Context) {
4361     let (view, doc) = current!(cx.editor);
4362 
4363     if let Some(syntax) = doc.syntax() {
4364         let pos = doc
4365             .selection(view.id)
4366             .primary()
4367             .cursor(doc.text().slice(..));
4368         if let Some(pos) = match_brackets::find(syntax, doc.text(), pos) {
4369             let selection = Selection::point(pos);
4370             doc.set_selection(view.id, selection);
4371         };
4372     }
4373 }
4374 
4375 //
4376 
jump_forward(cx: &mut Context)4377 fn jump_forward(cx: &mut Context) {
4378     let count = cx.count();
4379     let (view, _doc) = current!(cx.editor);
4380 
4381     if let Some((id, selection)) = view.jumps.forward(count) {
4382         view.doc = *id;
4383         let selection = selection.clone();
4384         let (view, doc) = current!(cx.editor); // refetch doc
4385         doc.set_selection(view.id, selection);
4386 
4387         align_view(doc, view, Align::Center);
4388     };
4389 }
4390 
jump_backward(cx: &mut Context)4391 fn jump_backward(cx: &mut Context) {
4392     let count = cx.count();
4393     let (view, doc) = current!(cx.editor);
4394 
4395     if let Some((id, selection)) = view.jumps.backward(view.id, doc, count) {
4396         // manually set the alternate_file as we cannot use the Editor::switch function here.
4397         if view.doc != *id {
4398             view.last_accessed_doc = Some(view.doc)
4399         }
4400         view.doc = *id;
4401         let selection = selection.clone();
4402         let (view, doc) = current!(cx.editor); // refetch doc
4403         doc.set_selection(view.id, selection);
4404 
4405         align_view(doc, view, Align::Center);
4406     };
4407 }
4408 
rotate_view(cx: &mut Context)4409 fn rotate_view(cx: &mut Context) {
4410     cx.editor.focus_next()
4411 }
4412 
jump_view_right(cx: &mut Context)4413 fn jump_view_right(cx: &mut Context) {
4414     cx.editor.focus_right()
4415 }
4416 
jump_view_left(cx: &mut Context)4417 fn jump_view_left(cx: &mut Context) {
4418     cx.editor.focus_left()
4419 }
4420 
jump_view_up(cx: &mut Context)4421 fn jump_view_up(cx: &mut Context) {
4422     cx.editor.focus_up()
4423 }
4424 
jump_view_down(cx: &mut Context)4425 fn jump_view_down(cx: &mut Context) {
4426     cx.editor.focus_down()
4427 }
4428 
4429 // split helper, clear it later
split(cx: &mut Context, action: Action)4430 fn split(cx: &mut Context, action: Action) {
4431     let (view, doc) = current!(cx.editor);
4432     let id = doc.id();
4433     let selection = doc.selection(view.id).clone();
4434     let offset = view.offset;
4435 
4436     cx.editor.switch(id, action);
4437 
4438     // match the selection in the previous view
4439     let (view, doc) = current!(cx.editor);
4440     view.offset = offset;
4441     doc.set_selection(view.id, selection);
4442 }
4443 
hsplit(cx: &mut Context)4444 fn hsplit(cx: &mut Context) {
4445     split(cx, Action::HorizontalSplit);
4446 }
4447 
vsplit(cx: &mut Context)4448 fn vsplit(cx: &mut Context) {
4449     split(cx, Action::VerticalSplit);
4450 }
4451 
wclose(cx: &mut Context)4452 fn wclose(cx: &mut Context) {
4453     if cx.editor.tree.views().count() == 1 {
4454         if let Err(err) = cmd::buffers_remaining_impl(cx.editor) {
4455             cx.editor.set_error(err.to_string());
4456             return;
4457         }
4458     }
4459     let view_id = view!(cx.editor).id;
4460     // close current split
4461     cx.editor.close(view_id, /* close_buffer */ false);
4462 }
4463 
select_register(cx: &mut Context)4464 fn select_register(cx: &mut Context) {
4465     cx.on_next_key(move |cx, event| {
4466         if let Some(ch) = event.char() {
4467             cx.editor.selected_register = Some(ch);
4468         }
4469     })
4470 }
4471 
align_view_top(cx: &mut Context)4472 fn align_view_top(cx: &mut Context) {
4473     let (view, doc) = current!(cx.editor);
4474     align_view(doc, view, Align::Top);
4475 }
4476 
align_view_center(cx: &mut Context)4477 fn align_view_center(cx: &mut Context) {
4478     let (view, doc) = current!(cx.editor);
4479     align_view(doc, view, Align::Center);
4480 }
4481 
align_view_bottom(cx: &mut Context)4482 fn align_view_bottom(cx: &mut Context) {
4483     let (view, doc) = current!(cx.editor);
4484     align_view(doc, view, Align::Bottom);
4485 }
4486 
align_view_middle(cx: &mut Context)4487 fn align_view_middle(cx: &mut Context) {
4488     let (view, doc) = current!(cx.editor);
4489     let text = doc.text().slice(..);
4490     let pos = doc.selection(view.id).primary().cursor(text);
4491     let pos = coords_at_pos(text, pos);
4492 
4493     view.offset.col = pos
4494         .col
4495         .saturating_sub((view.inner_area().width as usize) / 2);
4496 }
4497 
scroll_up(cx: &mut Context)4498 fn scroll_up(cx: &mut Context) {
4499     scroll(cx, cx.count(), Direction::Backward);
4500 }
4501 
scroll_down(cx: &mut Context)4502 fn scroll_down(cx: &mut Context) {
4503     scroll(cx, cx.count(), Direction::Forward);
4504 }
4505 
select_textobject_around(cx: &mut Context)4506 fn select_textobject_around(cx: &mut Context) {
4507     select_textobject(cx, textobject::TextObject::Around);
4508 }
4509 
select_textobject_inner(cx: &mut Context)4510 fn select_textobject_inner(cx: &mut Context) {
4511     select_textobject(cx, textobject::TextObject::Inside);
4512 }
4513 
select_textobject(cx: &mut Context, objtype: textobject::TextObject)4514 fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
4515     let count = cx.count();
4516     cx.on_next_key(move |cx, event| {
4517         if let Some(ch) = event.char() {
4518             let textobject = move |editor: &mut Editor| {
4519                 let (view, doc) = current!(editor);
4520                 let text = doc.text().slice(..);
4521 
4522                 let textobject_treesitter = |obj_name: &str, range: Range| -> Range {
4523                     let (lang_config, syntax) = match doc.language_config().zip(doc.syntax()) {
4524                         Some(t) => t,
4525                         None => return range,
4526                     };
4527                     textobject::textobject_treesitter(
4528                         text,
4529                         range,
4530                         objtype,
4531                         obj_name,
4532                         syntax.tree().root_node(),
4533                         lang_config,
4534                         count,
4535                     )
4536                 };
4537 
4538                 let selection = doc.selection(view.id).clone().transform(|range| {
4539                     match ch {
4540                         'w' => textobject::textobject_word(text, range, objtype, count),
4541                         'c' => textobject_treesitter("class", range),
4542                         'f' => textobject_treesitter("function", range),
4543                         'p' => textobject_treesitter("parameter", range),
4544                         // TODO: cancel new ranges if inconsistent surround matches across lines
4545                         ch if !ch.is_ascii_alphanumeric() => {
4546                             textobject::textobject_surround(text, range, objtype, ch, count)
4547                         }
4548                         _ => range,
4549                     }
4550                 });
4551                 doc.set_selection(view.id, selection);
4552             };
4553             textobject(&mut cx.editor);
4554             cx.editor.last_motion = Some(Motion(Box::new(textobject)));
4555         }
4556     })
4557 }
4558 
surround_add(cx: &mut Context)4559 fn surround_add(cx: &mut Context) {
4560     cx.on_next_key(move |cx, event| {
4561         if let Some(ch) = event.char() {
4562             let (view, doc) = current!(cx.editor);
4563             let selection = doc.selection(view.id);
4564             let (open, close) = surround::get_pair(ch);
4565 
4566             let mut changes = Vec::new();
4567             for range in selection.iter() {
4568                 changes.push((range.from(), range.from(), Some(Tendril::from_char(open))));
4569                 changes.push((range.to(), range.to(), Some(Tendril::from_char(close))));
4570             }
4571 
4572             let transaction = Transaction::change(doc.text(), changes.into_iter());
4573             doc.apply(&transaction, view.id);
4574             doc.append_changes_to_history(view.id);
4575         }
4576     })
4577 }
4578 
surround_replace(cx: &mut Context)4579 fn surround_replace(cx: &mut Context) {
4580     let count = cx.count();
4581     cx.on_next_key(move |cx, event| {
4582         if let Some(from) = event.char() {
4583             cx.on_next_key(move |cx, event| {
4584                 if let Some(to) = event.char() {
4585                     let (view, doc) = current!(cx.editor);
4586                     let text = doc.text().slice(..);
4587                     let selection = doc.selection(view.id);
4588 
4589                     let change_pos = match surround::get_surround_pos(text, selection, from, count)
4590                     {
4591                         Some(c) => c,
4592                         None => return,
4593                     };
4594 
4595                     let (open, close) = surround::get_pair(to);
4596                     let transaction = Transaction::change(
4597                         doc.text(),
4598                         change_pos.iter().enumerate().map(|(i, &pos)| {
4599                             (
4600                                 pos,
4601                                 pos + 1,
4602                                 Some(Tendril::from_char(if i % 2 == 0 { open } else { close })),
4603                             )
4604                         }),
4605                     );
4606                     doc.apply(&transaction, view.id);
4607                     doc.append_changes_to_history(view.id);
4608                 }
4609             });
4610         }
4611     })
4612 }
4613 
surround_delete(cx: &mut Context)4614 fn surround_delete(cx: &mut Context) {
4615     let count = cx.count();
4616     cx.on_next_key(move |cx, event| {
4617         if let Some(ch) = event.char() {
4618             let (view, doc) = current!(cx.editor);
4619             let text = doc.text().slice(..);
4620             let selection = doc.selection(view.id);
4621 
4622             let change_pos = match surround::get_surround_pos(text, selection, ch, count) {
4623                 Some(c) => c,
4624                 None => return,
4625             };
4626 
4627             let transaction =
4628                 Transaction::change(doc.text(), change_pos.into_iter().map(|p| (p, p + 1, None)));
4629             doc.apply(&transaction, view.id);
4630             doc.append_changes_to_history(view.id);
4631         }
4632     })
4633 }
4634 
4635 #[derive(Eq, PartialEq)]
4636 enum ShellBehavior {
4637     Replace,
4638     Ignore,
4639     Insert,
4640     Append,
4641 }
4642 
shell_pipe(cx: &mut Context)4643 fn shell_pipe(cx: &mut Context) {
4644     shell(cx, "pipe:".into(), ShellBehavior::Replace);
4645 }
4646 
shell_pipe_to(cx: &mut Context)4647 fn shell_pipe_to(cx: &mut Context) {
4648     shell(cx, "pipe-to:".into(), ShellBehavior::Ignore);
4649 }
4650 
shell_insert_output(cx: &mut Context)4651 fn shell_insert_output(cx: &mut Context) {
4652     shell(cx, "insert-output:".into(), ShellBehavior::Insert);
4653 }
4654 
shell_append_output(cx: &mut Context)4655 fn shell_append_output(cx: &mut Context) {
4656     shell(cx, "append-output:".into(), ShellBehavior::Append);
4657 }
4658 
shell_keep_pipe(cx: &mut Context)4659 fn shell_keep_pipe(cx: &mut Context) {
4660     let prompt = Prompt::new(
4661         "keep-pipe:".into(),
4662         Some('|'),
4663         |_input: &str| Vec::new(),
4664         move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
4665             let shell = &cx.editor.config.shell;
4666             if event != PromptEvent::Validate {
4667                 return;
4668             }
4669             if input.is_empty() {
4670                 return;
4671             }
4672             let (view, doc) = current!(cx.editor);
4673             let selection = doc.selection(view.id);
4674 
4675             let mut ranges = SmallVec::with_capacity(selection.len());
4676             let old_index = selection.primary_index();
4677             let mut index: Option<usize> = None;
4678             let text = doc.text().slice(..);
4679 
4680             for (i, range) in selection.ranges().iter().enumerate() {
4681                 let fragment = range.fragment(text);
4682                 let (_output, success) = match shell_impl(shell, input, Some(fragment.as_bytes())) {
4683                     Ok(result) => result,
4684                     Err(err) => {
4685                         cx.editor.set_error(err.to_string());
4686                         return;
4687                     }
4688                 };
4689 
4690                 // if the process exits successfully, keep the selection
4691                 if success {
4692                     ranges.push(*range);
4693                     if i >= old_index && index.is_none() {
4694                         index = Some(ranges.len() - 1);
4695                     }
4696                 }
4697             }
4698 
4699             if ranges.is_empty() {
4700                 cx.editor.set_error("No selections remaining".to_string());
4701                 return;
4702             }
4703 
4704             let index = index.unwrap_or_else(|| ranges.len() - 1);
4705             doc.set_selection(view.id, Selection::new(ranges, index));
4706         },
4707     );
4708 
4709     cx.push_layer(Box::new(prompt));
4710 }
4711 
shell_impl( shell: &[String], cmd: &str, input: Option<&[u8]>, ) -> anyhow::Result<(Tendril, bool)>4712 fn shell_impl(
4713     shell: &[String],
4714     cmd: &str,
4715     input: Option<&[u8]>,
4716 ) -> anyhow::Result<(Tendril, bool)> {
4717     use std::io::Write;
4718     use std::process::{Command, Stdio};
4719     if shell.is_empty() {
4720         bail!("No shell set");
4721     }
4722 
4723     let mut process = match Command::new(&shell[0])
4724         .args(&shell[1..])
4725         .arg(cmd)
4726         .stdin(Stdio::piped())
4727         .stdout(Stdio::piped())
4728         .stderr(Stdio::piped())
4729         .spawn()
4730     {
4731         Ok(process) => process,
4732         Err(e) => {
4733             log::error!("Failed to start shell: {}", e);
4734             return Err(e.into());
4735         }
4736     };
4737     if let Some(input) = input {
4738         let mut stdin = process.stdin.take().unwrap();
4739         stdin.write_all(input)?;
4740     }
4741     let output = process.wait_with_output()?;
4742 
4743     if !output.stderr.is_empty() {
4744         log::error!("Shell error: {}", String::from_utf8_lossy(&output.stderr));
4745     }
4746 
4747     let tendril = Tendril::try_from_byte_slice(&output.stdout)
4748         .map_err(|_| anyhow!("Process did not output valid UTF-8"))?;
4749     Ok((tendril, output.status.success()))
4750 }
4751 
shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior)4752 fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
4753     let pipe = match behavior {
4754         ShellBehavior::Replace | ShellBehavior::Ignore => true,
4755         ShellBehavior::Insert | ShellBehavior::Append => false,
4756     };
4757     let prompt = Prompt::new(
4758         prompt,
4759         Some('|'),
4760         |_input: &str| Vec::new(),
4761         move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
4762             let shell = &cx.editor.config.shell;
4763             if event != PromptEvent::Validate {
4764                 return;
4765             }
4766             if input.is_empty() {
4767                 return;
4768             }
4769             let (view, doc) = current!(cx.editor);
4770             let selection = doc.selection(view.id);
4771 
4772             let mut changes = Vec::with_capacity(selection.len());
4773             let text = doc.text().slice(..);
4774 
4775             for range in selection.ranges() {
4776                 let fragment = range.fragment(text);
4777                 let (output, success) =
4778                     match shell_impl(shell, input, pipe.then(|| fragment.as_bytes())) {
4779                         Ok(result) => result,
4780                         Err(err) => {
4781                             cx.editor.set_error(err.to_string());
4782                             return;
4783                         }
4784                     };
4785 
4786                 if !success {
4787                     cx.editor.set_error("Command failed".to_string());
4788                     return;
4789                 }
4790 
4791                 let (from, to) = match behavior {
4792                     ShellBehavior::Replace => (range.from(), range.to()),
4793                     ShellBehavior::Insert => (range.from(), range.from()),
4794                     ShellBehavior::Append => (range.to(), range.to()),
4795                     _ => (range.from(), range.from()),
4796                 };
4797                 changes.push((from, to, Some(output)));
4798             }
4799 
4800             if behavior != ShellBehavior::Ignore {
4801                 let transaction = Transaction::change(doc.text(), changes.into_iter());
4802                 doc.apply(&transaction, view.id);
4803                 doc.append_changes_to_history(view.id);
4804             }
4805         },
4806     );
4807 
4808     cx.push_layer(Box::new(prompt));
4809 }
4810 
suspend(_cx: &mut Context)4811 fn suspend(_cx: &mut Context) {
4812     #[cfg(not(windows))]
4813     signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP).unwrap();
4814 }
4815 
add_newline_above(cx: &mut Context)4816 fn add_newline_above(cx: &mut Context) {
4817     add_newline_impl(cx, Open::Above);
4818 }
4819 
add_newline_below(cx: &mut Context)4820 fn add_newline_below(cx: &mut Context) {
4821     add_newline_impl(cx, Open::Below)
4822 }
4823 
add_newline_impl(cx: &mut Context, open: Open)4824 fn add_newline_impl(cx: &mut Context, open: Open) {
4825     let count = cx.count();
4826     let (view, doc) = current!(cx.editor);
4827     let selection = doc.selection(view.id);
4828     let text = doc.text();
4829     let slice = text.slice(..);
4830 
4831     let changes = selection.into_iter().map(|range| {
4832         let (start, end) = range.line_range(slice);
4833         let line = match open {
4834             Open::Above => start,
4835             Open::Below => end + 1,
4836         };
4837         let pos = text.line_to_char(line);
4838         (
4839             pos,
4840             pos,
4841             Some(doc.line_ending.as_str().repeat(count).into()),
4842         )
4843     });
4844 
4845     let transaction = Transaction::change(text, changes);
4846     doc.apply(&transaction, view.id);
4847     doc.append_changes_to_history(view.id);
4848 }
4849