Sagents.TextLines (Sagents v0.7.0)

Copy Markdown

Pure helper for line-number math on text content.

Shared by file system tools, document editing tools, and any other surface that needs consistent line-numbered text operations. All line numbers are 1-indexed.

The line-numbered format uses right-aligned 6-char numbers with a tab separator:

"     1 First line of content"
"     2 "
"     3 Third line"

Summary

Functions

Counts occurrences of old_string in body.

Finds matches of pattern in body, returning structured results with line numbers and optional context.

Renders lines with right-aligned 6-char line numbers and a tab separator.

Replaces lines start_line..end_line (1-indexed, inclusive) with new_lines_text and returns the rejoined body.

Splits body on newlines and returns the total line count. An empty or nil body is treated as a single empty line.

Functions

count_occurrences(body, old_string)

@spec count_occurrences(String.t(), String.t()) :: non_neg_integer()

Counts occurrences of old_string in body.

find(body, pattern, opts \\ [])

@spec find(String.t() | nil, String.t(), keyword()) ::
  {:ok, [map()], boolean()} | {:error, String.t()}

Finds matches of pattern in body, returning structured results with line numbers and optional context.

Options:

  • :regex - treat pattern as regex (default false)
  • :case_sensitive - (default true)
  • :context_lines - lines of context before/after each match (default 2)
  • :max_matches - cap on returned matches (default 20)

Returns {:ok, matches, truncated?} or {:error, reason}. Each match is %{line_number: n, line: text, context_before: [...], context_after: [...]}.

render(body, opts \\ [])

@spec render(
  String.t() | nil,
  keyword()
) :: {String.t(), pos_integer(), pos_integer(), pos_integer()}

Renders lines with right-aligned 6-char line numbers and a tab separator.

Options:

  • :start_line - 1-indexed start line (default 1)
  • :limit - max lines to return (default: all)

Returns {formatted_string, start_line, end_line, total_lines}.

replace_range(body, start_line, end_line, new_lines_text)

@spec replace_range(String.t() | nil, pos_integer(), pos_integer(), String.t()) ::
  {:ok, String.t(), non_neg_integer()} | {:error, String.t()}

Replaces lines start_line..end_line (1-indexed, inclusive) with new_lines_text and returns the rejoined body.

new_lines_text semantics:

  • "" deletes the targeted range (zero lines inserted)
  • "\n" inserts exactly one blank line
  • A trailing "\n" on non-empty content is a line terminator, not an extra blank line: "foo\n" and "foo" both insert one line "foo"

Returns {:ok, new_body, lines_replaced} or {:error, reason}.

replace_text(body, old_string, new_string, replace_all \\ false)

@spec replace_text(String.t(), String.t(), String.t(), boolean()) ::
  {:ok, String.t(), pos_integer()} | {:error, String.t()}

Replaces old_string in body.

When replace_all is false, requires exactly one occurrence. Returns {:ok, new_body, replacement_count} or {:error, reason}.

split(body)

@spec split(String.t() | nil) :: {[String.t()], non_neg_integer()}

Splits body on newlines and returns the total line count. An empty or nil body is treated as a single empty line.