@@ -9,7 +9,7 @@ use helix_core::{
99 match_brackets,
1010 movement::{self, Direction},
1111 object, pos_at_coords,
12− regex::{self, Regex},
12+ regex::{self, Regex, RegexBuilder},
1313 register::Register,
1414 search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
1515 RopeSlice, Selection, SmallVec, Tendril, Transaction,
@@ -35,7 +35,7 @@ use crate::{
3535};
3636
3737use crate::job::{self, Job, Jobs};
38−use futures_util::FutureExt;
38+use futures_util::{FutureExt, StreamExt};
3939use std::num::NonZeroUsize;
4040use std::{collections::HashMap, fmt, future::Future};
4141
@@ -47,8 +47,13 @@ use std::{
4747use once_cell::sync::Lazy;
4848use serde::de::{self, Deserialize, Deserializer};
4949
50+use grep_regex::RegexMatcherBuilder;
51+use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
52+use ignore::{DirEntry, WalkBuilder, WalkState};
53+use tokio_stream::wrappers::UnboundedReceiverStream;
54+
5055pub struct Context<'a> {
51− pub selected_register: helix_view::RegisterSelection,
56+ pub register: Option<char>,
5257 pub count: Option<NonZeroUsize>,
5358 pub editor: &'a mut Editor,
5459
@@ -166,6 +171,7 @@ impl Command {
166171
167172 #[rustfmt::skip]
168173 commands!(
174+ no_op, "Do nothing",
169175 move_char_left, "Move left",
170176 move_char_right, "Move right",
171177 move_line_up, "Move up",
@@ -184,6 +190,9 @@ impl Command {
184190 move_next_long_word_end, "Move to end of next long word",
185191 extend_next_word_start, "Extend to beginning of next word",
186192 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",
187196 extend_next_word_end, "Extend to end of next word",
188197 find_till_char, "Move till next occurance of char",
189198 find_next_char, "Move to next occurance of char",
@@ -209,6 +218,7 @@ impl Command {
209218 search_next, "Select next search match",
210219 extend_search_next, "Add next search match to selection",
211220 search_selection, "Use current selection as search pattern",
221+ global_search, "Global Search in workspace folder",
212222 extend_line, "Select current line, if already selected, extend to next line",
213223 extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)",
214224 delete_selection, "Delete selection",
@@ -253,6 +263,9 @@ impl Command {
253263 // TODO: different description ?
254264 goto_line_end_newline, "Goto line end",
255265 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",
256269 signature_help, "Show signature help",
257270 insert_tab, "Insert tab char",
258271 insert_newline, "Insert newline char",
@@ -281,6 +294,7 @@ impl Command {
281294 join_selections, "Join lines inside selection",
282295 keep_selections, "Keep selections matching regex",
283296 keep_primary_selection, "Keep primary selection",
297+ remove_primary_selection, "Remove primary selection",
284298 completion, "Invoke completion popup",
285299 hover, "Show docs for item under cursor",
286300 toggle_comments, "Comment/uncomment selections",
@@ -372,52 +386,58 @@ impl PartialEq for Command {
372386 }
373387}
374388
375−fn move_char_left(cx: &mut Context) {
389+fn no_op(_cx: &mut Context) {}
390+
391+fn move_impl<F>(cx: &mut Context, move_fn: F, dir: Direction, behaviour: Movement)
392+where
393+ F: Fn(RopeSlice, Range, Direction, usize, Movement) -> Range,
394+{
376395 let count = cx.count();
377396 let (view, doc) = current!(cx.editor);
378397 let text = doc.text().slice(..);
379398
380− let selection = doc.selection(view.id).clone().transform(|range| {
381− movement::move_horizontally(text, range, Direction::Backward, count, Movement::Move)
382− });
399+ let selection = doc
400+ .selection(view.id)
401+ .clone()
402+ .transform(|range| move_fn(text, range, dir, count, behaviour));
383403 doc.set_selection(view.id, selection);
384404}
385405
386−fn move_char_right(cx: &mut Context) {
387− let count = cx.count();
388− let (view, doc) = current!(cx.editor);
389− let text = doc.text().slice(..);
406+use helix_core::movement::{move_horizontally, move_vertically};
390407
391− let selection = doc.selection(view.id).clone().transform(|range| {
392− movement::move_horizontally(text, range, Direction::Forward, count, Movement::Move)
393− });
394− doc.set_selection(view.id, selection);
408+fn move_char_left(cx: &mut Context) {
409+ move_impl(cx, move_horizontally, Direction::Backward, Movement::Move)
395410}
396411
397−fn move_line_up(cx: &mut Context) {
398− let count = cx.count();
399− let (view, doc) = current!(cx.editor);
400− let text = doc.text().slice(..);
412+fn move_char_right(cx: &mut Context) {
413+ move_impl(cx, move_horizontally, Direction::Forward, Movement::Move)
414+}
401415
402− let selection = doc.selection(view.id).clone().transform(|range| {
403− movement::move_vertically(text, range, Direction::Backward, count, Movement::Move)
404− });
405− doc.set_selection(view.id, selection);
416+fn move_line_up(cx: &mut Context) {
417+ move_impl(cx, move_vertically, Direction::Backward, Movement::Move)
406418}
407419
408420fn move_line_down(cx: &mut Context) {
409− let count = cx.count();
410− let (view, doc) = current!(cx.editor);
411− let text = doc.text().slice(..);
421+ move_impl(cx, move_vertically, Direction::Forward, Movement::Move)
422+}
412423
413− let selection = doc.selection(view.id).clone().transform(|range| {
414− movement::move_vertically(text, range, Direction::Forward, count, Movement::Move)
415− });
416− doc.set_selection(view.id, selection);
424+fn extend_char_left(cx: &mut Context) {
425+ move_impl(cx, move_horizontally, Direction::Backward, Movement::Extend)
417426}
418427
419−fn goto_line_end(cx: &mut Context) {
420− let (view, doc) = current!(cx.editor);
428+fn extend_char_right(cx: &mut Context) {
429+ move_impl(cx, move_horizontally, Direction::Forward, Movement::Extend)
430+}
431+
432+fn extend_line_up(cx: &mut Context) {
433+ move_impl(cx, move_vertically, Direction::Backward, Movement::Extend)
434+}
435+
436+fn extend_line_down(cx: &mut Context) {
437+ move_impl(cx, move_vertically, Direction::Forward, Movement::Extend)
438+}
439+
440+fn goto_line_end_impl(view: &mut View, doc: &mut Document, movement: Movement) {
421441 let text = doc.text().slice(..);
422442
423443 let selection = doc.selection(view.id).clone().transform(|range| {
@@ -427,26 +447,60 @@ fn goto_line_end(cx: &mut Context) {
427447 let pos = graphemes::prev_grapheme_boundary(text, line_end_char_index(&text, line))
428448 .max(line_start);
429449
430− range.put_cursor(text, pos, doc.mode == Mode::Select)
450+ range.put_cursor(text, pos, movement == Movement::Extend)
431451 });
432452 doc.set_selection(view.id, selection);
433453}
434454
435−fn goto_line_end_newline(cx: &mut Context) {
455+fn goto_line_end(cx: &mut Context) {
436456 let (view, doc) = current!(cx.editor);
457+ goto_line_end_impl(
458+ view,
459+ doc,
460+ if doc.mode == Mode::Select {
461+ Movement::Extend
462+ } else {
463+ Movement::Move
464+ },
465+ )
466+}
467+
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)
471+}
472+
473+fn goto_line_end_newline_impl(view: &mut View, doc: &mut Document, movement: Movement) {
437474 let text = doc.text().slice(..);
438475
439476 let selection = doc.selection(view.id).clone().transform(|range| {
440477 let line = range.cursor_line(text);
441478 let pos = line_end_char_index(&text, line);
442479
443− range.put_cursor(text, pos, doc.mode == Mode::Select)
480+ range.put_cursor(text, pos, movement == Movement::Extend)
444481 });
445482 doc.set_selection(view.id, selection);
446483}
447484
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(
488+ view,
489+ doc,
490+ if doc.mode == Mode::Select {
491+ Movement::Extend
492+ } else {
493+ Movement::Move
494+ },
495+ )
496+}
497+
498+fn extend_to_line_end_newline(cx: &mut Context) {
449499 let (view, doc) = current!(cx.editor);
500+ goto_line_end_newline_impl(view, doc, Movement::Extend)
501+}
502+
503+fn goto_line_start_impl(view: &mut View, doc: &mut Document, movement: Movement) {
450504 let text = doc.text().slice(..);
451505
452506 let selection = doc.selection(view.id).clone().transform(|range| {
@@ -454,11 +508,29 @@ fn goto_line_start(cx: &mut Context) {
454508
455509 // adjust to start of the line
456510 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)
458512 });
459513 doc.set_selection(view.id, selection);
460514}
461515
516+fn goto_line_start(cx: &mut Context) {
517+ let (view, doc) = current!(cx.editor);
518+ goto_line_start_impl(
519+ view,
520+ doc,
521+ if doc.mode == Mode::Select {
522+ Movement::Extend
523+ } else {
524+ Movement::Move
525+ },
526+ )
527+}
528+
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)
532+}
533+
462534fn goto_first_nonwhitespace(cx: &mut Context) {
463535 let (view, doc) = current!(cx.editor);
464536 let text = doc.text().slice(..);
@@ -512,7 +584,10 @@ fn goto_window_bottom(cx: &mut Context) {
512584 goto_window(cx, Align::Bottom)
513585}
514586
515−fn move_next_word_start(cx: &mut Context) {
587+fn move_word_impl<F>(cx: &mut Context, move_fn: F)
588+where
589+ F: Fn(RopeSlice, Range, usize) -> Range,
590+{
516591 let count = cx.count();
517592 let (view, doc) = current!(cx.editor);
518593 let text = doc.text().slice(..);
@@ -520,68 +595,32 @@ fn move_next_word_start(cx: &mut Context) {
520595 let selection = doc
521596 .selection(view.id)
522597 .clone()
523− .transform(|range| movement::move_next_word_start(text, range, count));
598+ .transform(|range| move_fn(text, range, count));
524599 doc.set_selection(view.id, selection);
525600}
526601
527−fn move_prev_word_start(cx: &mut Context) {
528− let count = cx.count();
529− let (view, doc) = current!(cx.editor);
530− let text = doc.text().slice(..);
602+fn move_next_word_start(cx: &mut Context) {
603+ move_word_impl(cx, movement::move_next_word_start)
604+}
531605
532− let selection = doc
533− .selection(view.id)
534− .clone()
535− .transform(|range| movement::move_prev_word_start(text, range, count));
536− doc.set_selection(view.id, selection);
606+fn move_prev_word_start(cx: &mut Context) {
607+ move_word_impl(cx, movement::move_prev_word_start)
537608}
538609
539610fn 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(..);
543−
544− let selection = doc
545− .selection(view.id)
546− .clone()
547− .transform(|range| movement::move_next_word_end(text, range, count));
548− doc.set_selection(view.id, selection);
611+ move_word_impl(cx, movement::move_next_word_end)
549612}
550613
551614fn 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(..);
555−
556− let selection = doc
557− .selection(view.id)
558− .clone()
559− .transform(|range| movement::move_next_long_word_start(text, range, count));
560− doc.set_selection(view.id, selection);
615+ move_word_impl(cx, movement::move_next_long_word_start)
561616}
562617
563618fn 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(..);
567−
568− let selection = doc
569− .selection(view.id)
570− .clone()
571− .transform(|range| movement::move_prev_long_word_start(text, range, count));
572− doc.set_selection(view.id, selection);
619+ move_word_impl(cx, movement::move_prev_long_word_start)
573620}
574621
575622fn 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(..);
579−
580− let selection = doc
581− .selection(view.id)
582− .clone()
583− .transform(|range| movement::move_next_long_word_end(text, range, count));
584− doc.set_selection(view.id, selection);
623+ move_word_impl(cx, movement::move_next_long_word_end)
585624}
586625
587626fn goto_file_start(cx: &mut Context) {
@@ -600,43 +639,44 @@ fn goto_file_end(cx: &mut Context) {
600639 doc.set_selection(view.id, Selection::point(doc.text().len_chars()));
601640}
602641
603−fn extend_next_word_start(cx: &mut Context) {
642+fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
643+where
644+ F: Fn(RopeSlice, Range, usize) -> Range,
645+{
604646 let count = cx.count();
605647 let (view, doc) = current!(cx.editor);
606648 let text = doc.text().slice(..);
607649
608650 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);
610652 let pos = word.cursor(text);
611653 range.put_cursor(text, pos, true)
612654 });
613655 doc.set_selection(view.id, selection);
614656}
615657
616−fn extend_prev_word_start(cx: &mut Context) {
617− let count = cx.count();
618− let (view, doc) = current!(cx.editor);
619− let text = doc.text().slice(..);
658+fn extend_next_word_start(cx: &mut Context) {
659+ extend_word_impl(cx, movement::move_next_word_start)
660+}
620661
621− let selection = doc.selection(view.id).clone().transform(|range| {
622− let word = movement::move_prev_word_start(text, range, count);
623− let pos = word.cursor(text);
624− range.put_cursor(text, pos, true)
625− });
626− doc.set_selection(view.id, selection);
662+fn extend_prev_word_start(cx: &mut Context) {
663+ extend_word_impl(cx, movement::move_prev_word_start)
627664}
628665
629666fn extend_next_word_end(cx: &mut Context) {
630− let count = cx.count();
631− let (view, doc) = current!(cx.editor);
632− let text = doc.text().slice(..);
667+ extend_word_impl(cx, movement::move_next_word_end)
668+}
633669
634− let selection = doc.selection(view.id).clone().transform(|range| {
635− let word = movement::move_next_word_end(text, range, count);
636− let pos = word.cursor(text);
637− range.put_cursor(text, pos, true)
638− });
639− doc.set_selection(view.id, selection);
670+fn extend_next_long_word_start(cx: &mut Context) {
671+ extend_word_impl(cx, movement::move_next_long_word_start)
672+}
673+
674+fn extend_prev_long_word_start(cx: &mut Context) {
675+ extend_word_impl(cx, movement::move_prev_long_word_start)
676+}
677+
678+fn extend_next_long_word_end(cx: &mut Context) {
679+ extend_word_impl(cx, movement::move_next_long_word_end)
640680}
641681
642682#[inline]
@@ -852,12 +892,25 @@ fn replace(cx: &mut Context) {
852892 })
853893}
854894
855−fn switch_case(cx: &mut Context) {
895+fn switch_case_impl<F>(cx: &mut Context, change_fn: F)
896+where
897+ F: Fn(Cow<str>) -> Tendril,
898+{
856899 let (view, doc) = current!(cx.editor);
857900 let selection = doc.selection(view.id);
858901 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(..)));
903+
904+ (range.from(), range.to(), Some(text))
905+ });
906+
907+ doc.apply(&transaction, view.id);
908+ doc.append_changes_to_history(view.id);
909+}
910+
911+fn switch_case(cx: &mut Context) {
912+ switch_case_impl(cx, |string| {
913+ string
861914 .chars()
862915 .flat_map(|ch| {
863916 if ch.is_lowercase() {
@@ -868,39 +921,16 @@ fn switch_case(cx: &mut Context) {
868921 vec![ch]
869922 }
870923 })
871− .collect();
872−
873− (range.from(), range.to(), Some(text))
924+ .collect()
874925 });
875−
876− doc.apply(&transaction, view.id);
877− doc.append_changes_to_history(view.id);
878926}
879927
880928fn switch_to_uppercase(cx: &mut Context) {
881− let (view, doc) = current!(cx.editor);
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();
885−
886− (range.from(), range.to(), Some(text))
887− });
888−
889− doc.apply(&transaction, view.id);
890− doc.append_changes_to_history(view.id);
929+ switch_case_impl(cx, |string| string.to_uppercase().into());
891930}
892931
893932fn switch_to_lowercase(cx: &mut Context) {
894− let (view, doc) = current!(cx.editor);
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();
898−
899− (range.from(), range.to(), Some(text))
900− });
901−
902− doc.apply(&transaction, view.id);
903− doc.append_changes_to_history(view.id);
933+ switch_case_impl(cx, |string| string.to_lowercase().into());
904934}
905935
906936pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
@@ -976,28 +1006,6 @@ fn half_page_down(cx: &mut Context) {
9761006 scroll(cx, offset, Direction::Forward);
9771007}
9781008
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(..);
983−
984− let selection = doc.selection(view.id).clone().transform(|range| {
985− movement::move_horizontally(text, range, Direction::Backward, count, Movement::Extend)
986− });
987− doc.set_selection(view.id, selection);
988−}
989−
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(..);
994−
995− let selection = doc.selection(view.id).clone().transform(|range| {
996− movement::move_horizontally(text, range, Direction::Forward, count, Movement::Extend)
997− });
998− doc.set_selection(view.id, selection);
999−}
1000−
10011009fn copy_selection_on_line(cx: &mut Context, direction: Direction) {
10021010 let count = cx.count();
10031011 let (view, doc) = current!(cx.editor);
@@ -1068,28 +1076,6 @@ fn copy_selection_on_next_line(cx: &mut Context) {
10681076 copy_selection_on_line(cx, Direction::Forward)
10691077}
10701078
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(..);
1075−
1076− let selection = doc.selection(view.id).clone().transform(|range| {
1077− movement::move_vertically(text, range, Direction::Backward, count, Movement::Extend)
1078− });
1079− doc.set_selection(view.id, selection);
1080−}
1081−
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(..);
1086−
1087− let selection = doc.selection(view.id).clone().transform(|range| {
1088− movement::move_vertically(text, range, Direction::Forward, count, Movement::Extend)
1089− });
1090− doc.set_selection(view.id, selection);
1091−}
1092−
10931079fn select_all(cx: &mut Context) {
10941080 let (view, doc) = current!(cx.editor);
10951081
@@ -1098,23 +1084,42 @@ fn select_all(cx: &mut Context) {
10981084}
10991085
11001086fn select_regex(cx: &mut Context) {
1101− let prompt = ui::regex_prompt(cx, "select:".into(), move |view, doc, _, regex| {
1102− let text = doc.text().slice(..);
1103− if let Some(selection) = selection::select_on_matches(text, doc.selection(view.id), ®ex)
1104− {
1105− doc.set_selection(view.id, selection);
1106− }
1107− });
1087+ let reg = cx.register.unwrap_or('/');
1088+ let prompt = ui::regex_prompt(
1089+ cx,
1090+ "select:".into(),
1091+ Some(reg),
1092+ move |view, doc, regex, event| {
1093+ if event != PromptEvent::Update {
1094+ return;
1095+ }
1096+ let text = doc.text().slice(..);
1097+ if let Some(selection) =
1098+ selection::select_on_matches(text, doc.selection(view.id), ®ex)
1099+ {
1100+ doc.set_selection(view.id, selection);
1101+ }
1102+ },
1103+ );
11081104
11091105 cx.push_layer(Box::new(prompt));
11101106}
11111107
11121108fn split_selection(cx: &mut Context) {
1113− let prompt = ui::regex_prompt(cx, "split:".into(), move |view, doc, _, regex| {
1114− let text = doc.text().slice(..);
1115− let selection = selection::split_on_matches(text, doc.selection(view.id), ®ex);
1116− doc.set_selection(view.id, selection);
1117− });
1109+ let reg = cx.register.unwrap_or('/');
1110+ let prompt = ui::regex_prompt(
1111+ cx,
1112+ "split:".into(),
1113+ Some(reg),
1114+ move |view, doc, regex, event| {
1115+ if event != PromptEvent::Update {
1116+ return;
1117+ }
1118+ let text = doc.text().slice(..);
1119+ let selection = selection::split_on_matches(text, doc.selection(view.id), ®ex);
1120+ doc.set_selection(view.id, selection);
1121+ },
1122+ );
11181123
11191124 cx.push_layer(Box::new(prompt));
11201125}
@@ -1168,6 +1173,7 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege
11681173
11691174// TODO: use one function for search vs extend
11701175fn search(cx: &mut Context) {
1176+ let reg = cx.register.unwrap_or('/');
11711177 let (_, doc) = current!(cx.editor);
11721178
11731179 // TODO: could probably share with select_on_matches?
@@ -1176,23 +1182,44 @@ fn search(cx: &mut Context) {
11761182 // feed chunks into the regex yet
11771183 let contents = doc.text().slice(..).to_string();
11781184
1179− let prompt = ui::regex_prompt(cx, "search:".into(), move |view, doc, registers, regex| {
1180− search_impl(doc, view, &contents, ®ex, false);
1181− // TODO: only store on enter (accept), not update
1182− registers.write('/', vec![regex.as_str().to_string()]);
1183− });
1185+ let prompt = ui::regex_prompt(
1186+ cx,
1187+ "search:".into(),
1188+ Some(reg),
1189+ move |view, doc, regex, event| {
1190+ if event != PromptEvent::Update {
1191+ return;
1192+ }
1193+ search_impl(doc, view, &contents, ®ex, false);
1194+ },
1195+ );
11841196
11851197 cx.push_layer(Box::new(prompt));
11861198}
11871199
11881200fn search_next_impl(cx: &mut Context, extend: bool) {
11891201 let (view, doc) = current!(cx.editor);
1190− let registers = &mut cx.editor.registers;
1202+ let registers = &cx.editor.registers;
11911203 if let Some(query) = registers.read('/') {
1192− let query = query.first().unwrap();
1204+ let query = query.last().unwrap();
11931205 let contents = doc.text().slice(..).to_string();
1194− let regex = Regex::new(query).unwrap();
1195− search_impl(doc, view, &contents, ®ex, extend);
1206+ let case_insensitive = if cx.editor.config.smart_case {
1207+ !query.chars().any(char::is_uppercase)
1208+ } else {
1209+ false
1210+ };
1211+ if let Ok(regex) = RegexBuilder::new(query)
1212+ .case_insensitive(case_insensitive)
1213+ .build()
1214+ {
1215+ search_impl(doc, view, &contents, ®ex, extend);
1216+ } else {
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));
1222+ }
11961223 }
11971224}
11981225
@@ -1209,8 +1236,119 @@ fn search_selection(cx: &mut Context) {
12091236 let contents = doc.text().slice(..);
12101237 let query = doc.selection(view.id).primary().fragment(contents);
12111238 let regex = regex::escape(&query);
1212− cx.editor.registers.write('/', vec![regex]);
1213− search_next(cx);
1239+ cx.editor.registers.get_mut('/').push(regex);
1240+ let msg = format!("register '{}' set to '{}'", '\\', query);
1241+ cx.editor.set_status(msg);
1242+}
1243+
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(
1249+ cx,
1250+ "global search:".into(),
1251+ None,
1252+ move |_view, _doc, regex, event| {
1253+ if event != PromptEvent::Validate {
1254+ return;
1255+ }
1256+
1257+ if let Ok(matcher) = RegexMatcherBuilder::new()
1258+ .case_smart(smart_case)
1259+ .build(regex.as_str())
1260+ {
1261+ let searcher = SearcherBuilder::new()
1262+ .binary_detection(BinaryDetection::quit(b'\x00'))
1263+ .build();
1264+
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 {
1273+ Ok(dent) => dent,
1274+ Err(_) => return WalkState::Continue,
1275+ };
1276+
1277+ match dent.file_type() {
1278+ Some(fi) => {
1279+ if !fi.is_file() {
1280+ return WalkState::Continue;
1281+ }
1282+ }
1283+ None => return WalkState::Continue,
1284+ }
1285+
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()))
1289+ {
1290+ Ok(_) => Ok(true),
1291+ Err(_) => Ok(false),
1292+ }
1293+ });
1294+ let result = searcher_cl.search_path(&matcher_cl, dent.path(), result_sink);
1295+
1296+ if let Err(err) = result {
1297+ log::error!("Global search error: {}, {}", dent.path().display(), err);
1298+ }
1299+ WalkState::Continue
1300+ })
1301+ });
1302+ } else {
1303+ // Otherwise do nothing
1304+ // log::warn!("Global Search Invalid Pattern")
1305+ }
1306+ },
1307+ );
1308+
1309+ cx.push_layer(Box::new(prompt));
1310+
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());
1318+ return;
1319+ }
1320+ let picker = FilePicker::new(
1321+ all_matches,
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) {
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);
12141352}
12151353
12161354fn extend_line(cx: &mut Context) {
@@ -1268,7 +1406,7 @@ fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId
12681406}
12691407
12701408fn delete_selection(cx: &mut Context) {
1271− let reg_name = cx.selected_register.name();
1409+ let reg_name = cx.register.unwrap_or('"');
12721410 let (view, doc) = current!(cx.editor);
12731411 let registers = &mut cx.editor.registers;
12741412 let reg = registers.get_mut(reg_name);
@@ -1281,7 +1419,7 @@ fn delete_selection(cx: &mut Context) {
12811419}
12821420
12831421fn change_selection(cx: &mut Context) {
1284− let reg_name = cx.selected_register.name();
1422+ let reg_name = cx.register.unwrap_or('"');
12851423 let (view, doc) = current!(cx.editor);
12861424 let registers = &mut cx.editor.registers;
12871425 let reg = registers.get_mut(reg_name);
@@ -1403,8 +1541,11 @@ mod cmd {
14031541 args: &[&str],
14041542 _event: PromptEvent,
14051543 ) -> anyhow::Result<()> {
1544+ use helix_core::path::expand_tilde;
14061545 let path = args.get(0).context("wrong argument count")?;
1407− let _ = cx.editor.open(path.into(), Action::Replace)?;
1546+ let _ = cx
1547+ .editor
1548+ .open(expand_tilde(Path::new(path)), Action::Replace)?;
14081549 Ok(())
14091550 }
14101551
@@ -1604,7 +1745,7 @@ mod cmd {
16041745
16051746 /// Results an error if there are modified buffers remaining and sets editor error,
16061747 /// 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<()> {
16081749 let modified: Vec<_> = editor
16091750 .documents()
16101751 .filter(|doc| doc.is_modified())
@@ -2577,7 +2718,7 @@ fn apply_workspace_edit(
25772718) {
25782719 if let Some(ref changes) = workspace_edit.changes {
25792720 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"));
25812722 return;
25822723 // 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
25832724 // TODO: find some example that uses workspace changes, and test it
@@ -2595,8 +2736,30 @@ fn apply_workspace_edit(
25952736 match document_changes {
25962737 lsp::DocumentChanges::Edits(document_edits) => {
25972738 for document_edit in document_edits {
2598− let (view, doc) = current!(editor);
2599− assert_eq!(doc.url().unwrap(), document_edit.text_document.uri);
2739+ let path = document_edit
2740+ .text_document
2741+ .uri
2742+ .to_file_path()
2743+ .expect("unable to convert URI to filepath");
2744+ let current_view_id = view!(editor).id;
2745+ let doc = editor
2746+ .document_by_path_mut(path)
2747+ .expect("Document for document_changes not found");
2748+
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
2753+ current_view_id
2754+ } else {
2755+ // Hack: we take the first available view_id
2756+ selections
2757+ .keys()
2758+ .next()
2759+ .copied()
2760+ .expect("No view_id available")
2761+ };
2762+
26002763 let edits = document_edit
26012764 .edits
26022765 .iter()
@@ -2614,8 +2777,8 @@ fn apply_workspace_edit(
26142777 edits,
26152778 offset_encoding,
26162779 );
2617− doc.apply(&transaction, view.id);
2618− doc.append_changes_to_history(view.id);
2780+ doc.apply(&transaction, view_id);
2781+ doc.append_changes_to_history(view_id);
26192782 }
26202783 }
26212784 lsp::DocumentChanges::Operations(operations) => {
@@ -2779,6 +2942,10 @@ fn open_above(cx: &mut Context) {
27792942fn normal_mode(cx: &mut Context) {
27802943 let (view, doc) = current!(cx.editor);
27812944
2945+ if doc.mode == Mode::Normal {
2946+ return;
2947+ }
2948+
27822949 doc.mode = Mode::Normal;
27832950
27842951 doc.append_changes_to_history(view.id);
@@ -3325,17 +3492,20 @@ pub mod insert {
33253492 }
33263493
33273494 use helix_core::auto_pairs;
3328− const HOOKS: &[Hook] = &[auto_pairs::hook, insert];
3329− const POST_HOOKS: &[PostHook] = &[completion, signature_help];
33303495
33313496 pub fn insert_char(cx: &mut Context, c: char) {
33323497 let (view, doc) = current!(cx.editor);
33333498
3499+ let hooks: &[Hook] = match cx.editor.config.auto_pairs {
3500+ true => &[auto_pairs::hook, insert],
3501+ false => &[insert],
3502+ };
3503+
33343504 let text = doc.text();
33353505 let selection = doc.selection(view.id).clone().cursors(text.slice(..));
33363506
33373507 // run through insert hooks, stopping on the first one that returns Some(t)
3338− for hook in HOOKS {
3508+ for hook in hooks {
33393509 if let Some(transaction) = hook(text, &selection, c) {
33403510 doc.apply(&transaction, view.id);
33413511 break;
@@ -3345,7 +3515,7 @@ pub mod insert {
33453515 // TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc)
33463516 // this could also generically look at Transaction, but it's a bit annoying to look at
33473517 // Operation instead of Change.
3348− for hook in POST_HOOKS {
3518+ for hook in &[completion, signature_help] {
33493519 hook(cx, c);
33503520 }
33513521 }
@@ -3510,12 +3680,12 @@ fn yank(cx: &mut Context) {
35103680 let msg = format!(
35113681 "yanked {} selection(s) to register {}",
35123682 values.len(),
3513− cx.selected_register.name()
3683+ cx.register.unwrap_or('"')
35143684 );
35153685
35163686 cx.editor
35173687 .registers
3518− .write(cx.selected_register.name(), values);
3688+ .write(cx.register.unwrap_or('"'), values);
35193689
35203690 cx.editor.set_status(msg);
35213691 exit_select_mode(cx);
@@ -3624,7 +3794,14 @@ fn paste_impl(
36243794 .iter()
36253795 .any(|value| get_line_ending_of_str(value).is_some());
36263796
3627− let mut values = values.iter().cloned().map(Tendril::from).chain(repeat);
3797+ // Only compiled once.
3798+ #[allow(clippy::trivial_regex)]
3799+ static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\r\n|\r|\n").unwrap());
3800+ let mut values = values
3801+ .iter()
3802+ .map(|value| REGEX.replace_all(value, doc.line_ending.as_str()))
3803+ .map(|value| Tendril::from(value.as_ref()))
3804+ .chain(repeat);
36283805
36293806 let text = doc.text();
36303807 let selection = doc.selection(view.id);
@@ -3688,7 +3865,7 @@ fn paste_primary_clipboard_before(cx: &mut Context) {
36883865}
36893866
36903867fn replace_with_yanked(cx: &mut Context) {
3691− let reg_name = cx.selected_register.name();
3868+ let reg_name = cx.register.unwrap_or('"');
36923869 let (view, doc) = current!(cx.editor);
36933870 let registers = &mut cx.editor.registers;
36943871
@@ -3739,7 +3916,7 @@ fn replace_selections_with_primary_clipboard(cx: &mut Context) {
37393916}
37403917
37413918fn paste_after(cx: &mut Context) {
3742− let reg_name = cx.selected_register.name();
3919+ let reg_name = cx.register.unwrap_or('"');
37433920 let (view, doc) = current!(cx.editor);
37443921 let registers = &mut cx.editor.registers;
37453922
@@ -3753,7 +3930,7 @@ fn paste_after(cx: &mut Context) {
37533930}
37543931
37553932fn paste_before(cx: &mut Context) {
3756− let reg_name = cx.selected_register.name();
3933+ let reg_name = cx.register.unwrap_or('"');
37573934 let (view, doc) = current!(cx.editor);
37583935 let registers = &mut cx.editor.registers;
37593936
@@ -3934,24 +4111,49 @@ fn join_selections(cx: &mut Context) {
39344111
39354112fn keep_selections(cx: &mut Context) {
39364113 // keep selections matching regex
3937− let prompt = ui::regex_prompt(cx, "keep:".into(), move |view, doc, _, regex| {
3938− let text = doc.text().slice(..);
4114+ let reg = cx.register.unwrap_or('/');
4115+ let prompt = ui::regex_prompt(
4116+ cx,
4117+ "keep:".into(),
4118+ Some(reg),
4119+ move |view, doc, regex, event| {
4120+ if event != PromptEvent::Update {
4121+ return;
4122+ }
4123+ let text = doc.text().slice(..);
39394124
3940− if let Some(selection) = selection::keep_matches(text, doc.selection(view.id), ®ex) {
3941− doc.set_selection(view.id, selection);
3942− }
3943− });
4125+ if let Some(selection) = selection::keep_matches(text, doc.selection(view.id), ®ex) {
4126+ doc.set_selection(view.id, selection);
4127+ }
4128+ },
4129+ );
39444130
39454131 cx.push_layer(Box::new(prompt));
39464132}
39474133
39484134fn keep_primary_selection(cx: &mut Context) {
39494135 let (view, doc) = current!(cx.editor);
4136+ // TODO: handle count
39504137
39514138 let range = doc.selection(view.id).primary();
39524139 doc.set_selection(view.id, Selection::single(range.anchor, range.head));
39534140}
39544141
4142+fn remove_primary_selection(cx: &mut Context) {
4143+ let (view, doc) = current!(cx.editor);
4144+ // TODO: handle count
4145+
4146+ let selection = doc.selection(view.id);
4147+ if selection.len() == 1 {
4148+ cx.editor.set_error("no selections remaining".to_owned());
4149+ return;
4150+ }
4151+ let index = selection.primary_index();
4152+ let selection = selection.clone().remove(index);
4153+
4154+ doc.set_selection(view.id, selection);
4155+}
4156+
39554157fn completion(cx: &mut Context) {
39564158 // trigger on trigger char, or if user calls it
39574159 // (or on word char typing??)
@@ -4259,6 +4461,12 @@ fn vsplit(cx: &mut Context) {
42594461}
42604462
42614463fn 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());
4467+ return;
4468+ }
4469+ }
42624470 let view_id = view!(cx.editor).id;
42634471 // close current split
42644472 cx.editor.close(view_id, /* close_buffer */ false);
@@ -4267,7 +4475,7 @@ fn wclose(cx: &mut Context) {
42674475fn select_register(cx: &mut Context) {
42684476 cx.on_next_key(move |cx, event| {
42694477 if let Some(ch) = event.char() {
4270− cx.editor.selected_register.select(ch);
4478+ cx.editor.selected_register = Some(ch);
42714479 }
42724480 })
42734481}