diff --git a/apps/language_server/lib/language_server/codefragment_utils.ex b/apps/language_server/lib/language_server/codefragment_utils.ex new file mode 100644 index 000000000..c80943d70 --- /dev/null +++ b/apps/language_server/lib/language_server/codefragment_utils.ex @@ -0,0 +1,18 @@ +defmodule ElixirLS.LanguageServer.CodeFragmentUtils do + alias ElixirSense.Core.Normalized.Code, as: NormalizedCode + + def surround_context_with_fallback(code, {line, column}, options \\ []) do + case NormalizedCode.Fragment.surround_context(code, {line, column}, options) do + :none -> + {NormalizedCode.Fragment.surround_context(code, {line, max(column - 1, 1)}, options), + column - 1} + + %{context: {:dot, _, _}} -> + {NormalizedCode.Fragment.surround_context(code, {line, max(column - 1, 1)}, options), + column - 1} + + context -> + {context, column} + end + end +end diff --git a/apps/language_server/lib/language_server/providers/definition/locator.ex b/apps/language_server/lib/language_server/providers/definition/locator.ex index 60cc0e3ce..0b2a0db52 100644 --- a/apps/language_server/lib/language_server/providers/definition/locator.ex +++ b/apps/language_server/lib/language_server/providers/definition/locator.ex @@ -25,14 +25,14 @@ defmodule ElixirLS.LanguageServer.Providers.Definition.Locator do alias ElixirSense.Core.Parser alias ElixirLS.LanguageServer.Plugins.Phoenix.Scope - alias ElixirSense.Core.Normalized.Code, as: NormalizedCode + alias ElixirLS.LanguageServer.CodeFragmentUtils def definition(code, line, column, options \\ []) do - case NormalizedCode.Fragment.surround_context(code, {line, column}) do - :none -> + case CodeFragmentUtils.surround_context_with_fallback(code, {line, column}) do + {:none, _} -> nil - context -> + {context, column} -> metadata = Keyword.get_lazy(options, :metadata, fn -> Parser.parse_string(code, true, true, {line, column}) diff --git a/apps/language_server/lib/language_server/providers/hover/docs.ex b/apps/language_server/lib/language_server/providers/hover/docs.ex index 99b1aa41b..65fe19f30 100644 --- a/apps/language_server/lib/language_server/providers/hover/docs.ex +++ b/apps/language_server/lib/language_server/providers/hover/docs.ex @@ -21,6 +21,8 @@ defmodule ElixirLS.LanguageServer.Providers.Hover.Docs do alias ElixirSense.Core.TypeInfo alias ElixirSense.Core.Parser + alias ElixirLS.LanguageServer.CodeFragmentUtils + @type markdown :: String.t() @type module_doc :: %{kind: :module, docs: markdown, metadata: map, module: module()} @@ -71,11 +73,11 @@ defmodule ElixirLS.LanguageServer.Providers.Hover.Docs do |> Kernel.--([:exception, :message]) def docs(code, line, column, options \\ []) do - case NormalizedCode.Fragment.surround_context(code, {line, column}) do - :none -> + case CodeFragmentUtils.surround_context_with_fallback(code, {line, column}) do + {:none, _} -> nil - %{begin: begin_pos, end: end_pos} = context -> + {%{begin: begin_pos, end: end_pos} = context, column} -> metadata = Keyword.get_lazy(options, :metadata, fn -> Parser.parse_string(code, true, true, {line, column}) diff --git a/apps/language_server/lib/language_server/providers/implementation/locator.ex b/apps/language_server/lib/language_server/providers/implementation/locator.ex index 78b28f8bc..8aed79dad 100644 --- a/apps/language_server/lib/language_server/providers/implementation/locator.ex +++ b/apps/language_server/lib/language_server/providers/implementation/locator.ex @@ -19,15 +19,16 @@ defmodule ElixirLS.LanguageServer.Providers.Implementation.Locator do alias ElixirLS.LanguageServer.Location alias ElixirSense.Core.Parser alias ElixirSense.Core.Normalized.Code, as: NormalizedCode + alias ElixirLS.LanguageServer.CodeFragmentUtils require ElixirSense.Core.Introspection, as: Introspection def implementations(code, line, column, options \\ []) do - case NormalizedCode.Fragment.surround_context(code, {line, column}) do - :none -> + case CodeFragmentUtils.surround_context_with_fallback(code, {line, column}) do + {:none, _} -> [] - context -> + {context, column} -> metadata = Keyword.get_lazy(options, :metadata, fn -> Parser.parse_string(code, true, true, {line, column}) diff --git a/apps/language_server/lib/language_server/providers/references/locator.ex b/apps/language_server/lib/language_server/providers/references/locator.ex index 5e96a6724..eb8623f70 100644 --- a/apps/language_server/lib/language_server/providers/references/locator.ex +++ b/apps/language_server/lib/language_server/providers/references/locator.ex @@ -19,14 +19,14 @@ defmodule ElixirLS.LanguageServer.Providers.References.Locator do alias ElixirSense.Core.SurroundContext alias ElixirSense.Core.Parser + alias ElixirLS.LanguageServer.CodeFragmentUtils + def references(code, line, column, trace, options \\ []) do - case NormalizedCode.Fragment.surround_context(code, {line, column}) do - :none -> + case CodeFragmentUtils.surround_context_with_fallback(code, {line, column}) do + {:none, _} -> [] - %{ - begin: {begin_line, begin_col} - } = context -> + {%{begin: {begin_line, begin_col}} = context, column} -> metadata = Keyword.get_lazy(options, :metadata, fn -> Parser.parse_string(code, true, true, {line, column}) diff --git a/apps/language_server/test/server_test.exs b/apps/language_server/test/server_test.exs index 9c04ad05e..372e75177 100644 --- a/apps/language_server/test/server_test.exs +++ b/apps/language_server/test/server_test.exs @@ -1384,6 +1384,70 @@ defmodule ElixirLS.LanguageServer.ServerTest do wait_until_compiled(server) end) end + + test "definition at end of symbol", %{server: server} do + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + code = ~S( + defmodule Test do + def test, do: nil + end + defmodule OtherModule do + def test do + Test + Test.test(\) + _ = &Test.test/0 + end + end + ) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + Server.receive_packet(server, definition_req(1, uri, 6, 17)) + + assert_receive( + response(1, %{ + "range" => %{ + "end" => %{"character" => 8, "line" => 1}, + "start" => %{"character" => 8, "line" => 1} + }, + "uri" => ^uri + }), + 3000 + ) + + wait_until_compiled(server) + + Server.receive_packet(server, definition_req(2, uri, 7, 17)) + + assert_receive( + response(2, %{ + "range" => %{ + "end" => %{"character" => 8, "line" => 1}, + "start" => %{"character" => 8, "line" => 1} + }, + "uri" => ^uri + }), + 3000 + ) + + wait_until_compiled(server) + + Server.receive_packet(server, definition_req(3, uri, 8, 22)) + + assert_receive( + response(3, %{ + "range" => %{ + "end" => %{"character" => 8, "line" => 1}, + "start" => %{"character" => 8, "line" => 1} + }, + "uri" => ^uri + }), + 3000 + ) + + wait_until_compiled(server) + end) + end end describe "textDocument/implementation" do