From d2e39a51542dec75932a20cdf430acff24896087 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Mon, 2 Oct 2023 22:16:02 +0200 Subject: [PATCH] Correctly highlight active param in calls with defaults Override active param on signature level Partially fixes https://github.com/elixir-lsp/elixir-ls/issues/994 --- lib/elixir_sense/core/metadata.ex | 1 + lib/elixir_sense/providers/signature.ex | 31 +++++--- test/elixir_sense/signature_test.exs | 98 +++++++++++++++++++++++-- 3 files changed, 113 insertions(+), 17 deletions(-) diff --git a/lib/elixir_sense/core/metadata.ex b/lib/elixir_sense/core/metadata.ex index 013342d3..f6fa0935 100644 --- a/lib/elixir_sense/core/metadata.ex +++ b/lib/elixir_sense/core/metadata.ex @@ -35,6 +35,7 @@ defmodule ElixirSense.Core.Metadata do moduledoc_positions: %{} @type signature_t :: %{ + optional(:active_param) => :non_neg_integer, name: String.t(), params: [String.t()], spec: String.t(), diff --git a/lib/elixir_sense/providers/signature.ex b/lib/elixir_sense/providers/signature.ex index 6d7be549..9ea5f244 100644 --- a/lib/elixir_sense/providers/signature.ex +++ b/lib/elixir_sense/providers/signature.ex @@ -46,6 +46,7 @@ defmodule ElixirSense.Providers.Signature do cursor_position ) do signatures = find_signatures({mod, fun}, npar, kind, env, metadata) + %{active_param: npar, signatures: signatures} else _ -> @@ -61,16 +62,28 @@ defmodule ElixirSense.Providers.Signature do end signatures - |> Enum.filter(fn %{params: params} -> - params_length = length(params) - - if params_length == 0 do - npar == 0 - else - params_length > npar - end + |> Enum.map(fn + %{params: []} = signature -> + if npar == 0 do + signature + end + + %{params: params} = signature -> + defaults = + params + |> Enum.with_index() + |> Enum.map(fn {param, index} -> {Regex.match?(~r/\\\\/, param), index} end) + |> Enum.sort() + |> Enum.at(npar) + + case defaults do + nil -> nil + {_, index} when index != npar -> Map.put(signature, :active_param, index) + _ -> signature + end end) - |> Enum.sort_by(&length(&1.params)) + |> Enum.reject(&is_nil/1) + |> Enum.sort_by(&{length(&1.params), -Map.get(&1, :active_param, npar)}) end defp find_function_signatures({nil, _fun}, _env, _metadata), do: [] diff --git a/test/elixir_sense/signature_test.exs b/test/elixir_sense/signature_test.exs index a23aa882..b1282021 100644 --- a/test/elixir_sense/signature_test.exs +++ b/test/elixir_sense/signature_test.exs @@ -454,7 +454,7 @@ defmodule ElixirSense.SignatureTest do } end - test "find signatures when function with many clausess" do + test "find signatures when function with many clauses" do code = """ defmodule MyModule do List.starts_with?( @@ -539,7 +539,8 @@ defmodule ElixirSense.SignatureTest do "Glues two documents (`doc1` and `doc2`) inserting the given\nbreak `break_string` between them.", name: "glue", params: ["doc1", "break_string \\\\ \" \"", "doc2"], - spec: "@spec glue(t(), binary(), t()) :: t()" + spec: "@spec glue(t(), binary(), t()) :: t()", + active_param: 2 } ] } @@ -560,7 +561,8 @@ defmodule ElixirSense.SignatureTest do "Glues two documents (`doc1` and `doc2`) inserting the given\nbreak `break_string` between them.", name: "glue", params: ["doc1", "break_string \\\\ \" \"", "doc2"], - spec: "@spec glue(t(), binary(), t()) :: t()" + spec: "@spec glue(t(), binary(), t()) :: t()", + active_param: 2 } ] } @@ -582,7 +584,8 @@ defmodule ElixirSense.SignatureTest do "Glues two documents (`doc1` and `doc2`) inserting the given\nbreak `break_string` between them.", name: "glue", params: ["doc1", "break_string \\\\ \" \"", "doc2"], - spec: "@spec glue(t(), binary(), t()) :: t()" + spec: "@spec glue(t(), binary(), t()) :: t()", + active_param: 2 } ] } @@ -605,7 +608,8 @@ defmodule ElixirSense.SignatureTest do "Glues two documents (`doc1` and `doc2`) inserting the given\nbreak `break_string` between them.", name: "glue", params: ["doc1", "break_string \\\\ \" \"", "doc2"], - spec: "@spec glue(t(), binary(), t()) :: t()" + spec: "@spec glue(t(), binary(), t()) :: t()", + active_param: 2 } ] } @@ -627,7 +631,8 @@ defmodule ElixirSense.SignatureTest do "Glues two documents (`doc1` and `doc2`) inserting the given\nbreak `break_string` between them.", name: "glue", params: ["doc1", "break_string \\\\ \" \"", "doc2"], - spec: "@spec glue(t(), binary(), t()) :: t()" + spec: "@spec glue(t(), binary(), t()) :: t()", + active_param: 2 } ] } @@ -780,7 +785,13 @@ defmodule ElixirSense.SignatureTest do assert ElixirSense.signature(code, 4, 21) == %{ active_param: 1, signatures: [ - %{documentation: "", name: "sum", spec: "", params: ["s \\\\ nil", "f"]}, + %{ + documentation: "", + name: "sum", + spec: "", + params: ["s \\\\ nil", "f"], + active_param: 0 + }, %{documentation: "", name: "sum", spec: "", params: ["arg", "x", "y"]} ] } @@ -810,7 +821,13 @@ defmodule ElixirSense.SignatureTest do assert ElixirSense.signature(code, 15, 21) == %{ active_param: 1, signatures: [ - %{documentation: "", name: "sum", params: ["s \\\\ nil", "f"], spec: ""}, + %{ + documentation: "", + name: "sum", + params: ["s \\\\ nil", "f"], + spec: "", + active_param: 0 + }, %{documentation: "", name: "sum", params: ["tuple", "x", "y"], spec: ""} ] } @@ -866,6 +883,71 @@ defmodule ElixirSense.SignatureTest do } end + test "finds signatures from metadata module functions with default param - correctly highlight active param" do + code = """ + defmodule MyModule do + @spec sum(integer, integer, integer, integer, integer, integer) :: integer + defp sum(a \\\\ 1, b \\\\ 1, c, d, e \\\\ 1, f \\\\ 1) do + a + b + end + + def run do + sum(1, 2, 3, 4, 5, 6) + end + end + """ + + assert ElixirSense.signature(code, 8, 10) == %{ + active_param: 0, + signatures: [ + %{ + name: "sum", + params: [ + "a \\\\ 1", + "b \\\\ 1", + "c", + "d", + "e \\\\ 1", + "f \\\\ 1" + ], + documentation: "", + spec: + "@spec sum(integer, integer, integer, integer, integer, integer) :: integer", + active_param: 2 + } + ] + } + + assert %{ + active_param: 1, + signatures: [%{active_param: 3}] + } = ElixirSense.signature(code, 8, 13) + + assert %{ + active_param: 2, + signatures: [%{active_param: 0}] + } = ElixirSense.signature(code, 8, 16) + + assert %{ + active_param: 3, + signatures: [%{active_param: 1}] + } = ElixirSense.signature(code, 8, 19) + + assert %{ + active_param: 4, + signatures: [signature] + } = ElixirSense.signature(code, 8, 22) + + refute Map.has_key?(signature, :active_param) + + assert %{ + active_param: 5, + signatures: [signature] + } = ElixirSense.signature(code, 8, 25) + + refute Map.has_key?(signature, :active_param) + end + test "finds signatures from metadata elixir behaviour call" do code = """ defmodule MyModule do