Minga.Editing.Search (Minga v0.1.0)

Copy Markdown View Source

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

direction()

@type direction() :: :forward | :backward

Search direction.

match()

@type match() :: Minga.Editing.Search.Match.t()

A search match with line, byte column, and byte length.

position()

@type position() :: {non_neg_integer(), non_neg_integer()}

A zero-indexed cursor position.

replacement_span()

@type replacement_span() :: {non_neg_integer(), non_neg_integer()}

A replacement span: {byte_col, byte_length} in the substituted line.

substitute_result()

@type substitute_result() :: {String.t(), non_neg_integer()}

Result of a substitution: new content and count of replacements.

Functions

find_all_in_range(lines, pattern, first_line)

@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}]

find_next(content, pattern, cursor, arg4)

@spec find_next(String.t(), String.t(), position(), direction()) :: position() | nil

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

substitute(content, pattern, replacement, global?)

@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}

substitute_line(line, pattern, replacement, global?)

@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}.

substitute_line_with_spans(line, pattern, replacement, global?)

@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}]}

word_at_cursor(buf, arg)

@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