on master help sign in
N as extend with search (for now, N should be search_prev).
73f4abbBlaž Hrastnik
helix-core/src/selection.rs+47−41
@@ -159,6 +159,12 @@ impl Selection {
159 }
159 }
160 }
160 }
161
161
162+ pub fn add(mut self, range: Range) -> Self {
163+ let index = self.ranges.len();
164+ self.ranges.push(range);
165+
166+ Self::normalize(self.ranges, index)
167+ }
162 // add_range // push
168 // add_range // push
163 // replace_range
169 // replace_range
164
170
@@ -204,53 +210,53 @@ impl Selection {
204 Self::single(pos, pos)
210 Self::single(pos, pos)
205 }
211 }
206
212
207− // TODO: consume an iterator or a vec to reduce allocations?
208− #[must_use]
209− pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self {
213+ fn normalize(mut ranges: SmallVec<[Range; 1]>, mut primary_index: usize) -> Selection {
210− assert!(!ranges.is_empty());
214+ let primary = ranges[primary_index];
215+ ranges.sort_unstable_by_key(Range::from);
216+ primary_index = ranges.iter().position(|&range| range == primary).unwrap();
211
217
212− fn normalize(mut ranges: SmallVec<[Range; 1]>, mut primary_index: usize) -> Selection {
213− let primary = ranges[primary_index];
214− ranges.sort_unstable_by_key(Range::from);
215− primary_index = ranges.iter().position(|&range| range == primary).unwrap();
216−
217− let mut result = SmallVec::with_capacity(ranges.len()); // approx
218+ let mut result = SmallVec::with_capacity(ranges.len()); // approx
218−
219+
219− // TODO: we could do with one vec by removing elements as we mutate
220+ // TODO: we could do with one vec by removing elements as we mutate
220−
221+
221− for (i, range) in ranges.into_iter().enumerate() {
222+ for (i, range) in ranges.into_iter().enumerate() {
222− // if previous value exists
223+ // if previous value exists
223− if let Some(prev) = result.last_mut() {
224+ if let Some(prev) = result.last_mut() {
224− // and we overlap it
225+ // and we overlap it
225− if range.overlaps(prev) {
226+ if range.overlaps(prev) {
226− let from = prev.from();
227+ let from = prev.from();
227− let to = std::cmp::max(range.to(), prev.to());
228+ let to = std::cmp::max(range.to(), prev.to());
228−
229+
229− if i <= primary_index {
230+ if i <= primary_index {
230− primary_index -= 1
231+ primary_index -= 1
231− }
232−
233− // merge into previous
234− if range.anchor > range.head {
235− prev.anchor = to;
236− prev.head = from;
237− } else {
238− prev.anchor = from;
239− prev.head = to;
240− }
241− continue;
242 }
232 }
243− }
244
233
245− result.push(range)
234+ // merge into previous
235+ if range.anchor > range.head {
236+ prev.anchor = to;
237+ prev.head = from;
238+ } else {
239+ prev.anchor = from;
240+ prev.head = to;
241+ }
242+ continue;
243+ }
246 }
244 }
247
245
248− Selection {
249− ranges: result,
246+ result.push(range)
250− primary_index,
251− }
252 }
247 }
253
248
249+ Selection {
250+ ranges: result,
251+ primary_index,
252+ }
253+ }
254+
255+ // TODO: consume an iterator or a vec to reduce allocations?
256+ #[must_use]
257+ pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self {
258+ assert!(!ranges.is_empty());
259+
254 // fast path for a single selection (cursor)
260 // fast path for a single selection (cursor)
255 if ranges.len() == 1 {
261 if ranges.len() == 1 {
256 return Self {
262 return Self {
@@ -260,7 +266,7 @@ impl Selection {
260 }
266 }
261
267
262 // TODO: only normalize if needed (any ranges out of order)
268 // TODO: only normalize if needed (any ranges out of order)
263− normalize(ranges, primary_index)
269+ Self::normalize(ranges, primary_index)
264 }
270 }
265
271
266 /// Takes a closure and maps each selection over the closure.
272 /// Takes a closure and maps each selection over the closure.
helix-term/src/commands.rs+21−6
@@ -608,9 +608,10 @@ pub fn split_selection_on_newline(cx: &mut Context) {
608// I'd probably collect all the matches right now and store the current index. The cache needs
608// I'd probably collect all the matches right now and store the current index. The cache needs
609// wiping if input happens.
609// wiping if input happens.
610
610
611−fn _search(doc: &mut Document, view_id: ViewId, contents: &str, regex: &Regex) {
611+fn _search(doc: &mut Document, view_id: ViewId, contents: &str, regex: &Regex, extend: bool) {
612 let text = doc.text();
612 let text = doc.text();
613− let start = doc.selection(view_id).cursor();
613+ let selection = doc.selection(view_id);
614+ let start = selection.cursor();
614
615
615 // use find_at to find the next match after the cursor, loop around the end
616 // use find_at to find the next match after the cursor, loop around the end
616 let mat = regex
617 let mat = regex
@@ -619,7 +620,13 @@ fn _search(doc: &mut Document, view_id: ViewId, contents: &str, regex: &Regex) {
619 if let Some(mat) = mat {
620 if let Some(mat) = mat {
620 let start = text.byte_to_char(mat.start());
621 let start = text.byte_to_char(mat.start());
621 let end = text.byte_to_char(mat.end());
622 let end = text.byte_to_char(mat.end());
623+
624+ let selection = if extend {
625+ selection.clone().add(Range::new(start, end - 1))
626+ } else {
622− let selection = Selection::single(start, end - 1);
627+ Selection::single(start, end - 1)
628+ };
629+
623 // TODO: (first_match, regex) stuff in register?
630 // TODO: (first_match, regex) stuff in register?
624 doc.set_selection(view_id, selection);
631 doc.set_selection(view_id, selection);
625 };
632 };
@@ -639,7 +646,7 @@ pub fn search(cx: &mut Context) {
639 let prompt = ui::regex_prompt(cx, "search:".to_string(), move |doc, regex| {
646 let prompt = ui::regex_prompt(cx, "search:".to_string(), move |doc, regex| {
640 let text = doc.text();
647 let text = doc.text();
641 let start = doc.selection(view_id).cursor();
648 let start = doc.selection(view_id).cursor();
642− _search(doc, view_id, &contents, &regex);
649+ _search(doc, view_id, &contents, &regex, false);
643
650
644 // TODO: only store on enter (accept), not update
651 // TODO: only store on enter (accept), not update
645 register::set('\\', vec![regex.as_str().to_string()]);
652 register::set('\\', vec![regex.as_str().to_string()]);
@@ -648,17 +655,25 @@ pub fn search(cx: &mut Context) {
648 cx.push_layer(Box::new(prompt));
655 cx.push_layer(Box::new(prompt));
649}
656}
650
657
651−pub fn search_next(cx: &mut Context) {
658+pub fn _search_next(cx: &mut Context, extend: bool) {
652 if let Some(query) = register::get('\\') {
659 if let Some(query) = register::get('\\') {
653 let query = query.first().unwrap();
660 let query = query.first().unwrap();
654 let view_id = cx.view_id;
661 let view_id = cx.view_id;
655 let doc = cx.doc();
662 let doc = cx.doc();
656 let contents = doc.text().slice(..).to_string();
663 let contents = doc.text().slice(..).to_string();
657 let regex = Regex::new(&query).unwrap();
664 let regex = Regex::new(&query).unwrap();
658− _search(doc, view_id, &contents, &regex);
665+ _search(doc, view_id, &contents, &regex, extend);
659 }
666 }
660}
667}
661
668
669+pub fn search_next(cx: &mut Context) {
670+ _search_next(cx, false);
671+}
672+
673+pub fn extend_search_next(cx: &mut Context) {
674+ _search_next(cx, true);
675+}
676+
662pub fn search_selection(cx: &mut Context) {
677pub fn search_selection(cx: &mut Context) {
663 let (view, doc) = cx.current();
678 let (view, doc) = cx.current();
664 let contents = doc.text().slice(..);
679 let contents = doc.text().slice(..);
helix-term/src/keymap.rs+1
@@ -199,6 +199,7 @@ pub fn default() -> Keymaps {
199 key!('/') => commands::search,
199 key!('/') => commands::search,
200 // ? for search_reverse
200 // ? for search_reverse
201 key!('n') => commands::search_next,
201 key!('n') => commands::search_next,
202+ shift!('N') => commands::extend_search_next,
202 // N for search_prev
203 // N for search_prev
203 key!('*') => commands::search_selection,
204 key!('*') => commands::search_selection,
204
205
helix-term/src/ui/editor.rs+1
@@ -61,6 +61,7 @@ impl EditorView {
61 view.area.width - OFFSET,
61 view.area.width - OFFSET,
62 view.area.height.saturating_sub(1),
62 view.area.height.saturating_sub(1),
63 ); // - 1 for statusline
63 ); // - 1 for statusline
64+
64 self.render_buffer(doc, view, area, surface, theme, is_focused);
65 self.render_buffer(doc, view, area, surface, theme, is_focused);
65
66
66 // if we're not at the edge of the screen, draw a right border
67 // if we're not at the edge of the screen, draw a right border