# `Minga.Project.ProjectSearch`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga/project/project_search.ex#L1)

Searches across project files using `ripgrep` or `grep`.

Shells out to the fastest available tool and parses structured output
into a flat list of match results. All functions are pure — no process
state is mutated.

## Tool preference

1. `rg` (ripgrep) — preferred, fast, respects `.gitignore`, JSON output
2. `grep -rn` — universally available fallback, slower, no column info

# `match`

```elixir
@type match() :: %{
  file: String.t(),
  line: pos_integer(),
  col: non_neg_integer(),
  text: String.t()
}
```

A single search match across the project.

# `result`

```elixir
@type result() :: {:ok, [match()], truncated :: boolean()} | {:error, String.t()}
```

Search result.

# `strategy`

```elixir
@type strategy() :: :rg | :grep | :none
```

Search strategy.

# `detect_strategy`

```elixir
@spec detect_strategy() :: strategy()
```

Detects which search strategy to use.

# `parse_grep_line`

```elixir
@spec parse_grep_line(String.t()) :: {:ok, match()} | :skip
```

Parses a single grep output line (`file:line:text`) into a match map.

Returns `{:ok, match}` for valid lines, `:skip` for unparseable lines.

## Examples

    iex> Minga.Project.ProjectSearch.parse_grep_line("lib/foo.ex:42:defmodule Foo")
    {:ok, %{file: "lib/foo.ex", line: 42, col: 0, text: "defmodule Foo"}}

    iex> Minga.Project.ProjectSearch.parse_grep_line("not a match")
    :skip

# `parse_rg_json_line`

```elixir
@spec parse_rg_json_line(String.t()) :: {:ok, match()} | :skip
```

Parses a single ripgrep JSON line into a match map.

Returns `{:ok, match}` for match lines, `:skip` for summary/context lines.

## Examples

    iex> json = ~s({"type":"match","data":{"path":{"text":"lib/foo.ex"},"lines":{"text":"defmodule Foo\n"},"line_number":1,"submatches":[{"match":{"text":"Foo"},"start":10,"end":13}]}})
    iex> Minga.Project.ProjectSearch.parse_rg_json_line(json)
    {:ok, %{file: "lib/foo.ex", line: 1, col: 10, text: "defmodule Foo"}}

# `search`

```elixir
@spec search(String.t(), String.t()) :: result()
```

Searches for `query` in all files under `root`.

Returns `{:ok, matches, truncated?}` on success where `truncated?` is
`true` if results were capped at 10000.

Returns `{:error, message}` if no search tool is available or the query
is empty.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
