diff --git a/apps/common/lib/lexical/ast.ex b/apps/common/lib/lexical/ast.ex index a7d506dab..a8e4e2c00 100644 --- a/apps/common/lib/lexical/ast.ex +++ b/apps/common/lib/lexical/ast.ex @@ -317,21 +317,9 @@ defmodule Lexical.Ast do def contains_position?(ast, %Position{} = position) do case Sourceror.get_range(ast) do %{start: start_pos, end: end_pos} -> - on_same_line? = start_pos[:line] == end_pos[:line] and position.line == start_pos[:line] - - cond do - on_same_line? -> - position.character >= start_pos[:column] and position.character <= end_pos[:column] - - position.line == start_pos[:line] -> - position.character >= start_pos[:column] - - position.line == end_pos[:line] -> - position.character <= end_pos[:column] - - true -> - position.line > start_pos[:line] and position.line < end_pos[:line] - end + start_pos = {start_pos[:line], start_pos[:column]} + end_pos = {end_pos[:line], end_pos[:column]} + within?(position, start_pos, end_pos) nil -> false @@ -514,7 +502,7 @@ defmodule Lexical.Ast do fn %Zipper{node: node} = zipper, {last_position, acc} -> current_position = node_position(node, last_position) - if within_range?(current_position, range) do + if within?(current_position, range.start, range.end) do {zipper, new_acc} = fun.(zipper, acc) {:cont, zipper, {current_position, new_acc}} @@ -528,27 +516,15 @@ defmodule Lexical.Ast do end end - defp within_range?({current_line, current_column}, %Range{} = range) do - start_pos = %Position{} = range.start - end_pos = %Position{} = range.end - - cond do - current_line == start_pos.line -> - current_column >= start_pos.character - - current_line == end_pos.line -> - current_column <= end_pos.character - - true -> - current_line >= start_pos.line and current_line <= end_pos.line - end + defp within?(pos, start_pos, end_pos) do + Position.compare(pos, start_pos) in [:gt, :eq] and + Position.compare(pos, end_pos) in [:lt, :eq] end defp at_or_after?(node, %Position{} = position) do - line = get_line(node, 0) - column = get_column(node, 0) + node_position = node_position(node, {0, 0}) - line > position.line or (line == position.line and column >= position.character) + Position.compare(node_position, position) in [:gt, :eq] end defp one_line_range(%Document{} = document, line_number) do diff --git a/apps/common/lib/lexical/ast/detection/string.ex b/apps/common/lib/lexical/ast/detection/string.ex index abfb51337..b9614e700 100644 --- a/apps/common/lib/lexical/ast/detection/string.ex +++ b/apps/common/lib/lexical/ast/detection/string.ex @@ -41,7 +41,7 @@ defmodule Lexical.Ast.Detection.String do # a string literal defp do_detect({:__block__, _, [literal]} = ast, %Position{} = position) when is_binary(literal) do - case fetch_range(ast, 0, -1) do + case fetch_range(ast) do {:ok, range} -> Range.contains?(range, position) :error -> false end @@ -56,7 +56,7 @@ defmodule Lexical.Ast.Detection.String do # String sigils defp do_detect({sigil, _, _} = ast, %Position{} = position) when sigil in @string_sigils do - case fetch_range(ast, 0, 0) do + case fetch_range(ast) do {:ok, range} -> Range.contains?(range, position) _ -> false end @@ -75,7 +75,7 @@ defmodule Lexical.Ast.Detection.String do |> Keyword.get(:delimiter, "\"") |> String.length() - with {:ok, string_range} <- fetch_range(ast, delimiter_length, -1), + with {:ok, string_range} <- fetch_range(ast, delimiter_length, 0), {:ok, interpolation_ranges} <- collect_interpolation_ranges(interpolations) do Range.contains?(string_range, position) and not Enum.any?(interpolation_ranges, &Range.contains?(&1, position)) @@ -92,7 +92,7 @@ defmodule Lexical.Ast.Detection.String do {ast, :error} {:"::", _, _} = interpolation, {:ok, acc} -> - case fetch_range(interpolation, 1, -1) do + case fetch_range(interpolation, 1, 0) do {:ok, range} -> {interpolation, {:ok, [range | acc]}} diff --git a/apps/remote_control/lib/lexical/remote_control/code_intelligence/variable.ex b/apps/remote_control/lib/lexical/remote_control/code_intelligence/variable.ex index 2a17e6851..241684574 100644 --- a/apps/remote_control/lib/lexical/remote_control/code_intelligence/variable.ex +++ b/apps/remote_control/lib/lexical/remote_control/code_intelligence/variable.ex @@ -82,8 +82,6 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Variable do definition_children = Map.get(block_id_to_children, definition_entry.block_id, []) - definition_start = definition_entry.range.start - # The algorithm here is to first clean up the entries so they either are definitions or references to a # variable with the given name. We sort them by their occurrence in the file, working backwards on a line, so # definitions earlier in the line shadow definitions later in the line. @@ -107,14 +105,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Variable do {entries, _, _} = entries |> Enum.filter(fn %Entry{} = entry -> - entry_start = entry.range.start - - after_definition? = - if entry_start.line == definition_start.line do - entry_start.character > definition_entry.range.end.character - else - entry_start.line > definition_start.line - end + after_definition? = Position.compare(entry.range.start, definition_entry.range.end) == :gt variable_type? = entry.type == :variable correct_subject? = entry.subject == definition_entry.subject diff --git a/apps/remote_control/test/lexical/remote_control/code_intelligence/references_test.exs b/apps/remote_control/test/lexical/remote_control/code_intelligence/references_test.exs index 1d73db782..dd0b66d09 100644 --- a/apps/remote_control/test/lexical/remote_control/code_intelligence/references_test.exs +++ b/apps/remote_control/test/lexical/remote_control/code_intelligence/references_test.exs @@ -59,7 +59,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do def func(x), do: Enum.map(x, & &1 + 1) end / - assert [%Location{} = location] = references(project, "Enum.map|(a, b)", code) + assert [%Location{} = location] = references(project, "Enum.|map(a, b)", code) assert decorate(code, location.range) =~ "def func(x), do: «Enum.map(x, & &1 + 1)»" end @@ -69,7 +69,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do defp func(x), do: Enum.map(x, & &1 + 1) end / - assert [%Location{} = location] = references(project, "Enum.map|(a, b)", code) + assert [%Location{} = location] = references(project, "Enum.|map(a, b)", code) assert decorate(code, location.range) =~ "defp func(x), do: «Enum.map(x, & &1 + 1)»" end @@ -80,7 +80,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do defp func(x), do: E.map(x, & &1 + 1) end / - assert [%Location{} = location] = references(project, "Enum.map|(a, b)", code) + assert [%Location{} = location] = references(project, "Enum.|map(a, b)", code) assert decorate(code, location.range) =~ "defp func(x), do: «E.map(x, & &1 + 1)»" end @@ -91,7 +91,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do defp func(x), do: map(x, & &1 + 1) end / - assert [%Location{} = location] = references(project, "Enum.map|(a, b)", code) + assert [%Location{} = location] = references(project, "Enum.|map(a, b)", code) assert decorate(code, location.range) =~ "defp func(x), do: «map(x, & &1 + 1)»" end @@ -104,7 +104,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do end / - assert [%Location{} = location] = references(project, "Functions.do_map|(a, b)", code) + assert [%Location{} = location] = references(project, "Functions.|do_map(a, b)", code) assert decorate(code, location.range) =~ "def func(x), do: «do_map(x, & &1 + 1)»" end end @@ -118,7 +118,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do end ] - assert [%Location{} = location] = references(project, "ReferencedModule|", code) + assert [%Location{} = location] = references(project, "|ReferencedModule", code) assert decorate(code, location.range) =~ ~s[alias «ReferencedModule»] end @@ -129,7 +129,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do end ] - assert [%Location{} = location] = references(project, "ReferencedModule|", code) + assert [%Location{} = location] = references(project, "|ReferencedModule", code) assert decorate(code, location.range) =~ ~s[@attr «ReferencedModule»] end @@ -138,7 +138,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do some_module = ReferencedModule ] - assert [%Location{} = location] = references(project, "ReferencedModule|", code) + assert [%Location{} = location] = references(project, "|ReferencedModule", code) assert decorate(code, location.range) =~ ~s[some_module = «ReferencedModule»] end @@ -148,7 +148,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do end ] - assert [%Location{} = location] = references(project, "ReferencedModule|", code) + assert [%Location{} = location] = references(project, "|ReferencedModule", code) assert decorate(code, location.range) =~ ~s[def some_fn(«ReferencedModule») do] end @@ -157,7 +157,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do %ReferencedModule{} = something_else ] - assert [%Location{} = location] = references(project, "ReferencedModule|", code) + assert [%Location{} = location] = references(project, "|ReferencedModule", code) assert decorate(code, location.range) =~ ~s[%«ReferencedModule»{} = something_else] end @@ -171,7 +171,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do end ] - assert [location_1, location_2] = references(project, "DefinedModule|", code, true) + assert [location_1, location_2] = references(project, "|DefinedModule", code, true) assert decorate(code, location_1.range) =~ ~s[defmodule «DefinedModule» do] assert decorate(code, location_2.range) =~ ~s[@attr «DefinedModule»] end @@ -185,7 +185,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do end ) - assert [location] = references(project, "%Struct|{}", code, true) + assert [location] = references(project, "%|Struct{}", code, true) assert decorate(code, location.range) =~ "«defstruct [:field]»" end @@ -198,22 +198,23 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do ] selector = ~q( - defmodule Struct do - defstruc|t [:name, :value] - end + defmodule Struct do + defstruc|t [:name, :value] + end ) + assert [location] = references(project, selector, code) assert decorate(code, location.range) =~ "def something(«%Struct{}») do" end test "excludes their definition", %{project: project} do code = ~q( - defmodule Struct do - defstruct [:field] - end + defmodule Struct do + defstruct [:field] + end ) - assert [] = references(project, "%Struct|{}", code) + assert [] = references(project, "%|Struct{}", code) end end @@ -264,7 +265,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do test "are found in a function body", %{project: project} do query = ~S[ def my_fun do - first| = 4 + |first = 4 y = first * 2 z = y * 3 + first end @@ -282,7 +283,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do query = ~S[ def my_fun do first = 4 - y = first| * 2 + y = |first * 2 z = y * 3 + first end ] diff --git a/apps/remote_control/test/lexical/remote_control/code_intelligence/variable_test.exs b/apps/remote_control/test/lexical/remote_control/code_intelligence/variable_test.exs index 4ede9e690..8a52bee5b 100644 --- a/apps/remote_control/test/lexical/remote_control/code_intelligence/variable_test.exs +++ b/apps/remote_control/test/lexical/remote_control/code_intelligence/variable_test.exs @@ -1,5 +1,6 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do alias Lexical.Ast + alias Lexical.RemoteControl.CodeIntelligence.Entity alias Lexical.RemoteControl.CodeIntelligence.Variable use ExUnit.Case @@ -11,9 +12,9 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do def find_definition(code) do {position, document} = pop_cursor(code, as: :document) analysis = Ast.analyze(document) - {:ok, {:local_or_var, var_name}} = Ast.cursor_context(analysis, position) + {:ok, {:variable, var_name}, _} = Entity.resolve(analysis, position) - case Variable.definition(analysis, position, List.to_atom(var_name)) do + case Variable.definition(analysis, position, var_name) do {:ok, entry} -> {:ok, entry.range, document} error -> error end @@ -22,11 +23,11 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do def find_references(code, include_definition? \\ false) do {position, document} = pop_cursor(code, as: :document) analysis = Ast.analyze(document) - {:ok, {:local_or_var, var_name}} = Ast.cursor_context(analysis, position) + {:ok, {:variable, var_name}, _} = Entity.resolve(analysis, position) ranges = analysis - |> Variable.references(position, List.to_atom(var_name), include_definition?) + |> Variable.references(position, var_name, include_definition?) |> Enum.map(& &1.range) {:ok, ranges, document} @@ -36,7 +37,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "are returned if it is selected" do {:ok, range, doc} = ~q[ - def foo(param|) do + def foo(|param) do param end ] @@ -49,7 +50,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do {:ok, range, doc} = ~q[ def foo(param) do - param| + |param end ] |> find_definition() @@ -61,7 +62,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do {:ok, range, doc} = ~q[ def foo(other_param, param) do - param| + |param end ] |> find_definition() @@ -74,7 +75,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do ~q[ def foo(param) do param = param + 1 - param| + |param end ] |> find_definition() @@ -86,7 +87,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do {:ok, range, doc} = ~q[ def foo(param) do - param = param| + 1 + param = |param + 1 end ] |> find_definition() @@ -97,9 +98,9 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "when there are multiple definitions on one line" do {:ok, range, doc} = ~q[ - param = 3 - foo = param = param + 1 - param| + param = 3 + foo = param = param + 1 + |param ] |> find_definition() @@ -110,7 +111,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do {:ok, range, doc} = ~q[ %{key: value} = map - value| + |value ] |> find_definition() @@ -125,7 +126,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do def my_fun do foo = 3 if something do - foo| + |foo end end ] @@ -140,7 +141,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do defmodule Parent do x = 3 def fun do - unquote(x|) + unquote(|x) end end ] @@ -155,7 +156,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do x = 3 defmodule Parent do def fun do - unquote(x|) + unquote(|x) end end ] @@ -169,7 +170,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "in a function parameter" do {:ok, [range], doc} = ~q[ - def something(param|) do + def something(|param) do param end ] @@ -181,7 +182,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "can include definitions" do {:ok, [definition, reference], doc} = ~q[ - def something(param|) do + def something(|param) do param end ] @@ -195,9 +196,9 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do {:ok, [first, second, third], doc} = ~q[ def something(param) do - y = param + 3 - z = param + 4 - param| + y + z + y = param + 3 + z = param + 4 + |param + y + z end ] |> find_references() @@ -210,7 +211,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "are found in a function body" do {:ok, [first, second, third, fourth, fifth], doc} = ~q[ - def something(param|) do + def something(|param) do x = param + param + 3 y = param + x z = 10 + param @@ -229,7 +230,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "are constrained to their definition function" do {:ok, [range], doc} = ~q[ - def something(param|) do + def something(|param) do param end @@ -245,7 +246,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "are visible across blocks" do {:ok, [first, second], doc} = ~q[ - def something(param|) do + def something(|param) do if something() do param + 1 else @@ -265,7 +266,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do def something(param) do if something() do - param| = 3 + |param = 3 param + 1 end param + 1 @@ -279,7 +280,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "are found in the head of a case statement" do {:ok, [range], doc} = ~q[ - def something(param|) do + def something(|param) do case param do _ -> :ok end @@ -295,7 +296,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do ~q[ def something(param) do case param do - param| when is_number(param) -> param + 1 + |param when is_number(param) -> param + 1 param -> 0 end end @@ -309,11 +310,11 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "are found in a module body" do {:ok, [range], doc} = ~q[ - defmodule Outer do - something| = 3 - def foo(unquote(something)) do + defmodule Outer do + |something = 3 + def foo(unquote(something)) do + end end - end ] |> find_references() @@ -323,13 +324,13 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "are found in anonymous function parameters" do {:ok, [first, second], doc} = ~q[ - def outer do - fn param| -> - y = param + 1 - x = param + 2 - x + y + def outer do + fn |param -> + y = param + 1 + x = param + 2 + x + y + end end - end ] |> find_references() @@ -340,11 +341,11 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "are found in a pin operator" do {:ok, [ref], doc} = ~q[ - def outer(param|) do - fn ^param -> - nil + def outer(|param) do + fn ^param -> + nil + end end - end ] |> find_references() @@ -354,7 +355,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "are found inside of string interpolation" do {:ok, [ref], doc} = ~S[ - name| = "Stinky" + |name = "Stinky" "#{name} Stinkman" ] |> find_references() @@ -367,7 +368,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "works for variables defined outside of an if while being shadowed" do {:ok, [first, second], doc} = ~q{ - entries| = [1, 2, 3] + |entries = [1, 2, 3] entries = if something() do [4 | entries] @@ -386,14 +387,14 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do ~q" shadowed? = false fn - {:foo, entries|} -> - if shadowed? do - [1, entries] - else + {:foo, |entries} -> + if shadowed? do + [1, entries] + else + entries + end + {:bar, entries} -> entries - end - {:bar, entries} -> - entries end " |> find_references() @@ -408,27 +409,27 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do {:ok, [], _doc} = ~q[ def something(param) do - other = other = other| = param + other = other = |other = param end - ] + ] |> find_references() end test "in a function body" do {:ok, [], _doc} = ~q[ - def something(param|) do + def something(|param) do param = 3 param end - ] + ] |> find_references() end test "in anonymous function arguments" do {:ok, [], _doc} = ~q[ - def something(param|) do + def something(|param) do fn param -> param + 1 end @@ -442,7 +443,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do {:ok, [range], doc} = ~q[ def something do - shadow| = 4 + |shadow = 4 if true do shadow = shadow + 1 shadow @@ -458,7 +459,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do {:ok, [range], doc} = ~q[ def something do - shadow| = 4 + |shadow = 4 if true do shadow = :ok shadow @@ -474,7 +475,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do test "exiting nested blocks" do {:ok, [range], doc} = ~q[ - def something(param| = arg) do + def something(|param = arg) do case arg do param when is_number(n) -> param + 4 diff --git a/projects/lexical_shared/lib/lexical/document/position.ex b/projects/lexical_shared/lib/lexical/document/position.ex index 53b2baf58..9738c10ef 100644 --- a/projects/lexical_shared/lib/lexical/document/position.ex +++ b/projects/lexical_shared/lib/lexical/document/position.ex @@ -70,6 +70,33 @@ defmodule Lexical.Document.Position do } end end + + @doc """ + Compares two positions. + + Returns `:gt`, `:lt`, or `:eq` depending on the location of the first + position relative to the second. + """ + @spec compare(t | {line, character}, t | {line, character}) :: :lt | :eq | :gt + def compare(%__MODULE__{} = pos1, %__MODULE__{} = pos2) do + compare({pos1.line, pos1.character}, {pos2.line, pos2.character}) + end + + def compare(%__MODULE__{} = pos1, pos2) do + compare({pos1.line, pos1.character}, pos2) + end + + def compare(pos1, %__MODULE__{} = pos2) do + compare(pos1, {pos2.line, pos2.character}) + end + + def compare({_, _} = first, {_, _} = second) do + cond do + first < second -> :lt + first > second -> :gt + true -> :eq + end + end end defimpl Inspect, for: Lexical.Document.Position do diff --git a/projects/lexical_shared/lib/lexical/document/range.ex b/projects/lexical_shared/lib/lexical/document/range.ex index e3ee41194..801aa28a2 100644 --- a/projects/lexical_shared/lib/lexical/document/range.ex +++ b/projects/lexical_shared/lib/lexical/document/range.ex @@ -40,19 +40,8 @@ defmodule Lexical.Document.Range do def contains?(%__MODULE__{} = range, %Position{} = position) do %__MODULE__{start: start_pos, end: end_pos} = range - cond do - position.line == start_pos.line and position.line == end_pos.line -> - position.character >= start_pos.character and position.character <= end_pos.character - - position.line == start_pos.line -> - position.character >= start_pos.character - - position.line == end_pos.line -> - position.character < end_pos.character - - true -> - position.line > start_pos.line and position.line < end_pos.line - end + Position.compare(position, start_pos) in [:gt, :eq] and + Position.compare(position, end_pos) == :lt end end diff --git a/projects/lexical_shared/mix.lock b/projects/lexical_shared/mix.lock index efcd11d56..7f9e4f0e2 100644 --- a/projects/lexical_shared/mix.lock +++ b/projects/lexical_shared/mix.lock @@ -1,5 +1,5 @@ %{ - "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"}, + "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, diff --git a/projects/lexical_shared/test/lexical/document/position_test.exs b/projects/lexical_shared/test/lexical/document/position_test.exs new file mode 100644 index 000000000..41561aff6 --- /dev/null +++ b/projects/lexical_shared/test/lexical/document/position_test.exs @@ -0,0 +1,45 @@ +defmodule Lexical.Document.PositionTest do + alias Lexical.Document.Line + alias Lexical.Document.Lines + alias Lexical.Document.Position + + import Line + + use ExUnit.Case, async: true + + describe "compare/2" do + test "positions on the same line" do + assert :eq = Position.compare(position(1, 10), position(1, 10)) + assert :gt = Position.compare(position(1, 11), position(1, 10)) + assert :lt = Position.compare(position(1, 9), position(1, 10)) + end + + test "position on earlier line" do + assert :lt = Position.compare(position(1, 10), position(2, 10)) + assert :lt = Position.compare(position(1, 11), position(2, 10)) + assert :lt = Position.compare(position(1, 9), position(2, 10)) + end + + test "position on later line" do + assert :gt = Position.compare(position(2, 10), position(1, 10)) + assert :gt = Position.compare(position(2, 11), position(1, 10)) + assert :gt = Position.compare(position(2, 9), position(1, 10)) + end + end + + defp position(line, character) do + stub_line = line(text: "", ending: "\n", line_number: line, ascii?: true) + + lines = + line + |> empty_lines() + |> put_in([Access.key(:lines), Access.elem(line - 1)], stub_line) + + Position.new(lines, line, character) + end + + defp empty_lines(length) do + tuple = List.to_tuple(for(x <- 1..length, do: x)) + %Lines{lines: tuple, starting_index: 1} + end +end diff --git a/projects/lexical_shared/test/lexical/document/range_test.exs b/projects/lexical_shared/test/lexical/document/range_test.exs index 1d4c8860a..6161888e7 100644 --- a/projects/lexical_shared/test/lexical/document/range_test.exs +++ b/projects/lexical_shared/test/lexical/document/range_test.exs @@ -13,7 +13,12 @@ defmodule Lexical.Document.RangeTest do assert Range.contains?(range, position(1, 1)) end - test "excludes the end position" do + test "excludes the end position in a single-line range" do + range = Range.new(position(1, 1), position(1, 10)) + refute Range.contains?(range, position(1, 10)) + end + + test "excludes the end position in a multi-line range" do range = Range.new(position(1, 1), position(2, 1)) refute Range.contains?(range, position(2, 1)) end