lib/lexical/document/position.ex

defmodule Lexical.Document.Position do
  @moduledoc """
  A position inside of a document

  This struct represents a cursor position inside a document, using one-based line and character
  numbers. It's important to note that the position starts before the character given, so positions
  are inclusive of the given character.

  Given the following line of text:
  "Hello there, welcome to lexical"

  the position: `%Lexical.Document.Position{line: 1, character: 1}` starts before the "H" in "Hello"
  """

  alias Lexical.Document
  alias Lexical.Document.Lines

  @derive {Inspect, only: [:line, :character]}
  defstruct [
    :line,
    :character,
    valid?: false,
    context_line: nil,
    document_line_count: 0,
    starting_index: 1
  ]

  @type line :: non_neg_integer()
  @type character :: non_neg_integer()
  @type line_container :: Document.t() | Lines.t()

  @type t :: %__MODULE__{
          character: character(),
          context_line: Document.Line.t() | nil,
          document_line_count: non_neg_integer(),
          line: line(),
          starting_index: non_neg_integer(),
          valid?: boolean()
        }

  use Lexical.StructAccess

  @spec new(line_container(), line(), character()) :: t
  def new(%Document{} = document, line, character)
      when is_number(line) and is_number(character) do
    new(document.lines, line, character)
  end

  def new(%Document.Lines{} = lines, line, character)
      when is_number(line) and is_number(character) do
    line_count = Document.Lines.size(lines)
    starting_index = lines.starting_index

    case Lines.fetch_line(lines, line) do
      {:ok, context_line} ->
        %__MODULE__{
          character: character,
          context_line: context_line,
          document_line_count: line_count,
          line: line,
          starting_index: starting_index,
          valid?: true
        }

      :error ->
        %__MODULE__{
          line: line,
          character: character,
          document_line_count: line_count,
          starting_index: starting_index
        }
    end
  end
end