@@ -9,7 +9,7 @@ use helix_core::{
9 match_brackets,
9 match_brackets,
10 movement::{self, Direction},
10 movement::{self, Direction},
11 object, pos_at_coords,
11 object, pos_at_coords,
12− regex::{self, Regex},
12+ regex::{self, Regex, RegexBuilder},
13 register::Register,
13 register::Register,
14 search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
14 search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
15 RopeSlice, Selection, SmallVec, Tendril, Transaction,
15 RopeSlice, Selection, SmallVec, Tendril, Transaction,
@@ -35,7 +35,7 @@ use crate::{
37use crate::job::{self, Job, Jobs};
37use crate::job::{self, Job, Jobs};
38−use futures_util::FutureExt;
38+use futures_util::{FutureExt, StreamExt};
39use std::num::NonZeroUsize;
39use std::num::NonZeroUsize;
40use std::{collections::HashMap, fmt, future::Future};
40use std::{collections::HashMap, fmt, future::Future};
@@ -47,8 +47,13 @@ use std::{
47use once_cell::sync::Lazy;
47use once_cell::sync::Lazy;
48use serde::de::{self, Deserialize, Deserializer};
48use serde::de::{self, Deserialize, Deserializer};
50+use grep_regex::RegexMatcherBuilder;
51+use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
52+use ignore::{DirEntry, WalkBuilder, WalkState};
53+use tokio_stream::wrappers::UnboundedReceiverStream;
50pub struct Context<'a> {
55pub struct Context<'a> {
51− pub selected_register: helix_view::RegisterSelection,
56+ pub register: Option<char>,
52 pub count: Option<NonZeroUsize>,
57 pub count: Option<NonZeroUsize>,
53 pub editor: &'a mut Editor,
58 pub editor: &'a mut Editor,
@@ -166,6 +171,7 @@ impl Command {
167 #[rustfmt::skip]
172 #[rustfmt::skip]
168 commands!(
173 commands!(
174+ no_op, "Do nothing",
169 move_char_left, "Move left",
175 move_char_left, "Move left",
170 move_char_right, "Move right",
176 move_char_right, "Move right",
171 move_line_up, "Move up",
177 move_line_up, "Move up",
@@ -184,6 +190,9 @@ impl Command {
184 move_next_long_word_end, "Move to end of next long word",
190 move_next_long_word_end, "Move to end of next long word",
185 extend_next_word_start, "Extend to beginning of next word",
191 extend_next_word_start, "Extend to beginning of next word",
186 extend_prev_word_start, "Extend to beginning of previous word",
192 extend_prev_word_start, "Extend to beginning of previous word",
193+ extend_next_long_word_start, "Extend to beginning of next long word",
194+ extend_prev_long_word_start, "Extend to beginning of previous long word",
195+ extend_next_long_word_end, "Extend to end of next long word",
187 extend_next_word_end, "Extend to end of next word",
196 extend_next_word_end, "Extend to end of next word",
188 find_till_char, "Move till next occurance of char",
197 find_till_char, "Move till next occurance of char",
189 find_next_char, "Move to next occurance of char",
198 find_next_char, "Move to next occurance of char",
@@ -209,6 +218,7 @@ impl Command {
209 search_next, "Select next search match",
218 search_next, "Select next search match",
210 extend_search_next, "Add next search match to selection",
219 extend_search_next, "Add next search match to selection",
211 search_selection, "Use current selection as search pattern",
220 search_selection, "Use current selection as search pattern",
221+ global_search, "Global Search in workspace folder",
212 extend_line, "Select current line, if already selected, extend to next line",
222 extend_line, "Select current line, if already selected, extend to next line",
213 extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)",
223 extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)",
214 delete_selection, "Delete selection",
224 delete_selection, "Delete selection",
@@ -253,6 +263,9 @@ impl Command {
253 // TODO: different description ?
263 // TODO: different description ?
254 goto_line_end_newline, "Goto line end",
264 goto_line_end_newline, "Goto line end",
255 goto_first_nonwhitespace, "Goto first non-blank in line",
265 goto_first_nonwhitespace, "Goto first non-blank in line",
266+ extend_to_line_start, "Extend to line start",
267+ extend_to_line_end, "Extend to line end",
268+ extend_to_line_end_newline, "Extend to line end",
256 signature_help, "Show signature help",
269 signature_help, "Show signature help",
257 insert_tab, "Insert tab char",
270 insert_tab, "Insert tab char",
258 insert_newline, "Insert newline char",
271 insert_newline, "Insert newline char",
@@ -281,6 +294,7 @@ impl Command {
281 join_selections, "Join lines inside selection",
294 join_selections, "Join lines inside selection",
282 keep_selections, "Keep selections matching regex",
295 keep_selections, "Keep selections matching regex",
283 keep_primary_selection, "Keep primary selection",
296 keep_primary_selection, "Keep primary selection",
297+ remove_primary_selection, "Remove primary selection",
284 completion, "Invoke completion popup",
298 completion, "Invoke completion popup",
285 hover, "Show docs for item under cursor",
299 hover, "Show docs for item under cursor",
286 toggle_comments, "Comment/uncomment selections",
300 toggle_comments, "Comment/uncomment selections",
@@ -372,52 +386,58 @@ impl PartialEq for Command {
375−fn move_char_left(cx: &mut Context) {
389+fn no_op(_cx: &mut Context) {}
391+fn move_impl<F>(cx: &mut Context, move_fn: F, dir: Direction, behaviour: Movement)
393+ F: Fn(RopeSlice, Range, Direction, usize, Movement) -> Range,
376 let count = cx.count();
395 let count = cx.count();
377 let (view, doc) = current!(cx.editor);
396 let (view, doc) = current!(cx.editor);
378 let text = doc.text().slice(..);
397 let text = doc.text().slice(..);
380− let selection = doc.selection(view.id).clone().transform(|range| {
399+ let selection = doc
381− movement::move_horizontally(text, range, Direction::Backward, count, Movement::Move)
400+ .selection(view.id)
402+ .transform(|range| move_fn(text, range, dir, count, behaviour));
383 doc.set_selection(view.id, selection);
403 doc.set_selection(view.id, selection);
386−fn move_char_right(cx: &mut Context) {
406+use helix_core::movement::{move_horizontally, move_vertically};
387− let count = cx.count();
388− let (view, doc) = current!(cx.editor);
389− let text = doc.text().slice(..);
391− let selection = doc.selection(view.id).clone().transform(|range| {
408+fn move_char_left(cx: &mut Context) {
392− movement::move_horizontally(text, range, Direction::Forward, count, Movement::Move)
409+ move_impl(cx, move_horizontally, Direction::Backward, Movement::Move)
394− doc.set_selection(view.id, selection);
397−fn move_line_up(cx: &mut Context) {
412+fn move_char_right(cx: &mut Context) {
398− let count = cx.count();
413+ move_impl(cx, move_horizontally, Direction::Forward, Movement::Move)
399− let (view, doc) = current!(cx.editor);
414+}
400− let text = doc.text().slice(..);
402− let selection = doc.selection(view.id).clone().transform(|range| {
416+fn move_line_up(cx: &mut Context) {
403− movement::move_vertically(text, range, Direction::Backward, count, Movement::Move)
417+ move_impl(cx, move_vertically, Direction::Backward, Movement::Move)
405− doc.set_selection(view.id, selection);
408fn move_line_down(cx: &mut Context) {
420fn move_line_down(cx: &mut Context) {
409− let count = cx.count();
421+ move_impl(cx, move_vertically, Direction::Forward, Movement::Move)
410− let (view, doc) = current!(cx.editor);
422+}
411− let text = doc.text().slice(..);
413− let selection = doc.selection(view.id).clone().transform(|range| {
424+fn extend_char_left(cx: &mut Context) {
414− movement::move_vertically(text, range, Direction::Forward, count, Movement::Move)
425+ move_impl(cx, move_horizontally, Direction::Backward, Movement::Extend)
416− doc.set_selection(view.id, selection);
428+fn extend_char_right(cx: &mut Context) {
429+ move_impl(cx, move_horizontally, Direction::Forward, Movement::Extend)
432+fn extend_line_up(cx: &mut Context) {
433+ move_impl(cx, move_vertically, Direction::Backward, Movement::Extend)
419−fn goto_line_end(cx: &mut Context) {
436+fn extend_line_down(cx: &mut Context) {
420− let (view, doc) = current!(cx.editor);
437+ move_impl(cx, move_vertically, Direction::Forward, Movement::Extend)
440+fn goto_line_end_impl(view: &mut View, doc: &mut Document, movement: Movement) {
421 let text = doc.text().slice(..);
441 let text = doc.text().slice(..);
423 let selection = doc.selection(view.id).clone().transform(|range| {
443 let selection = doc.selection(view.id).clone().transform(|range| {
@@ -427,26 +447,60 @@ fn goto_line_end(cx: &mut Context) {
427 let pos = graphemes::prev_grapheme_boundary(text, line_end_char_index(&text, line))
447 let pos = graphemes::prev_grapheme_boundary(text, line_end_char_index(&text, line))
428 .max(line_start);
448 .max(line_start);
430− range.put_cursor(text, pos, doc.mode == Mode::Select)
450+ range.put_cursor(text, pos, movement == Movement::Extend)
432 doc.set_selection(view.id, selection);
452 doc.set_selection(view.id, selection);
435−fn goto_line_end_newline(cx: &mut Context) {
455+fn goto_line_end(cx: &mut Context) {
436 let (view, doc) = current!(cx.editor);
456 let (view, doc) = current!(cx.editor);
460+ if doc.mode == Mode::Select {
468+fn extend_to_line_end(cx: &mut Context) {
469+ let (view, doc) = current!(cx.editor);
470+ goto_line_end_impl(view, doc, Movement::Extend)
473+fn goto_line_end_newline_impl(view: &mut View, doc: &mut Document, movement: Movement) {
437 let text = doc.text().slice(..);
474 let text = doc.text().slice(..);
439 let selection = doc.selection(view.id).clone().transform(|range| {
476 let selection = doc.selection(view.id).clone().transform(|range| {
440 let line = range.cursor_line(text);
477 let line = range.cursor_line(text);
441 let pos = line_end_char_index(&text, line);
478 let pos = line_end_char_index(&text, line);
443− range.put_cursor(text, pos, doc.mode == Mode::Select)
480+ range.put_cursor(text, pos, movement == Movement::Extend)
445 doc.set_selection(view.id, selection);
482 doc.set_selection(view.id, selection);
448−fn goto_line_start(cx: &mut Context) {
485+fn goto_line_end_newline(cx: &mut Context) {
486+ let (view, doc) = current!(cx.editor);
487+ goto_line_end_newline_impl(
490+ if doc.mode == Mode::Select {
498+fn extend_to_line_end_newline(cx: &mut Context) {
449 let (view, doc) = current!(cx.editor);
499 let (view, doc) = current!(cx.editor);
500+ goto_line_end_newline_impl(view, doc, Movement::Extend)
503+fn goto_line_start_impl(view: &mut View, doc: &mut Document, movement: Movement) {
450 let text = doc.text().slice(..);
504 let text = doc.text().slice(..);
452 let selection = doc.selection(view.id).clone().transform(|range| {
506 let selection = doc.selection(view.id).clone().transform(|range| {
@@ -454,11 +508,29 @@ fn goto_line_start(cx: &mut Context) {
455 // adjust to start of the line
509 // adjust to start of the line
456 let pos = text.line_to_char(line);
510 let pos = text.line_to_char(line);
457− range.put_cursor(text, pos, doc.mode == Mode::Select)
511+ range.put_cursor(text, pos, movement == Movement::Extend)
459 doc.set_selection(view.id, selection);
513 doc.set_selection(view.id, selection);
516+fn goto_line_start(cx: &mut Context) {
517+ let (view, doc) = current!(cx.editor);
518+ goto_line_start_impl(
521+ if doc.mode == Mode::Select {
529+fn extend_to_line_start(cx: &mut Context) {
530+ let (view, doc) = current!(cx.editor);
531+ goto_line_start_impl(view, doc, Movement::Extend)
462fn goto_first_nonwhitespace(cx: &mut Context) {
534fn goto_first_nonwhitespace(cx: &mut Context) {
463 let (view, doc) = current!(cx.editor);
535 let (view, doc) = current!(cx.editor);
464 let text = doc.text().slice(..);
536 let text = doc.text().slice(..);
@@ -512,7 +584,10 @@ fn goto_window_bottom(cx: &mut Context) {
512 goto_window(cx, Align::Bottom)
584 goto_window(cx, Align::Bottom)
515−fn move_next_word_start(cx: &mut Context) {
587+fn move_word_impl<F>(cx: &mut Context, move_fn: F)
589+ F: Fn(RopeSlice, Range, usize) -> Range,
516 let count = cx.count();
591 let count = cx.count();
517 let (view, doc) = current!(cx.editor);
592 let (view, doc) = current!(cx.editor);
518 let text = doc.text().slice(..);
593 let text = doc.text().slice(..);
@@ -520,68 +595,32 @@ fn move_next_word_start(cx: &mut Context) {
520 let selection = doc
595 let selection = doc
521 .selection(view.id)
596 .selection(view.id)
523− .transform(|range| movement::move_next_word_start(text, range, count));
598+ .transform(|range| move_fn(text, range, count));
524 doc.set_selection(view.id, selection);
599 doc.set_selection(view.id, selection);
527−fn move_prev_word_start(cx: &mut Context) {
602+fn move_next_word_start(cx: &mut Context) {
528− let count = cx.count();
603+ move_word_impl(cx, movement::move_next_word_start)
529− let (view, doc) = current!(cx.editor);
604+}
530− let text = doc.text().slice(..);
532− let selection = doc
606+fn move_prev_word_start(cx: &mut Context) {
535− .transform(|range| movement::move_prev_word_start(text, range, count));
607+ move_word_impl(cx, movement::move_prev_word_start)
536− doc.set_selection(view.id, selection);
539fn move_next_word_end(cx: &mut Context) {
610fn move_next_word_end(cx: &mut Context) {
540− let count = cx.count();
541− let (view, doc) = current!(cx.editor);
542− let text = doc.text().slice(..);
547− .transform(|range| movement::move_next_word_end(text, range, count));
611+ move_word_impl(cx, movement::move_next_word_end)
548− doc.set_selection(view.id, selection);
551fn move_next_long_word_start(cx: &mut Context) {
614fn move_next_long_word_start(cx: &mut Context) {
552− let count = cx.count();
553− let (view, doc) = current!(cx.editor);
554− let text = doc.text().slice(..);
559− .transform(|range| movement::move_next_long_word_start(text, range, count));
615+ move_word_impl(cx, movement::move_next_long_word_start)
560− doc.set_selection(view.id, selection);
563fn move_prev_long_word_start(cx: &mut Context) {
618fn move_prev_long_word_start(cx: &mut Context) {
564− let count = cx.count();
565− let (view, doc) = current!(cx.editor);
566− let text = doc.text().slice(..);
571− .transform(|range| movement::move_prev_long_word_start(text, range, count));
619+ move_word_impl(cx, movement::move_prev_long_word_start)
572− doc.set_selection(view.id, selection);
575fn move_next_long_word_end(cx: &mut Context) {
622fn move_next_long_word_end(cx: &mut Context) {
576− let count = cx.count();
577− let (view, doc) = current!(cx.editor);
578− let text = doc.text().slice(..);
583− .transform(|range| movement::move_next_long_word_end(text, range, count));
623+ move_word_impl(cx, movement::move_next_long_word_end)
584− doc.set_selection(view.id, selection);
587fn goto_file_start(cx: &mut Context) {
626fn goto_file_start(cx: &mut Context) {
@@ -600,43 +639,44 @@ fn goto_file_end(cx: &mut Context) {
600 doc.set_selection(view.id, Selection::point(doc.text().len_chars()));
639 doc.set_selection(view.id, Selection::point(doc.text().len_chars()));
603−fn extend_next_word_start(cx: &mut Context) {
642+fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
644+ F: Fn(RopeSlice, Range, usize) -> Range,
604 let count = cx.count();
646 let count = cx.count();
605 let (view, doc) = current!(cx.editor);
647 let (view, doc) = current!(cx.editor);
606 let text = doc.text().slice(..);
648 let text = doc.text().slice(..);
608 let selection = doc.selection(view.id).clone().transform(|range| {
650 let selection = doc.selection(view.id).clone().transform(|range| {
609− let word = movement::move_next_word_start(text, range, count);
651+ let word = extend_fn(text, range, count);
610 let pos = word.cursor(text);
652 let pos = word.cursor(text);
611 range.put_cursor(text, pos, true)
653 range.put_cursor(text, pos, true)
613 doc.set_selection(view.id, selection);
655 doc.set_selection(view.id, selection);
616−fn extend_prev_word_start(cx: &mut Context) {
658+fn extend_next_word_start(cx: &mut Context) {
617− let count = cx.count();
659+ extend_word_impl(cx, movement::move_next_word_start)
618− let (view, doc) = current!(cx.editor);
660+}
619− let text = doc.text().slice(..);
621− let selection = doc.selection(view.id).clone().transform(|range| {
662+fn extend_prev_word_start(cx: &mut Context) {
622− let word = movement::move_prev_word_start(text, range, count);
663+ extend_word_impl(cx, movement::move_prev_word_start)
623− let pos = word.cursor(text);
624− range.put_cursor(text, pos, true)
626− doc.set_selection(view.id, selection);
629fn extend_next_word_end(cx: &mut Context) {
666fn extend_next_word_end(cx: &mut Context) {
630− let count = cx.count();
667+ extend_word_impl(cx, movement::move_next_word_end)
631− let (view, doc) = current!(cx.editor);
668+}
632− let text = doc.text().slice(..);
634− let selection = doc.selection(view.id).clone().transform(|range| {
670+fn extend_next_long_word_start(cx: &mut Context) {
635− let word = movement::move_next_word_end(text, range, count);
671+ extend_word_impl(cx, movement::move_next_long_word_start)
636− let pos = word.cursor(text);
672+}
637− range.put_cursor(text, pos, true)
673+
674+fn extend_prev_long_word_start(cx: &mut Context) {
675+ extend_word_impl(cx, movement::move_prev_long_word_start)
639− doc.set_selection(view.id, selection);
677+
678+fn extend_next_long_word_end(cx: &mut Context) {
679+ extend_word_impl(cx, movement::move_next_long_word_end)
@@ -852,12 +892,25 @@ fn replace(cx: &mut Context) {
855−fn switch_case(cx: &mut Context) {
895+fn switch_case_impl<F>(cx: &mut Context, change_fn: F)
897+ F: Fn(Cow<str>) -> Tendril,
856 let (view, doc) = current!(cx.editor);
899 let (view, doc) = current!(cx.editor);
857 let selection = doc.selection(view.id);
900 let selection = doc.selection(view.id);
858 let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
901 let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
859− let text: Tendril = range
860− .fragment(doc.text().slice(..))
902+ let text: Tendril = change_fn(range.fragment(doc.text().slice(..)));
904+ (range.from(), range.to(), Some(text))
907+ doc.apply(&transaction, view.id);
908+ doc.append_changes_to_history(view.id);
911+fn switch_case(cx: &mut Context) {
912+ switch_case_impl(cx, |string| {
862 .flat_map(|ch| {
915 .flat_map(|ch| {
863 if ch.is_lowercase() {
916 if ch.is_lowercase() {
@@ -868,39 +921,16 @@ fn switch_case(cx: &mut Context) {
871− .collect();
924+ .collect()
873− (range.from(), range.to(), Some(text))
876− doc.apply(&transaction, view.id);
877− doc.append_changes_to_history(view.id);
880fn switch_to_uppercase(cx: &mut Context) {
928fn switch_to_uppercase(cx: &mut Context) {
881− let (view, doc) = current!(cx.editor);
929+ switch_case_impl(cx, |string| string.to_uppercase().into());
882− let selection = doc.selection(view.id);
883− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
884− let text: Tendril = range.fragment(doc.text().slice(..)).to_uppercase().into();
886− (range.from(), range.to(), Some(text))
889− doc.apply(&transaction, view.id);
890− doc.append_changes_to_history(view.id);
893fn switch_to_lowercase(cx: &mut Context) {
932fn switch_to_lowercase(cx: &mut Context) {
894− let (view, doc) = current!(cx.editor);
933+ switch_case_impl(cx, |string| string.to_lowercase().into());
895− let selection = doc.selection(view.id);
896− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
897− let text: Tendril = range.fragment(doc.text().slice(..)).to_lowercase().into();
899− (range.from(), range.to(), Some(text))
902− doc.apply(&transaction, view.id);
903− doc.append_changes_to_history(view.id);
906pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
936pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
@@ -976,28 +1006,6 @@ fn half_page_down(cx: &mut Context) {
976 scroll(cx, offset, Direction::Forward);
1006 scroll(cx, offset, Direction::Forward);
979−fn extend_char_left(cx: &mut Context) {
980− let count = cx.count();
981− let (view, doc) = current!(cx.editor);
982− let text = doc.text().slice(..);
984− let selection = doc.selection(view.id).clone().transform(|range| {
985− movement::move_horizontally(text, range, Direction::Backward, count, Movement::Extend)
987− doc.set_selection(view.id, selection);
990−fn extend_char_right(cx: &mut Context) {
991− let count = cx.count();
992− let (view, doc) = current!(cx.editor);
993− let text = doc.text().slice(..);
995− let selection = doc.selection(view.id).clone().transform(|range| {
996− movement::move_horizontally(text, range, Direction::Forward, count, Movement::Extend)
998− doc.set_selection(view.id, selection);
1001fn copy_selection_on_line(cx: &mut Context, direction: Direction) {
1009fn copy_selection_on_line(cx: &mut Context, direction: Direction) {
1002 let count = cx.count();
1010 let count = cx.count();
1003 let (view, doc) = current!(cx.editor);
1011 let (view, doc) = current!(cx.editor);
@@ -1068,28 +1076,6 @@ fn copy_selection_on_next_line(cx: &mut Context) {
1068 copy_selection_on_line(cx, Direction::Forward)
1076 copy_selection_on_line(cx, Direction::Forward)
1071−fn extend_line_up(cx: &mut Context) {
1072− let count = cx.count();
1073− let (view, doc) = current!(cx.editor);
1074− let text = doc.text().slice(..);
1076− let selection = doc.selection(view.id).clone().transform(|range| {
1077− movement::move_vertically(text, range, Direction::Backward, count, Movement::Extend)
1079− doc.set_selection(view.id, selection);
1082−fn extend_line_down(cx: &mut Context) {
1083− let count = cx.count();
1084− let (view, doc) = current!(cx.editor);
1085− let text = doc.text().slice(..);
1087− let selection = doc.selection(view.id).clone().transform(|range| {
1088− movement::move_vertically(text, range, Direction::Forward, count, Movement::Extend)
1090− doc.set_selection(view.id, selection);
1093fn select_all(cx: &mut Context) {
1079fn select_all(cx: &mut Context) {
1094 let (view, doc) = current!(cx.editor);
1080 let (view, doc) = current!(cx.editor);
@@ -1098,23 +1084,42 @@ fn select_all(cx: &mut Context) {
1100fn select_regex(cx: &mut Context) {
1086fn select_regex(cx: &mut Context) {
1087+ let reg = cx.register.unwrap_or('/');
1101− let prompt = ui::regex_prompt(cx, "select:".into(), move |view, doc, _, regex| {
1088+ let prompt = ui::regex_prompt(
1092+ move |view, doc, regex, event| {
1093+ if event != PromptEvent::Update {
1102− let text = doc.text().slice(..);
1096+ let text = doc.text().slice(..);
1097+ if let Some(selection) =
1103− if let Some(selection) = selection::select_on_matches(text, doc.selection(view.id), ®ex)
1098+ selection::select_on_matches(text, doc.selection(view.id), ®ex)
1105− doc.set_selection(view.id, selection);
1100+ doc.set_selection(view.id, selection);
1109 cx.push_layer(Box::new(prompt));
1105 cx.push_layer(Box::new(prompt));
1112fn split_selection(cx: &mut Context) {
1108fn split_selection(cx: &mut Context) {
1109+ let reg = cx.register.unwrap_or('/');
1113− let prompt = ui::regex_prompt(cx, "split:".into(), move |view, doc, _, regex| {
1110+ let prompt = ui::regex_prompt(
1114+ move |view, doc, regex, event| {
1115+ if event != PromptEvent::Update {
1114− let text = doc.text().slice(..);
1118+ let text = doc.text().slice(..);
1115− let selection = selection::split_on_matches(text, doc.selection(view.id), ®ex);
1119+ let selection = selection::split_on_matches(text, doc.selection(view.id), ®ex);
1116− doc.set_selection(view.id, selection);
1120+ doc.set_selection(view.id, selection);
1119 cx.push_layer(Box::new(prompt));
1124 cx.push_layer(Box::new(prompt));
@@ -1168,6 +1173,7 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege
1169// TODO: use one function for search vs extend
1174// TODO: use one function for search vs extend
1170fn search(cx: &mut Context) {
1175fn search(cx: &mut Context) {
1176+ let reg = cx.register.unwrap_or('/');
1171 let (_, doc) = current!(cx.editor);
1177 let (_, doc) = current!(cx.editor);
1173 // TODO: could probably share with select_on_matches?
1179 // TODO: could probably share with select_on_matches?
@@ -1176,23 +1182,44 @@ fn search(cx: &mut Context) {
1176 // feed chunks into the regex yet
1182 // feed chunks into the regex yet
1177 let contents = doc.text().slice(..).to_string();
1183 let contents = doc.text().slice(..).to_string();
1179− let prompt = ui::regex_prompt(cx, "search:".into(), move |view, doc, registers, regex| {
1185+ let prompt = ui::regex_prompt(
1189+ move |view, doc, regex, event| {
1190+ if event != PromptEvent::Update {
1180− search_impl(doc, view, &contents, ®ex, false);
1193+ search_impl(doc, view, &contents, ®ex, false);
1181− // TODO: only store on enter (accept), not update
1194+ },
1182− registers.write('/', vec![regex.as_str().to_string()]);
1185 cx.push_layer(Box::new(prompt));
1197 cx.push_layer(Box::new(prompt));
1188fn search_next_impl(cx: &mut Context, extend: bool) {
1200fn search_next_impl(cx: &mut Context, extend: bool) {
1189 let (view, doc) = current!(cx.editor);
1201 let (view, doc) = current!(cx.editor);
1190− let registers = &mut cx.editor.registers;
1202+ let registers = &cx.editor.registers;
1191 if let Some(query) = registers.read('/') {
1203 if let Some(query) = registers.read('/') {
1192− let query = query.first().unwrap();
1204+ let query = query.last().unwrap();
1193 let contents = doc.text().slice(..).to_string();
1205 let contents = doc.text().slice(..).to_string();
1206+ let case_insensitive = if cx.editor.config.smart_case {
1207+ !query.chars().any(char::is_uppercase)
1194− let regex = Regex::new(query).unwrap();
1211+ if let Ok(regex) = RegexBuilder::new(query)
1212+ .case_insensitive(case_insensitive)
1195− search_impl(doc, view, &contents, ®ex, extend);
1215+ search_impl(doc, view, &contents, ®ex, extend);
1217+ // get around warning `mutable_borrow_reservation_conflict`
1218+ // which will be a hard error in the future
1219+ // see: https://github.com/rust-lang/rust/issues/59159
1220+ let query = query.clone();
1221+ cx.editor.set_error(format!("Invalid regex: {}", query));
@@ -1209,8 +1236,119 @@ fn search_selection(cx: &mut Context) {
1209 let contents = doc.text().slice(..);
1236 let contents = doc.text().slice(..);
1210 let query = doc.selection(view.id).primary().fragment(contents);
1237 let query = doc.selection(view.id).primary().fragment(contents);
1211 let regex = regex::escape(&query);
1238 let regex = regex::escape(&query);
1212− cx.editor.registers.write('/', vec![regex]);
1239+ cx.editor.registers.get_mut('/').push(regex);
1213− search_next(cx);
1240+ let msg = format!("register '{}' set to '{}'", '\\', query);
1241+ cx.editor.set_status(msg);
1244+fn global_search(cx: &mut Context) {
1245+ let (all_matches_sx, all_matches_rx) =
1246+ tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>();
1247+ let smart_case = cx.editor.config.smart_case;
1248+ let prompt = ui::regex_prompt(
1250+ "global search:".into(),
1252+ move |_view, _doc, regex, event| {
1253+ if event != PromptEvent::Validate {
1257+ if let Ok(matcher) = RegexMatcherBuilder::new()
1258+ .case_smart(smart_case)
1259+ .build(regex.as_str())
1261+ let searcher = SearcherBuilder::new()
1262+ .binary_detection(BinaryDetection::quit(b'\x00'))
1265+ let search_root = std::env::current_dir()
1266+ .expect("Global search error: Failed to get current dir");
1267+ WalkBuilder::new(search_root).build_parallel().run(|| {
1268+ let mut searcher_cl = searcher.clone();
1269+ let matcher_cl = matcher.clone();
1270+ let all_matches_sx_cl = all_matches_sx.clone();
1271+ Box::new(move |dent: Result<DirEntry, ignore::Error>| -> WalkState {
1272+ let dent = match dent {
1274+ Err(_) => return WalkState::Continue,
1277+ match dent.file_type() {
1280+ return WalkState::Continue;
1283+ None => return WalkState::Continue,
1286+ let result_sink = sinks::UTF8(|line_num, _| {
1287+ match all_matches_sx_cl
1288+ .send((line_num as usize - 1, dent.path().to_path_buf()))
1291+ Err(_) => Ok(false),
1294+ let result = searcher_cl.search_path(&matcher_cl, dent.path(), result_sink);
1296+ if let Err(err) = result {
1297+ log::error!("Global search error: {}, {}", dent.path().display(), err);
1299+ WalkState::Continue
1303+ // Otherwise do nothing
1304+ // log::warn!("Global Search Invalid Pattern")
1309+ cx.push_layer(Box::new(prompt));
1311+ let show_picker = async move {
1312+ let all_matches: Vec<(usize, PathBuf)> =
1313+ UnboundedReceiverStream::new(all_matches_rx).collect().await;
1314+ let call: job::Callback =
1315+ Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
1316+ if all_matches.is_empty() {
1317+ editor.set_status("No matches found".to_string());
1320+ let picker = FilePicker::new(
1322+ move |(_line_num, path)| path.to_str().unwrap().into(),
1323+ move |editor: &mut Editor, (line_num, path), action| {
1324+ match editor.open(path.into(), action) {
1327+ editor.set_error(format!(
1328+ "Failed to open file '{}': {}",
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()));
1342+ doc.set_selection(view.id, Selection::single(start, end));
1343+ align_view(doc, view, Align::Center);
1345+ |_editor, (line_num, path)| Some((path.clone(), Some((*line_num, *line_num)))),
1347+ compositor.push(Box::new(picker));
1351+ cx.jobs.callback(show_picker);
1216fn extend_line(cx: &mut Context) {
1354fn extend_line(cx: &mut Context) {
@@ -1268,7 +1406,7 @@ fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId
1270fn delete_selection(cx: &mut Context) {
1408fn delete_selection(cx: &mut Context) {
1271− let reg_name = cx.selected_register.name();
1409+ let reg_name = cx.register.unwrap_or('"');
1272 let (view, doc) = current!(cx.editor);
1410 let (view, doc) = current!(cx.editor);
1273 let registers = &mut cx.editor.registers;
1411 let registers = &mut cx.editor.registers;
1274 let reg = registers.get_mut(reg_name);
1412 let reg = registers.get_mut(reg_name);
@@ -1281,7 +1419,7 @@ fn delete_selection(cx: &mut Context) {
1283fn change_selection(cx: &mut Context) {
1421fn change_selection(cx: &mut Context) {
1284− let reg_name = cx.selected_register.name();
1422+ let reg_name = cx.register.unwrap_or('"');
1285 let (view, doc) = current!(cx.editor);
1423 let (view, doc) = current!(cx.editor);
1286 let registers = &mut cx.editor.registers;
1424 let registers = &mut cx.editor.registers;
1287 let reg = registers.get_mut(reg_name);
1425 let reg = registers.get_mut(reg_name);
@@ -1403,8 +1541,11 @@ mod cmd {
1403 args: &[&str],
1541 args: &[&str],
1404 _event: PromptEvent,
1542 _event: PromptEvent,
1405 ) -> anyhow::Result<()> {
1543 ) -> anyhow::Result<()> {
1544+ use helix_core::path::expand_tilde;
1406 let path = args.get(0).context("wrong argument count")?;
1545 let path = args.get(0).context("wrong argument count")?;
1407− let _ = cx.editor.open(path.into(), Action::Replace)?;
1548+ .open(expand_tilde(Path::new(path)), Action::Replace)?;
@@ -1604,7 +1745,7 @@ mod cmd {
1605 /// Results an error if there are modified buffers remaining and sets editor error,
1746 /// Results an error if there are modified buffers remaining and sets editor error,
1606 /// otherwise returns `Ok(())`
1747 /// otherwise returns `Ok(())`
1607− fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> {
1748+ pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> {
1608 let modified: Vec<_> = editor
1749 let modified: Vec<_> = editor
1609 .documents()
1750 .documents()
1610 .filter(|doc| doc.is_modified())
1751 .filter(|doc| doc.is_modified())
@@ -2577,7 +2718,7 @@ fn apply_workspace_edit(
2578 if let Some(ref changes) = workspace_edit.changes {
2719 if let Some(ref changes) = workspace_edit.changes {
2579 log::debug!("workspace changes: {:?}", changes);
2720 log::debug!("workspace changes: {:?}", changes);
2580− editor.set_error(String::from("Handling workspace changesis not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
2721+ editor.set_error(String::from("Handling workspace_edit.changes is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
2582 // 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
2723 // 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
2583 // TODO: find some example that uses workspace changes, and test it
2724 // TODO: find some example that uses workspace changes, and test it
@@ -2595,8 +2736,30 @@ fn apply_workspace_edit(
2595 match document_changes {
2736 match document_changes {
2596 lsp::DocumentChanges::Edits(document_edits) => {
2737 lsp::DocumentChanges::Edits(document_edits) => {
2597 for document_edit in document_edits {
2738 for document_edit in document_edits {
2598− let (view, doc) = current!(editor);
2739+ let path = document_edit
2599− assert_eq!(doc.url().unwrap(), document_edit.text_document.uri);
2740+ .text_document
2743+ .expect("unable to convert URI to filepath");
2744+ let current_view_id = view!(editor).id;
2746+ .document_by_path_mut(path)
2747+ .expect("Document for document_changes not found");
2749+ // Need to determine a view for apply/append_changes_to_history
2750+ let selections = doc.selections();
2751+ let view_id = if selections.contains_key(¤t_view_id) {
2752+ // use current if possible
2755+ // Hack: we take the first available view_id
2760+ .expect("No view_id available")
2600 let edits = document_edit
2763 let edits = document_edit
@@ -2614,8 +2777,8 @@ fn apply_workspace_edit(
2615 offset_encoding,
2778 offset_encoding,
2617− doc.apply(&transaction, view.id);
2780+ doc.apply(&transaction, view_id);
2618− doc.append_changes_to_history(view.id);
2781+ doc.append_changes_to_history(view_id);
2621 lsp::DocumentChanges::Operations(operations) => {
2784 lsp::DocumentChanges::Operations(operations) => {
@@ -2779,6 +2942,10 @@ fn open_above(cx: &mut Context) {
2779fn normal_mode(cx: &mut Context) {
2942fn normal_mode(cx: &mut Context) {
2780 let (view, doc) = current!(cx.editor);
2943 let (view, doc) = current!(cx.editor);
2945+ if doc.mode == Mode::Normal {
2782 doc.mode = Mode::Normal;
2949 doc.mode = Mode::Normal;
2784 doc.append_changes_to_history(view.id);
2951 doc.append_changes_to_history(view.id);
@@ -3325,17 +3492,20 @@ pub mod insert {
3327 use helix_core::auto_pairs;
3494 use helix_core::auto_pairs;
3328− const HOOKS: &[Hook] = &[auto_pairs::hook, insert];
3329− const POST_HOOKS: &[PostHook] = &[completion, signature_help];
3331 pub fn insert_char(cx: &mut Context, c: char) {
3496 pub fn insert_char(cx: &mut Context, c: char) {
3332 let (view, doc) = current!(cx.editor);
3497 let (view, doc) = current!(cx.editor);
3499+ let hooks: &[Hook] = match cx.editor.config.auto_pairs {
3500+ true => &[auto_pairs::hook, insert],
3501+ false => &[insert],
3334 let text = doc.text();
3504 let text = doc.text();
3335 let selection = doc.selection(view.id).clone().cursors(text.slice(..));
3505 let selection = doc.selection(view.id).clone().cursors(text.slice(..));
3337 // run through insert hooks, stopping on the first one that returns Some(t)
3507 // run through insert hooks, stopping on the first one that returns Some(t)
3338− for hook in HOOKS {
3508+ for hook in hooks {
3339 if let Some(transaction) = hook(text, &selection, c) {
3509 if let Some(transaction) = hook(text, &selection, c) {
3340 doc.apply(&transaction, view.id);
3510 doc.apply(&transaction, view.id);
@@ -3345,7 +3515,7 @@ pub mod insert {
3345 // TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc)
3515 // TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc)
3346 // this could also generically look at Transaction, but it's a bit annoying to look at
3516 // this could also generically look at Transaction, but it's a bit annoying to look at
3347 // Operation instead of Change.
3517 // Operation instead of Change.
3348− for hook in POST_HOOKS {
3518+ for hook in &[completion, signature_help] {
3349 hook(cx, c);
3519 hook(cx, c);
@@ -3510,12 +3680,12 @@ fn yank(cx: &mut Context) {
3510 let msg = format!(
3680 let msg = format!(
3511 "yanked {} selection(s) to register {}",
3681 "yanked {} selection(s) to register {}",
3512 values.len(),
3682 values.len(),
3513− cx.selected_register.name()
3683+ cx.register.unwrap_or('"')
3516 cx.editor
3686 cx.editor
3517 .registers
3687 .registers
3518− .write(cx.selected_register.name(), values);
3688+ .write(cx.register.unwrap_or('"'), values);
3520 cx.editor.set_status(msg);
3690 cx.editor.set_status(msg);
3521 exit_select_mode(cx);
3691 exit_select_mode(cx);
@@ -3624,7 +3794,14 @@ fn paste_impl(
3625 .any(|value| get_line_ending_of_str(value).is_some());
3795 .any(|value| get_line_ending_of_str(value).is_some());
3797+ // Only compiled once.
3798+ #[allow(clippy::trivial_regex)]
3799+ static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\r\n|\r|\n").unwrap());
3627− let mut values = values.iter().cloned().map(Tendril::from).chain(repeat);
3800+ let mut values = values
3802+ .map(|value| REGEX.replace_all(value, doc.line_ending.as_str()))
3803+ .map(|value| Tendril::from(value.as_ref()))
3629 let text = doc.text();
3806 let text = doc.text();
3630 let selection = doc.selection(view.id);
3807 let selection = doc.selection(view.id);
@@ -3688,7 +3865,7 @@ fn paste_primary_clipboard_before(cx: &mut Context) {
3690fn replace_with_yanked(cx: &mut Context) {
3867fn replace_with_yanked(cx: &mut Context) {
3691− let reg_name = cx.selected_register.name();
3868+ let reg_name = cx.register.unwrap_or('"');
3692 let (view, doc) = current!(cx.editor);
3869 let (view, doc) = current!(cx.editor);
3693 let registers = &mut cx.editor.registers;
3870 let registers = &mut cx.editor.registers;
@@ -3739,7 +3916,7 @@ fn replace_selections_with_primary_clipboard(cx: &mut Context) {
3741fn paste_after(cx: &mut Context) {
3918fn paste_after(cx: &mut Context) {
3742− let reg_name = cx.selected_register.name();
3919+ let reg_name = cx.register.unwrap_or('"');
3743 let (view, doc) = current!(cx.editor);
3920 let (view, doc) = current!(cx.editor);
3744 let registers = &mut cx.editor.registers;
3921 let registers = &mut cx.editor.registers;
@@ -3753,7 +3930,7 @@ fn paste_after(cx: &mut Context) {
3755fn paste_before(cx: &mut Context) {
3932fn paste_before(cx: &mut Context) {
3756− let reg_name = cx.selected_register.name();
3933+ let reg_name = cx.register.unwrap_or('"');
3757 let (view, doc) = current!(cx.editor);
3934 let (view, doc) = current!(cx.editor);
3758 let registers = &mut cx.editor.registers;
3935 let registers = &mut cx.editor.registers;
@@ -3934,24 +4111,49 @@ fn join_selections(cx: &mut Context) {
3935fn keep_selections(cx: &mut Context) {
4112fn keep_selections(cx: &mut Context) {
3936 // keep selections matching regex
4113 // keep selections matching regex
4114+ let reg = cx.register.unwrap_or('/');
3937− let prompt = ui::regex_prompt(cx, "keep:".into(), move |view, doc, _, regex| {
4115+ let prompt = ui::regex_prompt(
4119+ move |view, doc, regex, event| {
4120+ if event != PromptEvent::Update {
3938− let text = doc.text().slice(..);
4123+ let text = doc.text().slice(..);
3940− if let Some(selection) = selection::keep_matches(text, doc.selection(view.id), ®ex) {
4125+ if let Some(selection) = selection::keep_matches(text, doc.selection(view.id), ®ex) {
3941− doc.set_selection(view.id, selection);
4126+ doc.set_selection(view.id, selection);
3945 cx.push_layer(Box::new(prompt));
4131 cx.push_layer(Box::new(prompt));
3948fn keep_primary_selection(cx: &mut Context) {
4134fn keep_primary_selection(cx: &mut Context) {
3949 let (view, doc) = current!(cx.editor);
4135 let (view, doc) = current!(cx.editor);
4136+ // TODO: handle count
3951 let range = doc.selection(view.id).primary();
4138 let range = doc.selection(view.id).primary();
3952 doc.set_selection(view.id, Selection::single(range.anchor, range.head));
4139 doc.set_selection(view.id, Selection::single(range.anchor, range.head));
4142+fn remove_primary_selection(cx: &mut Context) {
4143+ let (view, doc) = current!(cx.editor);
4144+ // TODO: handle count
4146+ let selection = doc.selection(view.id);
4147+ if selection.len() == 1 {
4148+ cx.editor.set_error("no selections remaining".to_owned());
4151+ let index = selection.primary_index();
4152+ let selection = selection.clone().remove(index);
4154+ doc.set_selection(view.id, selection);
3955fn completion(cx: &mut Context) {
4157fn completion(cx: &mut Context) {
3956 // trigger on trigger char, or if user calls it
4158 // trigger on trigger char, or if user calls it
3957 // (or on word char typing??)
4159 // (or on word char typing??)
@@ -4259,6 +4461,12 @@ fn vsplit(cx: &mut Context) {
4261fn wclose(cx: &mut Context) {
4463fn wclose(cx: &mut Context) {
4464+ if cx.editor.tree.views().count() == 1 {
4465+ if let Err(err) = cmd::buffers_remaining_impl(cx.editor) {
4466+ cx.editor.set_error(err.to_string());
4262 let view_id = view!(cx.editor).id;
4470 let view_id = view!(cx.editor).id;
4263 // close current split
4471 // close current split
4264 cx.editor.close(view_id, /* close_buffer */ false);
4472 cx.editor.close(view_id, /* close_buffer */ false);
@@ -4267,7 +4475,7 @@ fn wclose(cx: &mut Context) {
4267fn select_register(cx: &mut Context) {
4475fn select_register(cx: &mut Context) {
4268 cx.on_next_key(move |cx, event| {
4476 cx.on_next_key(move |cx, event| {
4269 if let Some(ch) = event.char() {
4477 if let Some(ch) = event.char() {
4270− cx.editor.selected_register.select(ch);
4478+ cx.editor.selected_register = Some(ch);