2.0 KiB · text History 6280797
defmodule GitGud.PullRequests.Suggestion do
@moduledoc """
A ```suggestion fenced block inside a line comment: the reviewer's
proposed replacement for the line they commented on.
Parsing lives here rather than in the Markdown renderer because
applying one needs the raw replacement text, not its rendered HTML.
"""
@fence ~r/^```suggestion[ \t]*\r?\n(?<body>.*?)\r?\n?^```[ \t]*$/ms
@doc """
The suggested replacement in `body`, or nil when there isn't one.
An empty suggestion block is a real thing — it means "delete this
line" — so it returns `""` rather than nil.
"""
def parse(body) when is_binary(body) do
case Regex.named_captures(@fence, body) do
%{"body" => text} -> text
_ -> nil
end
end
def parse(_), do: nil
@doc "True when `body` carries a suggestion block."
def suggestion?(body), do: not is_nil(parse(body))
@doc """
The comment text with the suggestion block stripped out — what the
reviewer said *about* the change, as opposed to the change itself.
"""
def prose(body) when is_binary(body) do
body
|> String.replace(@fence, "")
|> String.trim()
end
def prose(_), do: ""
@doc """
Replace 1-indexed `line` of `content` with `replacement`.
A replacement spanning several lines expands in place; an empty one
removes the line. Returns `{:error, :out_of_range}` when the file no
longer has that line, which is how a stale suggestion surfaces.
"""
def apply_to(content, line, replacement)
when is_binary(content) and is_integer(line) and is_binary(replacement) do
lines = String.split(content, "\n")
if line < 1 or line > length(lines) do
{:error, :out_of_range}
else
idx = line - 1
replacement_lines = if replacement == "", do: [], else: String.split(replacement, "\n")
updated =
Enum.slice(lines, 0, idx) ++
replacement_lines ++
Enum.slice(lines, (idx + 1)..-1//1)
{:ok, Enum.join(updated, "\n")}
end
end
end