Skip to content

Commit

Permalink
WIP: Search location and total count.
Browse files Browse the repository at this point in the history
TODO: Documentation
  • Loading branch information
useche committed Sep 10, 2024
1 parent ac72adb commit fd64fc5
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 30 deletions.
85 changes: 56 additions & 29 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use helix_core::{
RopeReader, RopeSlice, Selection, SmallVec, Syntax, Tendril, Transaction,
};
use helix_view::{
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
document::{FormatterError, Mode, SearchMatch, SCRATCH_BUFFER_NAME},
editor::Action,
info::Info,
input::KeyEvent,
Expand Down Expand Up @@ -2097,54 +2097,81 @@ fn search_impl(
// it out, we need to add it back to the position of the selection.
let doc = doc!(editor).text().slice(..);

if regex.find(doc.regex_input()).is_none() {
if show_warnings {
editor.set_error("No matches");
}
return;
}

// use find_at to find the next match after the cursor, loop around the end
// Careful, `Regex` uses `bytes` as offsets, not character indices!
let mut mat = match direction {
Direction::Forward => regex.find(doc.regex_input_at_bytes(start..)),
Direction::Backward => regex.find_iter(doc.regex_input_at_bytes(..start)).last(),
};

if mat.is_none() {
if wrap_around {
mat = match direction {
Direction::Forward => regex.find(doc.regex_input()),
Direction::Backward => regex.find_iter(doc.regex_input_at_bytes(start..)).last(),
};
if mat.is_none() && !wrap_around {
if show_warnings {
editor.set_error("No more matches");
}
return;
}

// If we didn't find a match before, lets wrap the search.
if mat.is_none() {
mat = match direction {
Direction::Forward => regex.find(doc.regex_input()),
Direction::Backward => regex.find_iter(doc.regex_input_at_bytes(start..)).last(),
};

if show_warnings {
if wrap_around && mat.is_some() {
editor.set_status("Wrapped around document");
} else {
editor.set_error("No more matches");
}
editor.set_status("Wrapped around document");
}
}

let mat = mat.unwrap();

// Move the cursor to the match.
let (view, doc) = current!(editor);
let text = doc.text().slice(..);
let selection = doc.selection(view.id);

if let Some(mat) = mat {
let start = text.byte_to_char(mat.start());
let end = text.byte_to_char(mat.end());

if end == 0 {
// skip empty matches that don't make sense
return;
}
let start = text.byte_to_char(mat.start());
let end = text.byte_to_char(mat.end());

// Determine range direction based on the primary range
let primary = selection.primary();
let range = Range::new(start, end).with_direction(primary.direction());
if end == 0 {
// skip empty matches that don't make sense
return;
}

let selection = match movement {
Movement::Extend => selection.clone().push(range),
Movement::Move => selection.clone().replace(selection.primary_index(), range),
};
// Determine range direction based on the primary range
let primary = selection.primary();
let range = Range::new(start, end).with_direction(primary.direction());

doc.set_selection(view.id, selection);
view.ensure_cursor_in_view_center(doc, scrolloff);
let selection = match movement {
Movement::Extend => selection.clone().push(range),
Movement::Move => selection.clone().replace(selection.primary_index(), range),
};

doc.set_selection(view.id, selection);
view.ensure_cursor_in_view_center(doc, scrolloff);

// Set the index of this match and total number of matchs in the doc. It's
// important to set it after `set_selection` since that method sets it to
// `None`.
let doc = doc!(editor).text().slice(..);
let mut all_matches = regex.find_iter(doc.regex_input());
let (idx, _) = all_matches
.by_ref()
.enumerate()
.find(|(_, m)| *m == mat)
.unwrap();
let total_matches = all_matches.count() + idx + 1;
doc_mut!(editor).set_last_search_match(SearchMatch {
idx: idx + 1,
count: total_matches,
});
}

fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
Expand Down
12 changes: 11 additions & 1 deletion helix-term/src/ui/statusline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use helix_core::{coords_at_pos, encoding, Position};
use helix_lsp::lsp::DiagnosticSeverity;
use helix_view::document::DEFAULT_LANGUAGE_NAME;
use helix_view::{
document::{Mode, SCRATCH_BUFFER_NAME},
document::{Mode, SearchMatch, SCRATCH_BUFFER_NAME},
graphics::Rect,
theme::Style,
Document, Editor, View,
Expand Down Expand Up @@ -163,6 +163,7 @@ where
helix_view::editor::StatusLineElement::Spacer => render_spacer,
helix_view::editor::StatusLineElement::VersionControl => render_version_control,
helix_view::editor::StatusLineElement::Register => render_register,
helix_view::editor::StatusLineElement::SearchPosition => render_search_position,
}
}

Expand Down Expand Up @@ -531,3 +532,12 @@ where
write(context, format!(" reg={} ", reg), None)
}
}

fn render_search_position<F>(context: &mut RenderContext, write: F)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
{
if let Some(SearchMatch { idx, count }) = context.doc.get_last_search_match() {
write(context, format!(" [{}/{}] ", idx, count), None);
}
}
22 changes: 22 additions & 0 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ pub enum DocumentOpenError {
IoError(#[from] io::Error),
}

#[derive(Debug, Clone, Copy)]
pub struct SearchMatch {
/// nth match from the beginning of the document.
pub idx: usize,
/// Total number of matches in the document.
pub count: usize,
}

pub struct Document {
pub(crate) id: DocumentId,
text: Rope,
Expand All @@ -151,6 +159,9 @@ pub struct Document {

pub restore_cursor: bool,

/// Current search information.
last_search_match: Option<SearchMatch>,

/// Current indent style.
pub indent_style: IndentStyle,

Expand Down Expand Up @@ -666,6 +677,7 @@ impl Document {
indent_style: DEFAULT_INDENT,
line_ending,
restore_cursor: false,
last_search_match: None,
syntax: None,
language: None,
changes,
Expand Down Expand Up @@ -1215,6 +1227,8 @@ impl Document {

/// Select text within the [`Document`].
pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
self.last_search_match = None;

// TODO: use a transaction?
self.selections
.insert(view_id, selection.ensure_invariants(self.text().slice(..)));
Expand All @@ -1224,6 +1238,14 @@ impl Document {
})
}

pub fn set_last_search_match(&mut self, search_match: SearchMatch) {
self.last_search_match = Some(search_match);
}

pub fn get_last_search_match(&self) -> Option<SearchMatch> {
self.last_search_match
}

/// Find the origin selection of the text in a document, i.e. where
/// a single cursor would go if it were on the first grapheme. If
/// the text is empty, returns (0, 0).
Expand Down
3 changes: 3 additions & 0 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,9 @@ pub enum StatusLineElement {

/// Indicator for selected register
Register,

/// Search index and count
SearchPosition,
}

// Cursor shape is read and used on every rendered frame and so needs
Expand Down

0 comments on commit fd64fc5

Please sign in to comment.