Pure search functions for the Minga editor.
Provides substring search over buffer content. All functions are pure — they take content strings or line lists and return positions. No buffer or process state is mutated.
All positions use byte-indexed columns.
Match representation
A match is a {line, byte_col, byte_length} tuple where line and
byte_col are zero-indexed.
Summary
Types
Search direction.
A search match with line, byte column, and byte length.
A zero-indexed cursor position.
A replacement span: {byte_col, byte_length} in the substituted line.
Result of a substitution: new content and count of replacements.
Functions
Finds all occurrences of pattern in lines (a list of line strings).
Finds the next match for pattern starting from cursor in the given
direction. Wraps around the buffer if no match is found between cursor
and the end (or start for backward).
Replaces occurrences of pattern with replacement in content.
Substitutes occurrences of pattern with replacement in a single line.
Like substitute_line/4 but also returns the column spans of the
replacement text in the resulting line, for highlighting.
Returns the word under the cursor in the gap buffer, or nil if the
cursor is not on a word character.
Types
@type direction() :: :forward | :backward
Search direction.
@type match() :: Minga.Editing.Search.Match.t()
A search match with line, byte column, and byte length.
@type position() :: {non_neg_integer(), non_neg_integer()}
A zero-indexed cursor position.
@type replacement_span() :: {non_neg_integer(), non_neg_integer()}
A replacement span: {byte_col, byte_length} in the substituted line.
@type substitute_result() :: {String.t(), non_neg_integer()}
Result of a substitution: new content and count of replacements.
Functions
@spec find_all_in_range([String.t()], String.t(), non_neg_integer()) :: [match()]
Finds all occurrences of pattern in lines (a list of line strings).
first_line is the buffer line number of the first element in lines,
used to compute absolute line numbers in the returned matches.
Returns a list of Search.Match structs.
Examples
iex> Minga.Editing.Search.find_all_in_range(["foo bar foo", "baz foo"], "foo", 0)
[%Minga.Editing.Search.Match{line: 0, col: 0, length: 3}, %Minga.Editing.Search.Match{line: 0, col: 8, length: 3}, %Minga.Editing.Search.Match{line: 1, col: 4, length: 3}]
Finds the next match for pattern starting from cursor in the given
direction. Wraps around the buffer if no match is found between cursor
and the end (or start for backward).
Returns {line, byte_col} of the match start, or nil if no match exists.
Examples
iex> Minga.Editing.Search.find_next("hello world\nhello again", "hello", {0, 1}, :forward)
{1, 0}
iex> Minga.Editing.Search.find_next("hello world\nhello again", "hello", {1, 0}, :backward)
{0, 0}
iex> Minga.Editing.Search.find_next("no match here", "xyz", {0, 0}, :forward)
nil
@spec substitute(String.t(), String.t(), String.t(), boolean()) :: substitute_result()
Replaces occurrences of pattern with replacement in content.
When global? is true, replaces all occurrences. When false, replaces
only the first occurrence on each line (Vim :s default).
Returns {new_content, replacement_count}.
Examples
iex> Minga.Editing.Search.substitute("foo bar foo", "foo", "baz", true)
{"baz bar baz", 2}
iex> Minga.Editing.Search.substitute("foo bar foo", "foo", "baz", false)
{"baz bar foo", 1}
iex> Minga.Editing.Search.substitute("hello world", "xyz", "abc", true)
{"hello world", 0}
@spec substitute_line(String.t(), String.t(), String.t(), boolean()) :: {String.t(), non_neg_integer()}
Substitutes occurrences of pattern with replacement in a single line.
When global? is true, replaces all occurrences. When false, replaces
only the first occurrence.
Returns {new_line, replacement_count}.
@spec substitute_line_with_spans(String.t(), String.t(), String.t(), boolean()) :: {String.t(), non_neg_integer(), [replacement_span()]}
Like substitute_line/4 but also returns the column spans of the
replacement text in the resulting line, for highlighting.
Returns {new_line, replacement_count, spans}.
Examples
iex> Minga.Editing.Search.substitute_line_with_spans("foo bar foo", "foo", "hello", true)
{"hello bar hello", 2, [{0, 5}, {10, 5}]}
@spec word_at_cursor(Minga.Buffer.Document.t(), position()) :: String.t() | nil
Returns the word under the cursor in the gap buffer, or nil if the
cursor is not on a word character.
A word character is alphanumeric or underscore (matching Vim's \<word\>).
Examples
iex> buf = Minga.Buffer.Document.new("hello world")
iex> Minga.Editing.Search.word_at_cursor(buf, {0, 0})
"hello"
iex> buf = Minga.Buffer.Document.new("hello world")
iex> Minga.Editing.Search.word_at_cursor(buf, {0, 5})
nil